Manages the mapping of keyboard inputs to commands.
var CtrlDownStates = {
"NOT_YET_DETECTED" : 0,
"DETECTED" : 1,
"DETECTED_AND_IGNORED": 2 // For consecutive ctrl keydown events while a Ctrl key is being hold down
};
var _commandMap = {};
var _ctrlDown = CtrlDownStates.NOT_YET_DETECTED,
_altGrDown = false;
var _displayKeyMap = { "up": "\u2191",
"down": "\u2193",
"left": "\u2190",
"right": "\u2192",
"-": "\u2212" };
var _specialCommands = [Commands.EDIT_UNDO, Commands.EDIT_REDO, Commands.EDIT_SELECT_ALL,
Commands.EDIT_CUT, Commands.EDIT_COPY, Commands.EDIT_PASTE],
_reservedShortcuts = ["Ctrl-Z", "Ctrl-Y", "Ctrl-A", "Ctrl-X", "Ctrl-C", "Ctrl-V"],
_macReservedShortcuts = ["Cmd-,", "Cmd-H", "Cmd-Alt-H", "Cmd-M", "Cmd-Shift-Z", "Cmd-Q"],
_keyNames = ["Up", "Down", "Left", "Right", "Backspace", "Enter", "Space", "Tab",
"PageUp", "PageDown", "Home", "End", "Insert", "Delete"];
function _addBinding(commandID, keyBinding, platform, userBindings) {
var key,
result = null,
normalized,
normalizedDisplay,
explicitPlatform = keyBinding.platform || platform,
targetPlatform,
command,
bindingsToDelete = [],
existing;
// For platform: "all", use explicit current platform
if (explicitPlatform && explicitPlatform !== "all") {
targetPlatform = explicitPlatform;
} else {
targetPlatform = brackets.platform;
}
// Skip if the key binding is not for this platform.
if (explicitPlatform === "mac" && brackets.platform !== "mac") {
return null;
}
// if the request does not specify an explicit platform, and we're
// currently on a mac, then replace Ctrl with Cmd.
key = (keyBinding.key) || keyBinding;
if (brackets.platform === "mac" && (explicitPlatform === undefined || explicitPlatform === "all")) {
key = key.replace("Ctrl", "Cmd");
if (keyBinding.displayKey !== undefined) {
keyBinding.displayKey = keyBinding.displayKey.replace("Ctrl", "Cmd");
}
}
normalized = normalizeKeyDescriptorString(key);
// skip if the key binding is invalid
if (!normalized) {
console.error("Unable to parse key binding " + key + ". Permitted modifiers: Ctrl, Cmd, Alt, Opt, Shift; separated by '-' (not '+').");
return null;
}
// check for duplicate key bindings
existing = _keyMap[normalized];
// for cross-platform compatibility
if (exports.useWindowsCompatibleBindings) {
// windows-only key bindings are used as the default binding
// only if a default binding wasn't already defined
if (explicitPlatform === "win") {
// search for a generic or platform-specific binding if it
// already exists
if (existing && (!existing.explicitPlatform ||
existing.explicitPlatform === brackets.platform ||
existing.explicitPlatform === "all")) {
// do not clobber existing binding with windows-only binding
return null;
}
// target this windows binding for the current platform
targetPlatform = brackets.platform;
}
}
// skip if this binding doesn't match the current platform
if (targetPlatform !== brackets.platform) {
return null;
}
// skip if the key is already assigned
if (existing) {
if (!existing.explicitPlatform && explicitPlatform) {
// remove the the generic binding to replace with this new platform-specific binding
removeBinding(normalized);
existing = false;
}
}
// delete existing bindings when
// (1) replacing a windows-compatible binding with a generic or
// platform-specific binding
// (2) replacing a generic binding with a platform-specific binding
var existingBindings = _commandMap[commandID] || [],
isWindowsCompatible,
isReplaceGeneric,
ignoreGeneric;
existingBindings.forEach(function (binding) {
// remove windows-only bindings in _commandMap
isWindowsCompatible = exports.useWindowsCompatibleBindings &&
binding.explicitPlatform === "win";
// remove existing generic binding
isReplaceGeneric = !binding.explicitPlatform &&
explicitPlatform;
if (isWindowsCompatible || isReplaceGeneric) {
bindingsToDelete.push(binding);
} else {
// existing binding is platform-specific and the requested binding is generic
ignoreGeneric = binding.explicitPlatform && !explicitPlatform;
}
});
if (ignoreGeneric) {
// explicit command binding overrides this one
return null;
}
if (existing) {
// do not re-assign a key binding
console.error("Cannot assign " + normalized + " to " + commandID + ". It is already assigned to " + _keyMap[normalized].commandID);
return null;
}
// remove generic or windows-compatible bindings
bindingsToDelete.forEach(function (binding) {
removeBinding(binding.key);
});
// optional display-friendly string (e.g. CMD-+ instead of CMD-=)
normalizedDisplay = (keyBinding.displayKey) ? normalizeKeyDescriptorString(keyBinding.displayKey) : normalized;
// 1-to-many commandID mapping to key binding
if (!_commandMap[commandID]) {
_commandMap[commandID] = [];
}
result = {
key : normalized,
displayKey : normalizedDisplay,
explicitPlatform : explicitPlatform
};
_commandMap[commandID].push(result);
// 1-to-1 key binding to commandID
_keyMap[normalized] = {
commandID : commandID,
key : normalized,
displayKey : normalizedDisplay,
explicitPlatform : explicitPlatform
};
if (!userBindings) {
_updateCommandAndKeyMaps(_keyMap[normalized]);
}
// notify listeners
command = CommandManager.get(commandID);
if (command) {
command.trigger("keyBindingAdded", result);
}
return result;
}
function _applyUserKeyBindings() {
var remappedCommands = [],
remappedKeys = [],
restrictedCommands = [],
restrictedKeys = [],
invalidKeys = [],
invalidCommands = [],
multipleKeys = [],
duplicateBindings = [],
errorMessage = "";
_.forEach(_customKeyMap, function (commandID, key) {
var normalizedKey = normalizeKeyDescriptorString(key),
existingBindings = _commandMap[commandID] || [];
// Skip this since we don't allow user to update key binding of a special
// command like cut, copy, paste, undo, redo and select all.
if (_isSpecialCommand(commandID)) {
restrictedCommands.push(commandID);
return;
}
// Skip this since we don't allow user to update a shortcut used in
// a special command or any Mac system command.
if (_isReservedShortcuts(normalizedKey)) {
restrictedKeys.push(key);
return;
}
// Skip this if the key is invalid.
if (!normalizedKey) {
invalidKeys.push(key);
return;
}
if (_isKeyAssigned(normalizedKey)) {
if (remappedKeys.indexOf(normalizedKey) !== -1) {
// JSON parser already removed all the duplicates that have the exact
// same case or order in their keys. So we're only detecting duplicate
// bindings that have different orders or different cases used in the key.
duplicateBindings.push(key);
return;
}
// The same key binding already exists, so skip this.
if (_keyMap[normalizedKey].commandID === commandID) {
// Still need to add it to the remappedCommands so that
// we can detect any duplicate later on.
remappedCommands.push(commandID);
return;
}
removeBinding(normalizedKey);
}
if (remappedKeys.indexOf(normalizedKey) === -1) {
remappedKeys.push(normalizedKey);
}
// Remove another key binding if the new key binding is for a command
// that has a different key binding. e.g. "Ctrl-W": "edit.selectLine"
// requires us to remove "Ctrl-W" from "file.close" command, but we
// also need to remove "Ctrl-L" from "edit.selectLine".
if (existingBindings.length) {
existingBindings.forEach(function (binding) {
removeBinding(binding.key);
});
}
if (commandID) {
if (_allCommands.indexOf(commandID) !== -1) {
if (remappedCommands.indexOf(commandID) === -1) {
var keybinding = { key: normalizedKey };
keybinding.displayKey = _getDisplayKey(normalizedKey);
_addBinding(commandID, keybinding.displayKey ? keybinding : normalizedKey, brackets.platform, true);
remappedCommands.push(commandID);
} else {
multipleKeys.push(commandID);
}
} else {
invalidCommands.push(commandID);
}
}
});
if (restrictedCommands.length) {
errorMessage = StringUtils.format(Strings.ERROR_RESTRICTED_COMMANDS, _getBulletList(restrictedCommands));
}
if (restrictedKeys.length) {
errorMessage += StringUtils.format(Strings.ERROR_RESTRICTED_SHORTCUTS, _getBulletList(restrictedKeys));
}
if (multipleKeys.length) {
errorMessage += StringUtils.format(Strings.ERROR_MULTIPLE_SHORTCUTS, _getBulletList(multipleKeys));
}
if (duplicateBindings.length) {
errorMessage += StringUtils.format(Strings.ERROR_DUPLICATE_SHORTCUTS, _getBulletList(duplicateBindings));
}
if (invalidKeys.length) {
errorMessage += StringUtils.format(Strings.ERROR_INVALID_SHORTCUTS, _getBulletList(invalidKeys));
}
if (invalidCommands.length) {
errorMessage += StringUtils.format(Strings.ERROR_NONEXISTENT_COMMANDS, _getBulletList(invalidCommands));
}
if (_showErrors && errorMessage) {
_showErrorsAndOpenKeyMap("", errorMessage);
}
}
function _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key) {
if (!key) {
console.log("KeyBindingManager _buildKeyDescriptor() - No key provided!");
return "";
}
var keyDescriptor = [];
if (hasMacCtrl) {
keyDescriptor.push("Ctrl");
}
if (hasAlt) {
keyDescriptor.push("Alt");
}
if (hasShift) {
keyDescriptor.push("Shift");
}
if (hasCtrl) {
// Windows display Ctrl first, Mac displays Command symbol last
if (brackets.platform === "mac") {
keyDescriptor.push("Cmd");
} else {
keyDescriptor.unshift("Ctrl");
}
}
keyDescriptor.push(key);
return keyDescriptor.join("-");
}
function _detectAltGrKeyDown(e) {
if (brackets.platform !== "win") {
return;
}
if (!_altGrDown) {
if (_ctrlDown !== CtrlDownStates.DETECTED_AND_IGNORED && e.ctrlKey && e.keyIdentifier === "Control") {
_ctrlDown = CtrlDownStates.DETECTED;
} else if (e.repeat && e.ctrlKey && e.keyIdentifier === "Control") {
// We get here if the user is holding down left/right Control key. Set it to false
// so that we don't misidentify the combination of Ctrl and Alt keys as AltGr key.
_ctrlDown = CtrlDownStates.DETECTED_AND_IGNORED;
} else if (_ctrlDown === CtrlDownStates.DETECTED && e.altKey && e.ctrlKey && e.keyIdentifier === "Alt" &&
(e.timeStamp - _lastTimeStamp) < MAX_INTERVAL_FOR_CTRL_ALT_KEYS) {
_altGrDown = true;
_lastKeyIdentifier = "Alt";
_enabled = false;
$(window).on("keyup", _onCtrlUp);
} else {
// Reset _ctrlDown so that we can start over in detecting the two key events
// required for AltGr key.
_ctrlDown = CtrlDownStates.NOT_YET_DETECTED;
}
_lastTimeStamp = e.timeStamp;
} else if (e.keyIdentifier === "Control" || e.keyIdentifier === "Alt") {
// If the user is NOT holding down AltGr key or is also pressing Ctrl key,
// then _lastKeyIdentifier will be the same as keyIdentifier in the current
// key event. So we need to quit AltGr mode to re-enable KBM.
if (e.altKey && e.ctrlKey && e.keyIdentifier === _lastKeyIdentifier) {
_quitAltGrMode();
} else {
_lastKeyIdentifier = e.keyIdentifier;
}
}
}
function _getBulletList(list) {
var message = "<ul class='dialog-list'>";
list.forEach(function (info) {
message += "<li>" + info + "</li>";
});
message += "</ul>";
return message;
}
function _getDisplayKey(key) {
var displayKey = "",
match = key ? key.match(/(Up|Down|Left|Right|\-)$/i) : null;
if (match && !/Page(Up|Down)/.test(key)) {
displayKey = key.substr(0, match.index) + _displayKeyMap[match[0].toLowerCase()];
}
return displayKey;
}
function _getUserKeyMapFilePath() {
if (window.isBracketsTestWindow) {
return brackets.app.getApplicationSupportDirectory() + "/_test_/" + KEYMAP_FILENAME;
}
return _userKeyMapFilePath;
}
Adds default key bindings when commands are registered to CommandManager
function _handleCommandRegistered(event, command) {
var commandId = command.getID(),
defaults = KeyboardPrefs[commandId];
if (defaults) {
addBinding(commandId, defaults);
}
}
Process the keybinding for the current key.
function _handleKey(key) {
if (_enabled && _keyMap[key]) {
// The execute() function returns a promise because some commands are async.
// Generally, commands decide whether they can run or not synchronously,
// and reject immediately, so we can test for that synchronously.
var promise = CommandManager.execute(_keyMap[key].commandID);
return (promise.state() !== "rejected");
}
return false;
}
Handles a given keydown event, checking global hooks first before deciding to handle it ourselves.
function _handleKeyEvent(event) {
var i, handled = false;
for (i = _globalKeydownHooks.length - 1; i >= 0; i--) {
if (_globalKeydownHooks[i](event)) {
handled = true;
break;
}
}
_detectAltGrKeyDown(event);
if (!handled && _handleKey(_translateKeyboardEvent(event))) {
event.stopPropagation();
event.preventDefault();
}
}
AppInit.htmlReady(function () {
// Install keydown event listener.
window.document.body.addEventListener(
"keydown",
_handleKeyEvent,
true
);
exports.useWindowsCompatibleBindings = (brackets.platform !== "mac") &&
(brackets.platform !== "win");
});
function _initCommandAndKeyMaps() {
_allCommands = CommandManager.getAll();
// Keep a copy of the default key bindings before loading user key bindings.
_defaultKeyMap = _.cloneDeep(_keyMap);
}
function _isKeyAssigned(key) {
return (_keyMap[key] !== undefined);
}
function _isReservedShortcuts(normalizedKey) {
if (!normalizedKey) {
return false;
}
if (_reservedShortcuts.indexOf(normalizedKey) > -1 ||
_reservedShortcuts.indexOf(normalizedKey.replace("Cmd", "Ctrl")) > -1) {
return true;
}
if (brackets.platform === "mac" && _macReservedShortcuts.indexOf(normalizedKey) > -1) {
return true;
}
return false;
}
function _isSpecialCommand(commandID) {
if (brackets.platform === "mac" && commandID === "file.quit") {
return true;
}
return (_specialCommands.indexOf(commandID) > -1);
}
function _mapKeycodeToKey(keycode, key) {
// If keycode represents one of the digit keys (0-9), then return the corresponding digit
// by subtracting KeyEvent.DOM_VK_0 from keycode. ie. [48-57] --> [0-9]
if (keycode >= KeyEvent.DOM_VK_0 && keycode <= KeyEvent.DOM_VK_9) {
return String(keycode - KeyEvent.DOM_VK_0);
// Do the same with the numpad numbers
// by subtracting KeyEvent.DOM_VK_NUMPAD0 from keycode. ie. [96-105] --> [0-9]
} else if (keycode >= KeyEvent.DOM_VK_NUMPAD0 && keycode <= KeyEvent.DOM_VK_NUMPAD9) {
return String(keycode - KeyEvent.DOM_VK_NUMPAD0);
}
switch (keycode) {
case KeyEvent.DOM_VK_SEMICOLON:
return ";";
case KeyEvent.DOM_VK_EQUALS:
return "=";
case KeyEvent.DOM_VK_COMMA:
return ",";
case KeyEvent.DOM_VK_SUBTRACT:
case KeyEvent.DOM_VK_DASH:
return "-";
case KeyEvent.DOM_VK_ADD:
return "+";
case KeyEvent.DOM_VK_DECIMAL:
case KeyEvent.DOM_VK_PERIOD:
return ".";
case KeyEvent.DOM_VK_DIVIDE:
case KeyEvent.DOM_VK_SLASH:
return "/";
case KeyEvent.DOM_VK_BACK_QUOTE:
return "`";
case KeyEvent.DOM_VK_OPEN_BRACKET:
return "[";
case KeyEvent.DOM_VK_BACK_SLASH:
return "\\";
case KeyEvent.DOM_VK_CLOSE_BRACKET:
return "]";
case KeyEvent.DOM_VK_QUOTE:
return "'";
default:
return key;
}
}
function _openUserKeyMap() {
var userKeyMapPath = _getUserKeyMapFilePath(),
file = FileSystem.getFileForPath(userKeyMapPath);
file.exists(function (err, doesExist) {
if (doesExist) {
CommandManager.execute(Commands.FILE_OPEN, { fullPath: userKeyMapPath });
} else {
var defaultContent = "{\n \"documentation\": \"https://github.com/adobe/brackets/wiki/User-Key-Bindings\"," +
"\n \"overrides\": {" +
"\n \n }\n}\n";
FileUtils.writeText(file, defaultContent, true)
.done(function () {
CommandManager.execute(Commands.FILE_OPEN, { fullPath: userKeyMapPath });
});
}
});
}
// Due to circular dependencies, not safe to call on() directly
EventDispatcher.on_duringInit(CommandManager, "commandRegistered", _handleCommandRegistered);
CommandManager.register(Strings.CMD_OPEN_KEYMAP, Commands.FILE_OPEN_KEYMAP, _openUserKeyMap);
// Asynchronously loading DocumentManager to avoid the circular dependency
require(["document/DocumentManager"], function (DocumentManager) {
DocumentManager.on("documentSaved", function checkKeyMapUpdates(e, doc) {
if (doc && doc.file.fullPath === _userKeyMapFilePath) {
_loadUserKeyMap();
}
});
});
function _quitAltGrMode() {
_enabled = true;
_ctrlDown = CtrlDownStates.NOT_YET_DETECTED;
_altGrDown = false;
_lastTimeStamp = null;
_lastKeyIdentifier = null;
$(window).off("keyup", _onCtrlUp);
}
function _readUserKeyMap() {
var file = FileSystem.getFileForPath(_getUserKeyMapFilePath()),
result = new $.Deferred();
file.exists(function (err, doesExist) {
if (doesExist) {
FileUtils.readAsText(file)
.done(function (text) {
var keyMap = {};
try {
if (text) {
var json = JSON.parse(text);
// If no overrides, return an empty key map.
result.resolve((json && json.overrides) || keyMap);
} else {
// The file is empty, so return an empty key map.
result.resolve(keyMap);
}
} catch (err) {
// Cannot parse the text read from the key map file.
result.reject(err);
}
})
.fail(function (err) {
// Key map file cannot be loaded.
result.reject(err);
});
} else {
// Just resolve if no user key map file
result.resolve();
}
});
return result.promise();
}
function _reset() {
_keyMap = {};
_defaultKeyMap = {};
_customKeyMap = {};
_customKeyMapCache = {};
_commandMap = {};
_globalKeydownHooks = [];
_userKeyMapFilePath = brackets.app.getApplicationSupportDirectory() + "/" + KEYMAP_FILENAME;
}
function _setUserKeyMapFilePath(fullPath) {
_userKeyMapFilePath = fullPath;
}
AppInit.extensionsLoaded(function () {
var params = new UrlParams();
params.parse();
if (params.get("reloadWithoutUserExts") === "true") {
_showErrors = false;
}
_initCommandAndKeyMaps();
_loadUserKeyMap();
});
// unit test only
exports._reset = _reset;
exports._setUserKeyMapFilePath = _setUserKeyMapFilePath;
exports._getDisplayKey = _getDisplayKey;
exports._loadUserKeyMap = _loadUserKeyMap;
exports._initCommandAndKeyMaps = _initCommandAndKeyMaps;
exports._onCtrlUp = _onCtrlUp;
// Define public API
exports.getKeymap = getKeymap;
exports.addBinding = addBinding;
exports.removeBinding = removeBinding;
exports.formatKeyDescriptor = formatKeyDescriptor;
exports.getKeyBindings = getKeyBindings;
exports.addGlobalKeydownHook = addGlobalKeydownHook;
exports.removeGlobalKeydownHook = removeGlobalKeydownHook;
function _showErrorsAndOpenKeyMap(err, message) {
// Asynchronously loading Dialogs module to avoid the circular dependency
require(["widgets/Dialogs"], function (Dialogs) {
var errorMessage = Strings.ERROR_KEYMAP_CORRUPT;
if (err === FileSystemError.UNSUPPORTED_ENCODING) {
errorMessage = Strings.ERROR_LOADING_KEYMAP;
} else if (message) {
errorMessage = message;
}
Dialogs.showModalDialog(
DefaultDialogs.DIALOG_ID_ERROR,
Strings.ERROR_KEYMAP_TITLE,
errorMessage
)
.done(function () {
if (err !== FileSystemError.UNSUPPORTED_ENCODING) {
CommandManager.execute(Commands.FILE_OPEN_KEYMAP);
}
});
});
}
function _sortByPlatform(a, b) {
var a1 = (a.platform) ? 1 : 0,
b1 = (b.platform) ? 1 : 0;
return b1 - a1;
}
Takes a keyboard event and translates it into a key in a key map
function _translateKeyboardEvent(event) {
var hasMacCtrl = (brackets.platform === "mac") ? (event.ctrlKey) : false,
hasCtrl = (brackets.platform !== "mac") ? (event.ctrlKey) : (event.metaKey),
hasAlt = (event.altKey),
hasShift = (event.shiftKey),
key = String.fromCharCode(event.keyCode);
//From the W3C, if we can get the KeyboardEvent.keyIdentifier then look here
//As that will let us use keys like then function keys "F5" for commands. The
//full set of values we can use is here
//http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
var ident = event.keyIdentifier;
if (ident) {
if (ident.charAt(0) === "U" && ident.charAt(1) === "+") {
//This is a unicode code point like "U+002A", get the 002A and use that
key = String.fromCharCode(parseInt(ident.substring(2), 16));
} else {
//This is some non-character key, just use the raw identifier
key = ident;
}
}
// Translate some keys to their common names
if (key === "\t") {
key = "Tab";
} else if (key === " ") {
key = "Space";
} else if (key === "\b") {
key = "Backspace";
} else if (key === "Help") {
key = "Insert";
} else if (event.keyCode === KeyEvent.DOM_VK_DELETE) {
key = "Delete";
} else {
key = _mapKeycodeToKey(event.keyCode, key);
}
return _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key);
}
function _undoPriorUserKeyBindings() {
_.forEach(_customKeyMapCache, function (commandID, key) {
var normalizedKey = normalizeKeyDescriptorString(key),
defaults = _.find(_.toArray(_defaultKeyMap), { "commandID": commandID }),
defaultCommand = _defaultKeyMap[normalizedKey];
// We didn't modified this before, so skip it.
if (_isSpecialCommand(commandID) ||
_isReservedShortcuts(normalizedKey)) {
return;
}
if (_isKeyAssigned(normalizedKey) &&
_customKeyMap[key] !== commandID && _customKeyMap[normalizedKey] !== commandID) {
// Unassign the key from any command. e.g. "Cmd-W": "file.open" in _customKeyMapCache
// will require us to remove Cmd-W shortcut from file.open command.
removeBinding(normalizedKey);
}
// Reassign the default key binding. e.g. "Cmd-W": "file.open" in _customKeyMapCache
// will require us to reassign Cmd-O shortcut to file.open command.
if (defaults) {
addBinding(commandID, defaults, brackets.platform);
}
// Reassign the default key binding of the previously modified command.
// e.g. "Cmd-W": "file.open" in _customKeyMapCache will require us to reassign Cmd-W
// shortcut to file.close command.
if (defaultCommand && defaultCommand.key) {
addBinding(defaultCommand.commandID, defaultCommand.key, brackets.platform);
}
});
}
function _updateCommandAndKeyMaps(newBinding) {
if (_allCommands.length === 0) {
return;
}
if (newBinding && newBinding.commandID && _allCommands.indexOf(newBinding.commandID) === -1) {
_defaultKeyMap[newBinding.commandID] = _.cloneDeep(newBinding);
// Process user key map again to catch any reassignment to all new key bindings added from extensions.
_loadUserKeyMap();
}
}
Add one or more key bindings to a particular Command.
function addBinding(command, keyBindings, platform) {
var commandID = "",
results;
if (!command) {
console.error("addBinding(): missing required parameter: command");
return;
}
if (!keyBindings) { return; }
if (typeof (command) === "string") {
commandID = command;
} else {
commandID = command.getID();
}
if (Array.isArray(keyBindings)) {
var keyBinding;
results = [];
// process platform-specific bindings first
keyBindings.sort(_sortByPlatform);
keyBindings.forEach(function addSingleBinding(keyBindingRequest) {
// attempt to add keybinding
keyBinding = _addBinding(commandID, keyBindingRequest, keyBindingRequest.platform);
if (keyBinding) {
results.push(keyBinding);
}
});
} else {
results = _addBinding(commandID, keyBindings, platform);
}
return results;
}
Adds a global keydown hook that gets first crack at keydown events before standard keybindings do. This is intended for use by modal or semi-modal UI elements like dialogs or the code hint list that should execute before normal command bindings are run.
The hook is passed one parameter, the original keyboard event. If the hook handles the event (or wants to block other global hooks from handling the event), it should return true. Note that this will only stop other global hooks and KeyBindingManager from handling the event; to prevent further event propagation, you will need to call stopPropagation(), stopImmediatePropagation(), and/or preventDefault() as usual.
Multiple keydown hooks can be registered, and are executed in order, most-recently-added first.
(We have to have a special API for this because (1) handlers are normally called in least-recently-added order, and we want most-recently-added; (2) native DOM events don't have a way for us to find out if stopImmediatePropagation()/stopPropagation() has been called on the event, so we have to have some other way for one of the hooks to indicate that it wants to block the other hooks from running.)
function addGlobalKeydownHook(hook) {
_globalKeydownHooks.push(hook);
}
Convert normalized key representation to display appropriate for platform.
function formatKeyDescriptor(descriptor) {
var displayStr;
if (brackets.platform === "mac") {
displayStr = descriptor.replace(/-(?!$)/g, ""); // remove dashes
displayStr = displayStr.replace("Ctrl", "\u2303"); // Ctrl > control symbol
displayStr = displayStr.replace("Cmd", "\u2318"); // Cmd > command symbol
displayStr = displayStr.replace("Shift", "\u21E7"); // Shift > shift symbol
displayStr = displayStr.replace("Alt", "\u2325"); // Alt > option symbol
} else {
displayStr = descriptor.replace("Ctrl", Strings.KEYBOARD_CTRL);
displayStr = displayStr.replace("Shift", Strings.KEYBOARD_SHIFT);
displayStr = displayStr.replace(/-(?!$)/g, "+");
}
displayStr = displayStr.replace("Space", Strings.KEYBOARD_SPACE);
displayStr = displayStr.replace("PageUp", Strings.KEYBOARD_PAGE_UP);
displayStr = displayStr.replace("PageDown", Strings.KEYBOARD_PAGE_DOWN);
displayStr = displayStr.replace("Home", Strings.KEYBOARD_HOME);
displayStr = displayStr.replace("End", Strings.KEYBOARD_END);
displayStr = displayStr.replace("Ins", Strings.KEYBOARD_INSERT);
displayStr = displayStr.replace("Del", Strings.KEYBOARD_DELETE);
return displayStr;
}
Retrieve key bindings currently associated with a command
function getKeyBindings(command) {
var bindings = [],
commandID = "";
if (!command) {
console.error("getKeyBindings(): missing required parameter: command");
return [];
}
if (typeof (command) === "string") {
commandID = command;
} else {
commandID = command.getID();
}
bindings = _commandMap[commandID];
return bindings || [];
}
Returns a copy of the current key map. If the optional 'defaults' parameter is true, then a copy of the default key map is returned.
function getKeymap(defaults) {
return $.extend({}, defaults ? _defaultKeyMap : _keyMap);
}
normalizes the incoming key descriptor so the modifier keys are always specified in the correct order
function normalizeKeyDescriptorString(origDescriptor) {
var hasMacCtrl = false,
hasCtrl = false,
hasAlt = false,
hasShift = false,
key = "",
error = false;
function _compareModifierString(left, right) {
if (!left || !right) {
return false;
}
left = left.trim().toLowerCase();
right = right.trim().toLowerCase();
return (left.length > 0 && left === right);
}
origDescriptor.split("-").forEach(function parseDescriptor(ele, i, arr) {
if (_compareModifierString("ctrl", ele)) {
if (brackets.platform === "mac") {
hasMacCtrl = true;
} else {
hasCtrl = true;
}
} else if (_compareModifierString("cmd", ele)) {
if (brackets.platform === "mac") {
hasCtrl = true;
} else {
error = true;
}
} else if (_compareModifierString("alt", ele)) {
hasAlt = true;
} else if (_compareModifierString("opt", ele)) {
if (brackets.platform === "mac") {
hasAlt = true;
} else {
error = true;
}
} else if (_compareModifierString("shift", ele)) {
hasShift = true;
} else if (key.length > 0) {
console.log("KeyBindingManager normalizeKeyDescriptorString() - Multiple keys defined. Using key: " + key + " from: " + origDescriptor);
error = true;
} else {
key = ele;
}
});
if (error) {
return null;
}
// Check to see if the binding is for "-".
if (key === "" && origDescriptor.search(/^.+--$/) !== -1) {
key = "-";
}
// '+' char is valid if it's the only key. Keyboard shortcut strings should use
// unicode characters (unescaped). Keyboard shortcut display strings may use
// unicode escape sequences (e.g. \u20AC euro sign)
if ((key.indexOf("+")) >= 0 && (key.length > 1)) {
return null;
}
// Ensure that the first letter of the key name is in upper case and the rest are
// in lower case. i.e. 'a' => 'A' and 'up' => 'Up'
if (/^[a-z]/i.test(key)) {
key = _.capitalize(key.toLowerCase());
}
// Also make sure that the second word of PageUp/PageDown has the first letter in upper case.
if (/^Page/.test(key)) {
key = key.replace(/(up|down)$/, function (match, p1) {
return _.capitalize(p1);
});
}
// No restriction on single character key yet, but other key names are restricted to either
// Function keys or those listed in _keyNames array.
if (key.length > 1 && !/F\d+/.test(key) &&
_keyNames.indexOf(key) === -1) {
return null;
}
return _buildKeyDescriptor(hasMacCtrl, hasCtrl, hasAlt, hasShift, key);
}
Remove a key binding from _keymap
function removeBinding(key, platform) {
if (!key || ((platform !== null) && (platform !== undefined) && (platform !== brackets.platform))) {
return;
}
var normalizedKey = normalizeKeyDescriptorString(key);
if (!normalizedKey) {
console.log("Failed to normalize " + key);
} else if (_isKeyAssigned(normalizedKey)) {
var binding = _keyMap[normalizedKey],
command = CommandManager.get(binding.commandID),
bindings = _commandMap[binding.commandID];
// delete key binding record
delete _keyMap[normalizedKey];
if (bindings) {
// delete mapping from command to key binding
_commandMap[binding.commandID] = bindings.filter(function (b) {
return (b.key !== normalizedKey);
});
if (command) {
command.trigger("keyBindingRemoved", {key: normalizedKey, displayKey: binding.displayKey});
}
}
}
}
Removes a global keydown hook added by addGlobalKeydownHook
.
Does not need to be the most recently added hook.
function removeGlobalKeydownHook(hook) {
var index = _globalKeydownHooks.indexOf(hook);
if (index !== -1) {
_globalKeydownHooks.splice(index, 1);
}
}