diff --git a/plugins/cockpit/lib/cli.js b/plugins/cockpit/lib/cli.js
index 780408c2..06e3642a 100644
--- a/plugins/cockpit/lib/cli.js
+++ b/plugins/cockpit/lib/cli.js
@@ -66,16 +66,17 @@ exports.startup = function(data, reason) {
function Hint(status, message, start, end, predictions) {
this.status = status;
this.message = message;
- this.predictions = predictions;
if (typeof start === 'number') {
this.start = start;
this.end = end;
+ this.predictions = predictions;
}
else {
var arg = start;
this.start = arg.start;
this.end = arg.end;
+ this.predictions = arg.predictions;
}
}
Hint.prototype = {
@@ -191,6 +192,13 @@ Argument.prototype = {
var ev = { argument: this, oldText: this.text, text: text };
this.text = text;
this.emitter._dispatchEvent('argumentChange', ev);
+ },
+
+ /**
+ * Helper when we're putting arguments back together
+ */
+ toString: function() {
+ return this.priorSpace + this.text;
}
};
/**
@@ -309,6 +317,14 @@ Assignment.prototype = {
* make sense with more experience to alter this to function to be getHint()
*/
getHint: function() {
+ // Allow the parameter to provide documentation
+ if (this.param.getCustomHint && this.value && this.arg) {
+ var hint = this.param.getCustomHint(this.value, this.arg);
+ if (hint) {
+ return hint;
+ }
+ }
+
// If there is no argument, use the cursor position
var message = '' + this.param.name + ': ';
if (this.param.description) {
@@ -345,12 +361,6 @@ Assignment.prototype = {
message += 'Required<\strong>';
}
- // Allow the parameter to provide documentation
- // TODO: consider when we should do this
- if (status === Status.VALID && message === '' && this.param.documentValid) {
- message = this.param.documentValid(value);
- }
-
return new Hint(status, message, start, end, predictions);
},
@@ -385,6 +395,13 @@ Assignment.prototype = {
if (replacement != null) {
this.setValue(replacement);
}
+ },
+
+ /**
+ * Helper when we're rebuilding command lines.
+ */
+ toString: function() {
+ return this.arg ? this.arg.toString() : '';
}
};
exports.Assignment = Assignment;
@@ -401,7 +418,7 @@ var commandParam = {
/**
* Provide some documentation for a command.
*/
- documentValid: function(command) {
+ getCustomHint: function(command, arg) {
var docs = [];
docs.push(' > ');
docs.push(command.name);
@@ -440,7 +457,7 @@ var commandParam = {
docs.push('');
}
- return docs.join('');
+ return new Hint(Status.VALID, docs.join(''), arg);
}
};
@@ -716,11 +733,10 @@ oop.inherits(CliRequisition, Requisition);
CliRequisition.prototype.toString = function() {
// All the params
var parts = Object.keys(this._assignments).map(function(name) {
- var arg = this._assignments[name].arg;
- return arg ? arg.priorSpace + arg.text : '';
+ return this._assignments[name].toString();
}, this);
// Prefix with the command
- parts.unshift(this.commandAssignment.arg.text);
+ parts.unshift(this.commandAssignment.toString());
return parts.join('');
};
@@ -768,30 +784,33 @@ oop.inherits(CliRequisition, Requisition);
* at the given position.
*/
CliRequisition.prototype.getAssignmentAt = function(position) {
- var found;
-
var arg = this.commandAssignment.arg;
- if (arg && arg.start <= position && arg.end >= position) {
- found = this.commandAssignment;
+ if (arg && position <= arg.end) {
+ return this.commandAssignment;
}
- if (!found) {
- Object.keys(this._assignments).forEach(function(name) {
- var assignment = this._assignments[name];
- var arg = assignment.arg;
- if (arg && arg.start <= position && arg.end >= position) {
- found = assignment;
- }
- }, this);
+ var names = Object.keys(this._assignments);
+ for (var i = 0; i < names.length; i++) {
+ var assignment = this._assignments[names[i]];
+ if (assignment.arg && position <= assignment.arg.end) {
+ return assignment;
+ }
}
- return found;
+ // We can only have got here if
+ throw new Error('position (' + position +
+ ') is off end of requisition (' + this.toString() + ')');
};
/**
* Split up the input taking into account ' and "
*/
CliRequisition.prototype._tokenize = function(typed) {
+ // For blank input, place a dummy empty argument into the list
+ if (typed == null || typed.length === 0) {
+ return [ new Argument(this, '', 0, 0, '') ];
+ }
+
var OUTSIDE = 1; // The last character was whitespace
var IN_SIMPLE = 2; // The last character was part of a parameter
var IN_SINGLE_Q = 3; // We're inside a single quote: '
@@ -916,12 +935,6 @@ oop.inherits(CliRequisition, Requisition);
* typed at the command line.
*/
CliRequisition.prototype._split = function(args) {
- // Place a dummy empty argument into the list
- // TODO: should this go into _tokenize?
- if (args.length === 0) {
- args.push(new Argument(this, '', 0, 0, ''));
- }
-
var argsUsed = 1;
var arg;
@@ -981,7 +994,7 @@ oop.inherits(CliRequisition, Requisition);
// we determined that we had args that were all whitespace, but
// probably given our tighter tokenize() this won't be an issue?
this._hints.push(new Hint(Status.INVALID,
- this.command.name + ' does not take any parameters',
+ this.commandAssignment.value.name + ' does not take any parameters',
Argument.merge(args)));
return;
}
diff --git a/plugins/cockpit/lib/test/testCli.js b/plugins/cockpit/lib/test/testCli.js
index 809df6b1..26caf643 100644
--- a/plugins/cockpit/lib/test/testCli.js
+++ b/plugins/cockpit/lib/test/testCli.js
@@ -58,7 +58,11 @@ exports.testTokenize = function() {
var cli = new CliRequisition();
args = cli._tokenize('');
- test.verifyEqual(0, args.length);
+ test.verifyEqual(1, args.length);
+ test.verifyEqual('', args[0].text);
+ test.verifyEqual(0, args[0].start);
+ test.verifyEqual(0, args[0].end);
+ test.verifyEqual('', args[0].priorSpace);
args = cli._tokenize('s');
test.verifyEqual(1, args.length);
@@ -211,7 +215,7 @@ exports.testCli = function() {
test.verifyNull(cli.commandAssignment.value);
update({ typed: ' ', cursor: { start: 1, end: 1 } });
- test.verifyEqual('1', statuses);
+ test.verifyEqual('0', statuses);
test.verifyEqual(1, cli._hints.length);
test.verifyEqual(Status.INCOMPLETE, display.status);
test.verifyEqual(1, display.start);
@@ -220,7 +224,7 @@ exports.testCli = function() {
test.verifyNull(cli.commandAssignment.value);
update({ typed: ' ', cursor: { start: 0, end: 0 } });
- test.verifyEqual('1', statuses);
+ test.verifyEqual('0', statuses);
test.verifyEqual(1, cli._hints.length);
test.verifyEqual(Status.INCOMPLETE, display.status);
test.verifyEqual(1, display.start);
diff --git a/plugins/cockpit/lib/ui/cliView.js b/plugins/cockpit/lib/ui/cliView.js
index bd2c047f..5a9c1a3b 100644
--- a/plugins/cockpit/lib/ui/cliView.js
+++ b/plugins/cockpit/lib/ui/cliView.js
@@ -187,7 +187,6 @@ CliView.prototype = {
* Ensure that TAB isn't handled by the browser
*/
onKeyDown: function(ev) {
- this.isUpdating = true;
var handled;
// var handled = keyboardManager.processKeyEvent(ev, this, {
// isCommandLine: true, isKeyUp: false
@@ -197,7 +196,6 @@ CliView.prototype = {
ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
return true;
}
- this.isUpdating = false;
return handled;
},
@@ -205,7 +203,6 @@ CliView.prototype = {
* The main keyboard processing loop
*/
onKeyUp: function(ev) {
- this.isUpdating = true;
var handled;
/*
var handled = keyboardManager.processKeyEvent(ev, this, {
@@ -230,30 +227,28 @@ CliView.prototype = {
}
}
+ this.update();
+
// Special actions which delegate to the assignment
var current = this.cli.getAssignmentAt(this.element.selectionStart);
if (current) {
- this.isUpdating = false;
-
// TAB does a special complete thing
if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
current.complete();
+ this.update();
}
// UP/DOWN look for some history
if (ev.keyCode === keyutil.KeyHelper.KEY.UP) {
current.increment();
+ this.update();
}
if (ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
current.decrement();
+ this.update();
}
-
- this.isUpdating = true;
}
- this.update();
-
- this.isUpdating = false;
return handled;
},
@@ -261,82 +256,93 @@ CliView.prototype = {
* Actually parse the input and make sure we're all up to date
*/
update: function() {
- this.cli.update({
+ this.isUpdating = true;
+ var input = {
typed: this.element.value,
cursor: {
start: this.element.selectionStart,
end: this.element.selectionEnd
}
- });
+ };
+ this.cli.update(input);
- // TODO: borked implementation? This is modern browser only. Fix
+ var display = this.cli.getAssignmentAt(input.cursor.start).getHint();
+
+ // 1. Update the completer with prompt/error marker/TAB info
this.completer.classList.remove(Status.VALID.toString());
this.completer.classList.remove(Status.INCOMPLETE.toString());
this.completer.classList.remove(Status.INVALID.toString());
+ // TODO: borked implementation? This is modern browser only. Fix
// dom.removeCssClass(completer, Status.VALID.toString());
// dom.removeCssClass(completer, Status.INCOMPLETE.toString());
// dom.removeCssClass(completer, Status.INVALID.toString());
- // Create a marked up version of the input
- var highlightedInput = '> ';
+ var completion = '> ';
if (this.element.value.length > 0) {
var scores = this.cli.getInputStatusMarkup();
- // Create mark-up
- var i = 0;
- var lastStatus = -1;
- while (true) {
- if (lastStatus !== scores[i]) {
- highlightedInput += '';
- lastStatus = scores[i];
- }
- highlightedInput += this.element.value[i];
- i++;
- if (i === this.element.value.length) {
- highlightedInput += '';
- break;
- }
- if (lastStatus !== scores[i]) {
- highlightedInput += '';
- }
- }
+ completion += this.markupStatusScore(scores);
}
// Display the "-> prediction" at the end of the completer
- var display = this.cli.getAssignmentAt(this.element.selectionStart).getHint();
- var message = '';
- if (this.element.value.length !== 0) {
- message += display.message;
- if (display.predictions && display.predictions.length > 0) {
- message += ': [ ';
- display.predictions.forEach(function(prediction) {
- if (prediction.name) {
- message += prediction.name + ' | ';
- }
- else {
- message += prediction + ' | ';
- }
- }, this);
- message = message.replace(/\| $/, ']');
+ if (this.element.value.length > 0 &&
+ display.predictions && display.predictions.length > 0) {
+ var tab = display.predictions[0];
+ completion += ' ⇥ ' + (tab.name ? tab.name : tab);
+ }
+ this.completer.innerHTML = completion;
+ this.completer.classList.add(this.cli.getWorstHint().status.toString());
+ // dom.addCssClass(input, this.cli.getWorstHint().status.toString());
- var onTab = display.predictions[0];
- onTab = onTab.name ? onTab.name : onTab;
- this.completer.innerHTML = highlightedInput + ' ⇥ ' + onTab;
- }
- else {
- this.completer.innerHTML = highlightedInput;
+ // 2. Update the hint element
+ var hint = '';
+ if (this.element.value.length !== 0) {
+ hint += display.message;
+ if (display.predictions && display.predictions.length > 0) {
+ hint += ': [ ';
+ display.predictions.forEach(function(prediction) {
+ hint += (prediction.name ? prediction.name : prediction);
+ hint += ' | ';
+ }, this);
+ hint = hint.replace(/\| $/, ']');
}
}
- this.hinter.innerHTML = message;
- if (message.length === 0) {
+ this.hinter.innerHTML = hint;
+ if (hint.length === 0) {
this.hinter.classList.add('cptNoHints');
}
else {
this.hinter.classList.remove('cptNoHints');
}
- this.completer.classList.add(this.cli.getWorstHint().status.toString());
- // dom.addCssClass(input, this.cli.getWorstHint().status.toString());
+ this.isUpdating = false;
+ },
+
+ /**
+ * Markup an array of Status values with spans
+ */
+ markupStatusScore: function(scores) {
+ var completion = '';
+ // Create mark-up
+ var i = 0;
+ var lastStatus = -1;
+ while (true) {
+ if (lastStatus !== scores[i]) {
+ completion += '';
+ lastStatus = scores[i];
+ }
+ completion += this.element.value[i];
+ i++;
+ if (i === this.element.value.length) {
+ completion += '';
+ break;
+ }
+ if (lastStatus !== scores[i]) {
+ completion += '';
+ }
+ }
+
+ return completion;
},
/**
diff --git a/plugins/pilot/lib/types/basic.js b/plugins/pilot/lib/types/basic.js
index b119e879..9d775f6a 100644
--- a/plugins/pilot/lib/types/basic.js
+++ b/plugins/pilot/lib/types/basic.js
@@ -158,8 +158,13 @@ SelectionType.prototype.parse = function(str) {
return new Conversion(matchedValue);
}
else {
+ // This is something of a hack. settings
+ if (this.noMatch) {
+ this.noMatch();
+ }
+
if (completions.length > 0) {
- var msg = 'Several possibilities' +
+ var msg = 'Possibilities' +
(str.length === 0 ? '' : ' for \'' + str + '\'');
return new Conversion(null, Status.INCOMPLETE, msg, completions);
}
diff --git a/plugins/pilot/lib/types/settings.js b/plugins/pilot/lib/types/settings.js
index 4b64d069..065bdca5 100644
--- a/plugins/pilot/lib/types/settings.js
+++ b/plugins/pilot/lib/types/settings.js
@@ -66,6 +66,9 @@ var setting = new SelectionType({
fromString: function(str) {
lastSetting = settings.getSetting(str);
return lastSetting;
+ },
+ noMatch: function() {
+ lastSetting = null;
}
});
@@ -76,7 +79,12 @@ var setting = new SelectionType({
var settingValue = new DeferredType({
name: 'settingValue',
defer: function() {
- return lastSetting.type;
+ if (lastSetting) {
+ return lastSetting.type;
+ }
+ else {
+ return types.getType('text');
+ }
}
});