make the command line ui process hints at least to a basic extent
This commit is contained in:
parent
b75e43962a
commit
1e65d71bde
12 changed files with 911 additions and 90 deletions
|
|
@ -70,6 +70,50 @@ function Hint(status, message, start, end) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
// Calculate 'distance from cursor'
|
||||
if (cursor !== undefined) {
|
||||
hints.forEach(function(hint) {
|
||||
if (cursor < hint.start) {
|
||||
hint.distance = hint.start - cursor;
|
||||
}
|
||||
else {
|
||||
if (cursor > hint.end) {
|
||||
hint.distance = cursor - hint.end;
|
||||
}
|
||||
else {
|
||||
hint.distance = 0;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
// Sort
|
||||
hints.sort(function(hint1, hint2) {
|
||||
// Compare first based on distance from cursor
|
||||
if (cursor !== undefined) {
|
||||
var diff = hint1.distance - hint2.distance;
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
// otherwise go with hint severity
|
||||
return hint1 - hint2;
|
||||
});
|
||||
// tidy-up
|
||||
if (cursor !== undefined) {
|
||||
hints.forEach(function(hint) {
|
||||
delete hint.distance;
|
||||
}, this);
|
||||
}
|
||||
return hints[0];
|
||||
};
|
||||
exports.Hint = Hint;
|
||||
|
||||
/**
|
||||
|
|
@ -816,6 +860,4 @@ function documentCommand(command) {
|
|||
exports.documentCommand = documentCommand;
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,2 +1,181 @@
|
|||
|
||||
#cockpit { color: red; }
|
||||
/* Command line completion */
|
||||
.cptCompletion {
|
||||
color: #666;
|
||||
padding: 0 0 5px 2px;
|
||||
position: absolute;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
.cptHints {
|
||||
color: #000;
|
||||
position: absolute;
|
||||
border: 1px solid rgba(230, 230, 230, 0.8);
|
||||
background: rgba(250, 250, 250, 0.8);
|
||||
-moz-border-radius-topleft: 10px;
|
||||
-moz-border-radius-topright: 10px;
|
||||
border-top-left-radius: 10px; border-top-right-radius: 10px;
|
||||
z-index: 1000;
|
||||
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 span { color: #FFF; }
|
||||
.cptCompletion span.INCOMPLETE { text-shadow: 0px 0px 2px #ff8800; }
|
||||
.cptCompletion span.INVALID { text-shadow: 0px 0px 5px #ff0000; }
|
||||
|
||||
|
||||
#cockpit, .cptCompletion {
|
||||
font-family: consolas, courier, monospace;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
#cockpit {
|
||||
background: transparent;
|
||||
border: none; outline: none;
|
||||
}
|
||||
|
||||
|
||||
.cptHints article {
|
||||
margin-left: 25px;
|
||||
padding: 8px;
|
||||
z-index: 1;
|
||||
color: @text;
|
||||
display: block;
|
||||
border: 2px @border_bg solid;
|
||||
border-bottom: 0;
|
||||
font-size: 90%;
|
||||
-moz-border-top-colors: @border_bg @border_fg;
|
||||
-moz-border-left-colors: @border_bg @border_fg;
|
||||
-moz-border-right-colors: @border_bg @bg;
|
||||
-moz-border-radius-topleft: 10px;
|
||||
-moz-border-radius-topright: 10px;
|
||||
border-top-left-radius: 10px; border-top-right-radius: 10px;
|
||||
background: @input_bg2;
|
||||
background: -moz-linear-gradient(top, @input_bg_light, @input_bg);
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(@input_bg_light), to(@input_bg));
|
||||
-webkit-box-shadow: rgba(0, 0, 0, 0.5) 0px 1px 10px 3px;
|
||||
-moz-box-shadow: rgba(0, 0, 0, 0.5) 0px 1px 10px 3px;
|
||||
max-width: 500px;
|
||||
}
|
||||
.cptHints article h1 { font-size: 110%; text-align: center; }
|
||||
.cptHints article h2 { font-size: 95%; }
|
||||
.cptHints article h3 { font-size: 90%; }
|
||||
.cptHints article table th,
|
||||
.cptHints article label { color: @hi_text; }
|
||||
.cptHints article h1,
|
||||
.cptHints article h2,
|
||||
.cptHints article h3 { margin: 2px 0 1px 0; color: @hi_text; }
|
||||
.cptHints article p,
|
||||
.cptHints article pre,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Global visual styles */
|
||||
.cptBrackets { color: @hi_text; }
|
||||
.cptGt { color: @theme_text; font-weight: bold; font-size: 120%; }
|
||||
|
||||
/* Use absolute positioning to put all children 100%x100% */
|
||||
.cptStack {
|
||||
position: relative;
|
||||
}
|
||||
.cptStack > * {
|
||||
position: absolute;
|
||||
top: 0; height: 100%; bottom: 0; left: 0; width: 100%; right: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Layout */
|
||||
.cptOutput {
|
||||
background: @bg; color: @text;
|
||||
font-family: @fonts; font-size: 90%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* The input area */
|
||||
.cptInput {
|
||||
border-top: 1px solid @border_bg;
|
||||
background: @input_bg;
|
||||
background: -moz-linear-gradient(top, @input_bg, @input_bg2);
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(@input_bg), to(@input_bg2));
|
||||
|
||||
position: absolute;
|
||||
height: 25px; right: 0; bottom: 0; left: 0;
|
||||
}
|
||||
|
||||
.cptPrompt {
|
||||
width: 40px; padding-left: 5px;
|
||||
position: absolute;
|
||||
top: 0; width: 40px; bottom: 0; left: 0;
|
||||
}
|
||||
|
||||
.cptKbd {
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 40px;
|
||||
}
|
||||
|
||||
.cptOutput input.cptCliInput {
|
||||
color: @hi_text; font-size: 120%; font-family: @fonts;
|
||||
background: transparent;
|
||||
outline: none; border: 0;
|
||||
box-shadow: none; -moz-box-shadow: none; -webkit-box-shadow: none;
|
||||
padding: 4px 0 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.cptOutput input.cptCliInput:focus {
|
||||
outline: none; border: 0;
|
||||
box-shadow: none; -moz-box-shadow: none; -webkit-box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.cptRowIn {
|
||||
display: box; display: -moz-box; display: -webkit-box;
|
||||
box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;
|
||||
box-align: center; -moz-box-align: center; -webkit-box-align: center;
|
||||
color: @text;
|
||||
background-color: @input_bg;
|
||||
width: 100%;
|
||||
}
|
||||
.cptRowIn > * { padding-left: 2px; padding-right: 2px; }
|
||||
.cptRowIn > img { cursor: pointer; }
|
||||
.cptHover { display: none; }
|
||||
.cptRowIn:hover > .cptHover { display: block; }
|
||||
.cptRowIn:hover > .cptHover.cptHidden { display: none; }
|
||||
.cptOutTyped {
|
||||
box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;
|
||||
font-weight: bold; color: @hi_text; font-size: 120%;
|
||||
}
|
||||
.cptRowOutput { padding-left: 10px; line-height: 1.2em; }
|
||||
.cptRowOutput strong,
|
||||
.cptRowOutput b,
|
||||
.cptRowOutput th,
|
||||
.cptRowOutput h1,
|
||||
.cptRowOutput h2,
|
||||
.cptRowOutput h3 { color: @hi_text; }
|
||||
.cptRowOutput a { font-weight: bold; color: @link_text; text-decoration: none; }
|
||||
.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }
|
||||
.cptRowOutput input[type=password],
|
||||
.cptRowOutput input[type=text],
|
||||
.cptRowOutput textarea {
|
||||
color: @hi_text; font-size: 120%;
|
||||
background: transparent; padding: 3px;
|
||||
border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;
|
||||
}
|
||||
.cptRowOutput table,
|
||||
.cptRowOutput td,
|
||||
.cptRowOutput th { border: 0; padding: 0 2px; }
|
||||
.cptRowOutput .right { text-align: right; }
|
||||
|
|
|
|||
|
|
@ -42,17 +42,88 @@ var dom = require("pilot/dom").dom;
|
|||
dom.importCssString(editorCss);
|
||||
|
||||
var CliRequisition = require('cockpit/cli').CliRequisition;
|
||||
var Hint = require('cockpit/cli').Hint;
|
||||
var keyutil = require('pilot/keyboard/keyutil');
|
||||
|
||||
var plainRow = require("text!cockpit/ui/plainRow.html");
|
||||
var Templater = require("pilot/domtemplate").Templater;
|
||||
var canon = require("pilot/canon");
|
||||
var Status = require('pilot/types').Status;
|
||||
|
||||
/**
|
||||
* On startup we need to:
|
||||
* 1. Add 3 sets of elements to the DOM for:
|
||||
* - command line output
|
||||
* - input hints
|
||||
* - completion
|
||||
* 2. Attach a set of events so the command line works
|
||||
*/
|
||||
exports.startup = function(data, reason) {
|
||||
var doc = document;
|
||||
var win = doc.defaultView;
|
||||
|
||||
var cli = new CliRequisition();
|
||||
|
||||
// TODO: we should have a better way to specify command lines???
|
||||
this.input = document.getElementById('cockpit');
|
||||
if (!this.input) {
|
||||
var input = doc.getElementById('cockpit');
|
||||
if (!input) {
|
||||
console.log('No element with an id of cockpit. Bailing on plain cli');
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = new CliRequisition();
|
||||
var templates = doc.createElement('dic');
|
||||
templates.innerHTML = plainRow;
|
||||
var row = templates.firstChild;
|
||||
|
||||
var completer = doc.createElement('div');
|
||||
completer.className = 'cptCompletion VALID';
|
||||
input.parentNode.insertBefore(completer, input);
|
||||
|
||||
var hinter = doc.createElement('div');
|
||||
hinter.className = 'cptHints';
|
||||
input.parentNode.insertBefore(hinter, input);
|
||||
|
||||
var output = doc.createElement('div');
|
||||
output.className = 'cptOutput';
|
||||
input.parentNode.insertBefore(output, input);
|
||||
|
||||
function resizer() {
|
||||
var style = win.getComputedStyle(input, null);
|
||||
|
||||
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);
|
||||
|
||||
completer.style.top = top + 'px';
|
||||
completer.style.height = height + 'px';
|
||||
completer.style.left = left + 'px';
|
||||
completer.style.width = width + 'px';
|
||||
|
||||
hinter.style.bottom = (win.innerHeight - top) + 'px';
|
||||
hinter.style.left = (left + 30) + 'px';
|
||||
|
||||
output.style.bottom = (win.innerHeight - top) + 'px';
|
||||
output.style.left = left + 'px';
|
||||
output.style.width = width + 'px';
|
||||
}
|
||||
|
||||
win.addEventListener('resize', resizer.bind(this), true);
|
||||
resizer();
|
||||
|
||||
// TODO: be less brutal in how we update this
|
||||
output.innerHTML = '';
|
||||
canon.addEventListener('output', function(ev) {
|
||||
ev.requests.forEach(function(request) {
|
||||
request.outputs.forEach(function(out) {
|
||||
if (typeof out === 'string') {
|
||||
output.appendChild(doc.createTextNode(out));
|
||||
} else {
|
||||
output.appendChild(out);
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
}.bind(this));
|
||||
|
||||
/*
|
||||
// All this does is to kill TABs normal use. I wonder if we can train
|
||||
|
|
@ -69,7 +140,11 @@ exports.startup = function(data, reason) {
|
|||
}.bind(this));
|
||||
*/
|
||||
|
||||
this.input.addEventListener('keyup', function(ev) {
|
||||
var NO_HINT = new Hint(Status.VALID, '', 0, 0);
|
||||
var hints = [];
|
||||
var worst;
|
||||
|
||||
input.addEventListener('keyup', function(ev) {
|
||||
/*
|
||||
var handled = keyboardManager.processKeyEvent(ev, this, {
|
||||
isCommandLine: true, isKeyUp: true
|
||||
|
|
@ -78,16 +153,88 @@ exports.startup = function(data, reason) {
|
|||
|
||||
if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
|
||||
cli.exec();
|
||||
this.input.value = '';
|
||||
input.value = '';
|
||||
} else {
|
||||
cli.update({
|
||||
typed: this.input.value,
|
||||
typed: input.value,
|
||||
cursor: {
|
||||
start: this.input.selectionStart,
|
||||
end: this.input.selectionEnd
|
||||
start: input.selectionStart,
|
||||
end: input.selectionEnd
|
||||
}
|
||||
});
|
||||
console.log(JSON.stringify(cli.getHints()));
|
||||
|
||||
completer.classList.remove(Status.VALID.toString());
|
||||
completer.classList.remove(Status.INCOMPLETE.toString());
|
||||
completer.classList.remove(Status.INVALID.toString());
|
||||
|
||||
// TODO: borked implementation?
|
||||
// dom.removeCssClass(completer, Status.VALID.toString());
|
||||
// dom.removeCssClass(completer, Status.INCOMPLETE.toString());
|
||||
// dom.removeCssClass(completer, Status.INVALID.toString());
|
||||
|
||||
hints = cli.getHints();
|
||||
|
||||
// Create a marked up version of the input
|
||||
var highlightedInput = '';
|
||||
if (input.value.length > 0) {
|
||||
// 'scores' is an array which tells us what chars are errors
|
||||
// Initialize with everything VALID
|
||||
var scores = input.value.split('').map(function(char) {
|
||||
return Status.VALID;
|
||||
});
|
||||
// For all chars in all hints, check and upgrade the score
|
||||
hints.forEach(function(hint) {
|
||||
for (var i = hint.start; i <= hint.end; i++) {
|
||||
if (hint.status > scores[i]) {
|
||||
scores[i] = hint.status;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
// Create markup
|
||||
var i = 0;
|
||||
var lastStatus = -1;
|
||||
while (true) {
|
||||
if (lastStatus !== scores[i]) {
|
||||
highlightedInput += '<span class=' + scores[i].toString() + '>';
|
||||
lastStatus = scores[i];
|
||||
}
|
||||
highlightedInput += input.value[i];
|
||||
i++;
|
||||
if (i === input.value.length) {
|
||||
highlightedInput += '</span>';
|
||||
break;
|
||||
}
|
||||
if (lastStatus !== scores[i]) {
|
||||
highlightedInput += '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worst = Hint.worst(hints) || NO_HINT;
|
||||
var message = worst.message;
|
||||
if (worst.predictions && worst.predictions.length > 0) {
|
||||
message += ' [ ';
|
||||
worst.predictions.forEach(function(prediction) {
|
||||
if (prediction.name) {
|
||||
message += prediction.name + ' | ';
|
||||
}
|
||||
else {
|
||||
message += prediction + ' | ';
|
||||
}
|
||||
}, this);
|
||||
message = message.replace(/\| $/, ']');
|
||||
|
||||
var completion = worst.predictions[0];
|
||||
completion = completion.name ? completion.name : completion;
|
||||
completer.innerHTML = highlightedInput + ' -> ' + completion;
|
||||
}
|
||||
else {
|
||||
completer.innerHTML = highlightedInput;
|
||||
}
|
||||
hinter.innerHTML = message;
|
||||
|
||||
completer.classList.add(worst.status.toString());
|
||||
// dom.addCssClass(input, worst.status.toString());
|
||||
}
|
||||
|
||||
// return handled;
|
||||
|
|
|
|||
47
plugins/cockpit/lib/ui/plainRow.html
Normal file
47
plugins/cockpit/lib/ui/plainRow.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
|
||||
<!-- The input area, with fixed height -->
|
||||
<div id=cptInput class=cptInput>
|
||||
<!-- The prompt -->
|
||||
<div class="cptPrompt cptGt"><span class="cptBrackets">{ }</span> ></div>
|
||||
<!-- Where you type commands -->
|
||||
<div class="cptKbd cptStack">
|
||||
<div class=cptCompletion save="${cliInputView._completer}"></div>
|
||||
<input class=cptCliInput save="${cliInputView._inputer}"/>
|
||||
</div>
|
||||
<!-- Help as you type -->
|
||||
<div id=cptHints class=cptHints>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class=cptRow>
|
||||
<!-- The div for the input (i.e. what was typed) -->
|
||||
<div class="cptRowIn" save="${actions.rowin}"
|
||||
onclick="${actions.copyToInput}"
|
||||
ondblclick="${actions.executeRequest}">
|
||||
|
||||
<!-- What the user actually typed -->
|
||||
<div class="cptGt">> </div>
|
||||
<div class="cptOutTyped" save="${actions.typed}"></div>
|
||||
|
||||
<!-- The extra details that appear on hover -->
|
||||
<div class=cptHover save="${actions.duration}"></div>
|
||||
<img class=cptHover onclick="${actions.hideOutput}" save="${actions.hide}"
|
||||
alt="Hide command output" _src="${imagePath}/minus.png"/>
|
||||
<img class="cptHover cptHidden" onclick="${actions.showOutput}" save="${actions.show}"
|
||||
alt="Show command output" _src="${imagePath}/plus.png"/>
|
||||
<img class=cptHover onclick="${actions.remove}"
|
||||
alt="Remove this command from the history" _src="${imagePath}/closer.png"/>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- The div for the command output -->
|
||||
<div class="cptRowOut" save="${actions.rowout}">
|
||||
<div class="cptRowOutput" save="${actions.output}"></div>
|
||||
<img src="${imagePath}/throbber.gif" save="${actions.throb}"/>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue