lots of work fixing bugs with the command line, also removal of useless console.log

This commit is contained in:
Joe Walker 2010-12-11 19:01:02 +00:00
commit 9cff2e0908
10 changed files with 385 additions and 240 deletions

View file

@ -109,10 +109,6 @@ var setupPlugins = function(config, callback) {
} }
} }
} }
console.log(JSON.stringify({
packagePaths: pluginPackageInfo,
paths: paths
}));
require({ require({
packagePaths: pluginPackageInfo, packagePaths: pluginPackageInfo,
paths: paths paths: paths

View file

@ -54,9 +54,10 @@ var canon = require('pilot/canon');
* The information required to tell the user there is a problem with their * The information required to tell the user there is a problem with their
* input. * input.
*/ */
function Hint(status, message, start, end) { function Hint(status, message, start, end, predictions) {
this.status = status; this.status = status;
this.message = message; this.message = message;
this.predictions = predictions;
if (typeof start === 'number') { if (typeof start === 'number') {
this.start = start; this.start = start;
@ -74,23 +75,21 @@ Hint.prototype = {
* Loop over the array of hints finding the one we should display. * Loop over the array of hints finding the one we should display.
* @param hints array of hints * @param hints array of hints
*/ */
Hint.worst = function(hints, cursor) { Hint.sort = function(hints, cursor) {
if (hints.length === 0) {
return undefined;
}
// Calculate 'distance from cursor' // Calculate 'distance from cursor'
if (cursor !== undefined) { if (cursor !== undefined) {
hints.forEach(function(hint) { 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; hint.distance = hint.start - cursor;
} }
else if (cursor > hint.end) {
hint.distance = cursor - hint.end;
}
else { else {
if (cursor > hint.end) { hint.distance = 0;
hint.distance = cursor - hint.end;
}
else {
hint.distance = 0;
}
} }
}, this); }, this);
} }
@ -112,7 +111,7 @@ Hint.worst = function(hints, cursor) {
delete hint.distance; delete hint.distance;
}, this); }, this);
} }
return hints[0]; return hints;
}; };
exports.Hint = Hint; exports.Hint = Hint;
@ -157,6 +156,10 @@ Argument.prototype = {
this.priorSpace); 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) { setText: function(text) {
if (text == null) { if (text == null) {
throw new Error('Illegal text for Argument: ' + text); throw new Error('Illegal text for Argument: ' + text);
@ -183,6 +186,10 @@ Argument.merge = function(argArray, start, end) {
} }
return joined; 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 * Thus, null is a valid default value, and common because it identifies an
* parameter that is optional. undefined means there is no value from * parameter that is optional. undefined means there is no value from
* the command line. * the command line.
*
* <p>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 * @constructor
*/ */
function Assignment(param) { function Assignment(param) {
@ -208,6 +222,22 @@ Assignment.prototype = {
*/ */
param: undefined, 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) * The current value (i.e. not the string representation)
* Use setValue() to mutate * Use setValue() to mutate
@ -217,8 +247,10 @@ Assignment.prototype = {
if (this.value === value) { if (this.value === value) {
return; return;
} }
if (value === undefined) { if (value === undefined) {
value = this.param.defaultValue; value = this.param.defaultValue;
this.arg = undefined;
} }
this.value = value; this.value = value;
@ -228,7 +260,6 @@ Assignment.prototype = {
} }
this.conversion = undefined; this.conversion = undefined;
//this._dispatchEvent('change', { assignment: this });
}, },
/** /**
@ -242,40 +273,60 @@ Assignment.prototype = {
} }
this.arg = arg; this.arg = arg;
this.conversion = this.param.type.parse(arg.text); this.conversion = this.param.type.parse(arg.text);
this.conversion.arg = arg; // TODO: make this automatic?
this.value = this.conversion.value; 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() { getHints: function() {
var hints = []; var hints = [];
if (this.conversion != null &&
(this.conversion.status !== Status.VALID || // If there is no argument, use the cursor position
this.conversion.message)) { var message = '<strong>' + this.param.name + '</strong>: ';
hints.push(new ConversionHint(this.conversion, this.arg)); 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; var dataProvided = this.value !== undefined || argProvided;
if (this.param.defaultValue === undefined && !dataProvided) { if (this.param.defaultValue === undefined && !dataProvided) {
// If the there is no data provided, we have no start/end. Use -1 status = Status.INVALID;
hints.push(new Hint(Status.INVALID, message += '<strong>Required<\strong>';
'Argument for ' + param.name + ' is required'
-1, -1));
} }
return hints;
},
/** return [ new Hint(status, message, start, end, predictions) ];
* Report on the status of the last parse() conversion. }
* @see types.Conversion
*/
conversion: undefined
}; };
oop.implement(Assignment, EventEmitter);
exports.Assignment = Assignment; 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() { getHints: function() {
var hints = []; var hints = [];
Object.keys(this._assignments).map(function(name) { Object.keys(this._assignments).map(function(name) {
// Append the assignments hints to our list // Only use assignments with an argument
hints.push.apply(hints, this._assignments[name].getHints()); var assignment = this._assignments[name];
if (assignment.arg) {
hints.push.apply(hints, assignment.getHints());
}
}, this); }, this);
return hints; return Hint.sort(hints);
}, },
/** /**
@ -437,7 +492,9 @@ oop.inherits(CliRequisition, Requisition);
* *
*/ */
CliRequisition.prototype.update = function(input) { 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)) { if (util.none(input.typed)) {
this.setCommand(null); this.setCommand(null);
@ -450,38 +507,44 @@ oop.inherits(CliRequisition, Requisition);
// a complete novice a 'type help' message is very annoying, so we // 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 // need to find a way to only display this message once, or for
// until the user click a 'close' button or similar // 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); this.setCommand(null);
return; return;
} }
var command = _split(args); var conversion = _split(args);
if (!command) { if (!conversion.value) {
// No command found - bail helpfully. // No command found - bail helpfully.
var commandType = types.getType('command'); this.localHints.push(new ConversionHint(conversion, conversion.arg));
var conversion = commandType.parse(input.typed);
var arg = Argument.merge(args);
this._addHint(new ConversionHint(conversion, arg));
this.setCommand(null); this.setCommand(null);
} }
else { else {
// The user hasn't started to type any arguments var message = documentCommand(conversion.value);
if (args.length === 0) { this.localHints.push(new Hint(Status.VALID, message, conversion.arg));
var message = documentCommand(command);
this._addHint(Status.VALID, message, 0, input.typed.length);
}
this.setCommand(command); this.setCommand(conversion.value);
this._assign(args); 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 // Not knowing about cursor positioning, the requisition and assignments
// can't know this, but anything they mark as INCOMPLETE is actually // can't know this, but anything they mark as INCOMPLETE is actually
// INVALID unless the cursor is actually inside that argument. // INVALID unless the cursor is actually inside that argument.
var c = input.cursor; var c = this.input.cursor;
this.hints.forEach(function(hint) { hints.forEach(function(hint) {
var startInHint = c.start >= hint.start && c.start <= hint.end; var startInHint = c.start >= hint.start && c.start <= hint.end;
var endInHint = c.end >= hint.start && c.end <= hint.end; var endInHint = c.end >= hint.start && c.end <= hint.end;
var inHint = startInHint || endInHint; var inHint = startInHint || endInHint;
@ -490,30 +553,7 @@ oop.inherits(CliRequisition, Requisition);
} }
}, this); }, this);
return; return Hint.sort(hints, this.input.cursor.start);
};
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));
}
}; };
/** /**
@ -538,9 +578,9 @@ oop.inherits(CliRequisition, Requisition);
// TODO: previously we were doing some extra work to avoid this if // TODO: previously we were doing some extra work to avoid this if
// we determined that we had args that were all whitespace, but // we determined that we had args that were all whitespace, but
// probably given our tighter tokenize() this won't be an issue? // probably given our tighter tokenize() this won't be an issue?
this._addHint(Status.INVALID, this.localHints.push(new Hint(Status.INVALID,
this.command.name + ' does not take any parameters', this.command.name + ' does not take any parameters',
Argument.merge(args)); Argument.merge(args)));
return; return;
} }
@ -580,9 +620,9 @@ oop.inherits(CliRequisition, Requisition);
else { else {
if (i + 1 < args.length) { if (i + 1 < args.length) {
// Missing value portion of this named param // Missing value portion of this named param
this._addHint(Status.INCOMPLETE, this.localHints.push(new Hint(Status.INCOMPLETE,
'Missing value for: ' + namedArgText, 'Missing value for: ' + namedArgText,
args[i]); args[i]));
} }
else { else {
args.splice(i + 1, 1); args.splice(i + 1, 1);
@ -612,9 +652,9 @@ oop.inherits(CliRequisition, Requisition);
if (args.length > 0) { if (args.length > 0) {
var remaining = Argument.merge(args); var remaining = Argument.merge(args);
this._addHint(Status.INVALID, this.localHints.push(new Hint(Status.INVALID,
'Input \'' + remaining.text + '\' makes no sense.', 'Input \'' + remaining.text + '\' makes no sense.',
remaining); remaining));
} }
}; };
@ -671,63 +711,71 @@ function _tokenize(typed) {
var str = unescape(typed.substring(start, i)); var str = unescape(typed.substring(start, i));
args.push(new Argument(str, start, i, priorSpace)); 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; break;
} }
var c = typed[i]; var c = typed[i];
switch (mode) { switch (mode) {
case OUTSIDE: case OUTSIDE:
if (c === '\'') { if (c === '\'') {
priorSpace = typed.substring(start, i); priorSpace = typed.substring(start, i);
mode = IN_SINGLE_Q; mode = IN_SINGLE_Q;
start = i + 1; start = i + 1;
} }
else if (c === '"') { else if (c === '"') {
priorSpace = typed.substring(start, i); priorSpace = typed.substring(start, i);
mode = IN_DOUBLE_Q; mode = IN_DOUBLE_Q;
start = i + 1; start = i + 1;
} }
else if (/ /.test(c)) { else if (/ /.test(c)) {
// Still whitespace, do nothing // Still whitespace, do nothing
} }
else { else {
priorSpace = typed.substring(start, i); priorSpace = typed.substring(start, i);
mode = IN_SIMPLE; mode = IN_SIMPLE;
start = i; start = i;
} }
break; break;
case IN_SIMPLE: case IN_SIMPLE:
// There is an edge case of xx'xx which we are assuming to be // There is an edge case of xx'xx which we are assuming to be
// a single parameter (and same with ") // a single parameter (and same with ")
if (c === ' ') { if (c === ' ') {
var str = unescape(typed.substring(start, i)); var str = unescape(typed.substring(start, i));
args.push(new Argument(str, start, i, priorSpace)); args.push(new Argument(str, start, i, priorSpace));
mode = OUTSIDE; mode = OUTSIDE;
start = i; start = i;
priorSpace = ''; priorSpace = '';
} }
break; break;
case IN_SINGLE_Q: case IN_SINGLE_Q:
if (c === '\'') { if (c === '\'') {
var str = unescape(typed.substring(start, i)); var str = unescape(typed.substring(start, i));
args.push(new Argument(str, start, i, priorSpace)); args.push(new Argument(str, start, i, priorSpace));
mode = OUTSIDE; mode = OUTSIDE;
start = i + 1; start = i + 1;
priorSpace = ''; priorSpace = '';
} }
break; break;
case IN_DOUBLE_Q: case IN_DOUBLE_Q:
if (c === '"') { if (c === '"') {
var str = unescape(typed.substring(start, i)); var str = unescape(typed.substring(start, i));
args.push(new Argument(str, start, i, priorSpace)); args.push(new Argument(str, start, i, priorSpace));
mode = OUTSIDE; mode = OUTSIDE;
start = i + 1; start = i + 1;
priorSpace = ''; priorSpace = '';
} }
break; break;
} }
i++; i++;
@ -742,24 +790,23 @@ exports._tokenize = _tokenize;
* typed at the command line. * typed at the command line.
*/ */
function _split(args) { function _split(args) {
var commandType = types.getType('command');
if (args.length === 0) { if (args.length === 0) {
return undefined; return commandType.parse('');
} }
var argsUsed = 0; var argsUsed = 1;
var lookup = ''; var arg;
var command; var conversion;
while (true) { while (argsUsed <= args.length) {
argsUsed++; var arg = Argument.merge(args, 0, argsUsed);
lookup += args.map(function(arg) { conversion = commandType.parse(arg.text);
return arg.text; conversion.arg = arg; // TODO: make this automatic?
}).slice(0, argsUsed).join(' ');
command = canon.getCommand(lookup);
if (!command) { if (!conversion.value) {
// Not found. break with command == null // Not found. break with value == null
return undefined; break;
} }
/* /*
@ -772,21 +819,18 @@ function _split(args) {
} }
*/ */
if (command.exec) { if (conversion.value.exec) {
// Valid command, break with command valid // Valid command, break with command valid
for (var i = 0; i < argsUsed; i++) {
args.shift();
}
break; break;
} }
// command, but no exec - this must be a sub-command argsUsed++;
lookup += ' ';
} }
// Remove the used args return conversion;
for (var i = 0; i < argsUsed; i++) {
args.shift();
}
return command;
} }
exports._split = _split; exports._split = _split;

