define(function(require, exports, module) { main.consumes = ["Plugin", "settings"]; main.provides = ["commands"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var settings = imports.settings; var lang = require("ace/lib/lang"); var event = require("ace/lib/event"); var keyUtil = require("ace/lib/keys"); var KeyBinding = require("ace/keyboard/keybinding").KeyBinding; var CommandManager = require("ace/commands/command_manager").CommandManager; /***** Initialization *****/ var nav = navigator.platform.toLowerCase(); var platform = nav.indexOf("mac") > -1 ? "mac" : "win"; var commandManager = new CommandManager(platform); var commands = commandManager.commands; var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); var execSequenceID = 0; // Use our exec function commandManager.exec = exec; var loaded = false; function load(){ if (loaded) return false; loaded = true; var kb = new KeyBinding({ commands: commandManager, fake: true }); event.addCommandKeyListener(document.documentElement, kb.onCommandKey.bind(kb)); event.addListener(document.documentElement, "keyup", function(e) { if (e.keyCode === 18) // do not trigger browser menu on windows e.preventDefault(); }); settings.on("read", function(e) { settings.setDefaults("user/key-bindings", [ ["preset", "default"], ["platform", "auto"] ]); var platform = settings.get("user/key-bindings/@platform"); if (platform && platform != "auto") changePlatform(platform); }); addCommands([{ name: "passKeysToBrowser", group: "ignore", bindKey: { win: "F12|Ctrl-Shift-I", mac: "F12|Cmd-`|Cmd-Option-I|Cmd-H|Cmd-M" }, exec: function(){}, passEvent: true, hint: "Allow keys to be handled by the browser" }, { name: "cancelBrowserAction", group: "ignore", bindKey: { mac: "Cmd-S|Cmd-R|Cmd-[|Cmd-]", win: "Ctrl-S|Ctrl-R|Alt-Left|Alt-Right", position: -10000 }, exec: function(){}, hint: "This cancels some native browser keybindings that can be annoying if triggered accidentally" }], plugin); } /***** Methods *****/ function changePlatform(value) { platform = value == "auto" ? (apf.isMac ? "mac" : "win") : value; commandManager.platform = platform; Object.keys(commands).forEach(function(name) { var command = commands[name]; var displayKey = command.bindKey || command.nativeKey; if (displayKey) plugin.commandManager .setProperty(command.name, displayKey[platform]); }); } function getHotkey(command) { if (!commands[command] || !commands[command].bindKey) return ""; return commands[command].bindKey[platform]; } function getPrettyHotkey(command) { var key = getHotkey(command); if (platform == "mac") key = apf.hotkeys.toMacNotation(key); return key; } var markDirty = lang.delayedCall(function(){ emit("update"); }, 500); function exec(command, editor, args, e) { var sCommand = command; if (!editor || editor.fake) editor = emit("getEditor"); if (Array.isArray(command)) { for (var i = command.length; i--; ) { if (this.exec(command[i], editor, args, e)) return true; } return false; } if (typeof command === 'string') command = commands[command]; if (!command) { console.warn("Could not find command ", sCommand); return false; } if (command.isAvailable && !command.isAvailable(editor, args, e)) return; // Disable commands for other contexts if (command.findEditor) editor = command.findEditor(editor, e); if (editor && editor.$readOnly && !command.readOnly) return false; var execEvent = { editor: editor, command: command, args: args, sequenceID: ++execSequenceID }; emit("beforeExec", execEvent); var retvalue; if (editor && editor.commands) { retvalue = editor.commands._emit("exec", execEvent); editor.commands._signal("afterExec", execEvent); } else { retvalue = commandManager._emit("exec", execEvent); } if (retvalue !== false && args) { // e.returnValue = false; // e.preventDefault(); if (typeof apf != "undefined") apf.queue.empty(); } execEvent.returnValue = retvalue; emit("afterExec", execEvent); return retvalue !== false; } function addCommand(command, hostPlugin, asDefault) { if (!command.name) return console.error("trying to add a command without name", command); plugin.commandManager[command.name] = ""; if (command.readOnly === undefined) command.readOnly = true; if (typeof command.bindKey == "string") command.bindKey = {win: command.bindKey, mac: command.bindKey}; if (asDefault) command.isDefault = asDefault; commandManager.addCommand.apply(commandManager, arguments); var displayKey = command.bindKey || command.nativeKey; if (displayKey) plugin.commandManager .setProperty(command.name, displayKey[platform]); if (!command.originalBindKey) command.originalBindKey = command.bindKey || (command.bindKey = { mac: "", win: "" }); hostPlugin.addOther(function(){ removeCommand(command); }); markDirty.schedule(); return command; } function addCommands(list, hostPlugin, asDefault) { list && Object.keys(list).forEach(function(name) { var command = list[name]; if (typeof command === "string") return bindKey(command, name, asDefault); if (typeof command === "function") command = { exec: command }; if (!command.name) command.name = name; if (typeof command.bindKey == "string") command.bindKey = {mac: command.bindKey, win: command.bindKey}; if (asDefault && commands[command.name]) return; command.isDefault = asDefault; addCommand(command, hostPlugin, asDefault); }); } function removeCommands(commands) { Object.keys(commands).forEach(function(name) { removeCommand(commands[name]); }); } function removeCommand(command, context, clean) { if (!command) return; var name = (typeof command === 'string' ? command : command.name); if (name) { if (plugin.commandManager[name]) plugin.commandManager.setProperty(name, ""); command = commands[name]; if (!clean) delete commands[name]; } commandManager.removeCommand(command, clean); markDirty.schedule(); } function setDefault(name, keys) { var command = commands[name]; if (!command) return; // If bind key is not yet overridden by a custom one if (plugin.commandManager[name] == command.bindKey[platform]) bindKey(keys[platform], command); command.bindKey = keys; } function bindKey(key, command, asDefault) { removeCommand(command, null, true); if (!command) return; if (typeof key == "string" || !key) { command.bindKey = {}; command.bindKey[commandManager.platform] = key; } else command.bindKey = key; if (command.bindKey.position == undefined) command.bindKey.position = command.originalBindKey.position; commandManager.bindKey(command.bindKey, command, asDefault); plugin.commandManager.setProperty(command.name, command.bindKey[commandManager.platform]); } function findKey(key, scope) { var commands = commandManager.commandKeyBinding[key]; if (!commands) commands = commandManager.commandKeyBinding[key.toLowerCase()]; if (!commands) return []; if (!Array.isArray(commands)) commands = [commands]; if (scope == "global") { var exceptions = getExceptionList(); commands = commands.filter(function(c) { return exceptions.indexOf(c) != -1; }); } return commands; } function reset(noReload, toDefault){ commandManager.commandKeyBinding = {}; Object.keys(commands).forEach(function(name) { var cmd = commands[name]; bindKey(toDefault ? cmd.originalBindKey : cmd.bindKey, cmd); }); if (noReload) markDirty.cancel(); else markDirty.schedule(); } function flushUpdateQueue() { markDirty.call(); } function getExceptionList(){ // Whitelist certain IDE keys for use from terminal and preview return [ { bindKey: { win: null, mac: "Command-O" }, name: "navigateAlt", passEvent: true, exec: function(){} }, commands.openpreferences, commands.passKeysToBrowser, commands.commands, commands.find, commands.openterminal, commands.navigate, commands.searchinfiles, commands.close_term_pane, commands.closeallbutme, commands.closealltabs, commands.closealltotheleft, commands.closealltotheright, commands.closepane, commands.closetab, commands.gototabright, commands.gototableft, commands.movetabright, commands.movetableft, commands.movetabup, commands.movetabdown, commands.nexttab, commands.previoustab, commands.nextpane, commands.previouspane, commands.exit, commands.hidesearchreplace, commands.hidesearchinfiles, commands.toggleconsole, commands.runlast, commands.run, commands.resume, commands.stepinto, commands.stepover, commands.stepout, commands.devtools, commands.open, commands.settings, commands.new, commands.build, commands.switchterminal, commands.findinfiles, commands.tab1, commands.tab2, commands.tab3, commands.tab4, commands.tab5, commands.tab6, commands.tab7, commands.tab8, commands.tab9, commands.tab0, commands.reopenLastTab, ].filter(Boolean); } function getExceptionBindings(){ var list = []; getExceptionList().forEach(function(cmd) { var m = cmd && cmd.bindKey && cmd.bindKey[platform]; if (!m) return; list.push.apply(list, m.split("|").map(function(keyPart) { var binding = commandManager.parseKeys(keyPart, cmd); binding.key = keyUtil[binding.key]; return { binding: binding, command: cmd.name }; })); }); return list; } // commandManager.bindKey = bindKey; /***** Lifecycle *****/ plugin.on("load", function(){ load(); }); plugin.on("enable", function(){ }); plugin.on("disable", function(){ }); plugin.on("unload", function(){ loaded = false; }); /***** Register and define API *****/ /** * Manages the commands and the key bindings for Cloud9. * * Commands are named items that represent functionality that can be * triggered by a user by pressing a combination of keys at the same * time. Commands can also be executed via code: * * // Saves the currently focussed tab (if any) * commands.exec("save"); * * You can also specify arguments to commands * * // Runs the file /example.js with argument --test * var editor = tabs.findPage("/example.js").editor; * commands.exec("run", editor, ["--test"]); * * This example shows how you can define a command. This command is * available when there is a current editor and it's an {@link ace.Ace Ace} * editor. * * commands.addCommand({ * name : "gotoline", * group : "ace", * hint : "Triggers the goto line dialog", * bindKey : { mac: "Command-L", win: "Ctrl-G" }, * isAvailable : function(editor) { * return editor && editor.type == "ace"; * }, * exec : function() { * gotoline(); * } * }, plugin); * * This example is also from the gotoline plugin. In this example the * bind key is a commonly used key: Escape. There are many commands that * bind this key and the isAvailable function determines which command * will be executed. The first command found for which the isAvailable * function returns true will be used. In this case it returns true when * the gotoline dialog is visible (which is only visible when it has * focus). * * commands.addCommand({ * bindKey : { mac: "ESC", win: "ESC" }, * isAvailable : function(editor){ return win && win.visible; }, * exec : function() { * hide(); * var tab = tabs.focussedTab; * tab && tabs.focusTab(tab); * * if (originalLine) { * execGotoLine(originalLine, originalColumn, true); * originalPath = originalColumn = originalLine = undefined; * } * } * }, plugin); * * Commands are generally used by buttons and menu items to attach * functionality to them: * * menus.addItemByPath("Edit/Cut", new MenuItem({ command : "cut" }), 400, plugin); * menus.addItemByPath("Edit/Copy", new MenuItem({ command : "copy" }), 500, plugin); * menus.addItemByPath("Edit/Paste", new MenuItem({ command : "paste" }), 600, plugin); * * Or to ui.button elements: * * var button = new ui.button({ caption: "Save", command: "save" }); * * Users are able to change the key bindings of each command in the * key bindings editor. They are also able to set * key bindings for commands that have been defined without a key * binding. * * @singleton */ plugin.freezePublicAPI({ /** * @ignore */ flushUpdateQueue: flushUpdateQueue, /** * @ignore */ get commandKeyBinding() { commandManager.commandKeyBinding }, /** * @ignore */ commandManager: (typeof apf != "undefined" ? new apf.Class().$init() : { setProperty : function(x,y){ this[x] = y } }), /** * A hash table of all the commands. The index is the name of the * command. * * See {@link #addCommand} for a description of the command object. * * @property {Object[]} commands * @readonly */ get commands(){ return commands; }, /** * The operating system that is being run. * @property {String} platform Possible values are "mac", "win". * @readonly */ get platform(){ return platform; }, /** * By default the key bindings for the platform that the user is * currently running are choosen. Using this function the chosen * platform can be changed. * @param {String} platform The platform to change the key binding * set to. Possible values are "auto", "mac" or "win". */ changePlatform: changePlatform, /** * Retrieves a string that specifies the current hotkey for a * command. The (modifier) keys are space or dash (-) separated. * Special named keys are: * * * Ctrl * * Command * * Alt * * Option * * Shift * * Meta * * Tab * * Esc * * Enter * * F1-F12 * * Up * * Down * * Left * * Right * * PgUp * * PgDown * * Home * * End * * @param {String} name the name of the command. * @return {String} */ getHotkey: getHotkey, /** * returns result of getHotkey formatted for displaying in menus * * @param {String} name the name of the command. * @return {String} */ getPrettyHotkey: getPrettyHotkey, /** * Executes the action tied to a command. This method will call * the `isAvailable` method for a command and will not execute if * the command is not available. If there are multiple commands * with the same name, the command for which the `isAvailable` * method returns true first will be executed. * @param {String} command the name of the command to execute * @param {Editor} editor the editor that is the context for the * command. * @param {Array} args a list of arguments to pass to the * command. * @return {Boolean} Specifies whether the command was executed * successfully. */ exec: exec, /** * Adds a command to the list of available commands. * * @param {Object} command The command definition * to add. * @param {String} command.name The name of this command * @param {Object} [command.bindKey] Object containing an entry * for each platform. The (modifier) keys are space or dash (-) * separated. Special named keys are: * * Ctrl, Command, Alt, Option, Shift, Meta, Tab, Esc, Enter, F1-F12, * Up, Down, Left, Right, PgUp, PgDown, Home, End * * Example: * * bindKey : { mac: "Command-Option-Z", win: "Ctrl-Alt-Z" } * * @param {String} [command.bindKey.win] The bind key for windows * and unix. * @param {String} [command.bindKey.mac] The bind key for mac. * @param {String} [command.hint] A description of this * command. This is displayed in the key bindings editor. * @param {String} [command.group] The group to which this * command belongs. This is used by the key bindings editor to * group the commands.This function should return true when the * command is available and otherwise return false. Make sure that * you implement this to be as exact as possible. * @param {Function} [command.isAvailable] This function should * return true when the command is available and otherwise return * false. Make sure that you implement this to be as exact as * possible. * @param {Function} command.exec This function is called * when the command is triggered for execution. * @param {Plugin} plugin The plugin responsible for adding the * command. * @return {Object} The command definition */ addCommand: addCommand, /** * Adds multiple commands to the list of available commands. * @param {Object[]} list The list of commands to add. * See {@link #addCommand} for a description of the object * definition. * @param {Plugin} plugin The plugin responsible for adding the * commands. */ addCommands: addCommands, /** * Remove multiple commands from the list of available commans. * @param {String[]} list The list of names of commands to remove. */ removeCommands: removeCommands, /** * Remove a command from the list of available commands * @param {String} name the name of the command to remove. */ removeCommand: removeCommand, /** * Set a new bind key for a command * @param {String} key the description of the keys to press. * See {@link #getHotkey} for the way to construct this string. * @param {String} command The command object as described by * {@link #addCommand} */ bindKey: bindKey, /** * */ findKey: findKey, /** * */ reset: reset, /** * */ setDefault: setDefault, /** * */ getExceptionList: getExceptionList, /** * */ getExceptionBindings: getExceptionBindings }); register(null, { commands: plugin }); } });