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
|
|
@ -161,7 +161,7 @@ exports.getCommandNames = function() {
|
|||
* @param command Either a command, or the name of one
|
||||
*/
|
||||
exports.exec = function(command, args) {
|
||||
if (typeof name === 'string') {
|
||||
if (typeof command === 'string') {
|
||||
command = commands[command];
|
||||
}
|
||||
if (!command) {
|
||||
|
|
|
|||
|
|
@ -1,850 +0,0 @@
|
|||
/* ***** 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 console = require('pilot/console');
|
||||
var util = require('pilot/util');
|
||||
var oop = require('pilot/oop').oop;
|
||||
var EventEmitter = require('pilot/event_emitter').EventEmitter;
|
||||
|
||||
//var keyboard = require('keyboard/keyboard');
|
||||
var types = require('pilot/types');
|
||||
var Status = require('pilot/types').Status;
|
||||
var Conversion = require('pilot/types').Conversion;
|
||||
var canon = require('pilot/canon');
|
||||
|
||||
|
||||
/**
|
||||
* The information required to tell the user there is a problem with their
|
||||
* input.
|
||||
*/
|
||||
function Hint(status, message, start, end) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
|
||||
if (typeof start === 'number') {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
else {
|
||||
var arg = start;
|
||||
this.start = arg.start;
|
||||
this.end = arg.end;
|
||||
}
|
||||
}
|
||||
Hint.prototype = {
|
||||
};
|
||||
exports.Hint = Hint;
|
||||
|
||||
/**
|
||||
* A Hint that arose as a result of a Conversion
|
||||
*/
|
||||
function ConversionHint(conversion, arg) {
|
||||
this.status = conversion.status;
|
||||
this.message = conversion.message;
|
||||
if (arg) {
|
||||
this.start = arg.start;
|
||||
this.end = arg.end;
|
||||
}
|
||||
else {
|
||||
this.start = 0;
|
||||
this.end = 0;
|
||||
}
|
||||
this.predictions = conversion.predictions;
|
||||
};
|
||||
oop.inherits(ConversionHint, Hint);
|
||||
|
||||
|
||||
/**
|
||||
* We record where in the input string an argument comes so we can report errors
|
||||
* against those string positions.
|
||||
* @constructor
|
||||
*/
|
||||
function Argument(text, start, end, priorSpace) {
|
||||
this.setText(text);
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.priorSpace = priorSpace;
|
||||
}
|
||||
Argument.prototype = {
|
||||
/**
|
||||
* Return the result of merging these arguments
|
||||
*/
|
||||
merge: function(following) {
|
||||
return new Argument(
|
||||
this.text + following.priorSpace + following.text,
|
||||
this.start, following.end,
|
||||
this.priorSpace);
|
||||
},
|
||||
|
||||
setText: function(text) {
|
||||
if (text == null) {
|
||||
throw new Error('Illegal text for Argument: ' + text);
|
||||
}
|
||||
this.text = text;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Merge an array of arguments into a single argument.
|
||||
*/
|
||||
Argument.merge = function(argArray, start, end) {
|
||||
start = (start === undefined) ? 0 : start;
|
||||
end = (end === undefined) ? argArray.length : end;
|
||||
|
||||
var joined;
|
||||
for (var i = start; i < end; i++) {
|
||||
var arg = argArray[i];
|
||||
if (!joined) {
|
||||
joined = arg;
|
||||
}
|
||||
else {
|
||||
joined = joined.merge(arg);
|
||||
}
|
||||
}
|
||||
return joined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* CLI / UI Interface
|
||||
* The Cli interacts with the UI via an instance of CliUi.
|
||||
* This implementation is designed as a template rather than to be used.
|
||||
* It is expected that we will have a number of implementations of this:
|
||||
* - A firebug/webkit inspector cli shim
|
||||
* - A simple input[type=text] version
|
||||
* - A possible Cloud9 UI version
|
||||
* - A possible Skywriter UI version
|
||||
* This class will probably need refactoring as time goes on.
|
||||
*
|
||||
* TODO: Who should own the Requisition?
|
||||
*/
|
||||
function CliUi() {
|
||||
}
|
||||
CliUi.prototype = {
|
||||
getSelection: function() {},
|
||||
setHints: function() {},
|
||||
setRequisition: function() {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An object used during command line parsing to hold the various intermediate
|
||||
* data steps.
|
||||
* <p>The 'output' of the parse 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
|
||||
* single value.
|
||||
* <p>The other output value is input.requisition which gives access to an
|
||||
* args object for use in executing the final command.
|
||||
*
|
||||
* <p>The majority of the functions in this class are called in sequence by the
|
||||
* constructor. Their task is to add to <tt>hints</tt> fill out the requisition.
|
||||
* <p>The general sequence is:<ul>
|
||||
* <li>_tokenize(): convert _typed into _parts
|
||||
* <li>_split(): convert _parts into _command and _unparsedArgs
|
||||
* <li>_assign(): convert _unparsedArgs into requisition
|
||||
* </ul>
|
||||
*
|
||||
* @param typed {string} The instruction as typed by the user so far
|
||||
* @param options {object} A list of optional named parameters. Can be any of:
|
||||
* <b>flags</b>: Flags for us to check against the predicates specified with the
|
||||
* commands. Defaulted to <tt>keyboard.buildFlags({ });</tt>
|
||||
* if not specified.
|
||||
* @constructor
|
||||
*/
|
||||
function Cli(cliui, options) {
|
||||
this.cliui = cliui;
|
||||
if (options && options.flags) {
|
||||
this.flags = options.flags;
|
||||
}
|
||||
|
||||
this.requisition = new Requisition();
|
||||
this.cliui.setRequisition(this.requisition);
|
||||
}
|
||||
Cli.prototype = {
|
||||
/**
|
||||
* 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) {
|
||||
if (util.none(typed)) {
|
||||
this.requisition.setCommand(null);
|
||||
this.cliui.setHints([]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.typed = typed;
|
||||
this.hints = [];
|
||||
|
||||
var args = _tokenize(this.typed);
|
||||
if (args.length === 0) {
|
||||
// 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
|
||||
// need to find a way to only display this message once, or for
|
||||
// until the user click a 'close' button or similar
|
||||
this._addHint(Status.INCOMPLETE, '', 0, 0);
|
||||
this.requisition.setCommand(null);
|
||||
this.cliui.setHints(this.hints);
|
||||
return;
|
||||
}
|
||||
|
||||
var command = _split(args);
|
||||
|
||||
if (!command) {
|
||||
// No command found - bail helpfully.
|
||||
var commandType = types.getType('command');
|
||||
var conversion = commandType.parse(typed);
|
||||
var arg = Argument.merge(args);
|
||||
this._addHint(new ConversionHint(conversion, arg));
|
||||
|
||||
this.requisition.setCommand(null);
|
||||
}
|
||||
else {
|
||||
// The user hasn't started to type any arguments
|
||||
if (args.length === 0) {
|
||||
var message = documentCommand(command);
|
||||
this._addHint(Status.VALID, message, 0, typed.length);
|
||||
}
|
||||
|
||||
this.requisition.setCommand(command);
|
||||
this._assign(args);
|
||||
this._addHint(this.requisition.getHints());
|
||||
}
|
||||
|
||||
// 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
|
||||
// can't know this, but anything they mark as INCOMPLETE is actually
|
||||
// INVALID unless the cursor is actually inside that argument.
|
||||
var sel = this.cliui.getSelection();
|
||||
this.hints.forEach(function(hint) {
|
||||
var startInHint = sel.start >= hint.start && sel.start <= hint.end;
|
||||
var endInHint = sel.end >= hint.start && sel.end <= hint.end;
|
||||
var inHint = startInHint || endInHint;
|
||||
if (!inHint && hint.status === Status.INCOMPLETE) {
|
||||
hint.status = Status.INVALID;
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.cliui.setHints(this.hints);
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
_addHint: function(status, message, start, end) {
|
||||
if (status == null) {
|
||||
return;
|
||||
}
|
||||
if (status instanceof Hint) {
|
||||
this.hints.push(status);
|
||||
}
|
||||
else if (Array.isArray(status)) {
|
||||
this.hints.push.apply(this.hints, status);
|
||||
}
|
||||
else {
|
||||
this.hints.push(new Hint(status, message, start, end));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Work out which arguments are applicable to which parameters.
|
||||
* <p>This takes #_command.params and #_unparsedArgs and creates a map of
|
||||
* param names to 'assignment' objects, which have the following properties:
|
||||
* <ul>
|
||||
* <li>param - The matching parameter.
|
||||
* <li>index - Zero based index into where the match came from on the input
|
||||
* <li>value - The matching input
|
||||
* </ul>
|
||||
*/
|
||||
_assign: function(args) {
|
||||
if (args.length === 0) {
|
||||
this.requisition.setDefaultValues();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an error if the command does not take parameters, but we have
|
||||
// been given them ...
|
||||
if (this.requisition.assignmentCount === 0) {
|
||||
// TODO: previously we were doing some extra work to avoid this if
|
||||
// we determined that we had args that were all whitespace, but
|
||||
// probably given our tighter tokenize() this won't be an issue?
|
||||
this._addHint(Status.INVALID,
|
||||
this.requisition.command.name + ' does not take any parameters',
|
||||
Argument.merge(args));
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: if there is only 1 parameter, and that's of type
|
||||
// text we put all the params into the first param
|
||||
if (this.requisition.assignmentCount == 1) {
|
||||
var assignment = this.requisition.getAssignment(0);
|
||||
if (assignment.param.type.name === 'text') {
|
||||
assignment.setArgument(Argument.merge(args));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var assignments = this.requisition.cloneAssignments();
|
||||
var names = this.requisition.getParameterNames();
|
||||
|
||||
// Extract all the named parameters
|
||||
var used = [];
|
||||
assignments.forEach(function(assignment) {
|
||||
var namedArgText = '--' + assignment.name;
|
||||
|
||||
var i = 0;
|
||||
while (true) {
|
||||
var arg = args[i];
|
||||
if (namedArgText !== arg.text) {
|
||||
i++;
|
||||
if (i >= args.length) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// boolean parameters don't have values, default to false
|
||||
if (assignment.param.type.name === 'boolean') {
|
||||
assignment.setValue(true);
|
||||
}
|
||||
else {
|
||||
if (i + 1 < args.length) {
|
||||
// Missing value portion of this named param
|
||||
this._addHint(Status.INCOMPLETE,
|
||||
'Missing value for: ' + namedArgText,
|
||||
args[i]);
|
||||
}
|
||||
else {
|
||||
args.splice(i + 1, 1);
|
||||
assignment.setArgument(args[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
util.arrayRemove(names, assignment.name);
|
||||
args.splice(i, 1);
|
||||
// We don't need to i++ if we splice
|
||||
}
|
||||
}, this);
|
||||
|
||||
// What's left are positional parameters assign in order
|
||||
names.forEach(function(name) {
|
||||
var assignment = this.requisition.getAssignment(name);
|
||||
if (args.length === 0) {
|
||||
// No more values
|
||||
assignment.setValue(undefined); // i.e. default
|
||||
}
|
||||
else {
|
||||
var arg = args[0];
|
||||
args.splice(0, 1);
|
||||
assignment.setArgument(arg);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (args.length > 0) {
|
||||
var remaining = Argument.merge(args);
|
||||
this._addHint(Status.INVALID,
|
||||
'Input \'' + remaining.text + '\' makes no sense.',
|
||||
remaining);
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.Cli = Cli;
|
||||
|
||||
/**
|
||||
* Split up the input taking into account ' and "
|
||||
*/
|
||||
function _tokenize(typed) {
|
||||
var OUTSIDE = 1; // The last character was whitespace
|
||||
var IN_SIMPLE = 2; // The last character was part of a parameter
|
||||
var IN_SINGLE_Q = 3; // We're inside a single quote: '
|
||||
var IN_DOUBLE_Q = 4; // We're inside double quotes: "
|
||||
|
||||
var mode = OUTSIDE;
|
||||
|
||||
// First we un-escape. This list was taken from:
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Unicode
|
||||
// We are generally converting to their real values except for \', \"
|
||||
// and '\ ' which we are converting to unicode private characters so we
|
||||
// can distinguish them from ', " and ' ', which have special meaning.
|
||||
// They need swapping back post-split - see unescape()
|
||||
typed = typed
|
||||
.replace(/\\\\/g, '\\')
|
||||
.replace(/\\b/g, '\b')
|
||||
.replace(/\\f/g, '\f')
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\v/g, '\v')
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\ /g, '\uF000')
|
||||
.replace(/\\'/g, '\uF001')
|
||||
.replace(/\\"/g, '\uF002');
|
||||
|
||||
function unescape(str) {
|
||||
return str
|
||||
.replace(/\uF000/g, ' ')
|
||||
.replace(/\uF001/g, '\'')
|
||||
.replace(/\uF002/g, '"');
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var start = 0; // Where did this section start?
|
||||
var priorSpace = '';
|
||||
var args = [];
|
||||
|
||||
while (true) {
|
||||
if (i >= typed.length) {
|
||||
// There is no more - tidy up
|
||||
if (mode !== OUTSIDE) {
|
||||
var str = unescape(typed.substring(start, i));
|
||||
args.push(new Argument(str, start, i, priorSpace));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var c = typed[i];
|
||||
switch (mode) {
|
||||
case OUTSIDE:
|
||||
if (c === '\'') {
|
||||
priorSpace = typed.substring(start, i);
|
||||
mode = IN_SINGLE_Q;
|
||||
start = i + 1;
|
||||
}
|
||||
else if (c === '"') {
|
||||
priorSpace = typed.substring(start, i);
|
||||
mode = IN_DOUBLE_Q;
|
||||
start = i + 1;
|
||||
}
|
||||
else if (/ /.test(c)) {
|
||||
// Still whitespace, do nothing
|
||||
}
|
||||
else {
|
||||
priorSpace = typed.substring(start, i);
|
||||
mode = IN_SIMPLE;
|
||||
start = i;
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_SIMPLE:
|
||||
// There is an edge case of xx'xx which we are assuming to be
|
||||
// a single parameter (and same with ")
|
||||
if (c === ' ') {
|
||||
var str = unescape(typed.substring(start, i));
|
||||
args.push(new Argument(str, start, i, priorSpace));
|
||||
mode = OUTSIDE;
|
||||
start = i;
|
||||
priorSpace = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_SINGLE_Q:
|
||||
if (c === '\'') {
|
||||
var str = unescape(typed.substring(start, i));
|
||||
args.push(new Argument(str, start, i, priorSpace));
|
||||
mode = OUTSIDE;
|
||||
start = i + 1;
|
||||
priorSpace = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case IN_DOUBLE_Q:
|
||||
if (c === '"') {
|
||||
var str = unescape(typed.substring(start, i));
|
||||
args.push(new Argument(str, start, i, priorSpace));
|
||||
mode = OUTSIDE;
|
||||
start = i + 1;
|
||||
priorSpace = '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
exports._tokenize = _tokenize;
|
||||
|
||||
/**
|
||||
* Looks in the canon for a command extension that matches what has been
|
||||
* typed at the command line.
|
||||
*/
|
||||
function _split(args) {
|
||||
if (args.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var argsUsed = 0;
|
||||
var lookup = '';
|
||||
var command;
|
||||
|
||||
while (true) {
|
||||
argsUsed++;
|
||||
lookup += args.map(function(arg) {
|
||||
return arg.text;
|
||||
}).slice(0, argsUsed).join(' ');
|
||||
command = canon.getCommand(lookup);
|
||||
|
||||
if (!command) {
|
||||
// Not found. break with command == null
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
// Previously we needed a way to hide commands depending context.
|
||||
// We have not resurrected that feature yet.
|
||||
if (!keyboard.flagsMatch(command.predicates, this.flags)) {
|
||||
// If the predicates say 'no match' then go LA LA LA
|
||||
command = null;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
if (command.exec) {
|
||||
// Valid command, break with command valid
|
||||
break;
|
||||
}
|
||||
|
||||
// command, but no exec - this must be a sub-command
|
||||
lookup += ' ';
|
||||
}
|
||||
|
||||
// Remove the used args
|
||||
for (var i = 0; i < argsUsed; i++) {
|
||||
args.shift();
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
exports._split = _split;
|
||||
|
||||
|
||||
/**
|
||||
* Provide some documentation for a command.
|
||||
* TODO: this should return a hint
|
||||
*/
|
||||
function documentCommand(command) {
|
||||
var docs = [];
|
||||
docs.push('<h1>' + command.name + '</h1>');
|
||||
docs.push('<h2>Summary</h2>');
|
||||
docs.push('<p>' + command.description + '</p>');
|
||||
|
||||
if (command.manual) {
|
||||
docs.push('<h2>Description</h2>');
|
||||
docs.push('<p>' + command.description + '</p>');
|
||||
}
|
||||
|
||||
if (command.params && command.params.length > 0) {
|
||||
docs.push('<h2>Synopsis</h2>');
|
||||
docs.push('<pre>');
|
||||
docs.push(command.name);
|
||||
var optionalParamCount = 0;
|
||||
command.params.forEach(function(param) {
|
||||
if (param.defaultValue === undefined) {
|
||||
docs.push(' <i>');
|
||||
docs.push(param.name);
|
||||
docs.push('</i>');
|
||||
}
|
||||
else if (param.defaultValue === null) {
|
||||
docs.push(' <i>[');
|
||||
docs.push(param.name);
|
||||
docs.push(']</i>');
|
||||
}
|
||||
else {
|
||||
optionalParamCount++;
|
||||
}
|
||||
}, this);
|
||||
if (optionalParamCount > 3) {
|
||||
docs.push(' [options]');
|
||||
} else if (optionalParamCount > 0) {
|
||||
command.params.forEach(function(param) {
|
||||
if (param.defaultValue) {
|
||||
docs.push(' [--<i>');
|
||||
docs.push(param.name);
|
||||
if (param.type.name === 'boolean') {
|
||||
docs.push('</i>');
|
||||
}
|
||||
else {
|
||||
docs.push('</i> ' + param.type.name);
|
||||
}
|
||||
docs.push(']');
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
docs.push('</pre>');
|
||||
|
||||
docs.push('<h2>Parameters</h2>');
|
||||
command.params.forEach(function(param) {
|
||||
docs.push('<h3 class="cmd_body"><i>' + param.name + '</i></h3>');
|
||||
docs.push('<p>' + param.description + '</p>');
|
||||
if (param.type.defaultValue) {
|
||||
docs.push('<p>Default: ' + param.type.defaultValue + '</p>');
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
return docs.join('');
|
||||
};
|
||||
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;
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -40,11 +40,9 @@ var deps = [
|
|||
"pilot/types/basic",
|
||||
"pilot/types/command",
|
||||
"pilot/types/settings",
|
||||
"pilot/canon",
|
||||
"pilot/commands/settings",
|
||||
"pilot/settings/canon",
|
||||
"pilot/cli",
|
||||
"pilot/test/testCli"
|
||||
"pilot/canon"
|
||||
];
|
||||
|
||||
var packages = deps.slice();
|
||||
|
|
@ -52,6 +50,7 @@ packages.unshift("require", "exports", "module");
|
|||
|
||||
define(packages, function(require, exports, module) {
|
||||
|
||||
console.log(packages);
|
||||
exports.startup = function(data, reason) {
|
||||
deps.forEach(function(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));
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Skywriter.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Joe Walker (jwalker@mozilla.com)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
var test = {
|
||||
|
||||
success: function(message) {
|
||||
console.log(message);
|
||||
},
|
||||
|
||||
fail: function(message) {
|
||||
test._recordThrow("fail", arguments);
|
||||
},
|
||||
|
||||
assertTrue: function(value) {
|
||||
if (!value) {
|
||||
test._recordThrow("assertTrue", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyTrue: function(value) {
|
||||
if (!value) {
|
||||
test._recordTrace("verifyTrue", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertFalse: function(value) {
|
||||
if (value) {
|
||||
test._recordThrow("assertFalse", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyFalse: function(value) {
|
||||
if (value) {
|
||||
test._recordTrace("verifyFalse", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNull: function(value) {
|
||||
if (value !== null) {
|
||||
test._recordThrow("assertNull", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNull: function(value) {
|
||||
if (value !== null) {
|
||||
test._recordTrace("verifyNull", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotNull: function(value) {
|
||||
if (value === null) {
|
||||
test._recordThrow("assertNotNull", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNotNull: function(value) {
|
||||
if (value === null) {
|
||||
test._recordTrace("verifyNotNull", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertUndefined: function(value) {
|
||||
if (value !== undefined) {
|
||||
test._recordThrow("assertUndefined", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyUndefined: function(value) {
|
||||
if (value !== undefined) {
|
||||
test._recordTrace("verifyUndefined", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotUndefined: function(value) {
|
||||
if (value === undefined) {
|
||||
test._recordThrow("assertNotUndefined", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNotUndefined: function(value) {
|
||||
if (value === undefined) {
|
||||
test._recordTrace("verifyNotUndefined", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNaN: function(value) {
|
||||
if (!isNaN(value)) {
|
||||
test._recordThrow("assertNaN", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNaN: function(value) {
|
||||
if (!isNaN(value)) {
|
||||
test._recordTrace("verifyNaN", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotNaN: function(value) {
|
||||
if (isNaN(value)) {
|
||||
test._recordThrow("assertNotNaN", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNotNaN: function(value) {
|
||||
if (isNaN(value)) {
|
||||
test._recordTrace("verifyNotNaN", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertEqual: function(expected, actual) {
|
||||
if (!test._isEqual(expected, actual)) {
|
||||
test._recordThrow("assertEqual", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyEqual: function(expected, actual) {
|
||||
if (!test._isEqual(expected, actual)) {
|
||||
test._recordTrace("verifyEqual", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotEqual: function(expected, actual) {
|
||||
if (test._isEqual(expected, actual)) {
|
||||
test._recordThrow("assertNotEqual", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNotEqual: function(expected, actual) {
|
||||
if (test._isEqual(expected, actual)) {
|
||||
test._recordTrace("verifyNotEqual", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertSame: function(expected, actual) {
|
||||
if (expected !== actual) {
|
||||
test._recordThrow("assertSame", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifySame: function(expected, actual) {
|
||||
if (expected !== actual) {
|
||||
test._recordTrace("verifySame", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
assertNotSame: function(expected, actual) {
|
||||
if (expected !== actual) {
|
||||
test._recordThrow("assertNotSame", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
verifyNotSame: function(expected, actual) {
|
||||
if (expected !== actual) {
|
||||
test._recordTrace("verifyNotSame", arguments);
|
||||
}
|
||||
},
|
||||
|
||||
_recordTrace: function() {
|
||||
test._record.apply(this, arguments);
|
||||
console.trace();
|
||||
},
|
||||
|
||||
_recordThrow: function() {
|
||||
test._record.apply(this, arguments);
|
||||
throw new Error();
|
||||
},
|
||||
|
||||
_record: function() {
|
||||
console.error(arguments);
|
||||
var message = arguments[0] + "(";
|
||||
var data = arguments[1];
|
||||
if (typeof data == "string") {
|
||||
message += data;
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (i != 0){message += ", ";}
|
||||
message += data[i];
|
||||
}
|
||||
}
|
||||
message += ")";
|
||||
console.log(message);
|
||||
},
|
||||
|
||||
_isEqual: function(expected, actual, depth) {
|
||||
if (!depth){depth = 0;}
|
||||
// Rather than failing we assume that it works!
|
||||
if (depth > 10) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expected == null) {
|
||||
if (actual != null) {
|
||||
console.log("expected: null, actual non-null: ", actual);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof(expected) == "number" && isNaN(expected)) {
|
||||
if (!(typeof(actual) == "number" && isNaN(actual))) {
|
||||
console.log("expected: NaN, actual non-NaN: ", actual);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actual == null) {
|
||||
if (expected != null) {
|
||||
console.log("actual: null, expected non-null: ", expected);
|
||||
return false;
|
||||
}
|
||||
return true; // we wont get here of course ...
|
||||
}
|
||||
|
||||
if (typeof expected == "object") {
|
||||
if (!(typeof actual == "object")) {
|
||||
console.log("expected object, actual not an object");
|
||||
return false;
|
||||
}
|
||||
|
||||
var actualLength = 0;
|
||||
for (var prop in actual) {
|
||||
if (typeof actual[prop] != "function" ||
|
||||
typeof expected[prop] != "function") {
|
||||
var nest = test._isEqual(actual[prop], expected[prop], depth + 1);
|
||||
if (typeof nest != "boolean" || !nest) {
|
||||
console.log("element '" + prop + "' does not match: " + nest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
actualLength++;
|
||||
}
|
||||
|
||||
// need to check length too
|
||||
var expectedLength = 0;
|
||||
for (prop in expected) expectedLength++;
|
||||
if (actualLength != expectedLength) {
|
||||
console.log("expected object size = " + expectedLength +
|
||||
", actual object size = " + actualLength);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actual != expected) {
|
||||
console.log("expected = " + expected + " (type=" + typeof expected + "), " +
|
||||
"actual = " + actual + " (type=" + typeof actual + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected instanceof Array) {
|
||||
if (!(actual instanceof Array)) {
|
||||
console.log("expected array, actual not an array");
|
||||
return false;
|
||||
}
|
||||
if (actual.length != expected.length) {
|
||||
console.log("expected array length = " + expected.length +
|
||||
", actual array length = " + actual.length);
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < actual.length; i++) {
|
||||
var inner = test._isEqual(actual[i], expected[i], depth + 1);
|
||||
if (typeof inner != "boolean" || !inner) {
|
||||
console.log("element " + i + " does not match: " + inner);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.test = test;
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
/* ***** 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 test = require('pilot/test/assert').test;
|
||||
var cli = require('pilot/cli');
|
||||
var Status = require('pilot/types').Status;
|
||||
var settings = require('pilot/settings').settings;
|
||||
|
||||
exports.testAll = function() {
|
||||
exports.testTokenize();
|
||||
exports.testSplit();
|
||||
exports.testCli();
|
||||
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() {
|
||||
var args = cli._tokenize('');
|
||||
test.verifyEqual(0, args.length);
|
||||
|
||||
args = cli._tokenize('s');
|
||||
test.verifyEqual(1, args.length);
|
||||
test.verifyEqual('s', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
test.verifyEqual(1, args[0].end);
|
||||
test.verifyEqual('', args[0].priorSpace);
|
||||
|
||||
args = cli._tokenize('s s');
|
||||
test.verifyEqual(2, args.length);
|
||||
test.verifyEqual('s', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
test.verifyEqual(1, args[0].end);
|
||||
test.verifyEqual('', args[0].priorSpace);
|
||||
test.verifyEqual('s', args[1].text);
|
||||
test.verifyEqual(2, args[1].start);
|
||||
test.verifyEqual(3, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
|
||||
args = cli._tokenize(' 1234 \'12 34\'');
|
||||
test.verifyEqual(2, args.length);
|
||||
test.verifyEqual('1234', args[0].text);
|
||||
test.verifyEqual(1, args[0].start);
|
||||
test.verifyEqual(5, args[0].end);
|
||||
test.verifyEqual(' ', args[0].priorSpace);
|
||||
test.verifyEqual('12 34', args[1].text);
|
||||
test.verifyEqual(8, args[1].start);
|
||||
test.verifyEqual(13, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
|
||||
args = cli._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
|
||||
test.verifyEqual(3, args.length);
|
||||
test.verifyEqual('12\'34', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
test.verifyEqual(5, args[0].end);
|
||||
test.verifyEqual('', args[0].priorSpace);
|
||||
test.verifyEqual('12 34', args[1].text);
|
||||
test.verifyEqual(7, args[1].start);
|
||||
test.verifyEqual(12, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
test.verifyEqual('\\', args[2].text);
|
||||
test.verifyEqual(14, args[2].start);
|
||||
test.verifyEqual(15, args[2].end);
|
||||
test.verifyEqual(' ', args[2].priorSpace);
|
||||
|
||||
args = cli._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
|
||||
test.verifyEqual(4, args.length);
|
||||
test.verifyEqual('a b', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
test.verifyEqual(3, args[0].end);
|
||||
test.verifyEqual('', args[0].priorSpace);
|
||||
test.verifyEqual('\t\n\r', args[1].text);
|
||||
test.verifyEqual(4, args[1].start);
|
||||
test.verifyEqual(7, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
test.verifyEqual('\'x"', args[2].text);
|
||||
test.verifyEqual(8, args[2].start);
|
||||
test.verifyEqual(11, args[2].end);
|
||||
test.verifyEqual(' ', args[2].priorSpace);
|
||||
test.verifyEqual('d', args[3].text);
|
||||
test.verifyEqual(13, args[3].start);
|
||||
test.verifyEqual(14, args[3].end);
|
||||
test.verifyEqual(' ', args[3].priorSpace);
|
||||
|
||||
return "testTokenize Completed";
|
||||
};
|
||||
|
||||
var hints;
|
||||
var hint0;
|
||||
var requisition;
|
||||
var sel = { start: -1, end: -1 };
|
||||
var settingAssignment;
|
||||
var valueAssignment;
|
||||
mockCliUi = {
|
||||
getSelection: function() {
|
||||
return sel;
|
||||
},
|
||||
|
||||
setHints: function(h) {
|
||||
hints = h;
|
||||
hint0 = (h.length !== 0) ? h[0] : undefined;
|
||||
|
||||
if (requisition && requisition.command && requisition.command.name === 'set') {
|
||||
settingAssignment = requisition.getAssignment('setting');
|
||||
valueAssignment = requisition.getAssignment('value');
|
||||
}
|
||||
else {
|
||||
settingAssignment = undefined;
|
||||
valueAssignment = undefined;
|
||||
}
|
||||
},
|
||||
|
||||
setRequisition: function(r) {
|
||||
requisition = r;
|
||||
}
|
||||
};
|
||||
|
||||
exports.testCli = function() {
|
||||
var historyLengthSetting = settings.getSetting('historyLength');
|
||||
|
||||
var input = new cli.Cli(mockCliUi);
|
||||
|
||||
input.parse('');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||
test.verifyEqual(0, hint0.start);
|
||||
test.verifyEqual(0, hint0.end);
|
||||
test.verifyNull(requisition.command);
|
||||
|
||||
sel.start = sel.end = 1;
|
||||
input.parse('s');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||
test.verifyNotEqual(-1, hint0.message.indexOf('possibilities'));
|
||||
test.verifyEqual(0, hint0.start);
|
||||
test.verifyEqual(1, hint0.end);
|
||||
test.verifyTrue(hint0.predictions.length > 0);
|
||||
// This is slightly fragile because it depends on the configuration
|
||||
test.verifyTrue(hint0.predictions.length < 20);
|
||||
test.verifyNotEqual(-1, hint0.predictions.indexOf('set'));
|
||||
test.verifyNull(requisition.command);
|
||||
|
||||
input.parse('set');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.VALID, hint0.status);
|
||||
test.verifyEqual(0, hint0.start);
|
||||
test.verifyEqual(3, hint0.end);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
|
||||
input.parse('set ');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.VALID, hint0.status);
|
||||
test.verifyEqual(0, hint0.start);
|
||||
// Technically the command ends at 3, but we're returning 4 currently.
|
||||
// This is caused by us using the whole input to determine the length.
|
||||
// Maybe one day we should fix this?
|
||||
//test.verifyEqual(3, hint0.end);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
|
||||
sel.start = sel.end = 5;
|
||||
input.parse('set h');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||
test.verifyTrue(hint0.predictions.length > 0);
|
||||
test.verifyEqual(4, hint0.start);
|
||||
test.verifyEqual(5, hint0.end);
|
||||
test.verifyNotEqual(-1, hint0.predictions.indexOf('historyLength'));
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('h', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 16;
|
||||
input.parse('set historyLengt');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||
test.verifyEqual(1, hint0.predictions.length);
|
||||
test.verifyEqual(4, hint0.start);
|
||||
test.verifyEqual(16, hint0.end);
|
||||
test.verifyEqual('historyLength', hint0.predictions[0]);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 1;
|
||||
input.parse('set historyLengt');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INVALID, hint0.status);
|
||||
test.verifyEqual(4, hint0.start);
|
||||
test.verifyEqual(16, hint0.end);
|
||||
test.verifyEqual(1, hint0.predictions.length);
|
||||
test.verifyEqual('historyLength', hint0.predictions[0]);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 17;
|
||||
input.parse('set historyLengt ');
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INVALID, hint0.status);
|
||||
test.verifyEqual(4, hint0.start);
|
||||
test.verifyEqual(16, hint0.end);
|
||||
test.verifyEqual(1, hint0.predictions.length);
|
||||
test.verifyEqual('historyLength', hint0.predictions[0]);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength');
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength ');
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength 6');
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
test.verifyEqual('6', valueAssignment.arg.text);
|
||||
test.verifyEqual(6, valueAssignment.value);
|
||||
test.verifyEqual('number', typeof valueAssignment.value);
|
||||
|
||||
// TODO: Add test to see that a command without mandatory param causes INVALID
|
||||
|
||||
console.log(input);
|
||||
|
||||
return "testCli Completed";
|
||||
};
|
||||
|
||||
window.testCli = exports;
|
||||
|
||||
|
||||
});
|
||||
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