View file

@ -41,9 +41,8 @@ exports.startup = function(data, reason) {
window.testCli = require('cockpit/test/testCli'); window.testCli = require('cockpit/test/testCli');
var plain = require('cockpit/ui/plain'); require('cockpit/ui/settings').startup(data, reason);
plain.startup(data, reason); require('cockpit/ui/plain').startup(data, reason);
}; };
/* /*

View file

@ -125,19 +125,19 @@ exports.testTokenize = function() {
exports.testSplit = function() { exports.testSplit = function() {
var args = tokenize('s'); var args = tokenize('s');
var command = split(args); var conversion = split(args);
test.verifyEqual(1, args.length); test.verifyEqual(1, args.length);
test.verifyEqual('s', args[0].text); test.verifyEqual('s', args[0].text);
test.verifyUndefined(command); test.verifyNull(conversion.value);
var args = tokenize('set'); var args = tokenize('set');
var command = split(args); var conversion = split(args);
test.verifyEqual([], args); test.verifyEqual([], args);
test.verifyEqual('set', command.name); test.verifyEqual('set', conversion.value.name);
var args = tokenize('set a b'); var args = tokenize('set a b');
var command = split(args); var conversion = split(args);
test.verifyEqual('set', command.name); test.verifyEqual('set', conversion.value.name);
test.verifyEqual(2, args.length); test.verifyEqual(2, args.length);
test.verifyEqual('a', args[0].text); test.verifyEqual('a', args[0].text);
test.verifyEqual('b', args[1].text); test.verifyEqual('b', args[1].text);
@ -152,14 +152,26 @@ exports.testCli = function() {
var settingAssignment; var settingAssignment;
var valueAssignment; var valueAssignment;
var cli = new CliRequisition(); var cli = new CliRequisition();
var debug = true;
function update(input) { function update(input) {
if (debug) {
console.log('####### TEST: typed="' + input.typed + '" cursor:', input.cursor);
}
cli.update(input); cli.update(input);
hints = cli.getHints(); hints = cli.getHints();
hint0 = (hints.length !== 0) ? hints[0] : undefined; hint0 = (hints.length !== 0) ? hints[0] : undefined;
if (debug) {
console.log('cli=', cli);
console.log('hints=', hints);
}
if (cli.command && cli.command.name === 'set') { if (cli.command && cli.command.name === 'set') {
settingAssignment = cli.getAssignment('setting'); settingAssignment = cli.getAssignment('setting');
valueAssignment = cli.getAssignment('value'); valueAssignment = cli.getAssignment('value');
if (debug) {
console.log('settingAssignment=', settingAssignment);
console.log('valueAssignment=', valueAssignment);
}
} }
else { else {
settingAssignment = undefined; 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'); var historyLengthSetting = settings.getSetting('historyLength');
update({ typed: '', cursor: { start: 0, end: 0 } }); update({ typed: '', cursor: { start: 0, end: 0 } });
@ -184,8 +202,9 @@ exports.testCli = function() {
test.verifyEqual(1, hint0.end); test.verifyEqual(1, hint0.end);
test.verifyTrue(hint0.predictions.length > 0); test.verifyTrue(hint0.predictions.length > 0);
// This is slightly fragile because it depends on the configuration // 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.verifyTrue(hint0.predictions.length < 20);
test.verifyNotEqual(-1, hint0.predictions.indexOf('set')); verifyPredictionsContains('set', hint0.predictions);
test.verifyNull(cli.command); test.verifyNull(cli.command);
update({ typed: 'set', cursor: { start: 3, end: 3 } }); update({ typed: 'set', cursor: { start: 3, end: 3 } });
@ -196,73 +215,79 @@ exports.testCli = function() {
test.verifyEqual('set', cli.command.name); test.verifyEqual('set', cli.command.name);
update({ typed: 'set ', cursor: { start: 4, end: 4 } }); 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(Status.VALID, hint0.status);
test.verifyEqual(0, hint0.start); test.verifyEqual(4, hint0.start);
// Technically the command ends at 3, but we're returning 4 currently. test.verifyEqual(4, hint0.end);
// This is caused by us using the whole input to determine the length. test.verifyEqual(Status.VALID, hints[1].status);
// Maybe one day we should fix this? test.verifyEqual(0, hints[1].start);
//test.verifyEqual(3, hint0.end); test.verifyEqual(3, hints[1].end);
test.verifyEqual('set', cli.command.name); test.verifyEqual('set', cli.command.name);
update({ typed: 'set h', cursor: { start: 5, end: 5 } }); 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.verifyEqual(Status.INCOMPLETE, hint0.status);
test.verifyTrue(hint0.predictions.length > 0);
test.verifyEqual(4, hint0.start); test.verifyEqual(4, hint0.start);
test.verifyEqual(5, hint0.end); 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('set', cli.command.name);
test.verifyEqual('h', settingAssignment.arg.text); test.verifyEqual('h', settingAssignment.arg.text);
test.verifyEqual(undefined, settingAssignment.value); test.verifyEqual(undefined, settingAssignment.value);
update({ typed: 'set historyLengt', cursor: { start: 16, end: 16 } }); 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(Status.INCOMPLETE, hint0.status);
test.verifyEqual(1, hint0.predictions.length);
test.verifyEqual(4, hint0.start); test.verifyEqual(4, hint0.start);
test.verifyEqual(16, hint0.end); 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('set', cli.command.name);
test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual('historyLengt', settingAssignment.arg.text);
test.verifyEqual(undefined, settingAssignment.value); test.verifyEqual(undefined, settingAssignment.value);
update({ typed: 'set historyLengt', cursor: { start: 1, end: 1 } }); update({ typed: 'set historyLengt', cursor: { start: 1, end: 1 } });
test.verifyEqual(1, hints.length); test.verifyEqual(2, hints.length);
test.verifyEqual(Status.INVALID, hint0.status); test.verifyEqual(Status.VALID, hint0.status);
test.verifyEqual(4, hint0.start); test.verifyEqual(0, hint0.start);
test.verifyEqual(16, hint0.end); test.verifyEqual(3, hint0.end);
test.verifyEqual(1, hint0.predictions.length); test.verifyEqual(Status.INVALID, hints[1].status);
test.verifyEqual('historyLength', hint0.predictions[0]); 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('set', cli.command.name);
test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual('historyLengt', settingAssignment.arg.text);
test.verifyEqual(undefined, settingAssignment.value); test.verifyEqual(undefined, settingAssignment.value);
update({ typed: 'set historyLengt ', cursor: { start: 17, end: 17 } }); update({ typed: 'set historyLengt ', cursor: { start: 17, end: 17 } });
test.verifyEqual(1, hints.length); test.verifyEqual(3, hints.length);
test.verifyEqual(Status.INVALID, hint0.status); test.verifyEqual(Status.VALID, hint0.status);
test.verifyEqual(4, hint0.start); test.verifyEqual(17, hint0.start);
test.verifyEqual(16, hint0.end); test.verifyEqual(17, hint0.end);
test.verifyEqual(1, hint0.predictions.length); test.verifyEqual(Status.INVALID, hints[1].status);
test.verifyEqual('historyLength', hint0.predictions[0]); 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('set', cli.command.name);
test.verifyEqual('historyLengt', settingAssignment.arg.text); test.verifyEqual('historyLengt', settingAssignment.arg.text);
test.verifyEqual(undefined, settingAssignment.value); test.verifyEqual(undefined, settingAssignment.value);
update({ typed: 'set historyLength', cursor: { start: 17, end: 17 } }); 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('set', cli.command.name);
test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual('historyLength', settingAssignment.arg.text);
test.verifyEqual(historyLengthSetting, settingAssignment.value); test.verifyEqual(historyLengthSetting, settingAssignment.value);
update({ typed: 'set historyLength ', cursor: { start: 18, end: 18 } }); 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('set', cli.command.name);
test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual('historyLength', settingAssignment.arg.text);
test.verifyEqual(historyLengthSetting, settingAssignment.value); test.verifyEqual(historyLengthSetting, settingAssignment.value);
update({ typed: 'set historyLength 6', cursor: { start: 19, end: 19 } }); 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('set', cli.command.name);
test.verifyEqual('historyLength', settingAssignment.arg.text); test.verifyEqual('historyLength', settingAssignment.arg.text);
test.verifyEqual(historyLengthSetting, settingAssignment.value); test.verifyEqual(historyLengthSetting, settingAssignment.value);

View file

@ -19,13 +19,13 @@
padding: 8px; padding: 8px;
} }
.cptCompletion.VALID { background: rgba(210, 255, 210, 1); } .cptCompletion.VALID { background: #FFF; }
.cptCompletion.INCOMPLETE { background: rgba(255, 230, 210, 1); } .cptCompletion.INCOMPLETE { background: #DDD; }
.cptCompletion.INVALID { background: rgba(255, 210, 210, 1); } .cptCompletion.INVALID { background: #DDD; }
.cptCompletion span { color: #FFF; } .cptCompletion span { color: #FFF; }
.cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #ff8800; } .cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #F80; }
.cptCompletion span.INVALID { text-shadow: 0px 0px 5px #ff0000; } .cptCompletion span.INVALID { text-shadow: 0px 0px 5px #F00; }
#cockpit, .cptCompletion { #cockpit, .cptCompletion {

View file

@ -59,9 +59,10 @@ var Status = require('pilot/types').Status;
* 2. Attach a set of events so the command line works * 2. Attach a set of events so the command line works
*/ */
exports.startup = function(data, reason) { exports.startup = function(data, reason) {
// TODO: We should probably cut this up into an object
var settings = data.env.settings;
var doc = document; var doc = document;
var win = doc.defaultView; var win = doc.defaultView;
var cli = new CliRequisition(); var cli = new CliRequisition();
// TODO: we should have a better way to specify command lines??? // TODO: we should have a better way to specify command lines???
@ -71,7 +72,7 @@ exports.startup = function(data, reason) {
return; return;
} }
var templates = doc.createElement('dic'); var templates = doc.createElement('div');
templates.innerHTML = plainRow; templates.innerHTML = plainRow;
var row = templates.firstChild; var row = templates.firstChild;
@ -88,12 +89,22 @@ exports.startup = function(data, reason) {
input.parentNode.insertBefore(output, input); input.parentNode.insertBefore(output, input);
function resizer() { function resizer() {
var style = win.getComputedStyle(input, null); var top, height, left, width;
var top = parseInt(style.getPropertyValue('top'), 10); if (input.getClientRects) {
var height = parseInt(style.getPropertyValue('height'), 10); var rect = input.getClientRects()[0];
var left = parseInt(style.getPropertyValue('left'), 10); top = rect.top;
var width = parseInt(style.getPropertyValue('width'), 10); 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.top = top + 'px';
completer.style.height = height + 'px'; completer.style.height = height + 'px';
@ -125,6 +136,20 @@ exports.startup = function(data, reason) {
}, this); }, this);
}.bind(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 // All this does is to kill TABs normal use. I wonder if we can train
// people to use right arrow? Probably not? but ... // 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; var message = worst.message;
if (worst.predictions && worst.predictions.length > 0) { if (worst.predictions && worst.predictions.length > 0) {
message += ' [ '; message += ': [ ';
worst.predictions.forEach(function(prediction) { worst.predictions.forEach(function(prediction) {
if (prediction.name) { if (prediction.name) {
message += prediction.name + ' | '; message += prediction.name + ' | ';

View file

@ -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);
};
});

View file

@ -84,22 +84,15 @@ var setCommandSpec = {
'<br/>'; '<br/>';
}); });
} else { } else {
var setting = env.settings.get(args.setting);
if (!setting) {
request.doneWithError('No setting with the name <strong>' +
setting.name + '</strong>.');
return;
}
// set with only a setting, shows the value for that setting // set with only a setting, shows the value for that setting
if (args.value === undefined) { if (args.value === undefined) {
html = '<strong>' + setting.name + '</strong> = ' + html = '<strong>' + setting.name + '</strong> = ' +
setting.get(); setting.get();
} else { } else {
// Actually change the setting // Actually change the setting
setting.set(args.value); args.setting.set(args.value);
html = 'Setting: <strong>' + setting.name + '</strong> = ' + html = 'Setting: <strong>' + args.setting.name + '</strong> = ' +
setting.get(); args.setting.get();
} }
} }
request.done(html); request.done(html);

View file

@ -50,10 +50,8 @@ packages.unshift("require", "exports", "module");
define(packages, function(require, exports, module) { define(packages, function(require, exports, module) {
console.log(packages);
exports.startup = function(data, reason) { exports.startup = function(data, reason) {
deps.forEach(function(dep) { deps.forEach(function(dep) {
console.log("test startup for " + dep);
var module = require(dep); var module = require(dep);
if (typeof module.startup === "function") { if (typeof module.startup === "function") {
module.startup(data, reason); module.startup(data, reason);

View file

@ -128,32 +128,32 @@ SelectionType.prototype.parse = function(str) {
} }
var data = (typeof(this.data) === "function") ? this.data() : this.data; 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 = []; var completions = [];
data.forEach(function(option) { data.forEach(function(option) {
if (str == option) { if (str == option) {
match = this.fromString(option); matchedValue = this.fromString(option);
hasMatched = true;
} }
else if (option.indexOf(str) === 0) { else if (option.indexOf(str) === 0) {
completions.push(this.fromString(option)); completions.push(this.fromString(option));
} }
}, this); }, this);
if (match) { if (hasMatched) {
return new Conversion(match); return new Conversion(matchedValue);
} }
else { else {
if (completions.length > 0) { if (completions.length > 0) {
return new Conversion(null, var msg = 'Several possibilities' +
Status.INCOMPLETE, (str.length === 0 ? '' : ' for \'' + str + '\'');
'Several possibilities for \'' + str + '\'', return new Conversion(null, Status.INCOMPLETE, msg, completions);
completions);
} }
else { else {
return new Conversion(null, var msg = 'Can\'t use \'' + str + '\'.';
Status.INVALID, return new Conversion(null, Status.INVALID, msg, completions);
'Can\'t use \'' + str + '\'.',
completions);
} }
} }
}; };
@ -203,7 +203,6 @@ DeferredType.prototype.stringify = function(value) {
}; };
DeferredType.prototype.parse = function(value) { DeferredType.prototype.parse = function(value) {
console.log(this.defer);
return this.defer().parse(value); return this.defer().parse(value);
}; };