diff --git a/plugins/cockpit/lib/cli.js b/plugins/cockpit/lib/cli.js
index 01d81a34..169a5983 100644
--- a/plugins/cockpit/lib/cli.js
+++ b/plugins/cockpit/lib/cli.js
@@ -391,7 +391,7 @@ Requisition.prototype = {
* Where parameter name == assignment names - they are the same.
*/
getParameterNames: function() {
- return Object.keys(this._assignments);
+ return Object.keys(this._assignments);
},
/**
@@ -495,7 +495,7 @@ oop.inherits(CliRequisition, Requisition);
CliRequisition.prototype.update = function(input) {
this.input = input;
// TODO: We only store this so getHints can work. Find a better way.
- this.localHints = [];
+ this.hints = [];
if (util.none(input.typed)) {
this.setCommand(null);
@@ -508,44 +508,55 @@ 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.localHints.push(new Hint(Status.INCOMPLETE, '', 0, 0));
+ this.hints.push(new Hint(Status.INCOMPLETE, '', 0, 0));
this.setCommand(null);
+ this._annotateHints();
return;
}
var conversion = _split(args);
if (!conversion.value) {
// No command found - bail helpfully.
- this.localHints.push(new ConversionHint(conversion, conversion.arg));
+ this.hints.push(new ConversionHint(conversion, conversion.arg));
this.setCommand(null);
- }
- else {
- var message = documentCommand(conversion.value);
- this.localHints.push(new Hint(Status.VALID, message, conversion.arg));
-
- this.setCommand(conversion.value);
- this._assign(args);
+ this._annotateHints();
+ return;
}
- return;
- };
+ var message = documentCommand(conversion.value);
+ this.hints.push(new Hint(Status.VALID, message, conversion.arg));
- CliRequisition.prototype.getHints = function() {
- var hints = this.localHints.slice(0);
+ this.setCommand(conversion.value);
+ this._assign(args);
+ // Add the hints from the assignments to those already collected
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.hints.push.apply(this.hints, assignment.getHints());
}
}, this);
+ this._annotateHints();
+ return;
+ };
+
+ /**
+ * Marks up hints in a number of ways:
+ * - Makes INCOMPLETE hints that are not near the cursor INVALID since
+ * they can't be completed by typing
+ * - Finds the most severe hint, and annotates the array with it
+ * - Finds the hint to display, and also annotates the array with it
+ * TODO: I'm wondering if array annotation is evil and we should replace
+ * this with an object. Need to find out more.
+ */
+ CliRequisition.prototype._annotateHints = function() {
// 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 = this.input.cursor;
- hints.forEach(function(hint) {
+ this.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;
@@ -554,7 +565,35 @@ oop.inherits(CliRequisition, Requisition);
}
}, this);
- return Hint.sort(hints, this.input.cursor.start);
+ // Work out what the worst hint is (irrespective of the cursor). We
+ // return the hints in order of display importance - i.e. an INCOMPLETE
+ // hint under the cursor should be displayed before an INVALID hint
+ // somewhere else. That's good for displaying hints, but not good for
+ // deciding if we're good to go.
+ if (this.hints.length > 1) {
+ Hint.sort(this.hints);
+ this.hints.worst = this.hints[0];
+ }
+ else if (this.hints.length > 0) {
+ this.hints.worst = this.hints[0];
+ }
+
+ Hint.sort(this.hints, this.input.cursor.start);
+ this.hints.display = this.hints[0];
+
+ return this.hints;
+ };
+
+ /**
+ * Accessor for the hints array.
+ * While we could just use the hints property, using getHints() is
+ * preferred for symmetry with Requisition where it needs a function due to
+ * lack of an atomic update system.
+ * TODO: When we use this properly (i.e. with a fancy UI) then
+ * CliRequisition will also not have an atomic update system. Hmmmmm
+ */
+ CliRequisition.prototype.getHints = function() {
+ return this.hints;
};
/**
@@ -579,7 +618,7 @@ 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.localHints.push(new Hint(Status.INVALID,
+ this.hints.push(new Hint(Status.INVALID,
this.command.name + ' does not take any parameters',
Argument.merge(args)));
return;
@@ -621,7 +660,7 @@ oop.inherits(CliRequisition, Requisition);
else {
if (i + 1 < args.length) {
// Missing value portion of this named param
- this.localHints.push(new Hint(Status.INCOMPLETE,
+ this.hints.push(new Hint(Status.INCOMPLETE,
'Missing value for: ' + namedArgText,
args[i]));
}
@@ -653,7 +692,7 @@ oop.inherits(CliRequisition, Requisition);
if (args.length > 0) {
var remaining = Argument.merge(args);
- this.localHints.push(new Hint(Status.INVALID,
+ this.hints.push(new Hint(Status.INVALID,
'Input \'' + remaining.text + '\' makes no sense.',
remaining));
}
@@ -842,62 +881,41 @@ exports._split = _split;
*/
function documentCommand(command) {
var docs = [];
- docs.push('
' + command.name + '
');
- docs.push('Summary
');
- docs.push('' + command.description + '
');
-
- if (command.manual) {
- docs.push('Description
');
- docs.push('' + command.description + '
');
- }
-
+ docs.push(' > ');
+ docs.push(command.name);
if (command.params && command.params.length > 0) {
- docs.push('Synopsis
');
- docs.push('');
- docs.push(command.name);
- var optionalParamCount = 0;
command.params.forEach(function(param) {
if (param.defaultValue === undefined) {
- docs.push(' ');
- docs.push(param.name);
- docs.push('');
- }
- else if (param.defaultValue === null) {
- docs.push(' [');
- docs.push(param.name);
- docs.push(']');
+ docs.push(' [' + param.name + ']');
}
else {
- optionalParamCount++;
+ docs.push(' [' + param.name + ']');
}
}, this);
- if (optionalParamCount > 3) {
- docs.push(' [options]');
- } else if (optionalParamCount > 0) {
- command.params.forEach(function(param) {
- if (param.defaultValue) {
- docs.push(' [--');
- docs.push(param.name);
- if (param.type.name === 'boolean') {
- docs.push('');
- }
- else {
- docs.push(' ' + param.type.name);
- }
- docs.push(']');
- }
- }, this);
- }
- docs.push('');
+ }
+ docs.push('
');
- docs.push('Parameters
');
+ docs.push(command.description ? command.description : '(No description)');
+ docs.push('
');
+
+ if (command.params && command.params.length > 0) {
+ docs.push('');
command.params.forEach(function(param) {
- docs.push('' + param.name + '
');
- docs.push('' + param.description + '
');
- if (param.type.defaultValue) {
- docs.push('Default: ' + param.type.defaultValue + '
');
+ docs.push('- ');
+ docs.push('' + param.name + ': ');
+ docs.push(param.description ? param.description : '(No description)');
+ if (param.defaultValue === undefined) {
+ docs.push(' [Required]');
}
+ else if (param.defaultValue === null) {
+ docs.push(' [Optional]');
+ }
+ else {
+ docs.push(' [Default: ' + param.defaultValue + ']');
+ }
+ docs.push('
');
}, this);
+ docs.push('
');
}
return docs.join('');
diff --git a/plugins/cockpit/lib/ui/plain.css b/plugins/cockpit/lib/ui/plain.css
index bd1c000f..dfb47463 100644
--- a/plugins/cockpit/lib/ui/plain.css
+++ b/plugins/cockpit/lib/ui/plain.css
@@ -1,10 +1,37 @@
-/* Command line completion */
+#cockpit {
+ background: transparent;
+ border: none; outline: none;
+ font-family: consolas, courier, monospace;
+ font-size: 120%;
+}
+
+.cptOutput {
+ border: 1px solid #AAA;
+ -moz-border-radius-topleft: 10px;
+ -moz-border-radius-topright: 10px;
+ border-top-left-radius: 4px; border-top-right-radius: 4px;
+ padding: 10px;
+ margin: 0 15px;
+ background: #DDD; color: #000;
+ overflow: hidden;
+ position: absolute;
+ z-index: 999;
+ font-family: consolas, courier, monospace;
+ display: none;
+}
+
+#cockpit:focus ~ .cptOutput {
+ display: block;
+}
+
.cptCompletion {
color: #666;
padding: 0 0 5px 2px;
position: absolute;
z-index: -1000;
+ font-family: consolas, courier, monospace;
+ font-size: 120%;
}
.cptHints {
@@ -17,6 +44,17 @@
border-top-left-radius: 10px; border-top-right-radius: 10px;
z-index: 1000;
padding: 8px;
+ display: none;
+}
+
+.cptHints ul { margin: 0; padding: 0 15px; }
+
+#cockpit:focus ~ .cptHints {
+ display: block;
+}
+
+#cockpit:focus ~ .cptHints.cptNoHints {
+ display: none;
}
.cptCompletion.VALID { background: #FFF; }
@@ -24,34 +62,8 @@
.cptCompletion.INVALID { background: #DDD; }
.cptCompletion span { color: #FFF; }
-.cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #F80; }
-.cptCompletion span.INVALID { text-shadow: 0px 0px 5px #F00; }
-
-
-#cockpit, .cptCompletion {
- font-family: consolas, courier, monospace;
- font-size: 120%;
-}
-
-#cockpit {
- background: transparent;
- border: none; outline: none;
-}
-
-.cptOutput {
- border: 1px solid #AAA;
- -moz-border-radius-topleft: 10px;
- -moz-border-radius-topright: 10px;
- border-top-left-radius: 4px; border-top-right-radius: 4px;
- padding: 10px;
- margin: 0 15px;
- background: #DDD; color: #000;
- font-family: consolas, courier, monospace;
- font-size: 90%;
- overflow: hidden;
- position: absolute;
- z-index: 999;
-}
+.cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }
+.cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }
diff --git a/plugins/cockpit/lib/ui/plain.js b/plugins/cockpit/lib/ui/plain.js
index cb0ea8ac..fc4805d6 100644
--- a/plugins/cockpit/lib/ui/plain.js
+++ b/plugins/cockpit/lib/ui/plain.js
@@ -81,12 +81,11 @@ function CliView(data) {
this.cli = new CliRequisition();
this.settings = data.env.settings;
- this.showHint = this.settings.getSetting('showHint');
+ this.hintDirection = this.settings.getSetting('hintDirection');
+ this.outputDirection = this.settings.getSetting('outputDirection');
this.outputHeight = this.settings.getSetting('outputHeight');
this.hints = [];
- this.shownHint;
- this.worstHint;
this.createElements();
}
@@ -95,29 +94,35 @@ CliView.prototype = {
* Create divs for completion, hints and output
*/
createElements: function() {
+ var input = this.element;
+
this.completer = this.doc.createElement('div');
this.completer.className = 'cptCompletion VALID';
- this.element.parentNode.insertBefore(this.completer, this.element);
+ input.parentNode.insertBefore(this.completer, input.nextSibling);
this.hinter = this.doc.createElement('div');
this.hinter.className = 'cptHints';
- this.element.parentNode.insertBefore(this.hinter, this.element);
+ input.parentNode.insertBefore(this.hinter, input.nextSibling);
this.output = this.doc.createElement('div');
this.output.className = 'cptOutput';
- this.element.parentNode.insertBefore(this.output, this.element);
+ input.parentNode.insertBefore(this.output, input.nextSibling);
this.win.addEventListener('resize', this.resizer.bind(this), false);
+ this.hintDirection.addEventListener('change', this.resizer.bind(this));
+ this.outputDirection.addEventListener('change', this.resizer.bind(this));
this.resizer();
- canon.addEventListener('output', this.showOutput.bind(this));
+ canon.addEventListener('output', function(ev) {
+ new RequestView(ev.request, this);
+ }.bind(this));
- this.showHint.addEventListener('change', this.hintShower.bind(this));
- this.hintShower();
-
- keyutil.addKeyDownListener(this.element, this.onKeyDown.bind(this));
- this.element.addEventListener('mouseup', this.onMouseUp.bind(this), false);
- this.element.addEventListener('keyup', this.onKeyUp.bind(this), true);
+ keyutil.addKeyDownListener(input, this.onKeyDown.bind(this));
+ input.addEventListener('keyup', this.onKeyUp.bind(this), true);
+ // cursor position affects hint severity. TODO: shortcuts for speed
+ input.addEventListener('mouseup', function(ev) {
+ this.update();
+ }.bind(this), false);
},
/**
@@ -125,84 +130,34 @@ CliView.prototype = {
* with the input box.
*/
resizer: function() {
- var top, height, left, width;
+ var rect = this.element.getClientRects()[0];
- if (this.element.getClientRects) {
- var rect = this.element.getClientRects()[0];
- top = rect.top;
- height = rect.height;
- left = rect.left;
- width = rect.width;
+ this.completer.style.top = rect.top + 'px';
+ this.completer.style.height = rect.height + 'px';
+ this.completer.style.left = rect.left + 'px';
+ this.completer.style.width = rect.width + 'px';
+
+ if (this.hintDirection.get() === 'below') {
+ this.hinter.style.top = rect.bottom + 'px';
+ this.hinter.style.bottom = 'auto';
}
else {
- var style = this.win.getComputedStyle(this.element, 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);
+ this.hinter.style.top = 'auto';
+ this.hinter.style.bottom = (this.win.innerHeight - rect.top) + 'px';
}
+ this.hinter.style.left = (rect.left + 30) + 'px';
+ this.hinter.style.maxWidth = (rect.width - 90) + 'px';
- this.completer.style.top = top + 'px';
- this.completer.style.height = height + 'px';
- this.completer.style.left = left + 'px';
- this.completer.style.width = width + 'px';
-
- this.hinter.style.bottom = (this.win.innerHeight - top) + 'px';
- this.hinter.style.left = (left + 30) + 'px';
-
- this.output.style.bottom = (this.win.innerHeight - top) + 'px';
- this.output.style.left = left + 'px';
- this.output.style.width = (width - 60) + 'px';
- },
-
- /**
- * Update the display of executed commands
- */
- showOutput: function(ev) {
- var requestOutput = new RequestView(ev.request, this);
-
- /*
- // TODO: be less brutal in how we update this
- this.output.innerHTML = '';
-
- new RequestView(ev.request, this);
- ev.requests.forEach(function(request) {
- // this.duration.innerHTML = this.request.duration ?
- // 'completed in ' + (this.request.duration / 1000) + ' sec ' :
- // '';
- // this.typed.innerHTML = this.request.typed;
-
- request.outputs.forEach(function(output) {
- var node;
- if (typeof output == 'string') {
- node = document.createElement('p');
- node.innerHTML = output;
- } else {
- node = output;
- }
- this.output.appendChild(node);
- }, this);
-
- // this.cliView.scrollToBottom();
- // util.setClass(this.output, 'cmd_error', this.request.error);
- // this.throb.style.display = this.request.completed ? 'none' : 'block';
- }, this);
- */
- },
-
- /**
- * Show/hide the hint line.
- * It's not clear that this is actually useful, however it does help to
- * highlight some features for right now.
- * TODO: remove this?
- */
- hintShower: function() {
- if (this.showHint.get()) {
- this.hinter.style.display = 'block';
+ if (this.outputDirection.get() === 'below') {
+ this.output.style.top = rect.bottom + 'px';
+ this.output.style.bottom = 'auto';
}
else {
- this.hinter.style.display = 'none';
+ this.output.style.top = 'auto';
+ this.output.style.bottom = (this.win.innerHeight - rect.top) + 'px';
}
+ this.output.style.left = rect.left + 'px';
+ this.output.style.width = (rect.width - 60) + 'px';
},
/**
@@ -231,21 +186,17 @@ CliView.prototype = {
*/
if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
- if (this.worstHint && this.worstHint.status !== Status.VALID) {
- this.element.selectionStart = this.worstHint.start;
- this.element.selectionEnd = this.worstHint.end;
- }
- else {
+ if (this.hints.worst || this.hints.worst.status === Status.VALID) {
this.cli.exec();
this.element.value = '';
}
}
- if (ev.keyCode === keyutil.KeyHelper.KEY.TAB && this.shownHint &&
- this.shownHint.predictions && this.shownHint.predictions.length > 0) {
- var prefix = this.element.value.substring(0, this.shownHint.start);
- var suffix = this.element.value.substring(this.shownHint.end);
- var insert = this.shownHint.predictions[0];
+ if (ev.keyCode === keyutil.KeyHelper.KEY.TAB && this.hints.display &&
+ this.hints.display.predictions && this.hints.display.predictions.length > 0) {
+ var prefix = this.element.value.substring(0, this.hints.display.start);
+ var suffix = this.element.value.substring(this.hints.display.end);
+ var insert = this.hints.display.predictions[0];
insert = typeof insert === 'string' ? insert : insert.name;
this.element.value = prefix + insert + suffix;
// Fix the cursor.
@@ -256,15 +207,14 @@ CliView.prototype = {
this.update();
- return handled;
- },
+ if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
+ if (this.hints.worst && this.hints.worst.status !== Status.VALID) {
+ this.element.selectionStart = this.hints.worst.start;
+ this.element.selectionEnd = this.hints.worst.end;
+ }
+ }
- /**
- * Cause an update if the cursor changes position due to a mouse click
- * TODO: there are probably some performance wins here.
- */
- onMouseUp: function(ev) {
- this.update();
+ return handled;
},
/**
@@ -289,15 +239,6 @@ CliView.prototype = {
this.hints = this.cli.getHints();
- // Those hints came in order of display importance - i.e. an INCOMPLETE
- // hint under the cursor should be displayed before an INVALID hint
- // somewhere else. That's good for displaying hints, but not good for
- // deciding if we're good to go.
- if (this.hints.length > 1) {
- hintClone = this.hints.slice(0);
- this.worstHint = Hint.sort(hintClone)[0];
- }
-
// Create a marked up version of the input
var highlightedInput = '';
if (this.element.value.length > 0) {
@@ -335,11 +276,11 @@ CliView.prototype = {
}
// Display the "-> prediction" at the end of the completer
- this.shownHint = (this.hints.length > 0) ? this.hints[0] : NO_HINT;
- var message = this.shownHint.message;
- if (this.shownHint.predictions && this.shownHint.predictions.length > 0) {
+ var display = this.hints.display || NO_HINT;
+ var message = display.message;
+ if (display.predictions && display.predictions.length > 0) {
message += ': [ ';
- this.shownHint.predictions.forEach(function(prediction) {
+ display.predictions.forEach(function(prediction) {
if (prediction.name) {
message += prediction.name + ' | ';
}
@@ -349,16 +290,22 @@ CliView.prototype = {
}, this);
message = message.replace(/\| $/, ']');
- var onTab = this.shownHint.predictions[0];
+ var onTab = display.predictions[0];
onTab = onTab.name ? onTab.name : onTab;
- this.completer.innerHTML = highlightedInput + ' -> ' + onTab;
+ this.completer.innerHTML = highlightedInput + ' → ' + onTab;
}
else {
this.completer.innerHTML = highlightedInput;
}
this.hinter.innerHTML = message;
+ if (message.length === 0) {
+ this.hinter.classList.add('cptNoHints');
+ }
+ else {
+ this.hinter.classList.remove('cptNoHints');
+ }
- var status = this.worstHint ? this.worstHint.status : Status.VALID;
+ var status = this.hints.worst ? this.hints.worst.status : Status.VALID;
this.completer.classList.add(status.toString());
// dom.addCssClass(input, status.toString());
}
diff --git a/plugins/cockpit/lib/ui/requestView.js b/plugins/cockpit/lib/ui/requestView.js
index b9cb3708..83aa6c7c 100644
--- a/plugins/cockpit/lib/ui/requestView.js
+++ b/plugins/cockpit/lib/ui/requestView.js
@@ -61,7 +61,7 @@ function RequestView(request, cliView) {
this.request = request;
this.cliView = cliView;
- this.imagePath = '/plugins/ace/pluging/cockpit/ui';
+ this.imagePath = '/plugins/ace/plugins/cockpit/lib/ui/images';
// Elements attached to this by the templater. For info only
this.rowin = null;
@@ -86,7 +86,7 @@ RequestView.prototype = {
* the command line
*/
copyToInput: function() {
- this.cliView.input.value = this.request.typed;
+ this.cliView.element.value = this.request.typed;
},
/**
diff --git a/plugins/cockpit/lib/ui/settings.js b/plugins/cockpit/lib/ui/settings.js
index 80d95e73..dea79554 100644
--- a/plugins/cockpit/lib/ui/settings.js
+++ b/plugins/cockpit/lib/ui/settings.js
@@ -38,11 +38,26 @@
define(function(require, exports, module) {
-var showHintSetting = {
- name: "showHint",
- description: "Do we display hints while we type?",
- type: "bool",
- defaultValue: true
+var types = require("pilot/types");
+var SelectionType = require('pilot/types/basic').SelectionType;
+
+var direction = new SelectionType({
+ name: 'direction',
+ data: [ 'above', 'below' ]
+});
+
+var hintDirectionSetting = {
+ name: "hintDirection",
+ description: "Are hints shown above or below the command line?",
+ type: "direction",
+ defaultValue: "above"
+};
+
+var outputDirectionSetting = {
+ name: "outputDirection",
+ description: "Is the output window shown above or below the command line?",
+ type: "direction",
+ defaultValue: "above"
};
var outputHeightSetting = {
@@ -53,12 +68,16 @@ var outputHeightSetting = {
};
exports.startup = function(data, reason) {
- data.env.settings.addSetting(showHintSetting);
+ types.registerType(direction);
+ data.env.settings.addSetting(hintDirectionSetting);
+ data.env.settings.addSetting(outputDirectionSetting);
data.env.settings.addSetting(outputHeightSetting);
};
exports.shutdown = function(data, reason) {
- data.env.settings.removeSetting(showHintSetting);
+ types.unregisterType(direction);
+ data.env.settings.removeSetting(hintDirectionSetting);
+ data.env.settings.removeSetting(outputDirectionSetting);
data.env.settings.removeSetting(outputHeightSetting);
};
diff --git a/plugins/pilot/lib/index.js b/plugins/pilot/lib/index.js
index f576b29c..5166a833 100644
--- a/plugins/pilot/lib/index.js
+++ b/plugins/pilot/lib/index.js
@@ -41,6 +41,8 @@ var deps = [
"pilot/types/command",
"pilot/types/settings",
"pilot/commands/settings",
+ "pilot/commands/basic",
+ // "pilot/commands/history",
"pilot/settings/canon",
"pilot/canon"
];