a big lump of work to get the command line able to execute commands, and to hack in a trivial cli
This commit is contained in:
parent
1e01007990
commit
38c9fc7a93
17 changed files with 1715 additions and 379 deletions
|
|
@ -45,7 +45,7 @@ var setupPlugins = function(config, callback) {
|
||||||
// packages: ["ace"]
|
// packages: ["ace"]
|
||||||
// };
|
// };
|
||||||
config.pluginDirs["../plugins"] = {
|
config.pluginDirs["../plugins"] = {
|
||||||
packages: ["pilot"]
|
packages: ["pilot", "cockpit"]
|
||||||
};
|
};
|
||||||
|
|
||||||
var knownPlugins = [];
|
var knownPlugins = [];
|
||||||
|
|
@ -109,7 +109,10 @@ var setupPlugins = function(config, callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
packagePaths: pluginPackageInfo,
|
||||||
|
paths: paths
|
||||||
|
}));
|
||||||
require({
|
require({
|
||||||
packagePaths: pluginPackageInfo,
|
packagePaths: pluginPackageInfo,
|
||||||
paths: paths
|
paths: paths
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ exports.launch = function(env) {
|
||||||
|
|
||||||
function onResize() {
|
function onResize() {
|
||||||
container.style.width = (document.documentElement.clientWidth - 4) + "px";
|
container.style.width = (document.documentElement.clientWidth - 4) + "px";
|
||||||
container.style.height = (document.documentElement.clientHeight - 55 - 4) + "px";
|
container.style.height = (document.documentElement.clientHeight - 55 - 4 - 23) + "px";
|
||||||
env.editor.resize();
|
env.editor.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
15
editor.html
15
editor.html
|
|
@ -47,6 +47,11 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cockpit {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
require = {
|
require = {
|
||||||
|
|
@ -63,11 +68,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, function(plugin_manager, settings) {
|
}, function(plugin_manager, settings) {
|
||||||
var data = {
|
var data = { env: { settings: settings } };
|
||||||
env: {
|
|
||||||
settings: settings
|
|
||||||
}
|
|
||||||
};
|
|
||||||
plugin_manager.catalog.startupPlugins(data, plugin_manager.REASONS.APP_STARTUP).then(function() {
|
plugin_manager.catalog.startupPlugins(data, plugin_manager.REASONS.APP_STARTUP).then(function() {
|
||||||
var demo_startup = require("demo_startup");
|
var demo_startup = require("demo_startup");
|
||||||
demo_startup.launch(data.env);
|
demo_startup.launch(data.env);
|
||||||
|
|
@ -155,6 +156,8 @@
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<input id=cockpit type=text/>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -142,31 +142,221 @@ Argument.merge = function(argArray, start, end) {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLI / UI Interface
|
* A link between a parameter and the data for that parameter.
|
||||||
* The Cli interacts with the UI via an instance of CliUi.
|
* The data for the parameter is available as in the preferred type and as
|
||||||
* This implementation is designed as a template rather than to be used.
|
* an Argument for the CLI.
|
||||||
* It is expected that we will have a number of implementations of this:
|
* <p>We also record validity information where applicable.
|
||||||
* - A firebug/webkit inspector cli shim
|
* <p>For values, null and undefined have distinct definitions. null means
|
||||||
* - A simple input[type=text] version
|
* that a value has been provided, undefined means that it has not.
|
||||||
* - A possible Cloud9 UI version
|
* Thus, null is a valid default value, and common because it identifies an
|
||||||
* - A possible Skywriter UI version
|
* parameter that is optional. undefined means there is no value from
|
||||||
* This class will probably need refactoring as time goes on.
|
* the command line.
|
||||||
*
|
* @constructor
|
||||||
* TODO: Who should own the Requisition?
|
|
||||||
*/
|
*/
|
||||||
function CliUi() {
|
function Assignment(param) {
|
||||||
}
|
this.param = param;
|
||||||
CliUi.prototype = {
|
this.setValue(param.defaultValue);
|
||||||
getSelection: function() {},
|
|
||||||
setHints: function() {},
|
|
||||||
setRequisition: function() {}
|
|
||||||
};
|
};
|
||||||
|
Assignment.prototype = {
|
||||||
|
/**
|
||||||
|
* The parameter that we are assigning to
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
param: undefined,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current value (i.e. not the string representation)
|
||||||
|
* Use setValue() to mutate
|
||||||
|
*/
|
||||||
|
value: undefined,
|
||||||
|
setValue: function(value) {
|
||||||
|
if (this.value === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value === undefined) {
|
||||||
|
value = this.param.defaultValue;
|
||||||
|
}
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
var text = (value == null) ? '' : this.param.type.stringify(value);
|
||||||
|
if (this.arg) {
|
||||||
|
this.arg.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.conversion = undefined;
|
||||||
|
//this._dispatchEvent('change', { assignment: this });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The textual representation of the current value
|
||||||
|
* Use setValue() to mutate
|
||||||
|
*/
|
||||||
|
arg: undefined,
|
||||||
|
setArgument: function(arg) {
|
||||||
|
if (this.arg === arg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.arg = arg;
|
||||||
|
this.conversion = this.param.type.parse(arg.text);
|
||||||
|
this.value = this.conversion.value;
|
||||||
|
//this._dispatchEvent('change', { assignment: this });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of this hints associated with this parameter assignment
|
||||||
|
*/
|
||||||
|
getHints: function() {
|
||||||
|
var hints = [];
|
||||||
|
if (this.conversion != null &&
|
||||||
|
(this.conversion.status !== Status.VALID ||
|
||||||
|
this.conversion.message)) {
|
||||||
|
hints.push(new ConversionHint(this.conversion, this.arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
var argProvided = this.arg != null && 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));
|
||||||
|
}
|
||||||
|
return hints;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report on the status of the last parse() conversion.
|
||||||
|
* @see types.Conversion
|
||||||
|
*/
|
||||||
|
conversion: undefined
|
||||||
|
};
|
||||||
|
oop.implement(Assignment, EventEmitter);
|
||||||
|
exports.Assignment = Assignment;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Requisition collects the information needed to execute a command.
|
||||||
|
* There is no point in a requisition for parameter-less commands because there
|
||||||
|
* is no information to collect. A Requisition is a collection of assignments
|
||||||
|
* of values to parameters, each handled by an instance of Assignment.
|
||||||
|
* CliRequisition adds functions for parsing input from a command line to this
|
||||||
|
* class
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Requisition() {
|
||||||
|
}
|
||||||
|
Requisition.prototype = {
|
||||||
|
/**
|
||||||
|
* The command that we are about to execute.
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
command: undefined,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The count of assignments
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
assignmentCount: undefined,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a new command. We make no attempt to convert the args in the old
|
||||||
|
* command to args in the new command. The assignments need to be
|
||||||
|
* re-entered.
|
||||||
|
*/
|
||||||
|
setCommand: function(command) {
|
||||||
|
if (this.command === command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.command = command;
|
||||||
|
this._assignments = {};
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
command.params.forEach(function(param) {
|
||||||
|
this._assignments[param.name] = new Assignment(param);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assignmentCount = Object.keys(this._assignments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assignments have an order, so we need to store them in an array.
|
||||||
|
* But we also need named access ...
|
||||||
|
*/
|
||||||
|
getAssignment: function(nameOrNumber) {
|
||||||
|
var name = (typeof nameOrNumber === 'string') ?
|
||||||
|
nameOrNumber :
|
||||||
|
Object.keys(this._assignments)[nameOrNumber];
|
||||||
|
return this._assignments[name];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where parameter name == assignment names - they are the same.
|
||||||
|
*/
|
||||||
|
getParameterNames: function() {
|
||||||
|
return Object.keys(this._assignments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A *shallow* clone of the assignments.
|
||||||
|
* This is useful for systems that wish to go over all the assignments
|
||||||
|
* finding values one way or another and wish to trim an array as they go.
|
||||||
|
*/
|
||||||
|
cloneAssignments: function() {
|
||||||
|
return Object.keys(this._assignments).map(function(name) {
|
||||||
|
return this._assignments[name];
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the statuses from the Assignments
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}, this);
|
||||||
|
return hints;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the names and values of all the assignments, and return as
|
||||||
|
* an object.
|
||||||
|
*/
|
||||||
|
getArgs: function() {
|
||||||
|
var args = {};
|
||||||
|
Object.keys(this._assignments).forEach(function(name) {
|
||||||
|
args[name] = this.getAssignment(name).value;
|
||||||
|
}, this);
|
||||||
|
return args;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all the assignments to their default values
|
||||||
|
*/
|
||||||
|
setDefaultValues: function() {
|
||||||
|
Object.keys(this._assignments).forEach(function(name) {
|
||||||
|
this._assignments[name].setValue(undefined);
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to call canon.exec
|
||||||
|
*/
|
||||||
|
exec: function() {
|
||||||
|
canon.exec(this.command, this.getArgs());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
exports.Requisition = Requisition;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object used during command line parsing to hold the various intermediate
|
* An object used during command line parsing to hold the various intermediate
|
||||||
* data steps.
|
* data steps.
|
||||||
* <p>The 'output' of the parse is held in 2 objects: input.hints which is an
|
* <p>The 'output' of the update is held in 2 objects: input.hints which is an
|
||||||
* array of hints to display to the user. In the future this will become a
|
* array of hints to display to the user. In the future this will become a
|
||||||
* single value.
|
* single value.
|
||||||
* <p>The other output value is input.requisition which gives access to an
|
* <p>The other output value is input.requisition which gives access to an
|
||||||
|
|
@ -187,99 +377,87 @@ CliUi.prototype = {
|
||||||
* if not specified.
|
* if not specified.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function Cli(cliui, options) {
|
function CliRequisition(options) {
|
||||||
this.cliui = cliui;
|
|
||||||
if (options && options.flags) {
|
if (options && options.flags) {
|
||||||
|
/**
|
||||||
|
* TODO: We were using a default of keyboard.buildFlags({ });
|
||||||
|
* This allowed us to have commands that only existed in certain contexts
|
||||||
|
* - i.e. Javascript specific commands.
|
||||||
|
*/
|
||||||
this.flags = options.flags;
|
this.flags = options.flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requisition = new Requisition();
|
|
||||||
this.cliui.setRequisition(this.requisition);
|
|
||||||
}
|
}
|
||||||
Cli.prototype = {
|
oop.inherits(CliRequisition, Requisition);
|
||||||
/**
|
(function() {
|
||||||
* TODO: We were using a default of keyboard.buildFlags({ });
|
|
||||||
* This allowed us to have commands that only existed in certain contexts
|
|
||||||
* - i.e. Javascript specific commands.
|
|
||||||
*/
|
|
||||||
flags: {},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
parse: function(typed) {
|
CliRequisition.prototype.update = function(input) {
|
||||||
if (util.none(typed)) {
|
this.hints = [];
|
||||||
this.requisition.setCommand(null);
|
|
||||||
this.cliui.setHints([]);
|
if (util.none(input.typed)) {
|
||||||
|
this.setCommand(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.typed = typed;
|
var args = _tokenize(input.typed);
|
||||||
this.hints = [];
|
|
||||||
|
|
||||||
var args = _tokenize(this.typed);
|
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
// We would like to put some initial help here, but for anyone but
|
// We would like to put some initial help here, but for anyone but
|
||||||
// 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._addHint(Status.INCOMPLETE, '', 0, 0);
|
||||||
this.requisition.setCommand(null);
|
this.setCommand(null);
|
||||||
this.cliui.setHints(this.hints);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var command = _split(args);
|
var command = _split(args);
|
||||||
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
// No command found - bail helpfully.
|
// No command found - bail helpfully.
|
||||||
var commandType = types.getType('command');
|
var commandType = types.getType('command');
|
||||||
var conversion = commandType.parse(typed);
|
var conversion = commandType.parse(input.typed);
|
||||||
var arg = Argument.merge(args);
|
var arg = Argument.merge(args);
|
||||||
this._addHint(new ConversionHint(conversion, arg));
|
this._addHint(new ConversionHint(conversion, arg));
|
||||||
|
|
||||||
this.requisition.setCommand(null);
|
this.setCommand(null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// The user hasn't started to type any arguments
|
// The user hasn't started to type any arguments
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
var message = documentCommand(command);
|
var message = documentCommand(command);
|
||||||
this._addHint(Status.VALID, message, 0, typed.length);
|
this._addHint(Status.VALID, message, 0, input.typed.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requisition.setCommand(command);
|
this.setCommand(command);
|
||||||
this._assign(args);
|
this._assign(args);
|
||||||
this._addHint(this.requisition.getHints());
|
this._addHint(CliRequisition.super_.getHints.call(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is the wrong place to filter this.
|
|
||||||
// It should be done by the CliUi because:
|
|
||||||
// - the cursor could move without notice
|
|
||||||
// - not all interfaces will have a notion of one cursor for the whole assignment
|
|
||||||
|
|
||||||
// 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 sel = this.cliui.getSelection();
|
var c = input.cursor;
|
||||||
this.hints.forEach(function(hint) {
|
this.hints.forEach(function(hint) {
|
||||||
var startInHint = sel.start >= hint.start && sel.start <= hint.end;
|
var startInHint = c.start >= hint.start && c.start <= hint.end;
|
||||||
var endInHint = sel.end >= hint.start && sel.end <= hint.end;
|
var endInHint = c.end >= hint.start && c.end <= hint.end;
|
||||||
var inHint = startInHint || endInHint;
|
var inHint = startInHint || endInHint;
|
||||||
if (!inHint && hint.status === Status.INCOMPLETE) {
|
if (!inHint && hint.status === Status.INCOMPLETE) {
|
||||||
hint.status = Status.INVALID;
|
hint.status = Status.INVALID;
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.cliui.setHints(this.hints);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
},
|
};
|
||||||
|
|
||||||
|
CliRequisition.prototype.getHints = function() {
|
||||||
|
return this.hints;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some sugar around: 'this.hints.push(new Hint(...));', but you can also
|
* 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
|
* pass in an array of Hints or the parameters to create a hint
|
||||||
*/
|
*/
|
||||||
_addHint: function(status, message, start, end) {
|
CliRequisition.prototype._addHint = function(status, message, start, end) {
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -292,7 +470,7 @@ Cli.prototype = {
|
||||||
else {
|
else {
|
||||||
this.hints.push(new Hint(status, message, start, end));
|
this.hints.push(new Hint(status, message, start, end));
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Work out which arguments are applicable to which parameters.
|
* Work out which arguments are applicable to which parameters.
|
||||||
|
|
@ -304,36 +482,36 @@ Cli.prototype = {
|
||||||
* <li>value - The matching input
|
* <li>value - The matching input
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
_assign: function(args) {
|
CliRequisition.prototype._assign = function(args) {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
this.requisition.setDefaultValues();
|
this.setDefaultValues();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an error if the command does not take parameters, but we have
|
// Create an error if the command does not take parameters, but we have
|
||||||
// been given them ...
|
// been given them ...
|
||||||
if (this.requisition.assignmentCount === 0) {
|
if (this.assignmentCount === 0) {
|
||||||
// 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._addHint(Status.INVALID,
|
||||||
this.requisition.command.name + ' does not take any parameters',
|
this.command.name + ' does not take any parameters',
|
||||||
Argument.merge(args));
|
Argument.merge(args));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: if there is only 1 parameter, and that's of type
|
// Special case: if there is only 1 parameter, and that's of type
|
||||||
// text we put all the params into the first param
|
// text we put all the params into the first param
|
||||||
if (this.requisition.assignmentCount == 1) {
|
if (this.assignmentCount == 1) {
|
||||||
var assignment = this.requisition.getAssignment(0);
|
var assignment = this.getAssignment(0);
|
||||||
if (assignment.param.type.name === 'text') {
|
if (assignment.param.type.name === 'text') {
|
||||||
assignment.setArgument(Argument.merge(args));
|
assignment.setArgument(Argument.merge(args));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var assignments = this.requisition.cloneAssignments();
|
var assignments = this.cloneAssignments();
|
||||||
var names = this.requisition.getParameterNames();
|
var names = this.getParameterNames();
|
||||||
|
|
||||||
// Extract all the named parameters
|
// Extract all the named parameters
|
||||||
var used = [];
|
var used = [];
|
||||||
|
|
@ -376,7 +554,7 @@ Cli.prototype = {
|
||||||
|
|
||||||
// What's left are positional parameters assign in order
|
// What's left are positional parameters assign in order
|
||||||
names.forEach(function(name) {
|
names.forEach(function(name) {
|
||||||
var assignment = this.requisition.getAssignment(name);
|
var assignment = this.getAssignment(name);
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
// No more values
|
// No more values
|
||||||
assignment.setValue(undefined); // i.e. default
|
assignment.setValue(undefined); // i.e. default
|
||||||
|
|
@ -394,9 +572,10 @@ Cli.prototype = {
|
||||||
'Input \'' + remaining.text + '\' makes no sense.',
|
'Input \'' + remaining.text + '\' makes no sense.',
|
||||||
remaining);
|
remaining);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
exports.Cli = Cli;
|
})();
|
||||||
|
exports.CliRequisition = CliRequisition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split up the input taking into account ' and "
|
* Split up the input taking into account ' and "
|
||||||
|
|
@ -637,214 +816,6 @@ function documentCommand(command) {
|
||||||
exports.documentCommand = documentCommand;
|
exports.documentCommand = documentCommand;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Requisition collects the information needed to execute a command.
|
|
||||||
* There is no point in a requisition for parameter-less commands because there
|
|
||||||
* is no information to collect. A Requisition is a collection of assignments
|
|
||||||
* of values to parameters, each handled by an instance of Assignment.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Requisition() {
|
|
||||||
}
|
|
||||||
Requisition.prototype = {
|
|
||||||
/**
|
|
||||||
* The command that we are about to execute.
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
command: undefined,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The count of assignments
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
assignmentCount: undefined,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a new command. We make no attempt to convert the args in the old
|
|
||||||
* command to args in the new command. The assignments need to be
|
|
||||||
* re-entered.
|
|
||||||
*/
|
|
||||||
setCommand: function(command) {
|
|
||||||
if (this.command === command) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.command = command;
|
|
||||||
this._assignments = {};
|
|
||||||
|
|
||||||
if (command) {
|
|
||||||
command.params.forEach(function(param) {
|
|
||||||
this._assignments[param.name] = new Assignment(param);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.assignmentCount = Object.keys(this._assignments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assignments have an order, so we need to store them in an array.
|
|
||||||
* But we also need named access ...
|
|
||||||
*/
|
|
||||||
getAssignment: function(nameOrNumber) {
|
|
||||||
var name = (typeof nameOrNumber === 'string') ?
|
|
||||||
nameOrNumber :
|
|
||||||
Object.keys(this._assignments)[nameOrNumber];
|
|
||||||
return this._assignments[name];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Where parameter name == assignment names - they are the same.
|
|
||||||
*/
|
|
||||||
getParameterNames: function() {
|
|
||||||
return Object.keys(this._assignments);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A *shallow* clone of the assignments.
|
|
||||||
* This is useful for systems that wish to go over all the assignments
|
|
||||||
* finding values one way or another and wish to trim an array as they go.
|
|
||||||
*/
|
|
||||||
cloneAssignments: function() {
|
|
||||||
return Object.keys(this._assignments).map(function(name) {
|
|
||||||
return this._assignments[name];
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect the statuses from the Assignments
|
|
||||||
*/
|
|
||||||
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());
|
|
||||||
}, this);
|
|
||||||
return hints;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the names and values of all the assignments, and return as
|
|
||||||
* an object.
|
|
||||||
*/
|
|
||||||
getArgs: function() {
|
|
||||||
var args = {};
|
|
||||||
Object.keys(this._assignments).forEach(function(name) {
|
|
||||||
args[name] = getCommand(name);
|
|
||||||
}, this);
|
|
||||||
return args;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset all the assignments to their default values
|
|
||||||
*/
|
|
||||||
setDefaultValues: function() {
|
|
||||||
Object.keys(this._assignments).forEach(function(name) {
|
|
||||||
this._assignments[name].setValue(undefined);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to call canon.exec
|
|
||||||
*/
|
|
||||||
exec: function() {
|
|
||||||
exports.exec(this.command, this.getArgs());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
exports.Requisition = Requisition;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A link between a parameter and the data for that parameter.
|
|
||||||
* The data for the parameter is available as in the preferred type and as
|
|
||||||
* an Argument for the CLI.
|
|
||||||
* <p>We also record validity information where applicable.
|
|
||||||
* <p>For values, null and undefined have distinct definitions. null means
|
|
||||||
* that a value has been provided, undefined means that it has not.
|
|
||||||
* 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.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Assignment(param) {
|
|
||||||
this.param = param;
|
|
||||||
this.setValue(param.defaultValue);
|
|
||||||
};
|
|
||||||
Assignment.prototype = {
|
|
||||||
/**
|
|
||||||
* The parameter that we are assigning to
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
param: undefined,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current value (i.e. not the string representation)
|
|
||||||
* Use setValue() to mutate
|
|
||||||
*/
|
|
||||||
value: undefined,
|
|
||||||
setValue: function(value) {
|
|
||||||
if (this.value === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value === undefined) {
|
|
||||||
value = this.param.defaultValue;
|
|
||||||
}
|
|
||||||
this.value = value;
|
|
||||||
|
|
||||||
var text = (value == null) ? '' : this.param.type.stringify(value);
|
|
||||||
if (this.arg) {
|
|
||||||
this.arg.setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conversion = undefined;
|
|
||||||
//this._dispatchEvent('change', { assignment: this });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The textual representation of the current value
|
|
||||||
* Use setValue() to mutate
|
|
||||||
*/
|
|
||||||
arg: undefined,
|
|
||||||
setArgument: function(arg) {
|
|
||||||
if (this.arg === arg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.arg = arg;
|
|
||||||
this.conversion = this.param.type.parse(arg.text);
|
|
||||||
this.value = this.conversion.value;
|
|
||||||
//this._dispatchEvent('change', { assignment: this });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a list of this hints associated with this parameter assignment
|
|
||||||
*/
|
|
||||||
getHints: function() {
|
|
||||||
var hints = [];
|
|
||||||
if (this.conversion != null &&
|
|
||||||
(this.conversion.status !== Status.VALID ||
|
|
||||||
this.conversion.message)) {
|
|
||||||
hints.push(new ConversionHint(this.conversion, this.arg));
|
|
||||||
}
|
|
||||||
|
|
||||||
var argProvided = this.arg != null && 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));
|
|
||||||
}
|
|
||||||
return hints;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report on the status of the last parse() conversion.
|
|
||||||
* @see types.Conversion
|
|
||||||
*/
|
|
||||||
conversion: undefined
|
|
||||||
};
|
|
||||||
oop.implement(Assignment, EventEmitter);
|
|
||||||
exports.Assignment = Assignment;
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
59
plugins/cockpit/lib/index.js
Normal file
59
plugins/cockpit/lib/index.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* ***** 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):
|
||||||
|
* Kevin Dangoor (kdangoor@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) {
|
||||||
|
|
||||||
|
exports.startup = function(data, reason) {
|
||||||
|
|
||||||
|
window.testCli = require('cockpit/test/testCli');
|
||||||
|
|
||||||
|
var plain = require('cockpit/ui/plain');
|
||||||
|
plain.startup(data, reason);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
exports.shutdown(data, reason) {
|
||||||
|
deps.forEach(function(dep) {
|
||||||
|
var module = require(dep);
|
||||||
|
if (typeof module.shutdown === "function") {
|
||||||
|
module.shutdown(data, reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
@ -38,10 +38,13 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
|
|
||||||
var test = require('pilot/test/assert').test;
|
var test = require('cockpit/test/assert').test;
|
||||||
var cli = require('pilot/cli');
|
|
||||||
var Status = require('pilot/types').Status;
|
var Status = require('pilot/types').Status;
|
||||||
var settings = require('pilot/settings').settings;
|
var settings = require('pilot/settings').settings;
|
||||||
|
var tokenize = require('cockpit/cli')._tokenize;
|
||||||
|
var split = require('cockpit/cli')._split;
|
||||||
|
var CliRequisition = require('cockpit/cli').CliRequisition;
|
||||||
|
|
||||||
|
|
||||||
exports.testAll = function() {
|
exports.testAll = function() {
|
||||||
exports.testTokenize();
|
exports.testTokenize();
|
||||||
|
|
@ -50,41 +53,18 @@ exports.testAll = function() {
|
||||||
return "testAll Completed";
|
return "testAll Completed";
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.testSplit = function() {
|
|
||||||
var args = cli._tokenize('s');
|
|
||||||
var command = cli._split(args);
|
|
||||||
test.verifyEqual(1, args.length);
|
|
||||||
test.verifyEqual('s', args[0].text);
|
|
||||||
test.verifyUndefined(command);
|
|
||||||
|
|
||||||
var args = cli._tokenize('set');
|
|
||||||
var command = cli._split(args);
|
|
||||||
test.verifyEqual([], args);
|
|
||||||
test.verifyEqual('set', command.name);
|
|
||||||
|
|
||||||
var args = cli._tokenize('set a b');
|
|
||||||
var command = cli._split(args);
|
|
||||||
test.verifyEqual('set', command.name);
|
|
||||||
test.verifyEqual(2, args.length);
|
|
||||||
test.verifyEqual('a', args[0].text);
|
|
||||||
test.verifyEqual('b', args[1].text);
|
|
||||||
|
|
||||||
// TODO: add tests for sub commands
|
|
||||||
return "testSplit Completed";
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.testTokenize = function() {
|
exports.testTokenize = function() {
|
||||||
var args = cli._tokenize('');
|
var args = tokenize('');
|
||||||
test.verifyEqual(0, args.length);
|
test.verifyEqual(0, args.length);
|
||||||
|
|
||||||
args = cli._tokenize('s');
|
args = tokenize('s');
|
||||||
test.verifyEqual(1, args.length);
|
test.verifyEqual(1, args.length);
|
||||||
test.verifyEqual('s', args[0].text);
|
test.verifyEqual('s', args[0].text);
|
||||||
test.verifyEqual(0, args[0].start);
|
test.verifyEqual(0, args[0].start);
|
||||||
test.verifyEqual(1, args[0].end);
|
test.verifyEqual(1, args[0].end);
|
||||||
test.verifyEqual('', args[0].priorSpace);
|
test.verifyEqual('', args[0].priorSpace);
|
||||||
|
|
||||||
args = cli._tokenize('s s');
|
args = tokenize('s s');
|
||||||
test.verifyEqual(2, args.length);
|
test.verifyEqual(2, args.length);
|
||||||
test.verifyEqual('s', args[0].text);
|
test.verifyEqual('s', args[0].text);
|
||||||
test.verifyEqual(0, args[0].start);
|
test.verifyEqual(0, args[0].start);
|
||||||
|
|
@ -95,7 +75,7 @@ exports.testTokenize = function() {
|
||||||
test.verifyEqual(3, args[1].end);
|
test.verifyEqual(3, args[1].end);
|
||||||
test.verifyEqual(' ', args[1].priorSpace);
|
test.verifyEqual(' ', args[1].priorSpace);
|
||||||
|
|
||||||
args = cli._tokenize(' 1234 \'12 34\'');
|
args = tokenize(' 1234 \'12 34\'');
|
||||||
test.verifyEqual(2, args.length);
|
test.verifyEqual(2, args.length);
|
||||||
test.verifyEqual('1234', args[0].text);
|
test.verifyEqual('1234', args[0].text);
|
||||||
test.verifyEqual(1, args[0].start);
|
test.verifyEqual(1, args[0].start);
|
||||||
|
|
@ -106,7 +86,7 @@ exports.testTokenize = function() {
|
||||||
test.verifyEqual(13, args[1].end);
|
test.verifyEqual(13, args[1].end);
|
||||||
test.verifyEqual(' ', args[1].priorSpace);
|
test.verifyEqual(' ', args[1].priorSpace);
|
||||||
|
|
||||||
args = cli._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
|
args = tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
|
||||||
test.verifyEqual(3, args.length);
|
test.verifyEqual(3, args.length);
|
||||||
test.verifyEqual('12\'34', args[0].text);
|
test.verifyEqual('12\'34', args[0].text);
|
||||||
test.verifyEqual(0, args[0].start);
|
test.verifyEqual(0, args[0].start);
|
||||||
|
|
@ -121,7 +101,7 @@ exports.testTokenize = function() {
|
||||||
test.verifyEqual(15, args[2].end);
|
test.verifyEqual(15, args[2].end);
|
||||||
test.verifyEqual(' ', args[2].priorSpace);
|
test.verifyEqual(' ', args[2].priorSpace);
|
||||||
|
|
||||||
args = cli._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
|
args = tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
|
||||||
test.verifyEqual(4, args.length);
|
test.verifyEqual(4, args.length);
|
||||||
test.verifyEqual('a b', args[0].text);
|
test.verifyEqual('a b', args[0].text);
|
||||||
test.verifyEqual(0, args[0].start);
|
test.verifyEqual(0, args[0].start);
|
||||||
|
|
@ -143,50 +123,60 @@ exports.testTokenize = function() {
|
||||||
return "testTokenize Completed";
|
return "testTokenize Completed";
|
||||||
};
|
};
|
||||||
|
|
||||||
var hints;
|
exports.testSplit = function() {
|
||||||
var hint0;
|
var args = tokenize('s');
|
||||||
var requisition;
|
var command = split(args);
|
||||||
var sel = { start: -1, end: -1 };
|
test.verifyEqual(1, args.length);
|
||||||
var settingAssignment;
|
test.verifyEqual('s', args[0].text);
|
||||||
var valueAssignment;
|
test.verifyUndefined(command);
|
||||||
mockCliUi = {
|
|
||||||
getSelection: function() {
|
|
||||||
return sel;
|
|
||||||
},
|
|
||||||
|
|
||||||
setHints: function(h) {
|
var args = tokenize('set');
|
||||||
hints = h;
|
var command = split(args);
|
||||||
hint0 = (h.length !== 0) ? h[0] : undefined;
|
test.verifyEqual([], args);
|
||||||
|
test.verifyEqual('set', command.name);
|
||||||
|
|
||||||
if (requisition && requisition.command && requisition.command.name === 'set') {
|
var args = tokenize('set a b');
|
||||||
settingAssignment = requisition.getAssignment('setting');
|
var command = split(args);
|
||||||
valueAssignment = requisition.getAssignment('value');
|
test.verifyEqual('set', command.name);
|
||||||
|
test.verifyEqual(2, args.length);
|
||||||
|
test.verifyEqual('a', args[0].text);
|
||||||
|
test.verifyEqual('b', args[1].text);
|
||||||
|
|
||||||
|
// TODO: add tests for sub commands
|
||||||
|
return "testSplit Completed";
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testCli = function() {
|
||||||
|
var hints;
|
||||||
|
var hint0;
|
||||||
|
var settingAssignment;
|
||||||
|
var valueAssignment;
|
||||||
|
var cli = new CliRequisition();
|
||||||
|
|
||||||
|
function update(input) {
|
||||||
|
cli.update(input);
|
||||||
|
hints = cli.getHints();
|
||||||
|
hint0 = (hints.length !== 0) ? hints[0] : undefined;
|
||||||
|
if (cli.command && cli.command.name === 'set') {
|
||||||
|
settingAssignment = cli.getAssignment('setting');
|
||||||
|
valueAssignment = cli.getAssignment('value');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
settingAssignment = undefined;
|
settingAssignment = undefined;
|
||||||
valueAssignment = undefined;
|
valueAssignment = undefined;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
setRequisition: function(r) {
|
|
||||||
requisition = r;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
exports.testCli = function() {
|
|
||||||
var historyLengthSetting = settings.getSetting('historyLength');
|
var historyLengthSetting = settings.getSetting('historyLength');
|
||||||
|
|
||||||
var input = new cli.Cli(mockCliUi);
|
update({ typed: '', cursor: { start: 0, end: 0 } });
|
||||||
|
|
||||||
input.parse('');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||||
test.verifyEqual(0, hint0.start);
|
test.verifyEqual(0, hint0.start);
|
||||||
test.verifyEqual(0, hint0.end);
|
test.verifyEqual(0, hint0.end);
|
||||||
test.verifyNull(requisition.command);
|
test.verifyNull(cli.command);
|
||||||
|
|
||||||
sel.start = sel.end = 1;
|
update({ typed: 's', cursor: { start: 1, end: 1 } });
|
||||||
input.parse('s');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||||
test.verifyNotEqual(-1, hint0.message.indexOf('possibilities'));
|
test.verifyNotEqual(-1, hint0.message.indexOf('possibilities'));
|
||||||
|
|
@ -196,16 +186,16 @@ exports.testCli = function() {
|
||||||
// This is slightly fragile because it depends on the configuration
|
// This is slightly fragile because it depends on the configuration
|
||||||
test.verifyTrue(hint0.predictions.length < 20);
|
test.verifyTrue(hint0.predictions.length < 20);
|
||||||
test.verifyNotEqual(-1, hint0.predictions.indexOf('set'));
|
test.verifyNotEqual(-1, hint0.predictions.indexOf('set'));
|
||||||
test.verifyNull(requisition.command);
|
test.verifyNull(cli.command);
|
||||||
|
|
||||||
input.parse('set');
|
update({ typed: 'set', cursor: { start: 3, end: 3 } });
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.VALID, hint0.status);
|
test.verifyEqual(Status.VALID, hint0.status);
|
||||||
test.verifyEqual(0, hint0.start);
|
test.verifyEqual(0, hint0.start);
|
||||||
test.verifyEqual(3, hint0.end);
|
test.verifyEqual(3, hint0.end);
|
||||||
test.verifyEqual('set', requisition.command.name);
|
test.verifyEqual('set', cli.command.name);
|
||||||
|
|
||||||
input.parse('set ');
|
update({ typed: 'set ', cursor: { start: 4, end: 4 } });
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.VALID, hint0.status);
|
test.verifyEqual(Status.VALID, hint0.status);
|
||||||
test.verifyEqual(0, hint0.start);
|
test.verifyEqual(0, hint0.start);
|
||||||
|
|
@ -213,71 +203,67 @@ exports.testCli = function() {
|
||||||
// This is caused by us using the whole input to determine the length.
|
// This is caused by us using the whole input to determine the length.
|
||||||
// Maybe one day we should fix this?
|
// Maybe one day we should fix this?
|
||||||
//test.verifyEqual(3, hint0.end);
|
//test.verifyEqual(3, hint0.end);
|
||||||
test.verifyEqual('set', requisition.command.name);
|
test.verifyEqual('set', cli.command.name);
|
||||||
|
|
||||||
sel.start = sel.end = 5;
|
update({ typed: 'set h', cursor: { start: 5, end: 5 } });
|
||||||
input.parse('set h');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||||
test.verifyTrue(hint0.predictions.length > 0);
|
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.verifyNotEqual(-1, hint0.predictions.indexOf('historyLength'));
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
sel.start = sel.end = 16;
|
update({ typed: 'set historyLengt', cursor: { start: 16, end: 16 } });
|
||||||
input.parse('set historyLengt');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||||
test.verifyEqual(1, hint0.predictions.length);
|
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('historyLength', hint0.predictions[0]);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
sel.start = sel.end = 1;
|
update({ typed: 'set historyLengt', cursor: { start: 1, end: 1 } });
|
||||||
input.parse('set historyLengt');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INVALID, hint0.status);
|
test.verifyEqual(Status.INVALID, hint0.status);
|
||||||
test.verifyEqual(4, hint0.start);
|
test.verifyEqual(4, hint0.start);
|
||||||
test.verifyEqual(16, hint0.end);
|
test.verifyEqual(16, hint0.end);
|
||||||
test.verifyEqual(1, hint0.predictions.length);
|
test.verifyEqual(1, hint0.predictions.length);
|
||||||
test.verifyEqual('historyLength', hint0.predictions[0]);
|
test.verifyEqual('historyLength', hint0.predictions[0]);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
sel.start = sel.end = 17;
|
update({ typed: 'set historyLengt ', cursor: { start: 17, end: 17 } });
|
||||||
input.parse('set historyLengt ');
|
|
||||||
test.verifyEqual(1, hints.length);
|
test.verifyEqual(1, hints.length);
|
||||||
test.verifyEqual(Status.INVALID, hint0.status);
|
test.verifyEqual(Status.INVALID, hint0.status);
|
||||||
test.verifyEqual(4, hint0.start);
|
test.verifyEqual(4, hint0.start);
|
||||||
test.verifyEqual(16, hint0.end);
|
test.verifyEqual(16, hint0.end);
|
||||||
test.verifyEqual(1, hint0.predictions.length);
|
test.verifyEqual(1, hint0.predictions.length);
|
||||||
test.verifyEqual('historyLength', hint0.predictions[0]);
|
test.verifyEqual('historyLength', hint0.predictions[0]);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
input.parse('set historyLength');
|
update({ typed: 'set historyLength', cursor: { start: 17, end: 17 } });
|
||||||
test.verifyEqual(0, hints.length);
|
test.verifyEqual(0, hints.length);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
input.parse('set historyLength ');
|
update({ typed: 'set historyLength ', cursor: { start: 18, end: 18 } });
|
||||||
test.verifyEqual(0, hints.length);
|
test.verifyEqual(0, hints.length);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
|
|
||||||
input.parse('set historyLength 6');
|
update({ typed: 'set historyLength 6', cursor: { start: 19, end: 19 } });
|
||||||
test.verifyEqual(0, hints.length);
|
test.verifyEqual(0, hints.length);
|
||||||
test.verifyEqual('set', requisition.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);
|
||||||
test.verifyEqual('6', valueAssignment.arg.text);
|
test.verifyEqual('6', valueAssignment.arg.text);
|
||||||
|
|
@ -286,12 +272,8 @@ exports.testCli = function() {
|
||||||
|
|
||||||
// TODO: Add test to see that a command without mandatory param causes INVALID
|
// TODO: Add test to see that a command without mandatory param causes INVALID
|
||||||
|
|
||||||
console.log(input);
|
|
||||||
|
|
||||||
return "testCli Completed";
|
return "testCli Completed";
|
||||||
};
|
};
|
||||||
|
|
||||||
window.testCli = exports;
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
40
plugins/cockpit/lib/test/testNothing.js
Normal file
40
plugins/cockpit/lib/test/testNothing.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* ***** 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 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) {
|
||||||
|
|
||||||
|
});
|
||||||
2
plugins/cockpit/lib/ui/plain.css
Normal file
2
plugins/cockpit/lib/ui/plain.css
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
#cockpit { color: red; }
|
||||||
98
plugins/cockpit/lib/ui/plain.js
Normal file
98
plugins/cockpit/lib/ui/plain.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
/* ***** 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 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 editorCss = require("text!cockpit/ui/plain.css");
|
||||||
|
var dom = require("pilot/dom").dom;
|
||||||
|
dom.importCssString(editorCss);
|
||||||
|
|
||||||
|
var CliRequisition = require('cockpit/cli').CliRequisition;
|
||||||
|
var keyutil = require('pilot/keyboard/keyutil');
|
||||||
|
|
||||||
|
exports.startup = function(data, reason) {
|
||||||
|
// TODO: we should have a better way to specify command lines???
|
||||||
|
this.input = document.getElementById('cockpit');
|
||||||
|
if (!this.input) {
|
||||||
|
console.log('No element with an id of cockpit. Bailing on plain cli');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli = new CliRequisition();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// All this does is to kill TABs normal use. I wonder if we can train
|
||||||
|
// people to use right arrow? Probably not? but ...
|
||||||
|
keyutil.addKeyDownListener(input, function(ev) {
|
||||||
|
// env.commandLine = this;
|
||||||
|
// var handled = keyboardManager.processKeyEvent(ev, this, {
|
||||||
|
// isCommandLine: true, isKeyUp: false
|
||||||
|
// });
|
||||||
|
if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//return handled;
|
||||||
|
}.bind(this));
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.input.addEventListener('keyup', function(ev) {
|
||||||
|
/*
|
||||||
|
var handled = keyboardManager.processKeyEvent(ev, this, {
|
||||||
|
isCommandLine: true, isKeyUp: true
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
|
||||||
|
cli.exec();
|
||||||
|
this.input.value = '';
|
||||||
|
} else {
|
||||||
|
cli.update({
|
||||||
|
typed: this.input.value,
|
||||||
|
cursor: {
|
||||||
|
start: this.input.selectionStart,
|
||||||
|
end: this.input.selectionEnd
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(cli.getHints()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return handled;
|
||||||
|
}.bind(this), true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -161,7 +161,7 @@ exports.getCommandNames = function() {
|
||||||
* @param command Either a command, or the name of one
|
* @param command Either a command, or the name of one
|
||||||
*/
|
*/
|
||||||
exports.exec = function(command, args) {
|
exports.exec = function(command, args) {
|
||||||
if (typeof name === 'string') {
|
if (typeof command === 'string') {
|
||||||
command = commands[command];
|
command = commands[command];
|
||||||
}
|
}
|
||||||
if (!command) {
|
if (!command) {
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,9 @@ var deps = [
|
||||||
"pilot/types/basic",
|
"pilot/types/basic",
|
||||||
"pilot/types/command",
|
"pilot/types/command",
|
||||||
"pilot/types/settings",
|
"pilot/types/settings",
|
||||||
"pilot/canon",
|
|
||||||
"pilot/commands/settings",
|
"pilot/commands/settings",
|
||||||
"pilot/settings/canon",
|
"pilot/settings/canon",
|
||||||
"pilot/cli",
|
"pilot/canon"
|
||||||
"pilot/test/testCli"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var packages = deps.slice();
|
var packages = deps.slice();
|
||||||
|
|
@ -52,6 +50,7 @@ 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);
|
console.log("test startup for " + dep);
|
||||||
|
|
|
||||||
459
plugins/pilot/lib/keyboard/index.js
Normal file
459
plugins/pilot/lib/keyboard/index.js
Normal file
|
|
@ -0,0 +1,459 @@
|
||||||
|
/* ***** 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 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):
|
||||||
|
* Skywriter Team (skywriter@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(request, exports, module) {
|
||||||
|
|
||||||
|
var console = require('pilot/console');
|
||||||
|
var Trace = require('pilot/stacktrace').Trace;
|
||||||
|
var keyutil = require('pilot/keyboard/keyutil');
|
||||||
|
var history = require('canon/history');
|
||||||
|
var Request = require('canon/request').Request;
|
||||||
|
var env = require('environment').env;
|
||||||
|
|
||||||
|
exports.keymappings = {};
|
||||||
|
|
||||||
|
exports.addKeymapping = function(mapping) {
|
||||||
|
exports.keymappings[mapping.name] = mapping;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.removeKeymapping = function(name) {
|
||||||
|
delete exports.keymapping[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.startup = function(data, reason) {
|
||||||
|
var settings = data.env.settings;
|
||||||
|
// TODO register this
|
||||||
|
// catalog.addExtensionSpec("keymapping", {
|
||||||
|
// "description": "A keymapping defines how keystrokes are interpreted.",
|
||||||
|
// "params": [
|
||||||
|
// {
|
||||||
|
// "name": "states",
|
||||||
|
// "required": true,
|
||||||
|
// "description":
|
||||||
|
// "Holds the states and all the informations about the keymapping. See docs: pluginguide/keymapping"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
settings.settingChange.add({
|
||||||
|
match: "customKeymapping",
|
||||||
|
ref: exports.keyboardManager,
|
||||||
|
func: exports.keyboardManager._customKeymappingChanged
|
||||||
|
.bind(exports.keyboardManager)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.shutdown = function(data, reason) {
|
||||||
|
var settings = data.env.settings;
|
||||||
|
settings.settingChange.remove(exports.keyboardManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Things to do to sanitize this code:
|
||||||
|
* - 'no command' is a bizarre special value at the very least it should be a
|
||||||
|
* constant to make typos more obvious, but it would be better to refactor
|
||||||
|
* so that a natural value like null worked.
|
||||||
|
* - sender seems to be totally customized to the editor case, and the functions
|
||||||
|
* that we assume that it has make no sense for the commandLine case. We
|
||||||
|
* should either document and implement the same function set for both cases
|
||||||
|
* or admit that the cases are different enough to have separate
|
||||||
|
* implementations.
|
||||||
|
* - remove remaining sproutcore-isms
|
||||||
|
* - fold buildFlags into processKeyEvent or something better, preferably the
|
||||||
|
* latter. We don't want the environment to become a singleton
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every time we call processKeyEvent, we pass in some flags that require the
|
||||||
|
* same processing to set them up. This function can be called to do that
|
||||||
|
* setup.
|
||||||
|
* @param env Probably environment.env
|
||||||
|
* @param flags Probably {} (but check other places where this is called)
|
||||||
|
*/
|
||||||
|
exports.buildFlags = function(flags) {
|
||||||
|
flags.context = env.contexts[0];
|
||||||
|
return flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The canon, or the repository of commands, contains functions to process
|
||||||
|
* events and dispatch command messages to targets.
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
var KeyboardManager = function() { };
|
||||||
|
|
||||||
|
KeyboardManager.prototype = {
|
||||||
|
_customKeymappingCache: { states: {} },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches through the command canon for an event matching the given flags
|
||||||
|
* with a key equivalent matching the given SproutCore event, and, if the
|
||||||
|
* command is found, sends a message to the appropriate target.
|
||||||
|
*
|
||||||
|
* This will get a couple of upgrades in the not-too-distant future:
|
||||||
|
* 1. caching in the Canon for fast lookup based on key
|
||||||
|
* 2. there will be an extra layer in between to allow remapping via
|
||||||
|
* user preferences and keyboard mapping plugins
|
||||||
|
*
|
||||||
|
* @return True if a matching command was found, false otherwise.
|
||||||
|
*/
|
||||||
|
processKeyEvent: function(evt, sender, flags) {
|
||||||
|
// Use our modified commandCodes function to detect the meta key in
|
||||||
|
// more circumstances than SproutCore alone does.
|
||||||
|
var symbolicName = keyutil.commandCodes(evt, true)[0];
|
||||||
|
if (util.none(symbolicName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Maybe it should be the job of our caller to do this?
|
||||||
|
exports.buildFlags(flags);
|
||||||
|
|
||||||
|
flags.isCommandKey = true;
|
||||||
|
return this._matchCommand(symbolicName, sender, flags);
|
||||||
|
},
|
||||||
|
|
||||||
|
_matchCommand: function(symbolicName, sender, flags) {
|
||||||
|
var match = this._findCommandExtension(symbolicName, sender, flags);
|
||||||
|
if (match && match.commandExt !== 'no command') {
|
||||||
|
if (flags.isTextView) {
|
||||||
|
sender.resetKeyBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandExt = match.commandExt;
|
||||||
|
commandExt.load(function(command) {
|
||||||
|
var request = new Request({
|
||||||
|
command: command,
|
||||||
|
commandExt: commandExt
|
||||||
|
});
|
||||||
|
history.execute(match.args, request);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'no command' is returned if a keyevent is handled but there is no
|
||||||
|
// command executed (for example when switchting the keyboard state).
|
||||||
|
if (match && match.commandExt === 'no command') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildBindingsRegex: function(bindings) {
|
||||||
|
// Escape a given Regex string.
|
||||||
|
bindings.forEach(function(binding) {
|
||||||
|
if (!util.none(binding.key)) {
|
||||||
|
binding.key = new RegExp('^' + binding.key + '$');
|
||||||
|
} else if (Array.isArray(binding.regex)) {
|
||||||
|
binding.key = new RegExp('^' + binding.regex[1] + '$');
|
||||||
|
binding.regex = new RegExp(binding.regex.join('') + '$');
|
||||||
|
} else {
|
||||||
|
binding.regex = new RegExp(binding.regex + '$');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the RegExp from the keymapping as RegExp can't stored directly
|
||||||
|
* in the metadata JSON and as the RegExp used to match the keys/buffer
|
||||||
|
* need to be adapted.
|
||||||
|
*/
|
||||||
|
_buildKeymappingRegex: function(keymapping) {
|
||||||
|
for (state in keymapping.states) {
|
||||||
|
this._buildBindingsRegex(keymapping.states[state]);
|
||||||
|
}
|
||||||
|
keymapping._convertedRegExp = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop through the commands in the canon, looking for something that
|
||||||
|
* matches according to #_commandMatches, and return that.
|
||||||
|
*/
|
||||||
|
_findCommandExtension: function(symbolicName, sender, flags) {
|
||||||
|
// If the flags indicate that we handle the textView's input then take
|
||||||
|
// a look at keymappings as well.
|
||||||
|
if (flags.isTextView) {
|
||||||
|
var currentState = sender._keyState;
|
||||||
|
|
||||||
|
// Don't add the symbolic name to the key buffer if the alt_ key is
|
||||||
|
// part of the symbolic name. If it starts with alt_, this means
|
||||||
|
// that the user hit an alt keycombo and there will be a single,
|
||||||
|
// new character detected after this event, which then will be
|
||||||
|
// added to the buffer (e.g. alt_j will result in ∆).
|
||||||
|
if (!flags.isCommandKey || symbolicName.indexOf('alt_') === -1) {
|
||||||
|
sender._keyBuffer +=
|
||||||
|
symbolicName.replace(/ctrl_meta|meta/,'ctrl');
|
||||||
|
sender._keyMetaBuffer += symbolicName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of all the keymappings to look at.
|
||||||
|
var ak = [ this._customKeymappingCache ];
|
||||||
|
|
||||||
|
// Get keymapping extension points.
|
||||||
|
ak = ak.concat(catalog.getExtensions('keymapping'));
|
||||||
|
|
||||||
|
for (var i = 0; i < ak.length; i++) {
|
||||||
|
// Check if the keymapping has the current state.
|
||||||
|
if (util.none(ak[i].states[currentState])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (util.none(ak[i]._convertedRegExp)) {
|
||||||
|
this._buildKeymappingRegex(ak[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match the current mapping.
|
||||||
|
var result = this._bindingsMatch(
|
||||||
|
symbolicName,
|
||||||
|
flags,
|
||||||
|
sender,
|
||||||
|
ak[i]);
|
||||||
|
|
||||||
|
if (!util.none(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandExts = catalog.getExtensions('command');
|
||||||
|
var reply = null;
|
||||||
|
var args = {};
|
||||||
|
|
||||||
|
symbolicName = symbolicName.replace(/ctrl_meta|meta/,'ctrl');
|
||||||
|
|
||||||
|
commandExts.some(function(commandExt) {
|
||||||
|
if (this._commandMatches(commandExt, symbolicName, flags)) {
|
||||||
|
reply = commandExt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return util.none(reply) ? null : { commandExt: reply, args: args };
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given parameters fit to one binding in the given bindings.
|
||||||
|
* Returns the command and arguments if a command was matched.
|
||||||
|
*/
|
||||||
|
_bindingsMatch: function(symbolicName, flags, sender, keymapping) {
|
||||||
|
var match;
|
||||||
|
var commandExt = null;
|
||||||
|
var args = {};
|
||||||
|
var bufferToUse;
|
||||||
|
|
||||||
|
if (!util.none(keymapping.hasMetaKey)) {
|
||||||
|
bufferToUse = sender._keyBuffer;
|
||||||
|
} else {
|
||||||
|
bufferToUse = sender._keyMetaBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the alt_key to the buffer as we don't want it to be in the buffer
|
||||||
|
// that is saved but for matching, it needs to be there.
|
||||||
|
if (symbolicName.indexOf('alt_') === 0 && flags.isCommandKey) {
|
||||||
|
bufferToUse += symbolicName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all the bindings of the keymapp until a match is found.
|
||||||
|
keymapping.states[sender._keyState].some(function(binding) {
|
||||||
|
// Check if the key matches.
|
||||||
|
if (binding.key && !binding.key.test(symbolicName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the regex matches.
|
||||||
|
if (binding.regex && !(match = binding.regex.exec(bufferToUse))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for disallowed matches.
|
||||||
|
if (binding.disallowMatches) {
|
||||||
|
for (var i = 0; i < binding.disallowMatches.length; i++) {
|
||||||
|
if (!!match[binding.disallowMatches[i]]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check predicates.
|
||||||
|
if (!exports.flagsMatch(binding.predicates, flags)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a command to execute, then figure out the
|
||||||
|
// comand and the arguments.
|
||||||
|
if (binding.exec) {
|
||||||
|
// Get the command.
|
||||||
|
commandExt = catalog.getExtensionByKey('command', binding.exec);
|
||||||
|
if (util.none(commandExt)) {
|
||||||
|
throw new Error('Can\'t find command ' + binding.exec +
|
||||||
|
' in state=' + sender._keyState +
|
||||||
|
', symbolicName=' + symbolicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulid the arguments.
|
||||||
|
if (binding.params) {
|
||||||
|
var value;
|
||||||
|
binding.params.forEach(function(param) {
|
||||||
|
if (!util.none(param.match) && !util.none(match)) {
|
||||||
|
value = match[param.match] || param.defaultValue;
|
||||||
|
} else {
|
||||||
|
value = param.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.type === 'number') {
|
||||||
|
value = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
args[param.name] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sender.resetKeyBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the 'then' property.
|
||||||
|
if (binding.then) {
|
||||||
|
sender._keyState = binding.then;
|
||||||
|
sender.resetKeyBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no command matched now, then return a 'false'
|
||||||
|
// command to stop matching.
|
||||||
|
if (util.none(commandExt)) {
|
||||||
|
commandExt = 'no command';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (util.none(commandExt)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { commandExt: commandExt, args: args };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the given command fits the given key name and flags.
|
||||||
|
*/
|
||||||
|
_commandMatches: function(commandExt, symbolicName, flags) {
|
||||||
|
var mappedKeys = commandExt.key;
|
||||||
|
if (!mappedKeys) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check predicates
|
||||||
|
if (!exports.flagsMatch(commandExt.predicates, flags)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(mappedKeys) === 'string') {
|
||||||
|
if (mappedKeys != symbolicName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(mappedKeys)) {
|
||||||
|
mappedKeys = [mappedKeys];
|
||||||
|
commandExt.key = mappedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < mappedKeys.length; i++) {
|
||||||
|
var keymap = mappedKeys[i];
|
||||||
|
if (typeof(keymap) === 'string') {
|
||||||
|
if (keymap == symbolicName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keymap.key != symbolicName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports.flagsMatch(keymap.predicates, flags);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a cache of custom keymappings whenever the associated setting
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
_customKeymappingChanged: function(settingName, value) {
|
||||||
|
var ckc = this._customKeymappingCache =
|
||||||
|
JSON.parse(value);
|
||||||
|
|
||||||
|
ckc.states = ckc.states || {};
|
||||||
|
|
||||||
|
for (state in ckc.states) {
|
||||||
|
this._buildBindingsRegex(ckc.states[state]);
|
||||||
|
}
|
||||||
|
ckc._convertedRegExp = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
exports.flagsMatch = function(predicates, flags) {
|
||||||
|
if (util.none(predicates)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flags) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var flagName in predicates) {
|
||||||
|
if (flags[flagName] !== predicates[flagName]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global exported KeyboardManager
|
||||||
|
*/
|
||||||
|
exports.keyboardManager = new KeyboardManager();
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
273
plugins/pilot/lib/keyboard/keyutil.js
Normal file
273
plugins/pilot/lib/keyboard/keyutil.js
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*! @license
|
||||||
|
==========================================================================
|
||||||
|
SproutCore -- JavaScript Application Framework
|
||||||
|
copyright 2006-2009, Sprout Systems Inc., Apple Inc. and contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
SproutCore and the SproutCore logo are trademarks of Sprout Systems, Inc.
|
||||||
|
|
||||||
|
For more information about SproutCore, visit http://www.sproutcore.com
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================================
|
||||||
|
@license */
|
||||||
|
|
||||||
|
// Most of the following code is taken from SproutCore with a few changes.
|
||||||
|
|
||||||
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
|
var util = require('pilot/util');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions and hashes for key handling.
|
||||||
|
*/
|
||||||
|
exports.KeyHelper = function() {
|
||||||
|
var ret = {
|
||||||
|
MODIFIER_KEYS: {
|
||||||
|
16: 'shift', 17: 'ctrl', 18: 'alt', 224: 'meta'
|
||||||
|
},
|
||||||
|
|
||||||
|
FUNCTION_KEYS : {
|
||||||
|
8: 'backspace', 9: 'tab', 13: 'return', 19: 'pause',
|
||||||
|
27: 'escape', 33: 'pageup', 34: 'pagedown', 35: 'end',
|
||||||
|
36: 'home', 37: 'left', 38: 'up', 39: 'right',
|
||||||
|
40: 'down', 44: 'printscreen', 45: 'insert', 46: 'delete',
|
||||||
|
112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4',
|
||||||
|
116: 'f5', 117: 'f7', 119: 'f8', 120: 'f9',
|
||||||
|
121: 'f10', 122: 'f11', 123: 'f12', 144: 'numlock',
|
||||||
|
145: 'scrolllock'
|
||||||
|
},
|
||||||
|
|
||||||
|
PRINTABLE_KEYS: {
|
||||||
|
32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5',
|
||||||
|
54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 61: '=', 65: 'a',
|
||||||
|
66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h',
|
||||||
|
73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o',
|
||||||
|
80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v',
|
||||||
|
87: 'w', 88: 'x', 89: 'y', 90: 'z', 107: '+', 109: '-', 110: '.',
|
||||||
|
188: ',', 190: '.', 191: '/', 192: '`', 219: '[', 220: '\\',
|
||||||
|
221: ']', 222: '\"'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the lookup table for Firefox to convert charCodes to keyCodes
|
||||||
|
* in the keyPress event.
|
||||||
|
*/
|
||||||
|
PRINTABLE_KEYS_CHARCODE: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow us to lookup keyCodes by symbolic name rather than number
|
||||||
|
*/
|
||||||
|
KEY: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the PRINTABLE_KEYS_CHARCODE hash.
|
||||||
|
for (var i in ret.PRINTABLE_KEYS) {
|
||||||
|
var k = ret.PRINTABLE_KEYS[i];
|
||||||
|
ret.PRINTABLE_KEYS_CHARCODE[k.charCodeAt(0)] = i;
|
||||||
|
if (k.toUpperCase() != k) {
|
||||||
|
ret.PRINTABLE_KEYS_CHARCODE[k.toUpperCase().charCodeAt(0)] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A reverse map of FUNCTION_KEYS
|
||||||
|
for (i in ret.FUNCTION_KEYS) {
|
||||||
|
var name = ret.FUNCTION_KEYS[i].toUpperCase();
|
||||||
|
ret.KEY[name] = parseInt(i, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the keyDown event is a non-printable or function key.
|
||||||
|
* These kinds of events are processed as keyboard shortcuts.
|
||||||
|
* If no shortcut handles the event, then it will be sent as a regular
|
||||||
|
* keyDown event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var isFunctionOrNonPrintableKey = function(evt) {
|
||||||
|
return !!(evt.altKey || evt.ctrlKey || evt.metaKey ||
|
||||||
|
((evt.charCode !== evt.which) &&
|
||||||
|
exports.KeyHelper.FUNCTION_KEYS[evt.which]));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns character codes for the event.
|
||||||
|
* The first value is the normalized code string, with any Shift or Ctrl
|
||||||
|
* characters added to the beginning.
|
||||||
|
* The second value is the char string by itself.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
exports.commandCodes = function(evt, dontIgnoreMeta) {
|
||||||
|
var code = evt._keyCode || evt.keyCode;
|
||||||
|
var charCode = (evt._charCode === undefined ? evt.charCode : evt._charCode);
|
||||||
|
var ret = null;
|
||||||
|
var key = null;
|
||||||
|
var modifiers = '';
|
||||||
|
var lowercase;
|
||||||
|
var allowShift = true;
|
||||||
|
|
||||||
|
// Absent a value for 'keyCode' or 'which', we can't compute the
|
||||||
|
// command codes. Bail out.
|
||||||
|
if (code === 0 && evt.which === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the charCode is not zero, then we do not handle a command key
|
||||||
|
// here. Bail out.
|
||||||
|
if (charCode !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for modifier keys.
|
||||||
|
if (exports.KeyHelper.MODIFIER_KEYS[charCode]) {
|
||||||
|
return [exports.KeyHelper.MODIFIER_KEYS[charCode], null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle function keys.
|
||||||
|
if (code) {
|
||||||
|
ret = exports.KeyHelper.FUNCTION_KEYS[code];
|
||||||
|
if (!ret && (evt.altKey || evt.ctrlKey || evt.metaKey)) {
|
||||||
|
ret = exports.KeyHelper.PRINTABLE_KEYS[code];
|
||||||
|
// Don't handle the shift key if the combo is
|
||||||
|
// (meta_|ctrl_)<number>
|
||||||
|
// This is necessary for the French keyboard. On that keyboard,
|
||||||
|
// you have to hold down the shift key to access the number
|
||||||
|
// characters.
|
||||||
|
if (code > 47 && code < 58) {
|
||||||
|
allowShift = evt.altKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
if (evt.altKey) {
|
||||||
|
modifiers += 'alt_';
|
||||||
|
}
|
||||||
|
if (evt.ctrlKey) {
|
||||||
|
modifiers += 'ctrl_';
|
||||||
|
}
|
||||||
|
if (evt.metaKey) {
|
||||||
|
modifiers += 'meta_';
|
||||||
|
}
|
||||||
|
} else if (evt.ctrlKey || evt.metaKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise just go get the right key.
|
||||||
|
if (!ret) {
|
||||||
|
code = evt.which;
|
||||||
|
key = ret = String.fromCharCode(code);
|
||||||
|
lowercase = ret.toLowerCase();
|
||||||
|
|
||||||
|
if (evt.metaKey) {
|
||||||
|
modifiers = 'meta_';
|
||||||
|
ret = lowercase;
|
||||||
|
|
||||||
|
} else ret = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt.shiftKey && ret && allowShift) {
|
||||||
|
modifiers += 'shift_';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
ret = modifiers + ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dontIgnoreMeta && ret) {
|
||||||
|
ret = ret.replace(/ctrl_meta|meta/,'ctrl');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ret, key];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note: Most of the following code is taken from SproutCore with a few changes.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Firefox sends a few key events twice: the first time to the keydown event
|
||||||
|
* and then later again to the keypress event. To handle them correct, they
|
||||||
|
* should be processed only once. Due to this, we will skip these events
|
||||||
|
* in keydown and handle them then in keypress.
|
||||||
|
*/
|
||||||
|
exports.addKeyDownListener = function(element, boundFunction) {
|
||||||
|
|
||||||
|
var handleBoundFunction = function(ev) {
|
||||||
|
var handled = boundFunction(ev);
|
||||||
|
// If the boundFunction returned true, then stop the event.
|
||||||
|
if (handled) {
|
||||||
|
util.stopEvent(ev);
|
||||||
|
}
|
||||||
|
return handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener('keydown', function(ev) {
|
||||||
|
if (util.isMozilla) {
|
||||||
|
// Check for function keys (like DELETE, TAB, LEFT, RIGHT...)
|
||||||
|
if (exports.KeyHelper.FUNCTION_KEYS[ev.keyCode]) {
|
||||||
|
return true;
|
||||||
|
// Check for command keys (like ctrl_c, ctrl_z...)
|
||||||
|
} else if ((ev.ctrlKey || ev.metaKey) &&
|
||||||
|
exports.KeyHelper.PRINTABLE_KEYS[ev.keyCode]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFunctionOrNonPrintableKey(ev)) {
|
||||||
|
return handleBoundFunction(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
element.addEventListener('keypress', function(ev) {
|
||||||
|
if (util.isMozilla) {
|
||||||
|
// If this is a function key, we have to use the keyCode.
|
||||||
|
if (exports.KeyHelper.FUNCTION_KEYS[ev.keyCode]) {
|
||||||
|
return handleBoundFunction(ev);
|
||||||
|
} else if ((ev.ctrlKey || ev.metaKey) &&
|
||||||
|
exports.KeyHelper.PRINTABLE_KEYS_CHARCODE[ev.charCode]){
|
||||||
|
// Check for command keys (like ctrl_c, ctrl_z...).
|
||||||
|
// For command keys have to convert the charCode to a keyCode
|
||||||
|
// as it has been sent from the keydown event to be in line
|
||||||
|
// with the other browsers implementations.
|
||||||
|
|
||||||
|
// FF does not allow let you change the keyCode or charCode
|
||||||
|
// property. Store to a custom keyCode/charCode variable.
|
||||||
|
// The getCommandCodes() function takes care of these
|
||||||
|
// special variables.
|
||||||
|
ev._keyCode = exports.KeyHelper.PRINTABLE_KEYS_CHARCODE[ev.charCode];
|
||||||
|
ev._charCode = 0;
|
||||||
|
return handleBoundFunction(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal processing: send keyDown for printable keys.
|
||||||
|
if (ev.charCode !== undefined && ev.charCode === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleBoundFunction(ev);
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
99
plugins/pilot/lib/keyboard/tests/testKeyboard.js
Normal file
99
plugins/pilot/lib/keyboard/tests/testKeyboard.js
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
require.def(['require', 'exports', 'module',
|
||||||
|
'keyboard/keyboard',
|
||||||
|
'keyboard/tests/plugindev'
|
||||||
|
], function(require, exports, module,
|
||||||
|
keyboard,
|
||||||
|
t
|
||||||
|
) {
|
||||||
|
|
||||||
|
/* ***** 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 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):
|
||||||
|
* Skywriter Team (skywriter@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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.testKeyMatching = function() {
|
||||||
|
var km = keyboard.keyboardManager;
|
||||||
|
var command = {};
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {}), false,
|
||||||
|
'no keymapping means false');
|
||||||
|
|
||||||
|
command = {
|
||||||
|
key: 'meta_z'
|
||||||
|
};
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {}), true,
|
||||||
|
'matching keys, simple string');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_a', {}), false,
|
||||||
|
'not matching key, simple string');
|
||||||
|
|
||||||
|
command = {
|
||||||
|
key: {key: 'meta_z', predicates: {isGreen: true}}
|
||||||
|
};
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {}), false,
|
||||||
|
'object with not matching predicate');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {isGreen: true}), true,
|
||||||
|
'object with matching key and predicate');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_a', {isGreen: true}), false,
|
||||||
|
'object with not matching key');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_a', {isGreen: false}), false,
|
||||||
|
'object with neither matching');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {isGreen: false}), false,
|
||||||
|
'object with matching key and but different predicate');
|
||||||
|
|
||||||
|
command = {
|
||||||
|
key: ['meta_b', {key: 'meta_z', predicates: {isGreen: true}},
|
||||||
|
{key: 'meta_c'}]
|
||||||
|
};
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {}), false,
|
||||||
|
'list: object with not matching predicate');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {isGreen: true}), true,
|
||||||
|
'list: object with matching key and predicate');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_a', {isGreen: true}), false,
|
||||||
|
'list: object with not matching key');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_a', {isGreen: false}), false,
|
||||||
|
'list: object with neither matching');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_z', {isGreen: false}), false,
|
||||||
|
'list: object with matching key and but different predicate');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_b'), true,
|
||||||
|
'list: simple key match');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_c'), true,
|
||||||
|
'list: object without predicate match');
|
||||||
|
t.equal(km._commandMatches(command, 'meta_c', {isGreen: false}), true,
|
||||||
|
'list: flags don\'t matter without predicates');
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
185
plugins/pilot/lib/rangeutils.js
Normal file
185
plugins/pilot/lib/rangeutils.js
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
/* ***** 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):
|
||||||
|
* Patrick Walton (pwalton@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 util = require("util/util");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of adding the two positions.
|
||||||
|
*/
|
||||||
|
exports.addPositions = function(a, b) {
|
||||||
|
return { row: a.row + b.row, col: a.col + b.col };
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Returns a copy of the given range. */
|
||||||
|
exports.cloneRange = function(range) {
|
||||||
|
var oldStart = range.start, oldEnd = range.end;
|
||||||
|
var newStart = { row: oldStart.row, col: oldStart.col };
|
||||||
|
var newEnd = { row: oldEnd.row, col: oldEnd.col };
|
||||||
|
return { start: newStart, end: newEnd };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two positions a and b, returns a negative number if a < b, 0 if a = b,
|
||||||
|
* or a positive number if a > b.
|
||||||
|
*/
|
||||||
|
exports.comparePositions = function(positionA, positionB) {
|
||||||
|
var rowDiff = positionA.row - positionB.row;
|
||||||
|
return rowDiff === 0 ? positionA.col - positionB.col : rowDiff;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the two ranges are equal and false otherwise.
|
||||||
|
*/
|
||||||
|
exports.equal = function(rangeA, rangeB) {
|
||||||
|
return (exports.comparePositions(rangeA.start, rangeB.start) === 0 &&
|
||||||
|
exports.comparePositions(rangeA.end, rangeB.end) === 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.extendRange = function(range, delta) {
|
||||||
|
var end = range.end;
|
||||||
|
return {
|
||||||
|
start: range.start,
|
||||||
|
end: {
|
||||||
|
row: end.row + delta.row,
|
||||||
|
col: end.col + delta.col
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two sets of ranges, returns the ranges of characters that exist in one
|
||||||
|
* of the sets but not both.
|
||||||
|
*/
|
||||||
|
exports.intersectRangeSets = function(setA, setB) {
|
||||||
|
var stackA = util.clone(setA), stackB = util.clone(setB);
|
||||||
|
var result = [];
|
||||||
|
while (stackA.length > 0 && stackB.length > 0) {
|
||||||
|
var rangeA = stackA.shift(), rangeB = stackB.shift();
|
||||||
|
var startDiff = exports.comparePositions(rangeA.start, rangeB.start);
|
||||||
|
var endDiff = exports.comparePositions(rangeA.end, rangeB.end);
|
||||||
|
|
||||||
|
if (exports.comparePositions(rangeA.end, rangeB.start) < 0) {
|
||||||
|
// A is completely before B
|
||||||
|
result.push(rangeA);
|
||||||
|
stackB.unshift(rangeB);
|
||||||
|
} else if (exports.comparePositions(rangeB.end, rangeA.start) < 0) {
|
||||||
|
// B is completely before A
|
||||||
|
result.push(rangeB);
|
||||||
|
stackA.unshift(rangeA);
|
||||||
|
} else if (startDiff < 0) { // A starts before B
|
||||||
|
result.push({ start: rangeA.start, end: rangeB.start });
|
||||||
|
stackA.unshift({ start: rangeB.start, end: rangeA.end });
|
||||||
|
stackB.unshift(rangeB);
|
||||||
|
} else if (startDiff === 0) { // A and B start at the same place
|
||||||
|
if (endDiff < 0) { // A ends before B
|
||||||
|
stackB.unshift({ start: rangeA.end, end: rangeB.end });
|
||||||
|
} else if (endDiff > 0) { // A ends after B
|
||||||
|
stackA.unshift({ start: rangeB.end, end: rangeA.end });
|
||||||
|
}
|
||||||
|
} else if (startDiff > 0) { // A starts after B
|
||||||
|
result.push({ start: rangeB.start, end: rangeA.start });
|
||||||
|
stackA.unshift(rangeA);
|
||||||
|
stackB.unshift({ start: rangeA.start, end: rangeB.end });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.concat(stackA, stackB);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isZeroLength = function(range) {
|
||||||
|
return range.start.row === range.end.row &&
|
||||||
|
range.start.col === range.end.col;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the greater of the two positions.
|
||||||
|
*/
|
||||||
|
exports.maxPosition = function(a, b) {
|
||||||
|
return exports.comparePositions(a, b) > 0 ? a : b;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a range with swapped 'end' and 'start' values into one with the
|
||||||
|
* values in the correct order.
|
||||||
|
*
|
||||||
|
* TODO: Unit test.
|
||||||
|
*/
|
||||||
|
exports.normalizeRange = function(range) {
|
||||||
|
return this.comparePositions(range.start, range.end) < 0 ? range :
|
||||||
|
{ start: range.end, end: range.start };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a single range that spans the entire given set of ranges.
|
||||||
|
*/
|
||||||
|
exports.rangeSetBoundaries = function(rangeSet) {
|
||||||
|
return {
|
||||||
|
start: rangeSet[0].start,
|
||||||
|
end: rangeSet[rangeSet.length - 1].end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.toString = function(range) {
|
||||||
|
var start = range.start, end = range.end;
|
||||||
|
return '[ ' + start.row + ', ' + start.col + ' ' + end.row + ',' + + end.col +' ]';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the union of the two ranges.
|
||||||
|
*/
|
||||||
|
exports.unionRanges = function(a, b) {
|
||||||
|
return {
|
||||||
|
start: a.start.row < b.start.row ||
|
||||||
|
(a.start.row === b.start.row && a.start.col < b.start.col) ?
|
||||||
|
a.start : b.start,
|
||||||
|
end: a.end.row > b.end.row ||
|
||||||
|
(a.end.row === b.end.row && a.end.col > b.end.col) ?
|
||||||
|
a.end : b.end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isPosition = function(pos) {
|
||||||
|
return !util.none(pos) && !util.none(pos.row) && !util.none(pos.col);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.isRange = function(range) {
|
||||||
|
return (!util.none(range) && exports.isPosition(range.start) &&
|
||||||
|
exports.isPosition(range.end));
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
163
plugins/pilot/lib/tests/testRangeutils.js
Normal file
163
plugins/pilot/lib/tests/testRangeutils.js
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
require.def(['require', 'exports', 'module',
|
||||||
|
'rangeutils/tests/plugindev',
|
||||||
|
'rangeutils/tests/utils/range'
|
||||||
|
], function(require, exports, module,
|
||||||
|
t,
|
||||||
|
Range
|
||||||
|
) {
|
||||||
|
|
||||||
|
/* ***** 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 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):
|
||||||
|
* Skywriter Team (skywriter@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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.testAddPositions = function() {
|
||||||
|
t.deepEqual(Range.addPositions({ row: 0, col: 0 },
|
||||||
|
{ row: 0, col: 0 }), { row: 0, col: 0 }, '0,0 + 0,0 and 0,0');
|
||||||
|
t.deepEqual(Range.addPositions({ row: 1, col: 0 },
|
||||||
|
{ row: 2, col: 0 }), { row: 3, col: 0 }, '1,0 + 2,0 and 3,0');
|
||||||
|
t.deepEqual(Range.addPositions({ row: 0, col: 1 },
|
||||||
|
{ row: 0, col: 1 }), { row: 0, col: 2 }, '0,1 + 0,1 and 0,2');
|
||||||
|
t.deepEqual(Range.addPositions({ row: 1, col: 2 },
|
||||||
|
{ row: -1, col: -2 }), { row: 0, col: 0 }, '1,2 + -1,-2 and 0,0');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testCloneRange = function() {
|
||||||
|
var oldRange = { start: { row: 1, col: 2 }, end: { row: 3, col: 4 } };
|
||||||
|
var newRange = Range.cloneRange(oldRange);
|
||||||
|
t.deepEqual(oldRange, newRange, "the old range and the new range");
|
||||||
|
t.ok(oldRange.start !== newRange.start, "the old range's start position " +
|
||||||
|
"is distinct from the new range's start position");
|
||||||
|
t.ok(oldRange.end !== newRange.end, "the old range's end position is " +
|
||||||
|
"distinct from the new range's end position");
|
||||||
|
t.ok(oldRange !== newRange, "the old range is distinct from the new " +
|
||||||
|
"range");
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testComparePositions = function() {
|
||||||
|
t.equal(Range.comparePositions({ row: 0, col: 0 },
|
||||||
|
{ row: 0, col: 0 }), 0, '0,0 = 0,0');
|
||||||
|
t.ok(Range.comparePositions({ row: 0, col: 0 },
|
||||||
|
{ row: 1, col: 0 }) < 0, '0,0 < 1,0');
|
||||||
|
t.ok(Range.comparePositions({ row: 0, col: 0 },
|
||||||
|
{ row: 0, col: 1 }) < 0, '0,0 < 0,1');
|
||||||
|
t.ok(Range.comparePositions({ row: 1, col: 0 },
|
||||||
|
{ row: 0, col: 0 }) > 0, '1,0 > 0,0');
|
||||||
|
t.ok(Range.comparePositions({ row: 0, col: 1 },
|
||||||
|
{ row: 0, col: 0 }) > 0, '0,1 > 0,0');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testExtendRange = function() {
|
||||||
|
t.deepEqual(Range.extendRange({
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 3, col: 4 }
|
||||||
|
}, { row: 5, col: 6 }), {
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 8, col: 10 }
|
||||||
|
}, '[ 1,2 3,4 ] extended by 5,6 = [ 1,2 8,10 ]');
|
||||||
|
t.deepEqual(Range.extendRange({
|
||||||
|
start: { row: 7, col: 8 },
|
||||||
|
end: { row: 9, col: 10 }
|
||||||
|
}, { row: 0, col: 0 }), {
|
||||||
|
start: { row: 7, col: 8 },
|
||||||
|
end: { row: 9, col: 10 }
|
||||||
|
}, '[ 7,8 9,10 ] extended by 0,0 remains the same');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testMaxPosition = function() {
|
||||||
|
t.deepEqual(Range.maxPosition({ row: 0, col: 0 },
|
||||||
|
{ row: 0, col: 0 }), { row: 0, col: 0 }, 'max(0,0 0,0) = 0,0');
|
||||||
|
t.deepEqual(Range.maxPosition({ row: 0, col: 0 },
|
||||||
|
{ row: 1, col: 0 }), { row: 1, col: 0 }, 'max(0,0 1,0) = 1,0');
|
||||||
|
t.deepEqual(Range.maxPosition({ row: 0, col: 0 },
|
||||||
|
{ row: 0, col: 1 }), { row: 0, col: 1 }, 'max(0,0 0,1) = 0,1');
|
||||||
|
t.deepEqual(Range.maxPosition({ row: 1, col: 0 },
|
||||||
|
{ row: 0, col: 0 }), { row: 1, col: 0 }, 'max(1,0 0,0) = 1,0');
|
||||||
|
t.deepEqual(Range.maxPosition({ row: 0, col: 1 },
|
||||||
|
{ row: 0, col: 0 }), { row: 0, col: 1 }, 'max(0,1 0,0) = 0,1');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testNormalizeRange = function() {
|
||||||
|
t.deepEqual(Range.normalizeRange({
|
||||||
|
start: { row: 0, col: 0 },
|
||||||
|
end: { row: 0, col: 0 }
|
||||||
|
}), {
|
||||||
|
start: { row: 0, col: 0 },
|
||||||
|
end: { row: 0, col: 0 }
|
||||||
|
}, 'normalize(0,0 0,0) and (0,0 0,0)');
|
||||||
|
t.deepEqual(Range.normalizeRange({
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 3, col: 4 }
|
||||||
|
}), {
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 3, col: 4 }
|
||||||
|
}, 'normalize(1,2 3,4) and (1,2 3,4)');
|
||||||
|
t.deepEqual(Range.normalizeRange({
|
||||||
|
start: { row: 4, col: 3 },
|
||||||
|
end: { row: 2, col: 1 }
|
||||||
|
}), {
|
||||||
|
start: { row: 2, col: 1 },
|
||||||
|
end: { row: 4, col: 3 }
|
||||||
|
}, 'normalize(4,3 2,1) and (2,1 4,3)');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.testUnionRanges = function() {
|
||||||
|
t.deepEqual(Range.unionRanges({
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 3, col: 4 }
|
||||||
|
}, {
|
||||||
|
start: { row: 5, col: 6 },
|
||||||
|
end: { row: 7, col: 8 }
|
||||||
|
}), {
|
||||||
|
start: { row: 1, col: 2 },
|
||||||
|
end: { row: 7, col: 8 }
|
||||||
|
}, '[ 1,2 3,4 ] union [ 5,6 7,8 ] = [ 1,2 7,8 ]');
|
||||||
|
t.deepEqual(Range.unionRanges({
|
||||||
|
start: { row: 4, col: 4 },
|
||||||
|
end: { row: 5, col: 5 }
|
||||||
|
}, {
|
||||||
|
start: { row: 3, col: 3 },
|
||||||
|
end: { row: 4, col: 5 }
|
||||||
|
}), {
|
||||||
|
start: { row: 3, col: 3 },
|
||||||
|
end: { row: 5, col: 5 }
|
||||||
|
}, '[ 4,4 5,5 ] union [ 3,3 4,5 ] = [ 3,3 5,5 ]');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue