In addition to these types, Jetpack also accepts range, member, password + * that we are thinking of adding. + * + *
This module probably should not be accessed directly, but instead used + * through types.js + */ + +/** + * 'text' is the default if no type is given. + */ +var text = new Type(); + +text.stringify = function(value) { + return value; +}; + +text.parse = function(value) { + if (typeof value != 'string') { + throw new Error('non-string passed to text.parse()'); + } + return new Conversion(value); +}; + +text.name = 'text'; + +/** + * We don't currently plan to distinguish between integers and floats + */ +var number = new Type(); + +number.stringify = function(value) { + if (!value) { + return null; + } + return '' + value; +}; + +number.parse = function(value) { + if (typeof value != 'string') { + throw new Error('non-string passed to number.parse()'); + } + + if (value.replace(/\s/g, '').length === 0) { + return new Conversion(null, Status.INCOMPLETE, ''); + } + + var reply = new Conversion(parseInt(value, 10)); + if (isNaN(reply.value)) { + reply.status = Status.INVALID; + reply.message = 'Can\'t convert "' + value + '" to a number.'; + } + + return reply; +}; + +number.decrement = function(value) { + return value - 1; +}; + +number.increment = function(value) { + return value + 1; +}; + +number.name = 'number'; + +/** + * One of a known set of options + */ +function SelectionType(typeSpec) { + if (!Array.isArray(typeSpec.data) && typeof typeSpec.data !== 'function') { + throw new Error('instances of SelectionType need typeSpec.data to be an array or function that returns an array:' + JSON.stringify(typeSpec)); + } + Object.keys(typeSpec).forEach(function(key) { + this[key] = typeSpec[key]; + }, this); +}; + +SelectionType.prototype = new Type(); + +SelectionType.prototype.stringify = function(value) { + return value; +}; + +SelectionType.prototype.parse = function(str) { + if (typeof str != 'string') { + throw new Error('non-string passed to parse()'); + } + if (!this.data) { + throw new Error('Missing data on selection type extension.'); + } + var data = (typeof(this.data) === 'function') ? this.data() : this.data; + + // The matchedValue could be the boolean value false + var hasMatched = false; + var matchedValue; + var completions = []; + data.forEach(function(option) { + if (str == option) { + matchedValue = this.fromString(option); + hasMatched = true; + } + else if (option.indexOf(str) === 0) { + completions.push(this.fromString(option)); + } + }, this); + + if (hasMatched) { + return new Conversion(matchedValue); + } + else { + // This is something of a hack it basically allows us to tell the + // setting type to forget its last setting hack. + if (this.noMatch) { + this.noMatch(); + } + + if (completions.length > 0) { + var msg = 'Possibilities' + + (str.length === 0 ? '' : ' for \'' + str + '\''); + return new Conversion(null, Status.INCOMPLETE, msg, completions); + } + else { + var msg = 'Can\'t use \'' + str + '\'.'; + return new Conversion(null, Status.INVALID, msg, completions); + } + } +}; + +SelectionType.prototype.fromString = function(str) { + return str; +}; + +SelectionType.prototype.decrement = function(value) { + var data = (typeof this.data === 'function') ? this.data() : this.data; + var index; + if (value == null) { + index = data.length - 1; + } + else { + var name = this.stringify(value); + var index = data.indexOf(name); + index = (index === 0 ? data.length - 1 : index - 1); + } + return this.fromString(data[index]); +}; + +SelectionType.prototype.increment = function(value) { + var data = (typeof this.data === 'function') ? this.data() : this.data; + var index; + if (value == null) { + index = 0; + } + else { + var name = this.stringify(value); + var index = data.indexOf(name); + index = (index === data.length - 1 ? 0 : index + 1); + } + return this.fromString(data[index]); +}; + +SelectionType.prototype.name = 'selection'; + +/** + * SelectionType is a base class for other types + */ +exports.SelectionType = SelectionType; + +/** + * true/false values + */ +var bool = new SelectionType({ + name: 'bool', + data: [ 'true', 'false' ], + stringify: function(value) { + return '' + value; + }, + fromString: function(str) { + return str === 'true' ? true : false; + } +}); + + +/** + * A we don't know right now, but hope to soon. + */ +function DeferredType(typeSpec) { + if (typeof typeSpec.defer !== 'function') { + throw new Error('Instances of DeferredType need typeSpec.defer to be a function that returns a type'); + } + Object.keys(typeSpec).forEach(function(key) { + this[key] = typeSpec[key]; + }, this); +}; + +DeferredType.prototype = new Type(); + +DeferredType.prototype.stringify = function(value) { + return this.defer().stringify(value); +}; + +DeferredType.prototype.parse = function(value) { + return this.defer().parse(value); +}; + +DeferredType.prototype.decrement = function(value) { + var deferred = this.defer(); + return (deferred.decrement ? deferred.decrement(value) : undefined); +}; + +DeferredType.prototype.increment = function(value) { + var deferred = this.defer(); + return (deferred.increment ? deferred.increment(value) : undefined); +}; + +DeferredType.prototype.name = 'deferred'; + +/** + * DeferredType is a base class for other types + */ +exports.DeferredType = DeferredType; + + +/** + * A set of objects of the same type + */ +function ArrayType(typeSpec) { + if (typeSpec instanceof Type) { + this.subtype = typeSpec; + } + else if (typeof typeSpec === 'string') { + this.subtype = types.getType(typeSpec); + if (this.subtype == null) { + throw new Error('Unknown array subtype: ' + typeSpec); + } + } + else { + throw new Error('Can\' handle array subtype'); + } +}; + +ArrayType.prototype = new Type(); + +ArrayType.prototype.stringify = function(values) { + // TODO: Check for strings with spaces and add quotes + return values.join(' '); +}; + +ArrayType.prototype.parse = function(value) { + return this.defer().parse(value); +}; + +ArrayType.prototype.name = 'array'; + +/** + * Registration and de-registration. + */ +var isStarted = false; +exports.startup = function() { + if (isStarted) { + return; + } + isStarted = true; + types.registerType(text); + types.registerType(number); + types.registerType(bool); + types.registerType(SelectionType); + types.registerType(DeferredType); + types.registerType(ArrayType); +}; + +exports.shutdown = function() { + isStarted = false; + types.unregisterType(text); + types.unregisterType(number); + types.unregisterType(bool); + types.unregisterType(SelectionType); + types.unregisterType(DeferredType); + types.unregisterType(ArrayType); +}; + + +}); +/* ***** 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('pilot/types', ['require', 'exports', 'module' ], function(require, exports, module) { + +/** + * Some types can detect validity, that is to say they can distinguish between + * valid and invalid values. + * TODO: Change these constants to be numbers for more performance? + */ +var Status = { + /** + * The conversion process worked without any problem, and the value is + * valid. There are a number of failure states, so the best way to check + * for failure is (x !== Status.VALID) + */ + VALID: { + toString: function() { return 'VALID'; }, + valueOf: function() { return 0; } + }, + + /** + * A conversion process failed, however it was noted that the string + * provided to 'parse()' could be VALID by the addition of more characters, + * so the typing may not be actually incorrect yet, just unfinished. + * @see Status.INVALID + */ + INCOMPLETE: { + toString: function() { return 'INCOMPLETE'; }, + valueOf: function() { return 1; } + }, + + /** + * The conversion process did not work, the value should be null and a + * reason for failure should have been provided. In addition some completion + * values may be available. + * @see Status.INCOMPLETE + */ + INVALID: { + toString: function() { return 'INVALID'; }, + valueOf: function() { return 2; } + }, + + /** + * A combined status is the worser of the provided statuses + */ + combine: function(statuses) { + var combined = Status.VALID; + for (var i = 0; i < statuses.length; i++) { + if (statuses[i].valueOf() > combined.valueOf()) { + combined = statuses[i]; + } + } + return combined; + } +}; +exports.Status = Status; + +/** + * The type.parse() method returns a Conversion to inform the user about not + * only the result of a Conversion but also about what went wrong. + * We could use an exception, and throw if the conversion failed, but that + * seems to violate the idea that exceptions should be exceptional. Typos are + * not. Also in order to store both a status and a message we'd still need + * some sort of exception type... + */ +function Conversion(value, status, message, predictions) { + /** + * The result of the conversion process. Will be null if status != VALID + */ + this.value = value; + + /** + * The status of the conversion. + * @see Status + */ + this.status = status || Status.VALID; + + /** + * A message to go with the conversion. This could be present for any status + * including VALID in the case where we want to note a warning for example. + * I18N: On the one hand this nasty and un-internationalized, however with + * a command line it is hard to know where to start. + */ + this.message = message; + + /** + * A array of strings which are the systems best guess at better inputs than + * the one presented. + * We generally expect there to be about 7 predictions (to match human list + * comprehension ability) however it is valid to provide up to about 20, + * or less. It is the job of the predictor to decide a smart cut-off. + * For example if there are 4 very good matches and 4 very poor ones, + * probably only the 4 very good matches should be presented. + */ + this.predictions = predictions || []; +} +exports.Conversion = Conversion; + +/** + * Most of our types are 'static' e.g. there is only one type of 'text', however + * some types like 'selection' and 'deferred' are customizable. The basic + * Type type isn't useful, but does provide documentation about what types do. + */ +function Type() { +}; +Type.prototype = { + /** + * Convert the given value to a string representation. + * Where possible, there should be round-tripping between values and their + * string representations. + */ + stringify: function(value) { throw new Error("not implemented"); }, + + /** + * Convert the given str to an instance of this type. + * Where possible, there should be round-tripping between values and their + * string representations. + * @return Conversion + */ + parse: function(str) { throw new Error("not implemented"); }, + + /** + * The plug-in system, and other things need to know what this type is + * called. The name alone is not enough to fully specify a type. Types like + * 'selection' and 'deferred' need extra data, however this function returns + * only the name, not the extra data. + *
In old bespin, equality was based on the name. This may turn out to be + * important in Ace too. + */ + name: undefined, + + /** + * If there is some concept of a higher value, return it, + * otherwise return undefined. + */ + increment: function(value) { + return undefined; + }, + + /** + * If there is some concept of a lower value, return it, + * otherwise return undefined. + */ + decrement: function(value) { + return undefined; + }, + + /** + * There is interesting information (like predictions) in a conversion of + * nothing, the output of this can sometimes be customized. + * @return Conversion + */ + getDefault: function() { + return this.parse(''); + } +}; +exports.Type = Type; + +/** + * Private registry of types + * Invariant: types[name] = type.name + */ +var types = {}; + +/** + * Add a new type to the list available to the system. + * You can pass 2 things to this function - either an instance of Type, in + * which case we return this instance when #getType() is called with a 'name' + * that matches type.name. + * Also you can pass in a constructor (i.e. function) in which case when + * #getType() is called with a 'name' that matches Type.prototype.name we will + * pass the typeSpec into this constructor. See #reconstituteType(). + */ +exports.registerType = function(type) { + if (typeof type === 'object') { + if (type instanceof Type) { + if (!type.name) { + throw new Error('All registered types must have a name'); + } + types[type.name] = type; + } + else { + throw new Error('Can\'t registerType using: ' + type); + } + } + else if (typeof type === 'function') { + if (!type.prototype.name) { + throw new Error('All registered types must have a name'); + } + types[type.prototype.name] = type; + } + else { + throw new Error('Unknown type: ' + type); + } +}; + +exports.registerTypes = function registerTypes(types) { + Object.keys(types).forEach(function (name) { + var type = types[name]; + type.name = name; + exports.registerType(type); + }); +}; + +/** + * Remove a type from the list available to the system + */ +exports.deregisterType = function(type) { + delete types[type.name]; +}; + +/** + * See description of #exports.registerType() + */ +function reconstituteType(name, typeSpec) { + if (name.substr(-2) === '[]') { // i.e. endsWith('[]') + var subtypeName = name.slice(0, -2); + return new types['array'](subtypeName); + } + + var type = types[name]; + if (typeof type === 'function') { + type = new type(typeSpec); + } + return type; +} + +/** + * Find a type, previously registered using #registerType() + */ +exports.getType = function(typeSpec) { + if (typeof typeSpec === 'string') { + return reconstituteType(typeSpec); + } + + if (typeof typeSpec === 'object') { + if (!typeSpec.name) { + throw new Error('Missing \'name\' member to typeSpec'); + } + return reconstituteType(typeSpec.name, typeSpec); + } + + throw new Error('Can\'t extract type from ' + typeSpec); +}; + + +}); +/* ***** 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) + * 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('pilot/types/command', ['require', 'exports', 'module' , 'pilot/canon', 'pilot/types/basic', 'pilot/types'], function(require, exports, module) { + +var canon = require("pilot/canon"); +var SelectionType = require("pilot/types/basic").SelectionType; +var types = require("pilot/types"); + + +/** + * Select from the available commands + */ +var command = new SelectionType({ + name: 'command', + data: function() { + return canon.getCommandNames(); + }, + stringify: function(command) { + return command.name; + }, + fromString: function(str) { + return canon.getCommand(str); + } +}); + + +/** + * Registration and de-registration. + */ +exports.startup = function() { + types.registerType(command); +}; + +exports.shutdown = function() { + types.unregisterType(command); +}; + + +}); +/* ***** 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('pilot/canon', ['require', 'exports', 'module' , 'pilot/console', 'pilot/stacktrace', 'pilot/oop', 'pilot/useragent', 'pilot/keys', 'pilot/event_emitter', 'pilot/typecheck', 'pilot/catalog', 'pilot/types', 'pilot/lang'], function(require, exports, module) { + +var console = require('pilot/console'); +var Trace = require('pilot/stacktrace').Trace; +var oop = require('pilot/oop'); +var useragent = require('pilot/useragent'); +var keyUtil = require('pilot/keys'); +var EventEmitter = require('pilot/event_emitter').EventEmitter; +var typecheck = require('pilot/typecheck'); +var catalog = require('pilot/catalog'); +var Status = require('pilot/types').Status; +var types = require('pilot/types'); +var lang = require('pilot/lang'); + +/* +// TODO: this doesn't belong here - or maybe anywhere? +var dimensionsChangedExtensionSpec = { + name: 'dimensionsChanged', + description: 'A dimensionsChanged is a way to be notified of ' + + 'changes to the dimension of Skywriter' +}; +exports.startup = function(data, reason) { + catalog.addExtensionSpec(commandExtensionSpec); +}; +exports.shutdown = function(data, reason) { + catalog.removeExtensionSpec(commandExtensionSpec); +}; +*/ + +var commandExtensionSpec = { + name: 'command', + description: 'A command is a bit of functionality with optional ' + + 'typed arguments which can do something small like moving ' + + 'the cursor around the screen, or large like cloning a ' + + 'project from VCS.', + indexOn: 'name' +}; + +exports.startup = function(data, reason) { + // TODO: this is probably all kinds of evil, but we need something working + catalog.addExtensionSpec(commandExtensionSpec); +}; + +exports.shutdown = function(data, reason) { + catalog.removeExtensionSpec(commandExtensionSpec); +}; + +/** + * Manage a list of commands in the current canon + */ + +/** + * A Command is a discrete action optionally with a set of ways to customize + * how it happens. This is here for documentation purposes. + * TODO: Document better + */ +var thingCommand = { + name: 'thing', + description: 'thing is an example command', + params: [{ + name: 'param1', + description: 'an example parameter', + type: 'text', + defaultValue: null + }], + exec: function(env, args, request) { + thing(); + } +}; + +/** + * A lookup hash of our registered commands + */ +var commands = {}; + +/** + * A lookup has for command key bindings that use a string as sender. + */ +var commmandKeyBinding = {}; + +/** + * Array with command key bindings that use a function to determ the sender. + */ +var commandKeyBindingFunc = { }; + +function splitSafe(s, separator, limit, bLowerCase) { + return (bLowerCase && s.toLowerCase() || s) + .replace(/(?:^\s+|\n|\s+$)/g, "") + .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999); +} + +function parseKeys(keys, val, ret) { + var key, + hashId = 0, + parts = splitSafe(keys, "\\-", null, true), + i = 0, + l = parts.length; + + for (; i < l; ++i) { + if (keyUtil.KEY_MODS[parts[i]]) + hashId = hashId | keyUtil.KEY_MODS[parts[i]]; + else + key = parts[i] || "-"; //when empty, the splitSafe removed a '-' + } + + if (ret == null) { + return { + key: key, + hashId: hashId + } + } else { + (ret[hashId] || (ret[hashId] = {}))[key] = val; + } +} + +var platform = useragent.isMac ? "mac" : "win"; +function buildKeyHash(command) { + var binding = command.bindKey, + key = binding[platform], + ckb = commmandKeyBinding, + ckbf = commandKeyBindingFunc + + if (!binding.sender) { + throw new Error('All key bindings must have a sender'); + } + if (!binding.mac && binding.mac !== null) { + throw new Error('All key bindings must have a mac key binding'); + } + if (!binding.win && binding.win !== null) { + throw new Error('All key bindings must have a windows key binding'); + } + if(!binding[platform]) { + // No keymapping for this platform. + return; + } + if (typeof binding.sender == 'string') { + var targets = splitSafe(binding.sender, "\\|", null, true); + targets.forEach(function(target) { + if (!ckb[target]) { + ckb[target] = { }; + } + key.split("|").forEach(function(keyPart) { + parseKeys(keyPart, command, ckb[target]); + }); + }); + } else if (typecheck.isFunction(binding.sender)) { + var val = { + command: command, + sender: binding.sender + }; + + keyData = parseKeys(key); + if (!ckbf[keyData.hashId]) { + ckbf[keyData.hashId] = { }; + } + if (!ckbf[keyData.hashId][keyData.key]) { + ckbf[keyData.hashId][keyData.key] = [ val ]; + } else { + ckbf[keyData.hashId][keyData.key].push(val); + } + } else { + throw new Error('Key binding must have a sender that is a string or function'); + } +} + +function findKeyCommand(env, sender, hashId, textOrKey) { + // Convert keyCode to the string representation. + if (typecheck.isNumber(textOrKey)) { + textOrKey = keyUtil.keyCodeToString(textOrKey); + } + + // Check bindings with functions as sender first. + var bindFuncs = (commandKeyBindingFunc[hashId] || {})[textOrKey] || []; + for (var i = 0; i < bindFuncs.length; i++) { + if (bindFuncs[i].sender(env, sender, hashId, textOrKey)) { + return bindFuncs[i].command; + } + } + + var ckbr = commmandKeyBinding[sender]; + return ckbr && ckbr[hashId] && ckbr[hashId][textOrKey]; +} + +function execKeyCommand(env, sender, hashId, textOrKey) { + var command = findKeyCommand(env, sender, hashId, textOrKey); + if (command) { + return exec(command, env, sender, { }); + } else { + return false; + } +} + +/** + * A sorted list of command names, we regularly want them in order, so pre-sort + */ +var commandNames = []; + +/** + * This registration method isn't like other Ace registration methods because + * it doesn't return a decorated command because there is no functional + * decoration to be done. + * TODO: Are we sure that in the future there will be no such decoration? + */ +function addCommand(command) { + if (!command.name) { + throw new Error('All registered commands must have a name'); + } + if (command.params == null) { + command.params = []; + } + if (!Array.isArray(command.params)) { + throw new Error('command.params must be an array in ' + command.name); + } + // Replace the type + command.params.forEach(function(param) { + if (!param.name) { + throw new Error('In ' + command.name + ': all params must have a name'); + } + upgradeType(command.name, param); + }, this); + commands[command.name] = command; + + if (command.bindKey) { + buildKeyHash(command); + } + + commandNames.push(command.name); + commandNames.sort(); +}; + +function upgradeType(name, param) { + var lookup = param.type; + param.type = types.getType(lookup); + if (param.type == null) { + throw new Error('In ' + name + '/' + param.name + + ': can\'t find type for: ' + JSON.stringify(lookup)); + } +} + +function removeCommand(command) { + var name = (typeof command === 'string' ? command : command.name); + command = commands[name]; + delete commands[name]; + lang.arrayRemove(commandNames, name); + + // exaustive search is a little bit brute force but since removeCommand is + // not a performance critical operation this should be OK + var ckb = commmandKeyBinding; + for (var k1 in ckb) { + for (var k2 in ckb[k1]) { + for (var k3 in ckb[k1][k2]) { + if (ckb[k1][k2][k3] == command) + delete ckb[k1][k2][k3]; + } + } + } + + var ckbf = commandKeyBindingFunc; + for (var k1 in ckbf) { + for (var k2 in ckbf[k1]) { + ckbf[k1][k2].forEach(function(cmd, i) { + if (cmd.command == command) { + ckbf[k1][k2].splice(i, 1); + } + }) + } + } +}; + +function getCommand(name) { + return commands[name]; +}; + +function getCommandNames() { + return commandNames; +}; + +/** + * Default ArgumentProvider that is used if no ArgumentProvider is provided + * by the command's sender. + */ +function defaultArgsProvider(request, callback) { + var args = request.args, + params = request.command.params; + + for (var i = 0; i < params.length; i++) { + var param = params[i]; + + // If the parameter is already valid, then don't ask for it anymore. + if (request.getParamStatus(param) != Status.VALID || + // Ask for optional parameters as well. + param.defaultValue === null) + { + var paramPrompt = param.description; + if (param.defaultValue === null) { + paramPrompt += " (optional)"; + } + var value = prompt(paramPrompt, param.defaultValue || ""); + // No value but required -> nope. + if (!value) { + callback(); + return; + } else { + args[param.name] = value; + } + } + } + callback(); +} + +/** + * Entry point for keyboard accelerators or anything else that wants to execute + * a command. A new request object is created and a check performed, if the + * passed in arguments are VALID/INVALID or INCOMPLETE. If they are INCOMPLETE + * the ArgumentProvider on the sender is called or otherwise the default + * ArgumentProvider to get the still required arguments. + * If they are valid (or valid after the ArgumentProvider is done), the command + * is executed. + * + * @param command Either a command, or the name of one + * @param env Current environment to execute the command in + * @param sender String that should be the same as the senderObject stored on + * the environment in env[sender] + * @param args Arguments for the command + * @param typed (Optional) + */ +function exec(command, env, sender, args, typed) { + if (typeof command === 'string') { + command = commands[command]; + } + if (!command) { + // TODO: Should we complain more than returning false? + return false; + } + + var request = new Request({ + sender: sender, + command: command, + args: args || {}, + typed: typed + }); + + /** + * Executes the command and ensures request.done is called on the request in + * case it's not marked to be done already or async. + */ + function execute() { + command.exec(env, request.args, request); + + // If the request isn't asnync and isn't done, then make it done. + if (!request.isAsync && !request.isDone) { + request.done(); + } + } + + + if (request.getStatus() == Status.INVALID) { + console.error("Canon.exec: Invalid parameter(s) passed to " + + command.name); + return false; + } + // If the request isn't complete yet, try to complete it. + else if (request.getStatus() == Status.INCOMPLETE) { + // Check if the sender has a ArgsProvider, otherwise use the default + // build in one. + var argsProvider; + var senderObj = env[sender]; + if (!senderObj || !senderObj.getArgsProvider || + !(argsProvider = senderObj.getArgsProvider())) + { + argsProvider = defaultArgsProvider; + } + + // Ask the paramProvider to complete the request. + argsProvider(request, function() { + if (request.getStatus() == Status.VALID) { + execute(); + } + }); + return true; + } else { + execute(); + return true; + } +}; + +exports.removeCommand = removeCommand; +exports.addCommand = addCommand; +exports.getCommand = getCommand; +exports.getCommandNames = getCommandNames; +exports.findKeyCommand = findKeyCommand; +exports.exec = exec; +exports.execKeyCommand = execKeyCommand; +exports.upgradeType = upgradeType; + + +/** + * We publish a 'output' event whenever new command begins output + * TODO: make this more obvious + */ +oop.implement(exports, EventEmitter); + + +/** + * Current requirements are around displaying the command line, and provision + * of a 'history' command and cursor up|down navigation of history. + *
Future requirements could include: + *
The execute() command doesn't really live here, except as part of that + * last future requirement, and because it doesn't really have anywhere else to + * live. + */ + +/** + * The array of requests that wish to announce their presence + */ +var requests = []; + +/** + * How many requests do we store? + */ +var maxRequestLength = 100; + +/** + * To create an invocation, you need to do something like this (all the ctor + * args are optional): + *
+ * var request = new Request({ + * command: command, + * args: args, + * typed: typed + * }); + *+ * @constructor + */ +function Request(options) { + options = options || {}; + + // Will be used in the keyboard case and the cli case + this.command = options.command; + + // Will be used only in the cli case + this.args = options.args; + this.typed = options.typed; + + // Have we been initialized? + this._begunOutput = false; + + this.start = new Date(); + this.end = null; + this.completed = false; + this.error = false; +}; + +oop.implement(Request.prototype, EventEmitter); + +/** + * Return the status of a parameter on the request object. + */ +Request.prototype.getParamStatus = function(param) { + var args = this.args || {}; + + // Check if there is already a value for this parameter. + if (param.name in args) { + // If there is no value set and then the value is VALID if it's not + // required or INCOMPLETE if not set yet. + if (args[param.name] == null) { + if (param.defaultValue === null) { + return Status.VALID; + } else { + return Status.INCOMPLETE; + } + } + + // Check if the parameter value is valid. + var reply, + // The passed in value when parsing a type is a string. + argsValue = args[param.name].toString(); + + // Type.parse can throw errors. + try { + reply = param.type.parse(argsValue); + } catch (e) { + return Status.INVALID; + } + + if (reply.status != Status.VALID) { + return reply.status; + } + } + // Check if the param is marked as required. + else if (param.defaultValue === undefined) { + // The parameter is not set on the args object but it's required, + // which means, things are invalid. + return Status.INCOMPLETE; + } + + return Status.VALID; +} + +/** + * Return the status of a parameter name on the request object. + */ +Request.prototype.getParamNameStatus = function(paramName) { + var params = this.command.params || []; + + for (var i = 0; i < params.length; i++) { + if (params[i].name == paramName) { + return this.getParamStatus(params[i]); + } + } + + throw "Parameter '" + paramName + + "' not defined on command '" + this.command.name + "'"; +} + +/** + * Checks if all required arguments are set on the request such that it can + * get executed. + */ +Request.prototype.getStatus = function() { + var args = this.args || {}, + params = this.command.params; + + // If there are not parameters, then it's valid. + if (!params || params.length == 0) { + return Status.VALID; + } + + var status = []; + for (var i = 0; i < params.length; i++) { + status.push(this.getParamStatus(params[i])); + } + + return Status.combine(status); +} + +/** + * Lazy init to register with the history should only be done on output. + * init() is expensive, and won't be used in the majority of cases + */ +Request.prototype._beginOutput = function() { + this._begunOutput = true; + this.outputs = []; + + requests.push(this); + // This could probably be optimized with some maths, but 99.99% of the + // time we will only be off by one, and I'm feeling lazy. + while (requests.length > maxRequestLength) { + requests.shiftObject(); + } + + exports._dispatchEvent('output', { requests: requests, request: this }); +}; + +/** + * Sugar for: + *
request.error = true; request.done(output);+ */ +Request.prototype.doneWithError = function(content) { + this.error = true; + this.done(content); +}; + +/** + * Declares that this function will not be automatically done when + * the command exits + */ +Request.prototype.async = function() { + this.isAsync = true; + if (!this._begunOutput) { + this._beginOutput(); + } +}; + +/** + * Complete the currently executing command with successful output. + * @param output Either DOM node, an SproutCore element or something that + * can be used in the content of a DIV to create a DOM node. + */ +Request.prototype.output = function(content) { + if (!this._begunOutput) { + this._beginOutput(); + } + + if (typeof content !== 'string' && !(content instanceof Node)) { + content = content.toString(); + } + + this.outputs.push(content); + this.isDone = true; + this._dispatchEvent('output', {}); + + return this; +}; + +/** + * All commands that do output must call this to indicate that the command + * has finished execution. + */ +Request.prototype.done = function(content) { + this.completed = true; + this.end = new Date(); + this.duration = this.end.getTime() - this.start.getTime(); + + if (content) { + this.output(content); + } + + // Ensure to finish the request only once. + if (!this.isDone) { + this.isDone = true; + this._dispatchEvent('output', {}); + } +}; +exports.Request = Request; + + +}); +/* ***** 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) + * Patrick Walton (pwalton@mozilla.com) + * Julian Viereck (jviereck@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('pilot/console', ['require', 'exports', 'module' ], function(require, exports, module) { + +/** + * This object represents a "safe console" object that forwards debugging + * messages appropriately without creating a dependency on Firebug in Firefox. + */ + +var noop = function() {}; + +// These are the functions that are available in Chrome 4/5, Safari 4 +// and Firefox 3.6. Don't add to this list without checking browser support +var NAMES = [ + "assert", "count", "debug", "dir", "dirxml", "error", "group", "groupEnd", + "info", "log", "profile", "profileEnd", "time", "timeEnd", "trace", "warn" +]; + +if (typeof(window) === 'undefined') { + // We're in a web worker. Forward to the main thread so the messages + // will show up. + NAMES.forEach(function(name) { + exports[name] = function() { + var args = Array.prototype.slice.call(arguments); + var msg = { op: 'log', method: name, args: args }; + postMessage(JSON.stringify(msg)); + }; + }); +} else { + // For each of the console functions, copy them if they exist, stub if not + NAMES.forEach(function(name) { + if (window.console && window.console[name]) { + exports[name] = Function.prototype.bind.call(window.console[name], window.console); + } else { + exports[name] = noop; + } + }); +} + +}); +define('pilot/stacktrace', ['require', 'exports', 'module' , 'pilot/useragent', 'pilot/console'], function(require, exports, module) { + +var ua = require("pilot/useragent"); +var console = require('pilot/console'); + +// Changed to suit the specific needs of running within Skywriter + +// Domain Public by Eric Wendelin http://eriwen.com/ (2008) +// Luke Smith http://lucassmith.name/ (2008) +// Loic Dachary
key
setting to it's default
+ */
+ resetValue: function() {
+ this.set(this.defaultValue);
+ },
+ toString: function () {
+ return this.name;
+ }
+};
+oop.implement(Setting.prototype, EventEmitter);
+
+
+/**
+ * A base class for all the various methods of storing settings.
+ * Usage: + *
+ * // Create manually, or require 'settings' from the container. + * // This is the manual version: + * var settings = plugins.catalog.getObject('settings'); + * // Add a new setting + * settings.addSetting({ name:'foo', ... }); + * // Display the default value + * alert(settings.get('foo')); + * // Alter the value, which also publishes the change etc. + * settings.set('foo', 'bar'); + * // Reset the value to the default + * settings.resetValue('foo'); + *+ * @constructor + */ +function Settings(persister) { + // Storage for deactivated values + this._deactivated = {}; + + // Storage for the active settings + this._settings = {}; + // We often want sorted setting names. Cache + this._settingNames = []; + + if (persister) { + this.setPersister(persister); + } +}; + +Settings.prototype = { + /** + * Function to add to the list of available settings. + *
Example usage: + *
+ * var settings = plugins.catalog.getObject('settings'); + * settings.addSetting({ + * name: 'tabsize', // For use in settings.get('X') + * type: 'number', // To allow value checking. + * defaultValue: 4 // Default value for use when none is directly set + * }); + *+ * @param {object} settingSpec Object containing name/type/defaultValue members. + */ + addSetting: function(settingSpec) { + var setting = new Setting(settingSpec, this); + this._settings[setting.name] = setting; + this._settingNames.push(setting.name); + this._settingNames.sort(); + }, + + addSettings: function addSettings(settings) { + Object.keys(settings).forEach(function (name) { + var setting = settings[name]; + if (!('name' in setting)) setting.name = name; + this.addSetting(setting); + }, this); + }, + + removeSetting: function(setting) { + var name = (typeof setting === 'string' ? setting : setting.name); + setting = this._settings[name]; + delete this._settings[name]; + util.arrayRemove(this._settingNames, name); + settings.removeAllListeners('change'); + }, + + removeSettings: function removeSettings(settings) { + Object.keys(settings).forEach(function(name) { + var setting = settings[name]; + if (!('name' in setting)) setting.name = name; + this.removeSettings(setting); + }, this); + }, + + getSettingNames: function() { + return this._settingNames; + }, + + getSetting: function(name) { + return this._settings[name]; + }, + + /** + * A Persister is able to store settings. It is an object that defines + * two functions: + * loadInitialValues(settings) and persistValue(settings, key, value). + */ + setPersister: function(persister) { + this._persister = persister; + if (persister) { + persister.loadInitialValues(this); + } + }, + + resetAll: function() { + this.getSettingNames().forEach(function(key) { + this.resetValue(key); + }, this); + }, + + /** + * Retrieve a list of the known settings and their values + */ + _list: function() { + var reply = []; + this.getSettingNames().forEach(function(setting) { + reply.push({ + 'key': setting, + 'value': this.getSetting(setting).get() + }); + }, this); + return reply; + }, + + /** + * Prime the local cache with the defaults. + */ + _loadDefaultValues: function() { + this._loadFromObject(this._getDefaultValues()); + }, + + /** + * Utility to load settings from an object + */ + _loadFromObject: function(data) { + // We iterate over data rather than keys so we don't forget values + // which don't have a setting yet. + for (var key in data) { + if (data.hasOwnProperty(key)) { + var setting = this._settings[key]; + if (setting) { + var value = setting.type.parse(data[key]); + this.set(key, value); + } else { + this.set(key, data[key]); + } + } + } + }, + + /** + * Utility to grab all the settings and export them into an object + */ + _saveToObject: function() { + return this.getSettingNames().map(function(key) { + return this._settings[key].type.stringify(this.get(key)); + }.bind(this)); + }, + + /** + * The default initial settings + */ + _getDefaultValues: function() { + return this.getSettingNames().map(function(key) { + return this._settings[key].spec.defaultValue; + }.bind(this)); + } +}; +exports.settings = new Settings(); + +/** + * Save the settings in a cookie + * This code has not been tested since reboot + * @constructor + */ +function CookiePersister() { +}; + +CookiePersister.prototype = { + loadInitialValues: function(settings) { + settings._loadDefaultValues(); + var data = cookie.get('settings'); + settings._loadFromObject(JSON.parse(data)); + }, + + persistValue: function(settings, key, value) { + try { + var stringData = JSON.stringify(settings._saveToObject()); + cookie.set('settings', stringData); + } catch (ex) { + console.error('Unable to JSONify the settings! ' + ex); + return; + } + } +}; + +exports.CookiePersister = CookiePersister; + +}); +/* ***** 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('pilot/commands/settings', ['require', 'exports', 'module' , 'pilot/canon'], function(require, exports, module) { + + +var setCommandSpec = { + name: 'set', + params: [ + { + name: 'setting', + type: 'setting', + description: 'The name of the setting to display or alter', + defaultValue: null + }, + { + name: 'value', + type: 'settingValue', + description: 'The new value for the chosen setting', + defaultValue: null + } + ], + description: 'define and show settings', + exec: function(env, args, request) { + var html; + if (!args.setting) { + // 'set' by itself lists all the settings + var names = env.settings.getSettingNames(); + html = ''; + // first sort the settingsList based on the name + names.sort(function(name1, name2) { + return name1.localeCompare(name2); + }); + + names.forEach(function(name) { + var setting = env.settings.getSetting(name); + var url = 'https://wiki.mozilla.org/Labs/Skywriter/Settings#' + + setting.name; + html += '' + + setting.name + + ' = ' + + setting.value + + '
' + command.description + '
'); + } + else if (args.search) { + if (args.search == 'hidden') { // sneaky, sneaky. + args.search = ''; + showHidden = true; + } + output.push('' + command.name + ' | '); + output.push('' + command.description + ' | '); + output.push('
---|
"+e.description+"
")):b.search?(b.search=="hidden"&&(b.search="",f=!0),d.push("'+e.name+" | "),d.push(""+e.description+" | "),d.push("")}d.push("
---|
We also record validity information where applicable. + *
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, requisition) {
+ this.param = param;
+ this.requisition = requisition;
+ this.setValue(param.defaultValue);
+};
+Assignment.prototype = {
+ /**
+ * The parameter that we are assigning to
+ * @readonly
+ */
+ param: undefined,
+
+ /**
+ * Report on the status of the last parse() conversion.
+ * @see types.Conversion
+ */
+ conversion: undefined,
+
+ /**
+ * The current value in a type as specified by param.type
+ */
+ value: undefined,
+
+ /**
+ * The string version of the current value
+ */
+ arg: 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) {
+ this.value = this.param.defaultValue;
+ this.conversion = this.param.getDefault ?
+ this.param.getDefault() :
+ this.param.type.getDefault();
+ this.arg = undefined;
+ } else {
+ this.value = value;
+ this.conversion = undefined;
+ var text = (value == null) ? '' : this.param.type.stringify(value);
+ if (this.arg) {
+ this.arg.setText(text);
+ }
+ }
+
+ this.requisition._assignmentChanged(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.conversion.arg = arg; // TODO: make this automatic?
+ this.value = this.conversion.value;
+ this.requisition._assignmentChanged(this);
+ },
+
+ /**
+ * Create a list of the hints associated with this parameter assignment.
+ * Generally there will be only one hint generated because we're currently
+ * only displaying one hint at a time, ordering by distance from cursor
+ * and severity. Since distance from cursor will be the same for all hints
+ * from this assignment all but the most severe will ever be used. It might
+ * make sense with more experience to alter this to function to be getHint()
+ */
+ getHint: function() {
+ // Allow the parameter to provide documentation
+ if (this.param.getCustomHint && this.value && this.arg) {
+ var hint = this.param.getCustomHint(this.value, this.arg);
+ if (hint) {
+ return hint;
+ }
+ }
+
+ // If there is no argument, use the cursor position
+ var message = '' + this.param.name + ': ';
+ if (this.param.description) {
+ // TODO: This should be a short description - do we need to trim?
+ message += this.param.description.trim();
+
+ // Ensure the help text ends with '. '
+ if (message.charAt(message.length - 1) !== '.') {
+ message += '.';
+ }
+ if (message.charAt(message.length - 1) !== ' ') {
+ message += ' ';
+ }
+ }
+ var status = Status.VALID;
+ var start = this.arg ? this.arg.start : Argument.AT_CURSOR;
+ var end = this.arg ? this.arg.end : Argument.AT_CURSOR;
+ var predictions;
+
+ // Non-valid conversions will have useful information to pass on
+ if (this.conversion) {
+ status = this.conversion.status;
+ if (this.conversion.message) {
+ message += this.conversion.message;
+ }
+ predictions = this.conversion.predictions;
+ }
+
+ // Hint if the param is required, but not provided
+ var argProvided = this.arg && this.arg.text !== '';
+ var dataProvided = this.value !== undefined || argProvided;
+ if (this.param.defaultValue === undefined && !dataProvided) {
+ status = Status.INVALID;
+ message += 'Required<\strong>';
+ }
+
+ return new Hint(status, message, start, end, predictions);
+ },
+
+ /**
+ * Basically setValue(conversion.predictions[0]) done in a safe
+ * way.
+ */
+ complete: function() {
+ if (this.conversion && this.conversion.predictions &&
+ this.conversion.predictions.length > 0) {
+ this.setValue(this.conversion.predictions[0]);
+ }
+ },
+
+ /**
+ * If the cursor is at 'position', do we have sufficient data to start
+ * displaying the next hint. This is both complex and important.
+ * For example, if the user has just typed: Note that the input for 2 and 4 is identical, only the configuration
+ * has changed, so hint display is environmental.
+ *
+ * This function works out if the cursor is before the end of this
+ * assignment (assuming that we've asked the same thing of the previous
+ * assignment) and then attempts to work out if we should use the hint from
+ * the next assignment even though technically the cursor is still inside
+ * this one due to the rules above.
+ */
+ isPositionCaptured: function(position) {
+ if (!this.arg) {
+ return false;
+ }
+
+ // Note we don't check if position >= this.arg.start because that's
+ // implied by the fact that we're asking the assignments in turn, and
+ // we want to avoid thing falling between the cracks, but we do need
+ // to check that the argument does have a position
+ if (this.arg.start === -1) {
+ return false;
+ }
+
+ // We're clearly done if the position is past the end of the text
+ if (position > this.arg.end) {
+ return false;
+ }
+
+ // If we're AT the end, the position is captured if either the status
+ // is not valid or if there are other valid options including current
+ if (position === this.arg.end) {
+ return this.conversion.status !== Status.VALID ||
+ this.conversion.predictions.length !== 0;
+ }
+
+ // Otherwise we're clearly inside
+ return true;
+ },
+
+ /**
+ * Replace the current value with the lower value if such a concept
+ * exists.
+ */
+ decrement: function() {
+ var replacement = this.param.type.decrement(this.value);
+ if (replacement != null) {
+ this.setValue(replacement);
+ }
+ },
+
+ /**
+ * Replace the current value with the higher value if such a concept
+ * exists.
+ */
+ increment: function() {
+ var replacement = this.param.type.increment(this.value);
+ if (replacement != null) {
+ this.setValue(replacement);
+ }
+ },
+
+ /**
+ * Helper when we're rebuilding command lines.
+ */
+ toString: function() {
+ return this.arg ? this.arg.toString() : '';
+ }
+};
+exports.Assignment = Assignment;
+
+
+/**
+ * This is a special parameter to reflect the command itself.
+ */
+var commandParam = {
+ name: '__command',
+ type: 'command',
+ description: 'The command to execute',
+
+ /**
+ * Provide some documentation for a command.
+ */
+ getCustomHint: function(command, arg) {
+ var docs = [];
+ docs.push(' > ');
+ docs.push(command.name);
+ if (command.params && command.params.length > 0) {
+ command.params.forEach(function(param) {
+ if (param.defaultValue === undefined) {
+ docs.push(' [' + param.name + ']');
+ }
+ else {
+ docs.push(' [' + param.name + ']');
+ }
+ }, this);
+ }
+ docs.push(' 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.
+ * The other output value is input.requisition which gives access to an
+ * args object for use in executing the final command.
+ *
+ * The majority of the functions in this class are called in sequence by the
+ * constructor. Their task is to add to hints fill out the requisition.
+ * The general sequence is: This takes #_command.params and #_unparsedArgs and creates a map of
+ * param names to 'assignment' objects, which have the following properties:
+ * "+(e&&(e.length>80?e.slice(0,77)+"...":e).entityify())+" Implied global: "+n.join(", ")+" Unused variable: "+n.join(", ")+" JSON: bad. JSON: good. '+filename+' Saved! Failed! Saved! Saved!
t('The files you are trying to upload exceed the maximum size for file uploads on this server.');?>
diff --git a/files/templates/part.breadcrumb.php b/files/templates/part.breadcrumb.php
index 9a265a9c1e..63242dd326 100644
--- a/files/templates/part.breadcrumb.php
+++ b/files/templates/part.breadcrumb.php
@@ -2,4 +2,4 @@
+ *
+ *
');
+
+ docs.push(command.description ? command.description : '(No description)');
+ docs.push('
');
+
+ if (command.params && command.params.length > 0) {
+ docs.push('');
+ command.params.forEach(function(param) {
+ docs.push('
');
+ }
+
+ return new Hint(Status.VALID, docs.join(''), arg);
+ }
+};
+
+/**
+ * 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.
+ * Events
+ * We publish the following events:
+ *
+ *
+ *
+ * @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:
+ * flags: Flags for us to check against the predicates specified with the
+ * commands. Defaulted to keyboard.buildFlags({ });
+ * if not specified.
+ * @constructor
+ */
+function CliRequisition(env, options) {
+ Requisition.call(this, env);
+
+ 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;
+ }
+}
+oop.inherits(CliRequisition, Requisition);
+(function() {
+ /**
+ * Called by the UI when ever the user interacts with a command line input
+ * @param input A structure that details the state of the input field.
+ * It should look something like: { typed:a, cursor: { start:b, end:c } }
+ * Where a is the contents of the input field, and b and c are the start
+ * and end of the cursor/selection respectively.
+ */
+ CliRequisition.prototype.update = function(input) {
+ this.input = input;
+ this._hints = [];
+
+ var args = this._tokenize(input.typed);
+ this._split(args);
+
+ if (this.commandAssignment.value) {
+ this._assign(args);
+ }
+
+ this._updateHints();
+ };
+
+ /**
+ * Return an array of Status scores so we can create a marked up
+ * version of the command line input.
+ */
+ CliRequisition.prototype.getInputStatusMarkup = function() {
+ // 'scores' is an array which tells us what chars are errors
+ // Initialize with everything VALID
+ var scores = this.toString().split('').map(function(ch) {
+ return Status.VALID;
+ });
+ // For all chars in all hints, check and upgrade the score
+ this._hints.forEach(function(hint) {
+ for (var i = hint.start; i <= hint.end; i++) {
+ if (hint.status > scores[i]) {
+ scores[i] = hint.status;
+ }
+ }
+ }, this);
+ return scores;
+ };
+
+ /**
+ * Reconstitute the input from the args
+ */
+ CliRequisition.prototype.toString = function() {
+ return this.getAssignments(true).map(function(assignment) {
+ return assignment.toString();
+ }, this).join('');
+ };
+
+ var superUpdateHints = CliRequisition.prototype._updateHints;
+ /**
+ * Marks up hints in a number of ways:
+ * - Makes INCOMPLETE hints that are not near the cursor INVALID since
+ * they can't be completed by typing
+ * - Finds the most severe hint, and annotates the array with it
+ * - Finds the hint to display, and also annotates the array with it
+ * TODO: I'm wondering if array annotation is evil and we should replace
+ * this with an object. Need to find out more.
+ */
+ CliRequisition.prototype._updateHints = function() {
+ superUpdateHints.call(this);
+
+ // Not knowing about cursor positioning, the requisition and assignments
+ // can't know this, but anything they mark as INCOMPLETE is actually
+ // INVALID unless the cursor is actually inside that argument.
+ var c = this.input.cursor;
+ this._hints.forEach(function(hint) {
+ var startInHint = c.start >= hint.start && c.start <= hint.end;
+ var endInHint = c.end >= hint.start && c.end <= hint.end;
+ var inHint = startInHint || endInHint;
+ if (!inHint && hint.status === Status.INCOMPLETE) {
+ hint.status = Status.INVALID;
+ }
+ }, this);
+
+ Hint.sort(this._hints);
+ };
+
+ /**
+ * Accessor for the hints array.
+ * While we could just use the hints property, using getHints() is
+ * preferred for symmetry with Requisition where it needs a function due to
+ * lack of an atomic update system.
+ */
+ CliRequisition.prototype.getHints = function() {
+ return this._hints;
+ };
+
+ /**
+ * Look through the arguments attached to our assignments for the assignment
+ * at the given position.
+ */
+ CliRequisition.prototype.getAssignmentAt = function(position) {
+ var assignments = this.getAssignments(true);
+ for (var i = 0; i < assignments.length; i++) {
+ var assignment = assignments[i];
+ if (!assignment.arg) {
+ // There is no argument in this assignment, we've fallen off
+ // the end of the obvious answers - it must be this one.
+ return assignment;
+ }
+ if (assignment.isPositionCaptured(position)) {
+ return assignment;
+ }
+ }
+
+ return assignment;
+ };
+
+ /**
+ * Split up the input taking into account ' and "
+ */
+ CliRequisition.prototype._tokenize = function(typed) {
+ // For blank input, place a dummy empty argument into the list
+ if (typed == null || typed.length === 0) {
+ return [ new Argument(this, '', 0, 0, '', '') ];
+ }
+
+ 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 unescape2()
+ 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 unescape2(str) {
+ return str
+ .replace(/\uF000/g, ' ')
+ .replace(/\uF001/g, '\'')
+ .replace(/\uF002/g, '"');
+ }
+
+ var i = 0;
+ var start = 0; // Where did this section start?
+ var prefix = '';
+ var args = [];
+
+ while (true) {
+ if (i >= typed.length) {
+ // There is nothing else to read - tidy up
+ if (mode !== OUTSIDE) {
+ var str = unescape2(typed.substring(start, i));
+ args.push(new Argument(this, str, start, i, prefix, ''));
+ }
+ else {
+ if (i !== start) {
+ // There's a bunch of whitespace at the end of the
+ // command add it to the last argument's suffix,
+ // creating an empty argument if needed.
+ var extra = typed.substring(start, i);
+ var lastArg = args[args.length - 1];
+ if (!lastArg) {
+ lastArg = new Argument(this, '', i, i, extra, '');
+ args.push(lastArg);
+ }
+ else {
+ lastArg.suffix += extra;
+ }
+ }
+ }
+ break;
+ }
+
+ var c = typed[i];
+ switch (mode) {
+ case OUTSIDE:
+ if (c === '\'') {
+ prefix = typed.substring(start, i + 1);
+ mode = IN_SINGLE_Q;
+ start = i + 1;
+ }
+ else if (c === '"') {
+ prefix = typed.substring(start, i + 1);
+ mode = IN_DOUBLE_Q;
+ start = i + 1;
+ }
+ else if (/ /.test(c)) {
+ // Still whitespace, do nothing
+ }
+ else {
+ prefix = 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 = unescape2(typed.substring(start, i));
+ args.push(new Argument(this, str,
+ start, i, prefix, ''));
+ mode = OUTSIDE;
+ start = i;
+ prefix = '';
+ }
+ break;
+
+ case IN_SINGLE_Q:
+ if (c === '\'') {
+ var str = unescape2(typed.substring(start, i));
+ args.push(new Argument(this, str,
+ start - 1, i + 1, prefix, c));
+ mode = OUTSIDE;
+ start = i + 1;
+ prefix = '';
+ }
+ break;
+
+ case IN_DOUBLE_Q:
+ if (c === '"') {
+ var str = unescape2(typed.substring(start, i));
+ args.push(new Argument(this, str,
+ start - 1, i + 1, prefix, c));
+ mode = OUTSIDE;
+ start = i + 1;
+ prefix = '';
+ }
+ break;
+ }
+
+ i++;
+ }
+
+ return args;
+ };
+
+ /**
+ * Looks in the canon for a command extension that matches what has been
+ * typed at the command line.
+ */
+ CliRequisition.prototype._split = function(args) {
+ var argsUsed = 1;
+ var arg;
+
+ while (argsUsed <= args.length) {
+ var arg = Argument.merge(args, 0, argsUsed);
+ this.commandAssignment.setArgument(arg);
+
+ if (!this.commandAssignment.value) {
+ // Not found. break with value == null
+ break;
+ }
+
+ /*
+ // 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 (this.commandAssignment.value.exec) {
+ // Valid command, break with command valid
+ for (var i = 0; i < argsUsed; i++) {
+ args.shift();
+ }
+ break;
+ }
+
+ argsUsed++;
+ }
+ };
+
+ /**
+ * Work out which arguments are applicable to which parameters.
+ *
+ *
+ */
+ CliRequisition.prototype._assign = function(args) {
+ if (args.length === 0) {
+ this.setDefaultValues();
+ return;
+ }
+
+ // Create an error if the command does not take parameters, but we have
+ // been given them ...
+ 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._hints.push(new Hint(Status.INVALID,
+ this.commandAssignment.value.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.assignmentCount === 1) {
+ var assignment = this.getAssignment(0);
+ if (assignment.param.type.name === 'text') {
+ assignment.setArgument(Argument.merge(args));
+ return;
+ }
+ }
+
+ var assignments = this.cloneAssignments();
+ var names = this.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._hints.push(new Hint(Status.INCOMPLETE,
+ 'Missing value for: ' + namedArgText,
+ args[i]));
+ }
+ else {
+ args.splice(i + 1, 1);
+ assignment.setArgument(args[i + 1]);
+ }
+ }
+
+ lang.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.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._hints.push(new Hint(Status.INVALID,
+ 'Input \'' + remaining.text + '\' makes no sense.',
+ remaining));
+ }
+ };
+
+})();
+exports.CliRequisition = CliRequisition;
+
+
+});
+/* ***** 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('cockpit/ui/settings', ['require', 'exports', 'module' , 'pilot/types', 'pilot/types/basic'], function(require, exports, module) {
+
+
+var types = require("pilot/types");
+var SelectionType = require('pilot/types/basic').SelectionType;
+
+var direction = new SelectionType({
+ name: 'direction',
+ data: [ 'above', 'below' ]
+});
+
+var hintDirectionSetting = {
+ name: "hintDirection",
+ description: "Are hints shown above or below the command line?",
+ type: "direction",
+ defaultValue: "above"
+};
+
+var outputDirectionSetting = {
+ name: "outputDirection",
+ description: "Is the output window shown above or below the command line?",
+ type: "direction",
+ defaultValue: "above"
+};
+
+var outputHeightSetting = {
+ name: "outputHeight",
+ description: "What height should the output panel be?",
+ type: "number",
+ defaultValue: 300
+};
+
+exports.startup = function(data, reason) {
+ types.registerType(direction);
+ data.env.settings.addSetting(hintDirectionSetting);
+ data.env.settings.addSetting(outputDirectionSetting);
+ data.env.settings.addSetting(outputHeightSetting);
+};
+
+exports.shutdown = function(data, reason) {
+ types.unregisterType(direction);
+ data.env.settings.removeSetting(hintDirectionSetting);
+ data.env.settings.removeSetting(outputDirectionSetting);
+ data.env.settings.removeSetting(outputHeightSetting);
+};
+
+
+});
+/* ***** 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('cockpit/ui/cli_view', ['require', 'exports', 'module' , 'text!cockpit/ui/cli_view.css', 'pilot/event', 'pilot/dom', 'pilot/keys', 'pilot/canon', 'pilot/types', 'cockpit/cli', 'cockpit/ui/request_view'], function(require, exports, module) {
+
+
+var editorCss = require("text!cockpit/ui/cli_view.css");
+var event = require("pilot/event");
+var dom = require("pilot/dom");
+
+dom.importCssString(editorCss);
+
+var event = require("pilot/event");
+var keys = require("pilot/keys");
+var canon = require("pilot/canon");
+var Status = require('pilot/types').Status;
+
+var CliRequisition = require('cockpit/cli').CliRequisition;
+var Hint = require('cockpit/cli').Hint;
+var RequestView = require('cockpit/ui/request_view').RequestView;
+
+var NO_HINT = new Hint(Status.VALID, '', 0, 0);
+
+/**
+ * On startup we need to:
+ * 1. Add 3 sets of elements to the DOM for:
+ * - command line output
+ * - input hints
+ * - completion
+ * 2. Attach a set of events so the command line works
+ */
+exports.startup = function(data, reason) {
+ var cli = new CliRequisition(data.env);
+ var cliView = new CliView(cli, data.env);
+ data.env.cli = cli;
+};
+
+/**
+ * A class to handle the simplest UI implementation
+ */
+function CliView(cli, env) {
+ cli.cliView = this;
+ this.cli = cli;
+ this.doc = document;
+ this.win = dom.getParentWindow(this.doc);
+ this.env = env;
+
+ // TODO: we should have a better way to specify command lines???
+ this.element = this.doc.getElementById('cockpitInput');
+ if (!this.element) {
+ // console.log('No element with an id of cockpit. Bailing on cli');
+ return;
+ }
+
+ this.settings = env.settings;
+ this.hintDirection = this.settings.getSetting('hintDirection');
+ this.outputDirection = this.settings.getSetting('outputDirection');
+ this.outputHeight = this.settings.getSetting('outputHeight');
+
+ // If the requisition tells us something has changed, we use this to know
+ // if we should ignore it
+ this.isUpdating = false;
+
+ this.createElements();
+ this.update();
+}
+CliView.prototype = {
+ /**
+ * Create divs for completion, hints and output
+ */
+ createElements: function() {
+ var input = this.element;
+
+ this.element.spellcheck = false;
+
+ this.output = this.doc.getElementById('cockpitOutput');
+ this.popupOutput = (this.output == null);
+ if (!this.output) {
+ this.output = this.doc.createElement('div');
+ this.output.id = 'cockpitOutput';
+ this.output.className = 'cptOutput';
+ input.parentNode.insertBefore(this.output, input.nextSibling);
+
+ var setMaxOutputHeight = function() {
+ this.output.style.maxHeight = this.outputHeight.get() + 'px';
+ }.bind(this);
+ this.outputHeight.addEventListener('change', setMaxOutputHeight);
+ setMaxOutputHeight();
+ }
+
+ this.completer = this.doc.createElement('div');
+ this.completer.className = 'cptCompletion VALID';
+
+ this.completer.style.color = dom.computedStyle(input, "color");
+ this.completer.style.fontSize = dom.computedStyle(input, "fontSize");
+ this.completer.style.fontFamily = dom.computedStyle(input, "fontFamily");
+ this.completer.style.fontWeight = dom.computedStyle(input, "fontWeight");
+ this.completer.style.fontStyle = dom.computedStyle(input, "fontStyle");
+ input.parentNode.insertBefore(this.completer, input.nextSibling);
+
+ // Transfer background styling to the completer.
+ this.completer.style.backgroundColor = input.style.backgroundColor;
+ input.style.backgroundColor = 'transparent';
+
+ this.hinter = this.doc.createElement('div');
+ this.hinter.className = 'cptHints';
+ input.parentNode.insertBefore(this.hinter, input.nextSibling);
+
+ var resizer = this.resizer.bind(this);
+ event.addListener(this.win, 'resize', resizer);
+ this.hintDirection.addEventListener('change', resizer);
+ this.outputDirection.addEventListener('change', resizer);
+ resizer();
+
+ canon.addEventListener('output', function(ev) {
+ new RequestView(ev.request, this);
+ }.bind(this));
+ event.addCommandKeyListener(input, this.onCommandKey.bind(this));
+ event.addListener(input, 'keyup', this.onKeyUp.bind(this));
+
+ // cursor position affects hint severity. TODO: shortcuts for speed
+ event.addListener(input, 'mouseup', function(ev) {
+ this.isUpdating = true;
+ this.update();
+ this.isUpdating = false;
+ }.bind(this));
+
+ this.cli.addEventListener('argumentChange', this.onArgChange.bind(this));
+
+ event.addListener(input, "focus", function() {
+ dom.addCssClass(this.output, "cptFocusPopup");
+ dom.addCssClass(this.hinter, "cptFocusPopup");
+ }.bind(this));
+
+ function hideOutput() {
+ dom.removeCssClass(this.output, "cptFocusPopup");
+ dom.removeCssClass(this.hinter, "cptFocusPopup");
+ };
+ event.addListener(input, "blur", hideOutput.bind(this));
+ hideOutput.call(this);
+ },
+
+ /**
+ * We need to see the output of the latest command entered
+ */
+ scrollOutputToBottom: function() {
+ // Certain browsers have a bug such that scrollHeight is too small
+ // when content does not fill the client area of the element
+ var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight);
+ this.output.scrollTop = scrollHeight - this.output.clientHeight;
+ },
+
+ /**
+ * To be called on window resize or any time we want to align the elements
+ * with the input box.
+ */
+ resizer: function() {
+ var rect = this.element.getClientRects()[0];
+
+ this.completer.style.top = rect.top + 'px';
+ var height = rect.bottom - rect.top;
+ this.completer.style.height = height + 'px';
+ this.completer.style.lineHeight = height + 'px';
+ this.completer.style.left = rect.left + 'px';
+ var width = rect.right - rect.left;
+ this.completer.style.width = width + 'px';
+
+ if (this.hintDirection.get() === 'below') {
+ this.hinter.style.top = rect.bottom + 'px';
+ this.hinter.style.bottom = 'auto';
+ }
+ else {
+ this.hinter.style.top = 'auto';
+ this.hinter.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px';
+ }
+ this.hinter.style.left = (rect.left + 30) + 'px';
+ this.hinter.style.maxWidth = (width - 110) + 'px';
+
+ if (this.popupOutput) {
+ if (this.outputDirection.get() === 'below') {
+ this.output.style.top = rect.bottom + 'px';
+ this.output.style.bottom = 'auto';
+ }
+ else {
+ this.output.style.top = 'auto';
+ this.output.style.bottom = (this.doc.documentElement.clientHeight - rect.top) + 'px';
+ }
+ this.output.style.left = rect.left + 'px';
+ this.output.style.width = (width - 80) + 'px';
+ }
+ },
+
+ /**
+ * Ensure that TAB isn't handled by the browser
+ */
+onCommandKey: function(ev, hashId, keyCode) {
+ var stopEvent;
+ if (keyCode === keys.TAB ||
+ keyCode === keys.UP ||
+ keyCode === keys.DOWN) {
+ stopEvent = true;
+ } else if (hashId != 0 || keyCode != 0) {
+ stopEvent = canon.execKeyCommand(this.env, 'cli', hashId, keyCode);
+ }
+ stopEvent && event.stopEvent(ev);
+ },
+
+ /**
+ * The main keyboard processing loop
+ */
+ onKeyUp: function(ev) {
+ var handled;
+ /*
+ var handled = keyboardManager.processKeyEvent(ev, this, {
+ isCommandLine: true, isKeyUp: true
+ });
+ */
+
+ // RETURN does a special exec/highlight thing
+ if (ev.keyCode === keys.RETURN) {
+ var worst = this.cli.getWorstHint();
+ // Deny RETURN unless the command might work
+ if (worst.status === Status.VALID) {
+ this.cli.exec();
+ this.element.value = '';
+ }
+ else {
+ // If we've denied RETURN because the command was not VALID,
+ // select the part of the command line that is causing problems
+ // TODO: if there are 2 errors are we picking the right one?
+ dom.setSelectionStart(this.element, worst.start);
+ dom.setSelectionEnd(this.element, worst.end);
+ }
+ }
+
+ this.update();
+
+ // Special actions which delegate to the assignment
+ var current = this.cli.getAssignmentAt(dom.getSelectionStart(this.element));
+ if (current) {
+ // TAB does a special complete thing
+ if (ev.keyCode === keys.TAB) {
+ current.complete();
+ this.update();
+ }
+
+ // UP/DOWN look for some history
+ if (ev.keyCode === keys.UP) {
+ current.increment();
+ this.update();
+ }
+ if (ev.keyCode === keys.DOWN) {
+ current.decrement();
+ this.update();
+ }
+ }
+
+ return handled;
+ },
+
+ /**
+ * Actually parse the input and make sure we're all up to date
+ */
+ update: function() {
+ this.isUpdating = true;
+ var input = {
+ typed: this.element.value,
+ cursor: {
+ start: dom.getSelectionStart(this.element),
+ end: dom.getSelectionEnd(this.element.selectionEnd)
+ }
+ };
+ this.cli.update(input);
+
+ var display = this.cli.getAssignmentAt(input.cursor.start).getHint();
+
+ // 1. Update the completer with prompt/error marker/TAB info
+ dom.removeCssClass(this.completer, Status.VALID.toString());
+ dom.removeCssClass(this.completer, Status.INCOMPLETE.toString());
+ dom.removeCssClass(this.completer, Status.INVALID.toString());
+
+ var completion = '> ';
+ if (this.element.value.length > 0) {
+ var scores = this.cli.getInputStatusMarkup();
+ completion += this.markupStatusScore(scores);
+ }
+
+ // Display the "-> prediction" at the end of the completer
+ if (this.element.value.length > 0 &&
+ display.predictions && display.predictions.length > 0) {
+ var tab = display.predictions[0];
+ completion += ' ⇥ ' + (tab.name ? tab.name : tab);
+ }
+ this.completer.innerHTML = completion;
+ dom.addCssClass(this.completer, this.cli.getWorstHint().status.toString());
+
+ // 2. Update the hint element
+ var hint = '';
+ if (this.element.value.length !== 0) {
+ hint += display.message;
+ if (display.predictions && display.predictions.length > 0) {
+ hint += ': [ ';
+ display.predictions.forEach(function(prediction) {
+ hint += (prediction.name ? prediction.name : prediction);
+ hint += ' | ';
+ }, this);
+ hint = hint.replace(/\| $/, ']');
+ }
+ }
+
+ this.hinter.innerHTML = hint;
+ if (hint.length === 0) {
+ dom.addCssClass(this.hinter, 'cptNoPopup');
+ }
+ else {
+ dom.removeCssClass(this.hinter, 'cptNoPopup');
+ }
+
+ this.isUpdating = false;
+ },
+
+ /**
+ * Markup an array of Status values with spans
+ */
+ markupStatusScore: function(scores) {
+ var completion = '';
+ // Create mark-up
+ var i = 0;
+ var lastStatus = -1;
+ while (true) {
+ if (lastStatus !== scores[i]) {
+ completion += '';
+ lastStatus = scores[i];
+ }
+ completion += this.element.value[i];
+ i++;
+ if (i === this.element.value.length) {
+ completion += '';
+ break;
+ }
+ if (lastStatus !== scores[i]) {
+ completion += '';
+ }
+ }
+
+ return completion;
+ },
+
+ /**
+ * Update the input element to reflect the changed argument
+ */
+ onArgChange: function(ev) {
+ if (this.isUpdating) {
+ return;
+ }
+
+ var prefix = this.element.value.substring(0, ev.argument.start);
+ var suffix = this.element.value.substring(ev.argument.end);
+ var insert = typeof ev.text === 'string' ? ev.text : ev.text.name;
+ this.element.value = prefix + insert + suffix;
+ // Fix the cursor.
+ var insertEnd = (prefix + insert).length;
+ this.element.selectionStart = insertEnd;
+ this.element.selectionEnd = insertEnd;
+ }
+};
+exports.CliView = CliView;
+
+
+});
+/* ***** 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('cockpit/ui/request_view', ['require', 'exports', 'module' , 'pilot/dom', 'pilot/event', 'text!cockpit/ui/request_view.html', 'pilot/domtemplate', 'text!cockpit/ui/request_view.css'], function(require, exports, module) {
+
+var dom = require("pilot/dom");
+var event = require("pilot/event");
+var requestViewHtml = require("text!cockpit/ui/request_view.html");
+var Templater = require("pilot/domtemplate").Templater;
+
+var requestViewCss = require("text!cockpit/ui/request_view.css");
+dom.importCssString(requestViewCss);
+
+/**
+ * Pull the HTML into the DOM, but don't add it to the document
+ */
+var templates = document.createElement('div');
+templates.innerHTML = requestViewHtml;
+var row = templates.querySelector('.cptRow');
+
+/**
+ * Work out the path for images.
+ * TODO: This should probably live in some utility area somewhere
+ */
+function imageUrl(path) {
+ var dataUrl;
+ try {
+ dataUrl = require('text!cockpit/ui/' + path);
+ } catch (e) { }
+ if (dataUrl) {
+ return dataUrl;
+ }
+
+ var filename = module.id.split('/').pop() + '.js';
+ var imagePath;
+
+ if (module.uri.substr(-filename.length) !== filename) {
+ console.error('Can\'t work out path from module.uri/module.id');
+ return path;
+ }
+
+ if (module.uri) {
+ var end = module.uri.length - filename.length - 1;
+ return module.uri.substr(0, end) + "/" + path;
+ }
+
+ return filename + path;
+}
+
+
+/**
+ * Adds a row to the CLI output display
+ */
+function RequestView(request, cliView) {
+ this.request = request;
+ this.cliView = cliView;
+ this.imageUrl = imageUrl;
+
+ // Elements attached to this by the templater. For info only
+ this.rowin = null;
+ this.rowout = null;
+ this.output = null;
+ this.hide = null;
+ this.show = null;
+ this.duration = null;
+ this.throb = null;
+
+ new Templater().processNode(row.cloneNode(true), this);
+
+ this.cliView.output.appendChild(this.rowin);
+ this.cliView.output.appendChild(this.rowout);
+
+ this.request.addEventListener('output', this.onRequestChange.bind(this));
+};
+
+RequestView.prototype = {
+ /**
+ * A single click on an invocation line in the console copies the command to
+ * the command line
+ */
+ copyToInput: function() {
+ this.cliView.element.value = this.request.typed;
+ },
+
+ /**
+ * A double click on an invocation line in the console executes the command
+ */
+ executeRequest: function(ev) {
+ this.cliView.cli.update({
+ typed: this.request.typed,
+ cursor: { start:0, end:0 }
+ });
+ this.cliView.cli.exec();
+ },
+
+ hideOutput: function(ev) {
+ this.output.style.display = 'none';
+ dom.addCssClass(this.hide, 'cmd_hidden');
+ dom.removeCssClass(this.show, 'cmd_hidden');
+
+ event.stopPropagation(ev);
+ },
+
+ showOutput: function(ev) {
+ this.output.style.display = 'block';
+ dom.removeCssClass(this.hide, 'cmd_hidden');
+ dom.addCssClass(this.show, 'cmd_hidden');
+
+ event.stopPropagation(ev);
+ },
+
+ remove: function(ev) {
+ this.cliView.output.removeChild(this.rowin);
+ this.cliView.output.removeChild(this.rowout);
+ event.stopPropagation(ev);
+ },
+
+ onRequestChange: function(ev) {
+ this.duration.innerHTML = this.request.duration ?
+ 'completed in ' + (this.request.duration / 1000) + ' sec ' :
+ '';
+
+ this.output.innerHTML = '';
+ this.request.outputs.forEach(function(output) {
+ var node;
+ if (typeof output == 'string') {
+ node = document.createElement('p');
+ node.innerHTML = output;
+ } else {
+ node = output;
+ }
+ this.output.appendChild(node);
+ }, this);
+ this.cliView.scrollOutputToBottom();
+
+ dom.setCssClass(this.output, 'cmd_error', this.request.error);
+
+ this.throb.style.display = this.request.completed ? 'none' : 'block';
+ }
+};
+exports.RequestView = RequestView;
+
+
+});
+/* ***** 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 DomTemplate.
+ *
+ * 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) (original author)
+ *
+ * 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('pilot/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+// WARNING: do not 'use_strict' without reading the notes in envEval;
+
+/**
+ * A templater that allows one to quickly template DOM nodes.
+ */
+function Templater() {
+ this.scope = [];
+};
+
+/**
+ * Recursive function to walk the tree processing the attributes as it goes.
+ * @param node the node to process. If you pass a string in instead of a DOM
+ * element, it is assumed to be an id for use with document.getElementById()
+ * @param data the data to use for node processing.
+ */
+Templater.prototype.processNode = function(node, data) {
+ if (typeof node === 'string') {
+ node = document.getElementById(node);
+ }
+ if (data === null || data === undefined) {
+ data = {};
+ }
+ this.scope.push(node.nodeName + (node.id ? '#' + node.id : ''));
+ try {
+ // Process attributes
+ if (node.attributes && node.attributes.length) {
+ // We need to handle 'foreach' and 'if' first because they might stop
+ // some types of processing from happening, and foreach must come first
+ // because it defines new data on which 'if' might depend.
+ if (node.hasAttribute('foreach')) {
+ this.processForEach(node, data);
+ return;
+ }
+ if (node.hasAttribute('if')) {
+ if (!this.processIf(node, data)) {
+ return;
+ }
+ }
+ // Only make the node available once we know it's not going away
+ data.__element = node;
+ // It's good to clean up the attributes when we've processed them,
+ // but if we do it straight away, we mess up the array index
+ var attrs = Array.prototype.slice.call(node.attributes);
+ for (var i = 0; i < attrs.length; i++) {
+ var value = attrs[i].value;
+ var name = attrs[i].name;
+ this.scope.push(name);
+ try {
+ if (name === 'save') {
+ // Save attributes are a setter using the node
+ value = this.stripBraces(value);
+ this.property(value, data, node);
+ node.removeAttribute('save');
+ } else if (name.substring(0, 2) === 'on') {
+ // Event registration relies on property doing a bind
+ value = this.stripBraces(value);
+ var func = this.property(value, data);
+ if (typeof func !== 'function') {
+ this.handleError('Expected ' + value +
+ ' to resolve to a function, but got ' + typeof func);
+ }
+ node.removeAttribute(name);
+ var capture = node.hasAttribute('capture' + name.substring(2));
+ node.addEventListener(name.substring(2), func, capture);
+ if (capture) {
+ node.removeAttribute('capture' + name.substring(2));
+ }
+ } else {
+ // Replace references in all other attributes
+ var self = this;
+ var newValue = value.replace(/\$\{[^}]*\}/g, function(path) {
+ return self.envEval(path.slice(2, -1), data, value);
+ });
+ // Remove '_' prefix of attribute names so the DOM won't try
+ // to use them before we've processed the template
+ if (name.charAt(0) === '_') {
+ node.removeAttribute(name);
+ node.setAttribute(name.substring(1), newValue);
+ } else if (value !== newValue) {
+ attrs[i].value = newValue;
+ }
+ }
+ } finally {
+ this.scope.pop();
+ }
+ }
+ }
+
+ // Loop through our children calling processNode. First clone them, so the
+ // set of nodes that we visit will be unaffected by additions or removals.
+ var childNodes = Array.prototype.slice.call(node.childNodes);
+ for (var j = 0; j < childNodes.length; j++) {
+ this.processNode(childNodes[j], data);
+ }
+
+ if (node.nodeType === Node.TEXT_NODE) {
+ this.processTextNode(node, data);
+ }
+ } finally {
+ this.scope.pop();
+ }
+};
+
+/**
+ * Handle
+ *
+ * @param path An array of strings indicating the path through the data, or
+ * a string to be cut into an array using split('.')
+ * @param data An object to look in for the path argument
+ * @param newValue (optional) If defined, this value will replace the
+ * original value for the data at the path specified.
+ * @return The value pointed to by path before any
+ * newValue is applied.
+ */
+Templater.prototype.property = function(path, data, newValue) {
+ this.scope.push(path);
+ try {
+ if (typeof path === 'string') {
+ path = path.split('.');
+ }
+ var value = data[path[0]];
+ if (path.length === 1) {
+ if (newValue !== undefined) {
+ data[path[0]] = newValue;
+ }
+ if (typeof value === 'function') {
+ return function() {
+ return value.apply(data, arguments);
+ };
+ }
+ return value;
+ }
+ if (!value) {
+ this.handleError('Can\'t find path=' + path);
+ return null;
+ }
+ return this.property(path.slice(1), value, newValue);
+ } finally {
+ this.scope.pop();
+ }
+};
+
+/**
+ * Like eval, but that creates a context of the variables in env in
+ * which the script is evaluated.
+ * WARNING: This script uses 'with' which is generally regarded to be evil.
+ * The alternative is to create a Function at runtime that takes X parameters
+ * according to the X keys in the env object, and then call that function using
+ * the values in the env object. This is likely to be slow, but workable.
+ * @param script The string to be evaluated.
+ * @param env The environment in which to eval the script.
+ * @param context Optional debugging string in case of failure
+ * @return The return value of the script, or the error message if the script
+ * execution failed.
+ */
+Templater.prototype.envEval = function(script, env, context) {
+ with (env) {
+ try {
+ this.scope.push(context);
+ return eval(script);
+ } catch (ex) {
+ this.handleError('Template error evaluating \'' + script + '\'', ex);
+ return script;
+ } finally {
+ this.scope.pop();
+ }
+ }
+};
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ * @param ex optional associated exception.
+ */
+Templater.prototype.handleError = function(message, ex) {
+ this.logError(message);
+ this.logError('In: ' + this.scope.join(' > '));
+ if (ex) {
+ this.logError(ex);
+ }
+};
+
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ */
+Templater.prototype.logError = function(message) {
+ window.console && window.console.log && console.log(message);
+};
+
+exports.Templater = Templater;
+
+
+});
+/* ***** 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('cockpit/commands/basic', ['require', 'exports', 'module' , 'pilot/canon'], function(require, exports, module) {
+
+
+var canon = require('pilot/canon');
+
+/**
+ * '!' command
+ */
+var bangCommandSpec = {
+ name: 'sh',
+ description: 'Execute a system command (requires server support)',
+ params: [
+ {
+ name: 'command',
+ type: 'text',
+ description: 'The string to send to the os shell.'
+ }
+ ],
+ exec: function(env, args, request) {
+ var req = new XMLHttpRequest();
+ req.open('GET', '/exec?args=' + args.command, true);
+ req.onreadystatechange = function(ev) {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ request.done('' + req.responseText + '
');
+ }
+ }
+ };
+ req.send(null);
+ }
+};
+
+var canon = require('pilot/canon');
+
+exports.startup = function(data, reason) {
+ canon.addCommand(bangCommandSpec);
+};
+
+exports.shutdown = function(data, reason) {
+ canon.removeCommand(bangCommandSpec);
+};
+
+
+});
+define("text!cockpit/ui/cli_view.css", [], "" +
+ "#cockpitInput { padding-left: 16px; }" +
+ "" +
+ ".cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }" +
+ "" +
+ ".cptCompletion { padding: 0; position: absolute; z-index: -1000; }" +
+ ".cptCompletion.VALID { background: #FFF; }" +
+ ".cptCompletion.INCOMPLETE { background: #DDD; }" +
+ ".cptCompletion.INVALID { background: #DDD; }" +
+ ".cptCompletion span { color: #FFF; }" +
+ ".cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }" +
+ ".cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }" +
+ "span.cptPrompt { color: #66F; font-weight: bold; }" +
+ "" +
+ "" +
+ ".cptHints {" +
+ " color: #000;" +
+ " position: absolute;" +
+ " border: 1px solid rgba(230, 230, 230, 0.8);" +
+ " background: rgba(250, 250, 250, 0.8);" +
+ " -moz-border-radius-topleft: 10px;" +
+ " -moz-border-radius-topright: 10px;" +
+ " border-top-left-radius: 10px; border-top-right-radius: 10px;" +
+ " z-index: 1000;" +
+ " padding: 8px;" +
+ " display: none;" +
+ "}" +
+ "" +
+ ".cptFocusPopup { display: block; }" +
+ ".cptFocusPopup.cptNoPopup { display: none; }" +
+ "" +
+ ".cptHints ul { margin: 0; padding: 0 15px; }" +
+ "" +
+ ".cptGt { font-weight: bold; font-size: 120%; }" +
+ "");
+
+define("text!cockpit/ui/request_view.css", [], "" +
+ ".cptRowIn {" +
+ " display: box; display: -moz-box; display: -webkit-box;" +
+ " box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;" +
+ " box-align: center; -moz-box-align: center; -webkit-box-align: center;" +
+ " color: #333;" +
+ " background-color: #EEE;" +
+ " width: 100%;" +
+ " font-family: consolas, courier, monospace;" +
+ "}" +
+ ".cptRowIn > * { padding-left: 2px; padding-right: 2px; }" +
+ ".cptRowIn > img { cursor: pointer; }" +
+ ".cptHover { display: none; }" +
+ ".cptRowIn:hover > .cptHover { display: block; }" +
+ ".cptRowIn:hover > .cptHover.cptHidden { display: none; }" +
+ ".cptOutTyped {" +
+ " box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;" +
+ " font-weight: bold; color: #000; font-size: 120%;" +
+ "}" +
+ ".cptRowOutput { padding-left: 10px; line-height: 1.2em; }" +
+ ".cptRowOutput strong," +
+ ".cptRowOutput b," +
+ ".cptRowOutput th," +
+ ".cptRowOutput h1," +
+ ".cptRowOutput h2," +
+ ".cptRowOutput h3 { color: #000; }" +
+ ".cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }" +
+ ".cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }" +
+ ".cptRowOutput input[type=password]," +
+ ".cptRowOutput input[type=text]," +
+ ".cptRowOutput textarea {" +
+ " color: #000; font-size: 120%;" +
+ " background: transparent; padding: 3px;" +
+ " border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;" +
+ "}" +
+ ".cptRowOutput table," +
+ ".cptRowOutput td," +
+ ".cptRowOutput th { border: 0; padding: 0 2px; }" +
+ ".cptRowOutput .right { text-align: right; }" +
+ "");
+
+define("text!cockpit/ui/request_view.html", [], "" +
+ "
"),c.push(a.description?a.description:"(No description)"),c.push("
"),a.params&&a.params.length>0&&(c.push(""),a.params.forEach(function(a){c.push("
"));return new l(i.VALID,c.join(""),b)}};q.prototype={commandAssignment:undefined,assignmentCount:undefined,_assignments:undefined,_hints:undefined,_assignmentChanged:function(a){a.param.name==="__command"&&(this._assignments={},a.value&&a.value.params.forEach(function(a){this._assignments[a.name]=new o(a,this)},this),this.assignmentCount=Object.keys(this._assignments).length,this._dispatchEvent("commandChange",{command:a.value}))},getAssignment:function(a){var b=typeof a=="string"?a:Object.keys(this._assignments)[a];return this._assignments[b]},getParameterNames:function(){return Object.keys(this._assignments)},cloneAssignments:function(){return Object.keys(this._assignments).map(function(a){return this._assignments[a]},this)},_updateHints:function(){this.getAssignments(!0).forEach(function(a){this._hints.push(a.getHint())},this),l.sort(this._hints)},getWorstHint:function(){return this._hints[0]},getArgsObject:function(){var a={};this.getAssignments().forEach(function(b){a[b.param.name]=b.value},this);return a},getAssignments:function(a){var b=[];a===!0&&b.push(this.commandAssignment),Object.keys(this._assignments).forEach(function(a){b.push(this.getAssignment(a))},this);return b},setDefaultValues:function(){this.getAssignments().forEach(function(a){a.setValue(undefined)},this)},exec:function(){k.exec(this.commandAssignment.value,this.env,"cli",this.getArgsObject(),this.toCanonicalString())},toCanonicalString:function(){var a=[];a.push(this.commandAssignment.value.name),Object.keys(this._assignments).forEach(function(b){var c=this._assignments[b],d=c.param.type;c.value!==c.param.defaultValue&&(a.push(" "),a.push(d.stringify(c.value)))},this);return a.join("")}},f.implement(q.prototype,g),b.Requisition=q,f.inherits(r,q),function(){r.prototype.update=function(a){this.input=a,this._hints=[];var b=this._tokenize(a.typed);this._split(b),this.commandAssignment.value&&this._assign(b),this._updateHints()},r.prototype.getInputStatusMarkup=function(){var a=this.toString().split("").map(function(a){return i.VALID});this._hints.forEach(function(b){for(var c=b.start;c<=b.end;c++)b.status>a[c]&&(a[c]=b.status)},this);return a},r.prototype.toString=function(){return this.getAssignments(!0).map(function(a){return a.toString()},this).join("")};var a=r.prototype._updateHints;r.prototype._updateHints=function(){a.call(this);var b=this.input.cursor;this._hints.forEach(function(a){var c=b.start>=a.start&&b.start<=a.end,d=b.end>=a.start&&b.end<=a.end,e=c||d;!e&&a.status===i.INCOMPLETE&&(a.status=i.INVALID)},this),l.sort(this._hints)},r.prototype.getHints=function(){return this._hints},r.prototype.getAssignmentAt=function(a){var b=this.getAssignments(!0);for(var c=0;c"+d.responseText+"
")},d.send(null)}},d=a("pilot/canon");b.startup=function(a,b){d.addCommand(e)},b.shutdown=function(a,b){d.removeCommand(e)}}),define("text!cockpit/ui/cli_view.css",[],"#cockpitInput { padding-left: 16px; }.cptOutput { overflow: auto; position: absolute; z-index: 999; display: none; }.cptCompletion { padding: 0; position: absolute; z-index: -1000; }.cptCompletion.VALID { background: #FFF; }.cptCompletion.INCOMPLETE { background: #DDD; }.cptCompletion.INVALID { background: #DDD; }.cptCompletion span { color: #FFF; }.cptCompletion span.INCOMPLETE { color: #DDD; border-bottom: 2px dotted #F80; }.cptCompletion span.INVALID { color: #DDD; border-bottom: 2px dotted #F00; }span.cptPrompt { color: #66F; font-weight: bold; }.cptHints { color: #000; position: absolute; border: 1px solid rgba(230, 230, 230, 0.8); background: rgba(250, 250, 250, 0.8); -moz-border-radius-topleft: 10px; -moz-border-radius-topright: 10px; border-top-left-radius: 10px; border-top-right-radius: 10px; z-index: 1000; padding: 8px; display: none;}.cptFocusPopup { display: block; }.cptFocusPopup.cptNoPopup { display: none; }.cptHints ul { margin: 0; padding: 0 15px; }.cptGt { font-weight: bold; font-size: 120%; }"),define("text!cockpit/ui/request_view.css",[],".cptRowIn { display: box; display: -moz-box; display: -webkit-box; box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal; box-align: center; -moz-box-align: center; -webkit-box-align: center; color: #333; background-color: #EEE; width: 100%; font-family: consolas, courier, monospace;}.cptRowIn > * { padding-left: 2px; padding-right: 2px; }.cptRowIn > img { cursor: pointer; }.cptHover { display: none; }.cptRowIn:hover > .cptHover { display: block; }.cptRowIn:hover > .cptHover.cptHidden { display: none; }.cptOutTyped { box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1; font-weight: bold; color: #000; font-size: 120%;}.cptRowOutput { padding-left: 10px; line-height: 1.2em; }.cptRowOutput strong,.cptRowOutput b,.cptRowOutput th,.cptRowOutput h1,.cptRowOutput h2,.cptRowOutput h3 { color: #000; }.cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }.cptRowOutput input[type=password],.cptRowOutput input[type=text],.cptRowOutput textarea { color: #000; font-size: 120%; background: transparent; padding: 3px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;}.cptRowOutput table,.cptRowOutput td,.cptRowOutput th { border: 0; padding: 0 2px; }.cptRowOutput .right { text-align: right; }"),define("text!cockpit/ui/request_view.html",[],'|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/,R=/^[^\n\S]+/,k=/^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/,g=/^[-=]>/,D=/^(?:\n[^\n\S]*)+/,O=/^'[^\\']*(?:\\.[^\\']*)*'/,u=/^`[^\\`]*(?:\\.[^\\`]*)*`/,J=/^\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/[imgy]{0,4}(?!\w)/,q=/^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/,r=/\s+(?:#.*)?/g,C=/\n/g,p=/\n+([^\n\S]*)/g,o=/\*\//,d=/^\s*@?([$A-Za-z_][$\w\x7f-\uffff]*|['"].*['"])[^\n\S]*?[:=][^:=>]/,y=/^\s*(?:,|\??\.(?![.\d])|::)/,P=/\s+$/,G=/^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/,m=["-=","+=","/=","*=","%=","||=","&&=","?=","<<=",">>=",">>>=","&=","^=","|="],Q=["!","~","NEW","TYPEOF","DELETE","DO"],z=["&&","||","&","|","^"],N=["<<",">>",">>>"],l=["==","!=","<",">","<=",">="],B=["*","/","%"],K=["IN","OF","INSTANCEOF"],e=["TRUE","FALSE","NULL","UNDEFINED"],E=["NUMBER","REGEX","BOOL","++","--","]"],F=E.concat(")","}","THIS","IDENTIFIER","STRING"),f=["IDENTIFIER","STRING","REGEX",")","]","}","?","::","@","THIS","SUPER"],t=f.concat("NUMBER","BOOL"),x=["INDENT","OUTDENT","TERMINATOR"]}),define("ace/mode/coffee/rewriter",["require","exports","module"],function(a,b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b
"+b.unused[h]["function"]+"
";m.push("
",b.urls,"
"),b.json&&!f?m.push("/*members ",j=10;for(h=0;h
")}m.push("
"),k=" ",j=1),j+=l.length+2,b.member[i]===1&&(l=""+l+""),h\\\\w+)\\\\]$","U"),i=1,p=0;p