a big lump of work to get the command line able to execute commands, and to hack in a trivial cli
This commit is contained in:
parent
1e01007990
commit
38c9fc7a93
17 changed files with 1715 additions and 379 deletions
|
|
@ -45,7 +45,7 @@ var setupPlugins = function(config, callback) {
|
|||
// packages: ["ace"]
|
||||
// };
|
||||
config.pluginDirs["../plugins"] = {
|
||||
packages: ["pilot"]
|
||||
packages: ["pilot", "cockpit"]
|
||||
};
|
||||
|
||||
var knownPlugins = [];
|
||||
|
|
@ -109,7 +109,10 @@ var setupPlugins = function(config, callback) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
packagePaths: pluginPackageInfo,
|
||||
paths: paths
|
||||
}));
|
||||
require({
|
||||
packagePaths: pluginPackageInfo,
|
||||
paths: paths
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ exports.launch = function(env) {
|
|||
|
||||
function onResize() {
|
||||
container.style.width = (document.documentElement.clientWidth - 4) + "px";
|
||||
container.style.height = (document.documentElement.clientHeight - 55 - 4) + "px";
|
||||
container.style.height = (document.documentElement.clientHeight - 55 - 4 - 23) + "px";
|
||||
env.editor.resize();
|
||||
};
|
||||
|
||||
|
|
|
|||
15
editor.html
15
editor.html
|
|
@ -47,6 +47,11 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#cockpit {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
require = {
|
||||
|
|
@ -63,11 +68,7 @@
|
|||
}
|
||||
}
|
||||
}, function(plugin_manager, settings) {
|
||||
var data = {
|
||||
env: {
|
||||
settings: settings
|
||||
}
|
||||
};
|
||||
var data = { env: { settings: settings } };
|
||||
plugin_manager.catalog.startupPlugins(data, plugin_manager.REASONS.APP_STARTUP).then(function() {
|
||||
var demo_startup = require("demo_startup");
|
||||
demo_startup.launch(data.env);
|
||||
|
|
@ -155,6 +156,8 @@
|
|||
</body>
|
||||
</html>
|
||||
</script>
|
||||
|
||||
|
||||
<input id=cockpit type=text/>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -142,31 +142,221 @@ Argument.merge = function(argArray, start, end) {
|
|||
|
||||
|
||||
/**
|
||||
* 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?
|
||||
* 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 CliUi() {
|
||||
}
|
||||
CliUi.prototype = {
|
||||
getSelection: function() {},
|
||||
setHints: function() {},
|
||||
setRequisition: function() {}
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* A Requisition collects the information needed to execute a command.
|
||||
* There is no point in a requisition for parameter-less commands because there
|
||||
* is no information to collect. A Requisition is a collection of assignments
|
||||
* of values to parameters, each handled by an instance of Assignment.
|
||||
* CliRequisition adds functions for parsing input from a command line to this
|
||||
* class
|
||||
* @constructor
|
||||
*/
|
||||
function Requisition() {
|
||||
}
|
||||
Requisition.prototype = {
|
||||
/**
|
||||
* The command that we are about to execute.
|
||||
* @readonly
|
||||
*/
|
||||
command: undefined,
|
||||
|
||||
/**
|
||||
* The count of assignments
|
||||
* @readonly
|
||||
*/
|
||||
assignmentCount: undefined,
|
||||
|
||||
/**
|
||||
* Set a new command. We make no attempt to convert the args in the old
|
||||
* command to args in the new command. The assignments need to be
|
||||
* re-entered.
|
||||
*/
|
||||
setCommand: function(command) {
|
||||
if (this.command === command) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.command = command;
|
||||
this._assignments = {};
|
||||
|
||||
if (command) {
|
||||
command.params.forEach(function(param) {
|
||||
this._assignments[param.name] = new Assignment(param);
|
||||
}, this);
|
||||
}
|
||||
|
||||
this.assignmentCount = Object.keys(this._assignments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Assignments have an order, so we need to store them in an array.
|
||||
* But we also need named access ...
|
||||
*/
|
||||
getAssignment: function(nameOrNumber) {
|
||||
var name = (typeof nameOrNumber === 'string') ?
|
||||
nameOrNumber :
|
||||
Object.keys(this._assignments)[nameOrNumber];
|
||||
return this._assignments[name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Where parameter name == assignment names - they are the same.
|
||||
*/
|
||||
getParameterNames: function() {
|
||||
return Object.keys(this._assignments);
|
||||
},
|
||||
|
||||
/**
|
||||
* A *shallow* clone of the assignments.
|
||||
* This is useful for systems that wish to go over all the assignments
|
||||
* finding values one way or another and wish to trim an array as they go.
|
||||
*/
|
||||
cloneAssignments: function() {
|
||||
return Object.keys(this._assignments).map(function(name) {
|
||||
return this._assignments[name];
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect the statuses from the Assignments
|
||||
*/
|
||||
getHints: function() {
|
||||
var hints = [];
|
||||
Object.keys(this._assignments).map(function(name) {
|
||||
// Append the assignments hints to our list
|
||||
hints.push.apply(hints, this._assignments[name].getHints());
|
||||
}, this);
|
||||
return hints;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract the names and values of all the assignments, and return as
|
||||
* an object.
|
||||
*/
|
||||
getArgs: function() {
|
||||
var args = {};
|
||||
Object.keys(this._assignments).forEach(function(name) {
|
||||
args[name] = this.getAssignment(name).value;
|
||||
}, this);
|
||||
return args;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset all the assignments to their default values
|
||||
*/
|
||||
setDefaultValues: function() {
|
||||
Object.keys(this._assignments).forEach(function(name) {
|
||||
this._assignments[name].setValue(undefined);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to call canon.exec
|
||||
*/
|
||||
exec: function() {
|
||||
canon.exec(this.command, this.getArgs());
|
||||
}
|
||||
};
|
||||
exports.Requisition = Requisition;
|
||||
|
||||
|
||||
/**
|
||||
* An object used during command line parsing to hold the various intermediate
|
||||
* data steps.
|
||||
* <p>The 'output' of the parse is held in 2 objects: input.hints which is an
|
||||
* <p>The 'output' of the update is held in 2 objects: input.hints which is an
|
||||
* array of hints to display to the user. In the future this will become a
|
||||
* single value.
|
||||
* <p>The other output value is input.requisition which gives access to an
|
||||
|
|
@ -187,99 +377,87 @@ CliUi.prototype = {
|
|||
* if not specified.
|
||||
* @constructor
|
||||
*/
|
||||
function Cli(cliui, options) {
|
||||
this.cliui = cliui;
|
||||
function CliRequisition(options) {
|
||||
if (options && options.flags) {
|
||||
/**
|
||||
* TODO: We were using a default of keyboard.buildFlags({ });
|
||||
* This allowed us to have commands that only existed in certain contexts
|
||||
* - i.e. Javascript specific commands.
|
||||
*/
|
||||
this.flags = options.flags;
|
||||
}
|
||||
|
||||
this.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: {},
|
||||
|
||||
oop.inherits(CliRequisition, Requisition);
|
||||
(function() {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
parse: function(typed) {
|
||||
if (util.none(typed)) {
|
||||
this.requisition.setCommand(null);
|
||||
this.cliui.setHints([]);
|
||||
CliRequisition.prototype.update = function(input) {
|
||||
this.hints = [];
|
||||
|
||||
if (util.none(input.typed)) {
|
||||
this.setCommand(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.typed = typed;
|
||||
this.hints = [];
|
||||
|
||||
var args = _tokenize(this.typed);
|
||||
var args = _tokenize(input.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);
|
||||
this.setCommand(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var command = _split(args);
|
||||
|
||||
if (!command) {
|
||||
// No command found - bail helpfully.
|
||||
var commandType = types.getType('command');
|
||||
var conversion = commandType.parse(typed);
|
||||
var conversion = commandType.parse(input.typed);
|
||||
var arg = Argument.merge(args);
|
||||
this._addHint(new ConversionHint(conversion, arg));
|
||||
|
||||
this.requisition.setCommand(null);
|
||||
this.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._addHint(Status.VALID, message, 0, input.typed.length);
|
||||
}
|
||||
|
||||
this.requisition.setCommand(command);
|
||||
this.setCommand(command);
|
||||
this._assign(args);
|
||||
this._addHint(this.requisition.getHints());
|
||||
this._addHint(CliRequisition.super_.getHints.call(this));
|
||||
}
|
||||
|
||||
// TODO: This is the wrong place to filter this.
|
||||
// It should be done by the CliUi because:
|
||||
// - the cursor could move without notice
|
||||
// - not all interfaces will have a notion of one cursor for the whole assignment
|
||||
|
||||
// Not knowing about cursor positioning, the requisition and assignments
|
||||
// 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();
|
||||
var c = input.cursor;
|
||||
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 startInHint = c.start >= hint.start && c.start <= hint.end;
|
||||
var endInHint = c.end >= hint.start && c.end <= hint.end;
|
||||
var inHint = startInHint || endInHint;
|
||||
if (!inHint && hint.status === Status.INCOMPLETE) {
|
||||
hint.status = Status.INVALID;
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.cliui.setHints(this.hints);
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
CliRequisition.prototype.getHints = function() {
|
||||
return this.hints;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
CliRequisition.prototype._addHint = function(status, message, start, end) {
|
||||
if (status == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -292,7 +470,7 @@ Cli.prototype = {
|
|||
else {
|
||||
this.hints.push(new Hint(status, message, start, end));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Work out which arguments are applicable to which parameters.
|
||||
|
|
@ -304,36 +482,36 @@ Cli.prototype = {
|
|||
* <li>value - The matching input
|
||||
* </ul>
|
||||
*/
|
||||
_assign: function(args) {
|
||||
CliRequisition.prototype._assign = function(args) {
|
||||
if (args.length === 0) {
|
||||
this.requisition.setDefaultValues();
|
||||
this.setDefaultValues();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an error if the command does not take parameters, but we have
|
||||
// been given them ...
|
||||
if (this.requisition.assignmentCount === 0) {
|
||||
if (this.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',
|
||||
this.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 (this.assignmentCount == 1) {
|
||||
var assignment = this.getAssignment(0);
|
||||
if (assignment.param.type.name === 'text') {
|
||||
assignment.setArgument(Argument.merge(args));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var assignments = this.requisition.cloneAssignments();
|
||||
var names = this.requisition.getParameterNames();
|
||||
var assignments = this.cloneAssignments();
|
||||
var names = this.getParameterNames();
|
||||
|
||||
// Extract all the named parameters
|
||||
var used = [];
|
||||
|
|
@ -376,7 +554,7 @@ Cli.prototype = {
|
|||
|
||||
// What's left are positional parameters assign in order
|
||||
names.forEach(function(name) {
|
||||
var assignment = this.requisition.getAssignment(name);
|
||||
var assignment = this.getAssignment(name);
|
||||
if (args.length === 0) {
|
||||
// No more values
|
||||
assignment.setValue(undefined); // i.e. default
|
||||
|
|
@ -394,9 +572,10 @@ Cli.prototype = {
|
|||
'Input \'' + remaining.text + '\' makes no sense.',
|
||||
remaining);
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.Cli = Cli;
|
||||
};
|
||||
|
||||
})();
|
||||
exports.CliRequisition = CliRequisition;
|
||||
|
||||
/**
|
||||
* Split up the input taking into account ' and "
|
||||
|
|
@ -637,214 +816,6 @@ function documentCommand(command) {
|
|||
exports.documentCommand = documentCommand;
|
||||
|
||||
|
||||
/**
|
||||
* A Requisition collects the information needed to execute a command.
|
||||
* There is no point in a requisition for parameter-less commands because there
|
||||
* is no information to collect. A Requisition is a collection of assignments
|
||||
* of values to parameters, each handled by an instance of Assignment.
|
||||
* @constructor
|
||||
*/
|
||||
function Requisition() {
|
||||
}
|
||||
Requisition.prototype = {
|
||||
/**
|
||||
* The command that we are about to execute.
|
||||
* @readonly
|
||||
*/
|
||||
command: undefined,
|
||||
|
||||
/**
|
||||
* The count of assignments
|
||||
* @readonly
|
||||
*/
|
||||
assignmentCount: undefined,
|
||||
|
||||
/**
|
||||
* Set a new command. We make no attempt to convert the args in the old
|
||||
* command to args in the new command. The assignments need to be
|
||||
* re-entered.
|
||||
*/
|
||||
setCommand: function(command) {
|
||||
if (this.command === command) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.command = command;
|
||||
this._assignments = {};
|
||||
|
||||
if (command) {
|
||||
command.params.forEach(function(param) {
|
||||
this._assignments[param.name] = new Assignment(param);
|
||||
}, this);
|
||||
}
|
||||
|
||||
this.assignmentCount = Object.keys(this._assignments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Assignments have an order, so we need to store them in an array.
|
||||
* But we also need named access ...
|
||||
*/
|
||||
getAssignment: function(nameOrNumber) {
|
||||
var name = (typeof nameOrNumber === 'string') ?
|
||||
nameOrNumber :
|
||||
Object.keys(this._assignments)[nameOrNumber];
|
||||
return this._assignments[name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Where parameter name == assignment names - they are the same.
|
||||
*/
|
||||
getParameterNames: function() {
|
||||
return Object.keys(this._assignments);
|
||||
},
|
||||
|
||||
/**
|
||||
* A *shallow* clone of the assignments.
|
||||
* This is useful for systems that wish to go over all the assignments
|
||||
* finding values one way or another and wish to trim an array as they go.
|
||||
*/
|
||||
cloneAssignments: function() {
|
||||
return Object.keys(this._assignments).map(function(name) {
|
||||
return this._assignments[name];
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect the statuses from the Assignments
|
||||
*/
|
||||
getHints: function() {
|
||||
var hints = [];
|
||||
Object.keys(this._assignments).map(function(name) {
|
||||
// Append the assignments hints to our list
|
||||
hints.push.apply(hints, this._assignments[name].getHints());
|
||||
}, this);
|
||||
return hints;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract the names and values of all the assignments, and return as
|
||||
* an object.
|
||||
*/
|
||||
getArgs: function() {
|
||||
var args = {};
|
||||
Object.keys(this._assignments).forEach(function(name) {
|
||||
args[name] = getCommand(name);
|
||||
}, this);
|
||||
return args;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset all the assignments to their default values
|
||||
*/
|
||||
setDefaultValues: function() {
|
||||
Object.keys(this._assignments).forEach(function(name) {
|
||||
this._assignments[name].setValue(undefined);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to call canon.exec
|
||||
*/
|
||||
exec: function() {
|
||||
exports.exec(this.command, this.getArgs());
|
||||
}
|
||||
};
|
||||
exports.Requisition = Requisition;
|
||||
|
||||
|
||||
/**
|
||||
* A link between a parameter and the data for that parameter.
|
||||
* The data for the parameter is available as in the preferred type and as
|
||||
* an Argument for the CLI.
|
||||
* <p>We also record validity information where applicable.
|
||||
* <p>For values, null and undefined have distinct definitions. null means
|
||||
* that a value has been provided, undefined means that it has not.
|
||||
* Thus, null is a valid default value, and common because it identifies an
|
||||
* parameter that is optional. undefined means there is no value from
|
||||
* the command line.
|
||||
* @constructor
|
||||
*/
|
||||
function Assignment(param) {
|
||||
this.param = param;
|
||||
this.setValue(param.defaultValue);
|
||||
};
|
||||
Assignment.prototype = {
|
||||
/**
|
||||
* The parameter that we are assigning to
|
||||
* @readonly
|
||||
*/
|
||||
param: undefined,
|
||||
|
||||
/**
|
||||
* The current value (i.e. not the string representation)
|
||||
* Use setValue() to mutate
|
||||
*/
|
||||
value: undefined,
|
||||
setValue: function(value) {
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
if (value === undefined) {
|
||||
value = this.param.defaultValue;
|
||||
}
|
||||
this.value = value;
|
||||
|
||||
var text = (value == null) ? '' : this.param.type.stringify(value);
|
||||
if (this.arg) {
|
||||
this.arg.setText(text);
|
||||
}
|
||||
|
||||
this.conversion = undefined;
|
||||
//this._dispatchEvent('change', { assignment: this });
|
||||
},
|
||||
|
||||
/**
|
||||
* The textual representation of the current value
|
||||
* Use setValue() to mutate
|
||||
*/
|
||||
arg: undefined,
|
||||
setArgument: function(arg) {
|
||||
if (this.arg === arg) {
|
||||
return;
|
||||
}
|
||||
this.arg = arg;
|
||||
this.conversion = this.param.type.parse(arg.text);
|
||||
this.value = this.conversion.value;
|
||||
//this._dispatchEvent('change', { assignment: this });
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a list of this hints associated with this parameter assignment
|
||||
*/
|
||||
getHints: function() {
|
||||
var hints = [];
|
||||
if (this.conversion != null &&
|
||||
(this.conversion.status !== Status.VALID ||
|
||||
this.conversion.message)) {
|
||||
hints.push(new ConversionHint(this.conversion, this.arg));
|
||||
}
|
||||
|
||||
var argProvided = this.arg != null && this.arg.text !== '';
|
||||
var dataProvided = this.value !== undefined || argProvided;
|
||||
|
||||
if (this.param.defaultValue === undefined && !dataProvided) {
|
||||
// If the there is no data provided, we have no start/end. Use -1
|
||||
hints.push(new Hint(Status.INVALID,
|
||||
'Argument for ' + param.name + ' is required'
|
||||
-1, -1));
|
||||
}
|
||||
return hints;
|
||||
},
|
||||
|
||||
/**
|
||||
* Report on the status of the last parse() conversion.
|
||||
* @see types.Conversion
|
||||
*/
|
||||
conversion: undefined
|
||||
};
|
||||
oop.implement(Assignment, EventEmitter);
|
||||
exports.Assignment = Assignment;
|
||||
|
||||
|
||||
});
|
||||
59
plugins/cockpit/lib/index.js
Normal file
59
plugins/cockpit/lib/index.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Skywriter.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Kevin Dangoor (kdangoor@mozilla.com)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
exports.startup = function(data, reason) {
|
||||
|
||||
window.testCli = require('cockpit/test/testCli');
|
||||
|
||||
var plain = require('cockpit/ui/plain');
|
||||
plain.startup(data, reason);
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
exports.shutdown(data, reason) {
|
||||
deps.forEach(function(dep) {
|
||||
var module = require(dep);
|
||||
if (typeof module.shutdown === "function") {
|
||||
module.shutdown(data, reason);
|
||||
}
|
||||
});
|
||||
};
|
||||
*/
|
||||
});
|
||||
|
|
@ -38,10 +38,13 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
|
||||
var test = require('pilot/test/assert').test;
|
||||
var cli = require('pilot/cli');
|
||||
var test = require('cockpit/test/assert').test;
|
||||
var Status = require('pilot/types').Status;
|
||||
var settings = require('pilot/settings').settings;
|
||||
var tokenize = require('cockpit/cli')._tokenize;
|
||||
var split = require('cockpit/cli')._split;
|
||||
var CliRequisition = require('cockpit/cli').CliRequisition;
|
||||
|
||||
|
||||
exports.testAll = function() {
|
||||
exports.testTokenize();
|
||||
|
|
@ -50,41 +53,18 @@ exports.testAll = function() {
|
|||
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('');
|
||||
var args = tokenize('');
|
||||
test.verifyEqual(0, args.length);
|
||||
|
||||
args = cli._tokenize('s');
|
||||
args = 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');
|
||||
args = tokenize('s s');
|
||||
test.verifyEqual(2, args.length);
|
||||
test.verifyEqual('s', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
|
|
@ -95,7 +75,7 @@ exports.testTokenize = function() {
|
|||
test.verifyEqual(3, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
|
||||
args = cli._tokenize(' 1234 \'12 34\'');
|
||||
args = tokenize(' 1234 \'12 34\'');
|
||||
test.verifyEqual(2, args.length);
|
||||
test.verifyEqual('1234', args[0].text);
|
||||
test.verifyEqual(1, args[0].start);
|
||||
|
|
@ -106,7 +86,7 @@ exports.testTokenize = function() {
|
|||
test.verifyEqual(13, args[1].end);
|
||||
test.verifyEqual(' ', args[1].priorSpace);
|
||||
|
||||
args = cli._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
|
||||
args = tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
|
||||
test.verifyEqual(3, args.length);
|
||||
test.verifyEqual('12\'34', args[0].text);
|
||||
test.verifyEqual(0, args[0].start);
|
||||
|
|
@ -121,7 +101,7 @@ exports.testTokenize = function() {
|
|||
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
|
||||
args = 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);
|
||||
|
|
@ -143,50 +123,60 @@ exports.testTokenize = function() {
|
|||
return "testTokenize Completed";
|
||||
};
|
||||
|
||||
var hints;
|
||||
var hint0;
|
||||
var requisition;
|
||||
var sel = { start: -1, end: -1 };
|
||||
var settingAssignment;
|
||||
var valueAssignment;
|
||||
mockCliUi = {
|
||||
getSelection: function() {
|
||||
return sel;
|
||||
},
|
||||
exports.testSplit = function() {
|
||||
var args = tokenize('s');
|
||||
var command = split(args);
|
||||
test.verifyEqual(1, args.length);
|
||||
test.verifyEqual('s', args[0].text);
|
||||
test.verifyUndefined(command);
|
||||
|
||||
setHints: function(h) {
|
||||
hints = h;
|
||||
hint0 = (h.length !== 0) ? h[0] : undefined;
|
||||
var args = tokenize('set');
|
||||
var command = split(args);
|
||||
test.verifyEqual([], args);
|
||||
test.verifyEqual('set', command.name);
|
||||
|
||||
if (requisition && requisition.command && requisition.command.name === 'set') {
|
||||
settingAssignment = requisition.getAssignment('setting');
|
||||
valueAssignment = requisition.getAssignment('value');
|
||||
var args = tokenize('set a b');
|
||||
var command = 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.testCli = function() {
|
||||
var hints;
|
||||
var hint0;
|
||||
var settingAssignment;
|
||||
var valueAssignment;
|
||||
var cli = new CliRequisition();
|
||||
|
||||
function update(input) {
|
||||
cli.update(input);
|
||||
hints = cli.getHints();
|
||||
hint0 = (hints.length !== 0) ? hints[0] : undefined;
|
||||
if (cli.command && cli.command.name === 'set') {
|
||||
settingAssignment = cli.getAssignment('setting');
|
||||
valueAssignment = cli.getAssignment('value');
|
||||
}
|
||||
else {
|
||||
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('');
|
||||
update({ typed: '', cursor: { start: 0, end: 0 } });
|
||||
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);
|
||||
test.verifyNull(cli.command);
|
||||
|
||||
sel.start = sel.end = 1;
|
||||
input.parse('s');
|
||||
update({ typed: 's', cursor: { start: 1, end: 1 } });
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.INCOMPLETE, hint0.status);
|
||||
test.verifyNotEqual(-1, hint0.message.indexOf('possibilities'));
|
||||
|
|
@ -196,16 +186,16 @@ exports.testCli = function() {
|
|||
// 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);
|
||||
test.verifyNull(cli.command);
|
||||
|
||||
input.parse('set');
|
||||
update({ typed: 'set', cursor: { start: 3, end: 3 } });
|
||||
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);
|
||||
test.verifyEqual('set', cli.command.name);
|
||||
|
||||
input.parse('set ');
|
||||
update({ typed: 'set ', cursor: { start: 4, end: 4 } });
|
||||
test.verifyEqual(1, hints.length);
|
||||
test.verifyEqual(Status.VALID, hint0.status);
|
||||
test.verifyEqual(0, hint0.start);
|
||||
|
|
@ -213,71 +203,67 @@ exports.testCli = function() {
|
|||
// This is caused by us using the whole input to determine the length.
|
||||
// Maybe one day we should fix this?
|
||||
//test.verifyEqual(3, hint0.end);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('set', cli.command.name);
|
||||
|
||||
sel.start = sel.end = 5;
|
||||
input.parse('set h');
|
||||
update({ typed: 'set h', cursor: { start: 5, end: 5 } });
|
||||
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('set', cli.command.name);
|
||||
test.verifyEqual('h', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 16;
|
||||
input.parse('set historyLengt');
|
||||
update({ typed: 'set historyLengt', cursor: { start: 16, end: 16 } });
|
||||
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('set', cli.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 1;
|
||||
input.parse('set historyLengt');
|
||||
update({ typed: 'set historyLengt', cursor: { start: 1, end: 1 } });
|
||||
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('set', cli.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
sel.start = sel.end = 17;
|
||||
input.parse('set historyLengt ');
|
||||
update({ typed: 'set historyLengt ', cursor: { start: 17, end: 17 } });
|
||||
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('set', cli.command.name);
|
||||
test.verifyEqual('historyLengt', settingAssignment.arg.text);
|
||||
test.verifyEqual(undefined, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength');
|
||||
update({ typed: 'set historyLength', cursor: { start: 17, end: 17 } });
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('set', cli.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength ');
|
||||
update({ typed: 'set historyLength ', cursor: { start: 18, end: 18 } });
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('set', cli.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
|
||||
input.parse('set historyLength 6');
|
||||
update({ typed: 'set historyLength 6', cursor: { start: 19, end: 19 } });
|
||||
test.verifyEqual(0, hints.length);
|
||||
test.verifyEqual('set', requisition.command.name);
|
||||
test.verifyEqual('set', cli.command.name);
|
||||
test.verifyEqual('historyLength', settingAssignment.arg.text);
|
||||
test.verifyEqual(historyLengthSetting, settingAssignment.value);
|
||||
test.verifyEqual('6', valueAssignment.arg.text);
|
||||
|
|
@ -286,12 +272,8 @@ exports.testCli = function() {
|
|||
|
||||
// TODO: Add test to see that a command without mandatory param causes INVALID
|
||||
|
||||
console.log(input);
|
||||
|
||||
return "testCli Completed";
|
||||
};
|
||||
|
||||
window.testCli = exports;
|
||||
|
||||
|
||||
});
|
||||
40
plugins/cockpit/lib/test/testNothing.js
Normal file
40
plugins/cockpit/lib/test/testNothing.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Skywriter.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Joe Walker (jwalker@mozilla.com)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
});
|
||||
2
plugins/cockpit/lib/ui/plain.css
Normal file
2
plugins/cockpit/lib/ui/plain.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
#cockpit { color: red; }
|
||||
98
plugins/cockpit/lib/ui/plain.js
Normal file
98
plugins/cockpit/lib/ui/plain.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Skywriter.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Joe Walker (jwalker@mozilla.com)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
define(function(require, exports, module) {
|
||||
|
||||
var editorCss = require("text!cockpit/ui/plain.css");
|
||||
var dom = require("pilot/dom").dom;
|
||||
dom.importCssString(editorCss);
|
||||
|
||||
var CliRequisition = require('cockpit/cli').CliRequisition;
|
||||
var keyutil = require('pilot/keyboard/keyutil');
|
||||
|
||||
exports.startup = function(data, reason) {
|
||||
// TODO: we should have a better way to specify command lines???
|
||||
this.input = document.getElementById('cockpit');
|
||||
if (!this.input) {
|
||||
console.log('No element with an id of cockpit. Bailing on plain cli');
|
||||
return;
|
||||
}
|
||||
|
||||
var cli = new CliRequisition();
|
||||
|
||||
/*
|
||||
// All this does is to kill TABs normal use. I wonder if we can train
|
||||
// people to use right arrow? Probably not? but ...
|
||||
keyutil.addKeyDownListener(input, function(ev) {
|
||||
// env.commandLine = this;
|
||||
// var handled = keyboardManager.processKeyEvent(ev, this, {
|
||||
// isCommandLine: true, isKeyUp: false
|
||||
// });
|
||||
if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
|
||||
return true;
|
||||
}
|
||||
//return handled;
|
||||
}.bind(this));
|
||||
*/
|
||||
|
||||
this.input.addEventListener('keyup', function(ev) {
|
||||
/*
|
||||
var handled = keyboardManager.processKeyEvent(ev, this, {
|
||||
isCommandLine: true, isKeyUp: true
|
||||
});
|
||||
*/
|
||||
|
||||
if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
|
||||
cli.exec();
|
||||
this.input.value = '';
|
||||
} else {
|
||||
cli.update({
|
||||
typed: this.input.value,
|
||||
cursor: {
|
||||
start: this.input.selectionStart,
|
||||
end: this.input.selectionEnd
|
||||
}
|
||||
});
|
||||
console.log(JSON.stringify(cli.getHints()));
|
||||
}
|
||||
|
||||
// return handled;
|
||||
}.bind(this), true);
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -161,7 +161,7 @@ exports.getCommandNames = function() {
|
|||
* @param command Either a command, or the name of one
|
||||
*/
|
||||
exports.exec = function(command, args) {
|
||||
if (typeof name === 'string') {
|
||||
if (typeof command === 'string') {
|
||||
command = commands[command];
|
||||
}
|
||||
if (!command) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
};
|
||||
|
||||
});
|
||||
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