diff --git a/demo/boot.js b/demo/boot.js index eec770a1..f2601c37 100644 --- a/demo/boot.js +++ b/demo/boot.js @@ -109,10 +109,6 @@ var setupPlugins = function(config, callback) { } } } -console.log(JSON.stringify({ - packagePaths: pluginPackageInfo, - paths: paths - })); require({ packagePaths: pluginPackageInfo, paths: paths diff --git a/plugins/cockpit/lib/cli.js b/plugins/cockpit/lib/cli.js index 24a25a35..eeeb289a 100644 --- a/plugins/cockpit/lib/cli.js +++ b/plugins/cockpit/lib/cli.js @@ -54,9 +54,10 @@ var canon = require('pilot/canon'); * The information required to tell the user there is a problem with their * input. */ -function Hint(status, message, start, end) { +function Hint(status, message, start, end, predictions) { this.status = status; this.message = message; + this.predictions = predictions; if (typeof start === 'number') { this.start = start; @@ -74,23 +75,21 @@ Hint.prototype = { * Loop over the array of hints finding the one we should display. * @param hints array of hints */ -Hint.worst = function(hints, cursor) { - if (hints.length === 0) { - return undefined; - } +Hint.sort = function(hints, cursor) { // Calculate 'distance from cursor' if (cursor !== undefined) { hints.forEach(function(hint) { - if (cursor < hint.start) { + if (hint.start === Argument.AT_CURSOR) { + hint.distance = 0; + } + else if (cursor < hint.start) { hint.distance = hint.start - cursor; } + else if (cursor > hint.end) { + hint.distance = cursor - hint.end; + } else { - if (cursor > hint.end) { - hint.distance = cursor - hint.end; - } - else { - hint.distance = 0; - } + hint.distance = 0; } }, this); } @@ -112,7 +111,7 @@ Hint.worst = function(hints, cursor) { delete hint.distance; }, this); } - return hints[0]; + return hints; }; exports.Hint = Hint; @@ -157,6 +156,10 @@ Argument.prototype = { this.priorSpace); }, + /** + * See notes on events in Assignment. We might need to hook changes here + * into a CliRequisition so they appear of the command line. + */ setText: function(text) { if (text == null) { throw new Error('Illegal text for Argument: ' + text); @@ -183,6 +186,10 @@ Argument.merge = function(argArray, start, end) { } return joined; }; +/** + * We sometimes need a way to say 'this error occurs whereever the cursor is' + */ +Argument.AT_CURSOR = -1; /** @@ -195,6 +202,13 @@ Argument.merge = function(argArray, start, end) { * Thus, null is a valid default value, and common because it identifies an * parameter that is optional. undefined means there is no value from * the command line. + * + *

TODO: We might need events in the future + * The current hope is that a GUI and CLI that share an Assignment can be in + * direct connection due to assignment.getValue calling arg.setText, however + * we might need to use events if not. + * oop.implement(Assignment, EventEmitter); + * * @constructor */ function Assignment(param) { @@ -208,6 +222,22 @@ Assignment.prototype = { */ param: undefined, + /** + * Report on the status of the last parse() conversion. + * @see types.Conversion + */ + conversion: undefined, + + /** + * The current value in a type as specified by param.type + */ + value: undefined, + + /** + * The string version of the current value + */ + arg: undefined, + /** * The current value (i.e. not the string representation) * Use setValue() to mutate @@ -217,8 +247,10 @@ Assignment.prototype = { if (this.value === value) { return; } + if (value === undefined) { value = this.param.defaultValue; + this.arg = undefined; } this.value = value; @@ -228,7 +260,6 @@ Assignment.prototype = { } this.conversion = undefined; - //this._dispatchEvent('change', { assignment: this }); }, /** @@ -242,40 +273,60 @@ Assignment.prototype = { } this.arg = arg; this.conversion = this.param.type.parse(arg.text); + this.conversion.arg = arg; // TODO: make this automatic? this.value = this.conversion.value; - //this._dispatchEvent('change', { assignment: this }); }, /** - * Create a list of this hints associated with this parameter assignment + * Create a list of the hints associated with this parameter assignment. + * Generally there will be only one hint generated because we're currently + * only displaying one hint at a time, ordering by distance from cursor + * and severity. Since distance from cursor will be the same for all hints + * from this assignment all but the most severe will ever be used. It might + * make sense with more experience to alter this to function to be getHint() */ getHints: function() { var hints = []; - if (this.conversion != null && - (this.conversion.status !== Status.VALID || - this.conversion.message)) { - hints.push(new ConversionHint(this.conversion, this.arg)); + + // If there is no argument, use the cursor position + var message = '' + this.param.name + ': '; + if (this.param.description) { + // TODO: This should be a short description - do we need to trim? + message += this.param.description.trim(); + + // Ensure the help text ends with '. ' + if (message.charAt(message.length - 1) !== '.') { + message += '.'; + } + if (message.charAt(message.length - 1) !== ' ') { + message += ' '; + } + } + var status = Status.VALID; + var start = this.arg ? this.arg.start : Argument.AT_CURSOR; + var end = this.arg ? this.arg.end : Argument.AT_CURSOR; + var predictions; + + // Non-valid conversions will have useful information to pass on + if (this.conversion) { + status = this.conversion.status; + if (this.conversion.message) { + message += this.conversion.message; + } + predictions = this.conversion.predictions; } - var argProvided = this.arg != null && this.arg.text !== ''; + // Hint if the param is required, but not provided + var argProvided = this.arg && this.arg.text !== ''; var dataProvided = this.value !== undefined || argProvided; - if (this.param.defaultValue === undefined && !dataProvided) { - // If the there is no data provided, we have no start/end. Use -1 - hints.push(new Hint(Status.INVALID, - 'Argument for ' + param.name + ' is required' - -1, -1)); + status = Status.INVALID; + message += 'Required<\strong>'; } - return hints; - }, - /** - * Report on the status of the last parse() conversion. - * @see types.Conversion - */ - conversion: undefined + return [ new Hint(status, message, start, end, predictions) ]; + } }; -oop.implement(Assignment, EventEmitter); exports.Assignment = Assignment; @@ -355,15 +406,19 @@ Requisition.prototype = { }, /** - * Collect the statuses from the Assignments + * Collect the statuses from the Assignments. + * The hints returned are sorted by severity */ getHints: function() { var hints = []; Object.keys(this._assignments).map(function(name) { - // Append the assignments hints to our list - hints.push.apply(hints, this._assignments[name].getHints()); + // Only use assignments with an argument + var assignment = this._assignments[name]; + if (assignment.arg) { + hints.push.apply(hints, assignment.getHints()); + } }, this); - return hints; + return Hint.sort(hints); }, /** @@ -437,7 +492,9 @@ oop.inherits(CliRequisition, Requisition); * */ CliRequisition.prototype.update = function(input) { - this.hints = []; + // TODO: We only store this so getHints can work. Find a better way. + this.input = input; + this.localHints = []; if (util.none(input.typed)) { this.setCommand(null); @@ -450,38 +507,44 @@ oop.inherits(CliRequisition, Requisition); // a complete novice a 'type help' message is very annoying, so we // need to find a way to only display this message once, or for // until the user click a 'close' button or similar - this._addHint(Status.INCOMPLETE, '', 0, 0); + this.localHints.push(new Hint(Status.INCOMPLETE, '', 0, 0)); this.setCommand(null); return; } - var command = _split(args); - if (!command) { + var conversion = _split(args); + if (!conversion.value) { // No command found - bail helpfully. - var commandType = types.getType('command'); - var conversion = commandType.parse(input.typed); - var arg = Argument.merge(args); - this._addHint(new ConversionHint(conversion, arg)); - + this.localHints.push(new ConversionHint(conversion, conversion.arg)); this.setCommand(null); } else { - // The user hasn't started to type any arguments - if (args.length === 0) { - var message = documentCommand(command); - this._addHint(Status.VALID, message, 0, input.typed.length); - } + var message = documentCommand(conversion.value); + this.localHints.push(new Hint(Status.VALID, message, conversion.arg)); - this.setCommand(command); + this.setCommand(conversion.value); this._assign(args); - this._addHint(CliRequisition.super_.getHints.call(this)); } + return; + }; + + CliRequisition.prototype.getHints = function() { + var hints = this.localHints.slice(0); + + Object.keys(this._assignments).map(function(name) { + // Only use assignments with an argument + var assignment = this._assignments[name]; + if (assignment.arg) { + hints.push.apply(hints, assignment.getHints()); + } + }, this); + // Not knowing about cursor positioning, the requisition and assignments // can't know this, but anything they mark as INCOMPLETE is actually // INVALID unless the cursor is actually inside that argument. - var c = input.cursor; - this.hints.forEach(function(hint) { + var c = this.input.cursor; + hints.forEach(function(hint) { var startInHint = c.start >= hint.start && c.start <= hint.end; var endInHint = c.end >= hint.start && c.end <= hint.end; var inHint = startInHint || endInHint; @@ -490,30 +553,7 @@ oop.inherits(CliRequisition, Requisition); } }, this); - return; - }; - - CliRequisition.prototype.getHints = function() { - return this.hints; - }; - - /** - * Some sugar around: 'this.hints.push(new Hint(...));', but you can also - * pass in an array of Hints or the parameters to create a hint - */ - CliRequisition.prototype._addHint = function(status, message, start, end) { - if (status == null) { - return; - } - if (status instanceof Hint) { - this.hints.push(status); - } - else if (Array.isArray(status)) { - this.hints.push.apply(this.hints, status); - } - else { - this.hints.push(new Hint(status, message, start, end)); - } + return Hint.sort(hints, this.input.cursor.start); }; /** @@ -538,9 +578,9 @@ oop.inherits(CliRequisition, Requisition); // TODO: previously we were doing some extra work to avoid this if // we determined that we had args that were all whitespace, but // probably given our tighter tokenize() this won't be an issue? - this._addHint(Status.INVALID, - this.command.name + ' does not take any parameters', - Argument.merge(args)); + this.localHints.push(new Hint(Status.INVALID, + this.command.name + ' does not take any parameters', + Argument.merge(args))); return; } @@ -580,9 +620,9 @@ oop.inherits(CliRequisition, Requisition); else { if (i + 1 < args.length) { // Missing value portion of this named param - this._addHint(Status.INCOMPLETE, - 'Missing value for: ' + namedArgText, - args[i]); + this.localHints.push(new Hint(Status.INCOMPLETE, + 'Missing value for: ' + namedArgText, + args[i])); } else { args.splice(i + 1, 1); @@ -612,9 +652,9 @@ oop.inherits(CliRequisition, Requisition); if (args.length > 0) { var remaining = Argument.merge(args); - this._addHint(Status.INVALID, - 'Input \'' + remaining.text + '\' makes no sense.', - remaining); + this.localHints.push(new Hint(Status.INVALID, + 'Input \'' + remaining.text + '\' makes no sense.', + remaining)); } }; @@ -671,63 +711,71 @@ function _tokenize(typed) { var str = unescape(typed.substring(start, i)); args.push(new Argument(str, start, i, priorSpace)); } + else { + if (i !== start) { + // There's a bunch of whitespace at the end of the command + // treat it as another argument without any content + priorSpace = typed.substring(start, i); + args.push(new Argument('', i, i, priorSpace)); + } + } break; } var c = typed[i]; switch (mode) { - case OUTSIDE: - if (c === '\'') { - priorSpace = typed.substring(start, i); - mode = IN_SINGLE_Q; - start = i + 1; - } - else if (c === '"') { - priorSpace = typed.substring(start, i); - mode = IN_DOUBLE_Q; - start = i + 1; - } - else if (/ /.test(c)) { - // Still whitespace, do nothing - } - else { - priorSpace = typed.substring(start, i); - mode = IN_SIMPLE; - start = i; - } - break; + case OUTSIDE: + if (c === '\'') { + priorSpace = typed.substring(start, i); + mode = IN_SINGLE_Q; + start = i + 1; + } + else if (c === '"') { + priorSpace = typed.substring(start, i); + mode = IN_DOUBLE_Q; + start = i + 1; + } + else if (/ /.test(c)) { + // Still whitespace, do nothing + } + else { + priorSpace = typed.substring(start, i); + mode = IN_SIMPLE; + start = i; + } + break; - case IN_SIMPLE: - // There is an edge case of xx'xx which we are assuming to be - // a single parameter (and same with ") - if (c === ' ') { - var str = unescape(typed.substring(start, i)); - args.push(new Argument(str, start, i, priorSpace)); - mode = OUTSIDE; - start = i; - priorSpace = ''; - } - break; + case IN_SIMPLE: + // There is an edge case of xx'xx which we are assuming to be + // a single parameter (and same with ") + if (c === ' ') { + var str = unescape(typed.substring(start, i)); + args.push(new Argument(str, start, i, priorSpace)); + mode = OUTSIDE; + start = i; + priorSpace = ''; + } + break; - case IN_SINGLE_Q: - if (c === '\'') { - var str = unescape(typed.substring(start, i)); - args.push(new Argument(str, start, i, priorSpace)); - mode = OUTSIDE; - start = i + 1; - priorSpace = ''; - } - break; + case IN_SINGLE_Q: + if (c === '\'') { + var str = unescape(typed.substring(start, i)); + args.push(new Argument(str, start, i, priorSpace)); + mode = OUTSIDE; + start = i + 1; + priorSpace = ''; + } + break; - case IN_DOUBLE_Q: - if (c === '"') { - var str = unescape(typed.substring(start, i)); - args.push(new Argument(str, start, i, priorSpace)); - mode = OUTSIDE; - start = i + 1; - priorSpace = ''; - } - break; + case IN_DOUBLE_Q: + if (c === '"') { + var str = unescape(typed.substring(start, i)); + args.push(new Argument(str, start, i, priorSpace)); + mode = OUTSIDE; + start = i + 1; + priorSpace = ''; + } + break; } i++; @@ -742,24 +790,23 @@ exports._tokenize = _tokenize; * typed at the command line. */ function _split(args) { + var commandType = types.getType('command'); if (args.length === 0) { - return undefined; + return commandType.parse(''); } - var argsUsed = 0; - var lookup = ''; - var command; + var argsUsed = 1; + var arg; + var conversion; - while (true) { - argsUsed++; - lookup += args.map(function(arg) { - return arg.text; - }).slice(0, argsUsed).join(' '); - command = canon.getCommand(lookup); + while (argsUsed <= args.length) { + var arg = Argument.merge(args, 0, argsUsed); + conversion = commandType.parse(arg.text); + conversion.arg = arg; // TODO: make this automatic? - if (!command) { - // Not found. break with command == null - return undefined; + if (!conversion.value) { + // Not found. break with value == null + break; } /* @@ -772,21 +819,18 @@ function _split(args) { } */ - if (command.exec) { + if (conversion.value.exec) { // Valid command, break with command valid + for (var i = 0; i < argsUsed; i++) { + args.shift(); + } break; } - // command, but no exec - this must be a sub-command - lookup += ' '; + argsUsed++; } - // Remove the used args - for (var i = 0; i < argsUsed; i++) { - args.shift(); - } - - return command; + return conversion; } exports._split = _split; diff --git a/plugins/cockpit/lib/index.js b/plugins/cockpit/lib/index.js index beacc28f..7717e118 100644 --- a/plugins/cockpit/lib/index.js +++ b/plugins/cockpit/lib/index.js @@ -41,9 +41,8 @@ exports.startup = function(data, reason) { window.testCli = require('cockpit/test/testCli'); - var plain = require('cockpit/ui/plain'); - plain.startup(data, reason); - + require('cockpit/ui/settings').startup(data, reason); + require('cockpit/ui/plain').startup(data, reason); }; /* diff --git a/plugins/cockpit/lib/test/testCli.js b/plugins/cockpit/lib/test/testCli.js index bace956e..20b0cdd0 100644 --- a/plugins/cockpit/lib/test/testCli.js +++ b/plugins/cockpit/lib/test/testCli.js @@ -125,19 +125,19 @@ exports.testTokenize = function() { exports.testSplit = function() { var args = tokenize('s'); - var command = split(args); + var conversion = split(args); test.verifyEqual(1, args.length); test.verifyEqual('s', args[0].text); - test.verifyUndefined(command); + test.verifyNull(conversion.value); var args = tokenize('set'); - var command = split(args); + var conversion = split(args); test.verifyEqual([], args); - test.verifyEqual('set', command.name); + test.verifyEqual('set', conversion.value.name); var args = tokenize('set a b'); - var command = split(args); - test.verifyEqual('set', command.name); + var conversion = split(args); + test.verifyEqual('set', conversion.value.name); test.verifyEqual(2, args.length); test.verifyEqual('a', args[0].text); test.verifyEqual('b', args[1].text); @@ -152,14 +152,26 @@ exports.testCli = function() { var settingAssignment; var valueAssignment; var cli = new CliRequisition(); + var debug = true; function update(input) { + if (debug) { + console.log('####### TEST: typed="' + input.typed + '" cursor:', input.cursor); + } cli.update(input); hints = cli.getHints(); hint0 = (hints.length !== 0) ? hints[0] : undefined; + if (debug) { + console.log('cli=', cli); + console.log('hints=', hints); + } if (cli.command && cli.command.name === 'set') { settingAssignment = cli.getAssignment('setting'); valueAssignment = cli.getAssignment('value'); + if (debug) { + console.log('settingAssignment=', settingAssignment); + console.log('valueAssignment=', valueAssignment); + } } else { settingAssignment = undefined; @@ -167,6 +179,12 @@ exports.testCli = function() { } } + function verifyPredictionsContains(name, predictions) { + return predictions.every(function(prediction) { + return name === prediction || name === prediction.name; + }, this); + } + var historyLengthSetting = settings.getSetting('historyLength'); update({ typed: '', cursor: { start: 0, end: 0 } }); @@ -184,8 +202,9 @@ exports.testCli = function() { test.verifyEqual(1, hint0.end); test.verifyTrue(hint0.predictions.length > 0); // This is slightly fragile because it depends on the configuration + // TODO: Mock, but first we need a way to have a clear canon. test.verifyTrue(hint0.predictions.length < 20); - test.verifyNotEqual(-1, hint0.predictions.indexOf('set')); + verifyPredictionsContains('set', hint0.predictions); test.verifyNull(cli.command); update({ typed: 'set', cursor: { start: 3, end: 3 } }); @@ -196,73 +215,79 @@ exports.testCli = function() { test.verifyEqual('set', cli.command.name); update({ typed: 'set ', cursor: { start: 4, end: 4 } }); - test.verifyEqual(1, hints.length); + test.verifyEqual(2, hints.length); test.verifyEqual(Status.VALID, hint0.status); - test.verifyEqual(0, hint0.start); - // Technically the command ends at 3, but we're returning 4 currently. - // This is caused by us using the whole input to determine the length. - // Maybe one day we should fix this? - //test.verifyEqual(3, hint0.end); + test.verifyEqual(4, hint0.start); + test.verifyEqual(4, hint0.end); + test.verifyEqual(Status.VALID, hints[1].status); + test.verifyEqual(0, hints[1].start); + test.verifyEqual(3, hints[1].end); test.verifyEqual('set', cli.command.name); update({ typed: 'set h', cursor: { start: 5, end: 5 } }); - test.verifyEqual(1, hints.length); + test.verifyEqual(2, hints.length); test.verifyEqual(Status.INCOMPLETE, hint0.status); - test.verifyTrue(hint0.predictions.length > 0); test.verifyEqual(4, hint0.start); test.verifyEqual(5, hint0.end); - test.verifyNotEqual(-1, hint0.predictions.indexOf('historyLength')); + test.verifyTrue(hint0.predictions.length > 0); + verifyPredictionsContains('historyLength', hint0.predictions); test.verifyEqual('set', cli.command.name); test.verifyEqual('h', settingAssignment.arg.text); test.verifyEqual(undefined, settingAssignment.value); update({ typed: 'set historyLengt', cursor: { start: 16, end: 16 } }); - test.verifyEqual(1, hints.length); + test.verifyEqual(2, hints.length); test.verifyEqual(Status.INCOMPLETE, hint0.status); - test.verifyEqual(1, hint0.predictions.length); test.verifyEqual(4, hint0.start); test.verifyEqual(16, hint0.end); - test.verifyEqual('historyLength', hint0.predictions[0]); + test.verifyEqual(1, hint0.predictions.length); + verifyPredictionsContains('historyLength', hint0.predictions); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual(undefined, settingAssignment.value); update({ typed: 'set historyLengt', cursor: { start: 1, end: 1 } }); - test.verifyEqual(1, hints.length); - test.verifyEqual(Status.INVALID, hint0.status); - test.verifyEqual(4, hint0.start); - test.verifyEqual(16, hint0.end); - test.verifyEqual(1, hint0.predictions.length); - test.verifyEqual('historyLength', hint0.predictions[0]); + test.verifyEqual(2, hints.length); + test.verifyEqual(Status.VALID, hint0.status); + test.verifyEqual(0, hint0.start); + test.verifyEqual(3, hint0.end); + test.verifyEqual(Status.INVALID, hints[1].status); + test.verifyEqual(4, hints[1].start); + test.verifyEqual(16, hints[1].end); + test.verifyEqual(1, hints[1].predictions.length); + verifyPredictionsContains('historyLength', hints[1].predictions); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual(undefined, settingAssignment.value); update({ typed: 'set historyLengt ', cursor: { start: 17, end: 17 } }); - test.verifyEqual(1, hints.length); - test.verifyEqual(Status.INVALID, hint0.status); - test.verifyEqual(4, hint0.start); - test.verifyEqual(16, hint0.end); - test.verifyEqual(1, hint0.predictions.length); - test.verifyEqual('historyLength', hint0.predictions[0]); + test.verifyEqual(3, hints.length); + test.verifyEqual(Status.VALID, hint0.status); + test.verifyEqual(17, hint0.start); + test.verifyEqual(17, hint0.end); + test.verifyEqual(Status.INVALID, hints[1].status); + test.verifyEqual(4, hints[1].start); + test.verifyEqual(16, hints[1].end); + test.verifyEqual(1, hints[1].predictions.length); + verifyPredictionsContains('historyLength', hints[1].predictions); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual(undefined, settingAssignment.value); update({ typed: 'set historyLength', cursor: { start: 17, end: 17 } }); - test.verifyEqual(0, hints.length); + test.verifyEqual(2, hints.length); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual(historyLengthSetting, settingAssignment.value); update({ typed: 'set historyLength ', cursor: { start: 18, end: 18 } }); - test.verifyEqual(0, hints.length); + test.verifyEqual(3, hints.length); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual(historyLengthSetting, settingAssignment.value); update({ typed: 'set historyLength 6', cursor: { start: 19, end: 19 } }); - test.verifyEqual(0, hints.length); + test.verifyEqual(3, hints.length); test.verifyEqual('set', cli.command.name); test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual(historyLengthSetting, settingAssignment.value); diff --git a/plugins/cockpit/lib/ui/plain.css b/plugins/cockpit/lib/ui/plain.css index d6a59f54..2d72c6f0 100644 --- a/plugins/cockpit/lib/ui/plain.css +++ b/plugins/cockpit/lib/ui/plain.css @@ -19,13 +19,13 @@ padding: 8px; } -.cptCompletion.VALID { background: rgba(210, 255, 210, 1); } -.cptCompletion.INCOMPLETE { background: rgba(255, 230, 210, 1); } -.cptCompletion.INVALID { background: rgba(255, 210, 210, 1); } +.cptCompletion.VALID { background: #FFF; } +.cptCompletion.INCOMPLETE { background: #DDD; } +.cptCompletion.INVALID { background: #DDD; } .cptCompletion span { color: #FFF; } -.cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #ff8800; } -.cptCompletion span.INVALID { text-shadow: 0px 0px 5px #ff0000; } +.cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #F80; } +.cptCompletion span.INVALID { text-shadow: 0px 0px 5px #F00; } #cockpit, .cptCompletion { diff --git a/plugins/cockpit/lib/ui/plain.js b/plugins/cockpit/lib/ui/plain.js index f6953bf5..9df2bfb4 100644 --- a/plugins/cockpit/lib/ui/plain.js +++ b/plugins/cockpit/lib/ui/plain.js @@ -59,9 +59,10 @@ var Status = require('pilot/types').Status; * 2. Attach a set of events so the command line works */ exports.startup = function(data, reason) { + // TODO: We should probably cut this up into an object + var settings = data.env.settings; var doc = document; var win = doc.defaultView; - var cli = new CliRequisition(); // TODO: we should have a better way to specify command lines??? @@ -71,7 +72,7 @@ exports.startup = function(data, reason) { return; } - var templates = doc.createElement('dic'); + var templates = doc.createElement('div'); templates.innerHTML = plainRow; var row = templates.firstChild; @@ -88,12 +89,22 @@ exports.startup = function(data, reason) { input.parentNode.insertBefore(output, input); function resizer() { - var style = win.getComputedStyle(input, null); + var top, height, left, width; - var top = parseInt(style.getPropertyValue('top'), 10); - var height = parseInt(style.getPropertyValue('height'), 10); - var left = parseInt(style.getPropertyValue('left'), 10); - var width = parseInt(style.getPropertyValue('width'), 10); + if (input.getClientRects) { + var rect = input.getClientRects()[0]; + top = rect.top; + height = rect.height; + left = rect.left; + width = rect.width; + } + else { + var style = win.getComputedStyle(input, null); + top = parseInt(style.getPropertyValue('top'), 10); + height = parseInt(style.getPropertyValue('height'), 10); + left = parseInt(style.getPropertyValue('left'), 10); + width = parseInt(style.getPropertyValue('width'), 10); + } completer.style.top = top + 'px'; completer.style.height = height + 'px'; @@ -125,6 +136,20 @@ exports.startup = function(data, reason) { }, this); }.bind(this)); + var showHint = settings.getSetting('showHint'); + function hintShower() { + if (showHint.get()) { + hinter.style.display = 'block'; + } + else { + hinter.style.display = 'none'; + } + } + hintShower(); + showHint.addEventListener('change', hintShower.bind(this)); + + var outputHeight = settings.getSetting('outputHeight'); + /* // All this does is to kill TABs normal use. I wonder if we can train // people to use right arrow? Probably not? but ... @@ -210,10 +235,10 @@ exports.startup = function(data, reason) { } } - worst = Hint.worst(hints) || NO_HINT; + worst = (hints.length > 0) ? hints[0] : NO_HINT; var message = worst.message; if (worst.predictions && worst.predictions.length > 0) { - message += ' [ '; + message += ': [ '; worst.predictions.forEach(function(prediction) { if (prediction.name) { message += prediction.name + ' | '; diff --git a/plugins/cockpit/lib/ui/settings.js b/plugins/cockpit/lib/ui/settings.js new file mode 100644 index 00000000..80d95e73 --- /dev/null +++ b/plugins/cockpit/lib/ui/settings.js @@ -0,0 +1,66 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Skywriter. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Joe Walker (jwalker@mozilla.com) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + + +var showHintSetting = { + name: "showHint", + description: "Do we display hints while we type?", + type: "bool", + defaultValue: true +}; + +var outputHeightSetting = { + name: "outputHeight", + description: "What height should the output panel be?", + type: "number", + defaultValue: 300 +}; + +exports.startup = function(data, reason) { + data.env.settings.addSetting(showHintSetting); + data.env.settings.addSetting(outputHeightSetting); +}; + +exports.shutdown = function(data, reason) { + data.env.settings.removeSetting(showHintSetting); + data.env.settings.removeSetting(outputHeightSetting); +}; + + +}); diff --git a/plugins/pilot/lib/commands/settings.js b/plugins/pilot/lib/commands/settings.js index efcbc5ea..c6679bc9 100644 --- a/plugins/pilot/lib/commands/settings.js +++ b/plugins/pilot/lib/commands/settings.js @@ -84,22 +84,15 @@ var setCommandSpec = { '
'; }); } else { - var setting = env.settings.get(args.setting); - if (!setting) { - request.doneWithError('No setting with the name ' + - setting.name + '.'); - return; - } - // set with only a setting, shows the value for that setting if (args.value === undefined) { html = '' + setting.name + ' = ' + setting.get(); } else { // Actually change the setting - setting.set(args.value); - html = 'Setting: ' + setting.name + ' = ' + - setting.get(); + args.setting.set(args.value); + html = 'Setting: ' + args.setting.name + ' = ' + + args.setting.get(); } } request.done(html); diff --git a/plugins/pilot/lib/index.js b/plugins/pilot/lib/index.js index 73262d42..f576b29c 100644 --- a/plugins/pilot/lib/index.js +++ b/plugins/pilot/lib/index.js @@ -50,10 +50,8 @@ packages.unshift("require", "exports", "module"); define(packages, function(require, exports, module) { -console.log(packages); exports.startup = function(data, reason) { deps.forEach(function(dep) { - console.log("test startup for " + dep); var module = require(dep); if (typeof module.startup === "function") { module.startup(data, reason); diff --git a/plugins/pilot/lib/types/basic.js b/plugins/pilot/lib/types/basic.js index f6e2adc5..6eeb64a8 100644 --- a/plugins/pilot/lib/types/basic.js +++ b/plugins/pilot/lib/types/basic.js @@ -128,32 +128,32 @@ SelectionType.prototype.parse = function(str) { } var data = (typeof(this.data) === "function") ? this.data() : this.data; - var match; + // The matchedValue could be the boolean value false + var hasMatched = false; + var matchedValue; var completions = []; data.forEach(function(option) { if (str == option) { - match = this.fromString(option); + matchedValue = this.fromString(option); + hasMatched = true; } else if (option.indexOf(str) === 0) { completions.push(this.fromString(option)); } }, this); - if (match) { - return new Conversion(match); + if (hasMatched) { + return new Conversion(matchedValue); } else { if (completions.length > 0) { - return new Conversion(null, - Status.INCOMPLETE, - 'Several possibilities for \'' + str + '\'', - completions); + var msg = 'Several possibilities' + + (str.length === 0 ? '' : ' for \'' + str + '\''); + return new Conversion(null, Status.INCOMPLETE, msg, completions); } else { - return new Conversion(null, - Status.INVALID, - 'Can\'t use \'' + str + '\'.', - completions); + var msg = 'Can\'t use \'' + str + '\'.'; + return new Conversion(null, Status.INVALID, msg, completions); } } }; @@ -203,7 +203,6 @@ DeferredType.prototype.stringify = function(value) { }; DeferredType.prototype.parse = function(value) { - console.log(this.defer); return this.defer().parse(value); };