diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json index 5208e0ef756..b4af482bcc2 100644 --- a/src/base-config/keyboard.json +++ b/src/base-config/keyboard.json @@ -31,8 +31,7 @@ ], "edit.selectLine": [ { - "key": "Ctrl-L", - "platform": "win" + "key": "Ctrl-L" }, { "key": "Ctrl-L", @@ -47,8 +46,7 @@ ], "edit.findNext": [ { - "key": "F3", - "platform": "win" + "key": "F3" }, { "key": "Cmd-G", @@ -57,8 +55,7 @@ ], "edit.findPrevious": [ { - "key": "Shift-F3", - "platform": "win" + "key": "Shift-F3" }, { "key": "Cmd-Shift-G", @@ -67,8 +64,7 @@ ], "edit.replace": [ { - "key": "Ctrl-H", - "platform": "win" + "key": "Ctrl-H" }, { "key": "Cmd-Alt-F", @@ -96,8 +92,7 @@ "edit.lineUp": [ { "key": "Ctrl-Shift-Up", - "displayKey": "Ctrl-Shift-↑", - "platform": "win" + "displayKey": "Ctrl-Shift-↑" }, { "key": "Cmd-Ctrl-Up", @@ -108,8 +103,7 @@ "edit.lineDown": [ { "key": "Ctrl-Shift-Down", - "displayKey": "Ctrl-Shift-↓", - "platform": "win" + "displayKey": "Ctrl-Shift-↓" }, { "key": "Cmd-Ctrl-Down", @@ -150,8 +144,7 @@ ], "navigate.gotoLine": [ { - "key": "Ctrl-G", - "platform": "win" + "key": "Ctrl-G" }, { "key": "Cmd-L", @@ -163,8 +156,7 @@ ], "navigate.nextDoc": [ { - "key": "Ctrl-Tab", - "platform": "win" + "key": "Ctrl-Tab" }, { "key": "Ctrl-Tab", @@ -173,8 +165,7 @@ ], "navigate.prevDoc": [ { - "key": "Ctrl-Shift-Tab", - "platform": "win" + "key": "Ctrl-Shift-Tab" }, { "key": "Ctrl-Shift-Tab", @@ -198,8 +189,7 @@ ], "debug.showDeveloperTools": [ { - "key": "F12", - "platform": "win" + "key": "F12" }, { "key": "Cmd-Opt-I", @@ -208,8 +198,7 @@ ], "debug.refreshWindow": [ { - "key": "F5", - "platform": "win" + "key": "F5" }, { "key": "Cmd-R", diff --git a/src/command/KeyBindingManager.js b/src/command/KeyBindingManager.js index 0ba8655c8fb..63ba05b4c6d 100644 --- a/src/command/KeyBindingManager.js +++ b/src/command/KeyBindingManager.js @@ -232,8 +232,8 @@ define(function (require, exports, module) { * Takes a keyboard event and translates it into a key in a key map */ function _translateKeyboardEvent(event) { - var hasMacCtrl = (brackets.platform === "win") ? false : (event.ctrlKey), - hasCtrl = (brackets.platform === "win") ? (event.ctrlKey) : (event.metaKey), + 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); @@ -298,13 +298,51 @@ define(function (require, exports, module) { return (_keyMap[key] !== undefined); } + /** + * Remove a key binding from _keymap + * + * @param {!string} key - a key-description string that may or may not be normalized. + * @param {?string} platform - OS from which to remove the binding (all platforms if unspecified) + */ + function removeBinding(key, platform) { + if (!key || ((platform !== null) && (platform !== undefined) && (platform !== brackets.platform))) { + return; + } + + var normalizedKey = normalizeKeyDescriptorString(key); + + if (!normalizedKey) { + console.log("Fail to nomalize " + 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).triggerHandler("keyBindingRemoved", [{key: normalizedKey, displayKey: binding.displayKey}]); + } + } + } + } + /** * @private * * @param {string} commandID * @param {string|{{key: string, displayKey: string}}} keyBinding - a single shortcut. * @param {?string} platform - undefined indicates all platforms - * @return {?{key: string, displayKey:String}} Returns a record for valid key bindings + * @return {?{key: string, displayKey:String}} Returns a record for valid key bindings. + * Returns null when key binding platform does not match, binding does not normalize, + * or is already assigned. */ function _addBinding(commandID, keyBinding, platform) { var key, @@ -315,11 +353,6 @@ define(function (require, exports, module) { targetPlatform = explicitPlatform || brackets.platform, command; - // skip if this binding doesn't match the current platform - if (targetPlatform !== brackets.platform) { - return null; - } - key = (keyBinding.key) || keyBinding; if (brackets.platform === "mac" && explicitPlatform === undefined) { key = key.replace("Ctrl", "Cmd"); @@ -342,6 +375,50 @@ define(function (require, exports, module) { return null; } + // for cross-platform compatibility + if (brackets.platform !== "mac" && + brackets.platform !== "win") { + if (explicitPlatform === "win") { + // windows-only key bindings are used as the default binding + // only if a default binding wasn't already defined + var existing = _keyMap[normalized]; + + // search for a generic or platform-specific binding if it + // already exists + if (existing && + (!existing.explicitPlatform || existing.explicitPlatform === brackets.platform)) { + // do not clobber existing binding with windows-only binding + return null; + } + + // target this windows binding for the current platform + targetPlatform = brackets.platform; + } else if (!explicitPlatform || (explicitPlatform === brackets.platform)) { + // if adding a generic binding or a binding for the current + // platform, clobber any windows bindings that may have been + // installed + var existingBindings = _commandMap[commandID] || [], + bindingsToDelete = []; + + // filter out windows-only bindings in _commandMap + existingBindings.forEach(function (binding) { + if (binding.explicitPlatform === "win") { + bindingsToDelete.push(binding); + } + }); + + // delete windows-only bindings in _keyMap + bindingsToDelete.forEach(function (binding) { + removeBinding(binding.key); + }); + } + } + + // skip if this binding doesn't match the current platform + if (targetPlatform !== brackets.platform) { + return null; + } + // optional display-friendly string (e.g. CMD-+ instead of CMD-=) normalizedDisplay = (keyBinding.displayKey) ? normalizeKeyDescriptorString(keyBinding.displayKey) : normalized; @@ -350,11 +427,21 @@ define(function (require, exports, module) { _commandMap[commandID] = []; } - result = {key: normalized, displayKey: normalizedDisplay}; + 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}; + _keyMap[normalized] = { + commandID : commandID, + key : normalized, + displayKey : normalizedDisplay, + explicitPlatform : explicitPlatform + }; // notify listeners command = CommandManager.get(commandID); @@ -404,12 +491,16 @@ define(function (require, exports, module) { * Add one or more key bindings to a particular Command. * * @param {!string} commandID - * @param {?({key: string, displayKey: string} | Array.<{key: string, displayKey: string, platform: string}>)} keyBindings - a single key binding - * or an array of keybindings. Example: "Shift-Cmd-F". Mac and Win key equivalents are automatically - * mapped to each other. Use displayKey property to display a different string (e.g. "CMD+" instead of "CMD="). - * @param {?string} platform - the target OS of the keyBindings either "mac" or "win". If undefined, all platforms will use - * the key binding. Ignored if keyBindings is passed an Array. - * @return {{key: string, displayKey:String}|Array.<{key: string, displayKey:String}>} Returns record(s) for valid key binding(s) + * @param {?({key: string, displayKey: string} | Array.<{key: string, displayKey: string, platform: string}>)} keyBindings + * a single key binding or an array of keybindings. Example: + * "Shift-Cmd-F". Mac and Win key equivalents are automatically + * mapped to each other. Use displayKey property to display a different + * string (e.g. "CMD+" instead of "CMD="). + * @param {?string} platform - the target OS of the keyBindings either + * "mac", "win" or "linux". If undefined, all platforms not explicitly + * defined will use the key binding. + * @return {{key: string, displayKey:String}|Array.<{key: string, displayKey:String}>} + * Returns record(s) for valid key binding(s) */ function addBinding(commandID, keyBindings, platform) { if ((commandID === null) || (commandID === undefined) || !keyBindings) { @@ -424,7 +515,8 @@ define(function (require, exports, module) { var keyBinding; results = []; - keyBindings.forEach(function (keyBindingRequest) { + keyBindings.forEach(function addSingleBinding(keyBindingRequest) { + // attempt to add keybinding keyBinding = _addBinding(commandID, keyBindingRequest, keyBindingRequest.platform); if (keyBinding) { @@ -437,42 +529,6 @@ define(function (require, exports, module) { return results; } - - /** - * Remove a key binding from _keymap - * - * @param {!string} key - a key-description string that may or may not be normalized. - * @param {?string} platform - OS from which to remove the binding (all platforms if unspecified) - */ - function removeBinding(key, platform) { - if (!key || ((platform !== null) && (platform !== undefined) && (platform !== brackets.platform))) { - return; - } - - var normalizedKey = normalizeKeyDescriptorString(key); - - if (!normalizedKey) { - console.log("Fail to nomalize " + 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).triggerHandler("keyBindingRemoved", [{key: normalizedKey, displayKey: binding.displayKey}]); - } - } - } - } /** * Retrieve key bindings currently associated with a command @@ -485,6 +541,11 @@ define(function (require, exports, module) { return bindings || []; } + /** + * Adds default key bindings when commands are registered to CommandManager + * @param {$.Event} event jQuery event + * @param {Command} command Newly registered command + */ function _handleCommandRegistered(event, command) { var commandId = command.getID(), defaults = KeyboardPrefs[commandId]; diff --git a/test/spec/KeyBindingManager-test.js b/test/spec/KeyBindingManager-test.js index d83c2d5952e..10317a66a16 100644 --- a/test/spec/KeyBindingManager-test.js +++ b/test/spec/KeyBindingManager-test.js @@ -34,12 +34,16 @@ define(function (require, exports, module) { var CommandManager = require("command/CommandManager"), KeyBindingManager = require("command/KeyBindingManager"); - function key(k, displayKey) { - return {key: k, displayKey: displayKey || k }; + function key(k, displayKey, explicitPlatform) { + return { + key : k, + displayKey : displayKey || k, + explicitPlatform : explicitPlatform + }; } - function keyBinding(k, commandID, displayKey) { - var obj = key(k, displayKey); + function keyBinding(k, commandID, displayKey, explicitPlatform) { + var obj = key(k, displayKey, explicitPlatform); obj.commandID = commandID; return obj; @@ -86,26 +90,30 @@ define(function (require, exports, module) { }); it("should add single bindings to the keymap", function () { - var result = KeyBindingManager.addBinding("test.foo", "Ctrl-A"); - expect(result).toEqual(key("Ctrl-A")); - expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([key("Ctrl-A")]); + var result = KeyBindingManager.addBinding("test.foo", "Ctrl-A"), + keyTest = key("Ctrl-A"); + + expect(result).toEqual(keyTest); + expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([keyTest]); result = KeyBindingManager.addBinding("test.bar", "Ctrl-B"); - expect(result).toEqual(key("Ctrl-B")); - expect(KeyBindingManager.getKeyBindings("test.bar")).toEqual([key("Ctrl-B")]); + keyTest = key("Ctrl-B"); + expect(result).toEqual(keyTest); + expect(KeyBindingManager.getKeyBindings("test.bar")).toEqual([keyTest]); result = KeyBindingManager.addBinding("test.cat", "Ctrl-C", "bark"); expect(result).toBeNull(); result = KeyBindingManager.addBinding("test.dog", "Ctrl-D", "test"); - expect(result).toEqual(key("Ctrl-D")); - expect(KeyBindingManager.getKeyBindings("test.dog")).toEqual([key("Ctrl-D")]); + keyTest = key("Ctrl-D", null, "test"); + expect(result).toEqual(keyTest); + expect(KeyBindingManager.getKeyBindings("test.dog")).toEqual([keyTest]); // only "test" platform bindings var expected = keyMap([ keyBinding("Ctrl-A", "test.foo"), keyBinding("Ctrl-B", "test.bar"), - keyBinding("Ctrl-D", "test.dog") + keyBinding("Ctrl-D", "test.dog", null, "test") ]); expect(KeyBindingManager.getKeymap()).toEqual(expected); @@ -128,11 +136,11 @@ define(function (require, exports, module) { var results = KeyBindingManager.addBinding("test.foo", [{key: "Ctrl-A", platform: "test1"}, "Ctrl-1"]); expect(results).toEqual([ - key("Ctrl-A"), + key("Ctrl-A", null, "test1"), key("Ctrl-1") ]); expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([ - key("Ctrl-A"), + key("Ctrl-A", null, "test1"), key("Ctrl-1") ]); @@ -146,7 +154,7 @@ define(function (require, exports, module) { // only "test1" platform and cross-platform bindings var expected = keyMap([ - keyBinding("Ctrl-A", "test.foo"), + keyBinding("Ctrl-A", "test.foo", null, "test1"), keyBinding("Ctrl-1", "test.foo"), keyBinding("Ctrl-B", "test.bar") ]); @@ -187,10 +195,32 @@ define(function (require, exports, module) { KeyBindingManager.addBinding("test.cmdCtrlAlt", "Cmd-Ctrl-A", "mac"); var expected = keyMap([ - keyBinding("Cmd-A", "test.cmd"), - keyBinding("Ctrl-A", "test.ctrl"), - keyBinding("Ctrl-Alt-A", "test.ctrlAlt"), - keyBinding("Ctrl-Cmd-A", "test.cmdCtrlAlt") // KeyBindingManager changes the order + keyBinding("Cmd-A", "test.cmd", null, "mac"), + keyBinding("Ctrl-A", "test.ctrl", null, "mac"), + keyBinding("Ctrl-Alt-A", "test.ctrlAlt", null, "mac"), + keyBinding("Ctrl-Cmd-A", "test.cmdCtrlAlt", null, "mac") // KeyBindingManager changes the order + ]); + + expect(KeyBindingManager.getKeymap()).toEqual(expected); + }); + + it("should use windows key bindings on linux", function () { + brackets.platform = "linux"; + + // create a windows-specific binding + KeyBindingManager.addBinding("test.cmd", "Ctrl-A", "win"); + + var expected = keyMap([ + keyBinding("Ctrl-A", "test.cmd", null, "win") + ]); + + expect(KeyBindingManager.getKeymap()).toEqual(expected); + + // create a generic binding to replace the windows binding + KeyBindingManager.addBinding("test.cmd", "Ctrl-B"); + + expected = keyMap([ + keyBinding("Ctrl-B", "test.cmd", null) ]); expect(KeyBindingManager.getKeymap()).toEqual(expected); diff --git a/test/spec/LiveDevelopment-test.js b/test/spec/LiveDevelopment-test.js index cf3afe4a417..c04ced36cd4 100644 --- a/test/spec/LiveDevelopment-test.js +++ b/test/spec/LiveDevelopment-test.js @@ -46,7 +46,7 @@ define(function (require, exports, module) { CommandsManagerModule = require("command/CommandManager"), LiveDevelopmentModule = require("LiveDevelopment/LiveDevelopment"), InspectorModule = require("LiveDevelopment/Inspector/Inspector"), - CSSDocumentModule = require("LiveDevelopment/documents/CSSDocument"), + CSSDocumentModule = require("LiveDevelopment/Documents/CSSDocument"), CSSAgentModule = require("LiveDevelopment/Agents/CSSAgent"), HighlightAgentModule = require("LiveDevelopment/Agents/HighlightAgent");