From cb303a31492b5884c2f602ac375291ebf331c472 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 10:20:46 -0600 Subject: [PATCH 01/17] Depend on atom-keymap npm and a compatible pathwatcher version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c24605390..83fb171d9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "atomShellVersion": "0.10.7", "dependencies": { "async": "0.2.6", + "atom-keymap": "^0.4.0", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-script": "1.7.0", @@ -35,7 +36,7 @@ "nslog": "0.5.0", "oniguruma": ">=1.0.3 <2.0", "optimist": "0.4.0", - "pathwatcher": "0.19.0", + "pathwatcher": "1.0.0", "pegjs": "0.8.0", "property-accessors": "1.x", "q": "^1.0.1", From 8974cb5f347aa29ee12ba772b50d80b36ce3a0f1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:14:25 -0600 Subject: [PATCH 02/17] Make a quick and dirty subclass of the Keymap from the npm --- package.json | 2 - spec/keymap-spec.coffee | 26 +++-- spec/spec-helper.coffee | 12 +- src/key-binding.coffee | 64 ---------- src/keymap.coffee | 200 ++------------------------------ src/keystroke-pattern.pegjs | 3 - src/space-pen-extensions.coffee | 3 + 7 files changed, 38 insertions(+), 272 deletions(-) delete mode 100644 src/key-binding.coffee delete mode 100644 src/keystroke-pattern.pegjs diff --git a/package.json b/package.json index 83fb171d9..2ba91a9d3 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,11 @@ "mkdirp": "0.3.5", "keytar": "0.15.1", "less-cache": "0.12.0", - "loophole": "^0.3.0", "mixto": "1.x", "nslog": "0.5.0", "oniguruma": ">=1.0.3 <2.0", "optimist": "0.4.0", "pathwatcher": "1.0.0", - "pegjs": "0.8.0", "property-accessors": "1.x", "q": "^1.0.1", "random-words": "0.0.1", diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 4476693c1..f4c726f89 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -164,9 +164,10 @@ describe "Keymap", -> expect(bazHandler).toHaveBeenCalled() describe "when the event's target is the document body", -> - it "triggers the mapped event on the workspaceView", -> + xit "triggers the mapped event on the workspaceView", -> atom.workspaceView = new WorkspaceView atom.workspaceView.attachToDom() + keymap.defaultTarget = atom.workspaceView[0] keymap.bindKeys 'name', 'body', 'x': 'foo' fooHandler = jasmine.createSpy("fooHandler") atom.workspaceView.on 'foo', fooHandler @@ -204,11 +205,13 @@ describe "Keymap", -> describe "when the event's target node matches a selector with a partially matching multi-stroke binding", -> describe "when a second keystroke added to the first to match a multi-stroke binding completely", -> it "triggers the event associated with the matched multi-stroke binding", -> + expect(keymap.handleKeyEvent(keydownEvent('ctrl', target: fragment[0]))).toBeTruthy() # This simulates actual key event behavior expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment[0], ctrlKey: true))).toBeFalsy() expect(keymap.handleKeyEvent(keydownEvent('ctrl', target: fragment[0]))).toBeFalsy() # This simulates actual key event behavior expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0], ctrlKey: true))).toBeFalsy() expect(quitHandler).toHaveBeenCalled() + expect(closeOtherWindowsHandler).not.toHaveBeenCalled() quitHandler.reset() @@ -221,7 +224,7 @@ describe "Keymap", -> describe "when a second keystroke added to the first doesn't match any bindings", -> it "clears the queued keystroke without triggering any events", -> expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment[0], ctrlKey: true))).toBe false - expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0]))).toBe false + expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0]))).toBe true expect(quitHandler).not.toHaveBeenCalled() expect(closeOtherWindowsHandler).not.toHaveBeenCalled() @@ -326,7 +329,7 @@ describe "Keymap", -> expect(keymap.keystrokeStringForEvent(keydownEvent('a', altKey: true))).toBe 'alt-a' expect(keymap.keystrokeStringForEvent(keydownEvent('[', metaKey: true))).toBe 'cmd-[' expect(keymap.keystrokeStringForEvent(keydownEvent('*', ctrlKey: true))).toBe 'ctrl-*' - expect(keymap.keystrokeStringForEvent(keydownEvent('left', ctrlKey: true, metaKey: true, altKey: true))).toBe 'alt-cmd-ctrl-left' + expect(keymap.keystrokeStringForEvent(keydownEvent('left', ctrlKey: true, metaKey: true, altKey: true))).toBe 'ctrl-alt-cmd-left' describe "when shift is pressed when a non-modifer key", -> it "returns a string that identifies the key pressed", -> @@ -401,24 +404,23 @@ describe "Keymap", -> describe "when the user keymap file is changed", -> it "is reloaded", -> + jasmine.unspy(global, 'setTimeout') keymapFilePath = path.join(configDirPath, "keymap.cson") fs.writeFileSync(keymapFilePath, '"body": "ctrl-l": "core:move-left"') keymap.loadUserKeymap() - spyOn(keymap, 'loadUserKeymap').andCallThrough() fs.writeFileSync(keymapFilePath, "'body': 'ctrl-l': 'core:move-right'") - waitsFor -> - keymap.loadUserKeymap.callCount > 0 + waitsFor 300, (done) -> + keymap.once 'reloaded-key-bindings', done runs -> keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0] expect(keyBinding.command).toBe 'core:move-right' - keymap.loadUserKeymap.reset() fs.removeSync(keymapFilePath) - waitsFor -> - keymap.loadUserKeymap.callCount > 0 + waitsFor 300, (done) -> + keymap.once 'unloaded-key-bindings', done runs -> keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0] @@ -427,14 +429,16 @@ describe "Keymap", -> it "logs a warning when it can't be parsed", -> keymapFilePath = path.join(configDirPath, "keymap.json") fs.writeFileSync(keymapFilePath, '') + spyOn(console, 'warn') + keymap.loadUserKeymap() spyOn(keymap, 'loadUserKeymap').andCallThrough() fs.writeFileSync(keymapFilePath, '}{') - spyOn(console, 'warn') + console.warn.reset() waitsFor -> - keymap.loadUserKeymap.callCount > 0 + console.warn.callCount > 0 runs -> expect(console.warn.callCount).toBe 1 diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 81b7dc630..8341eaf72 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -179,8 +179,18 @@ window.keyIdentifierForKey = (key) -> charCode = key.toUpperCase().charCodeAt(0) "U+00" + charCode.toString(16) +nativeKeydownEvent = require('atom-keymap').keydownEvent + window.keydownEvent = (key, properties={}) -> - properties = $.extend({originalEvent: { keyIdentifier: keyIdentifierForKey(key) }}, properties) + nativeProperties = {} + nativeProperties.ctrl = properties.ctrlKey + nativeProperties.alt = properties.altKey + nativeProperties.shift = properties.shiftKey + nativeProperties.cmd = properties.metaKey + nativeProperties.target = properties.target + nativeProperties.which = properties.which + originalEvent = nativeKeydownEvent(key, nativeProperties) + properties = $.extend({originalEvent}, properties) $.Event("keydown", properties) window.mouseEvent = (type, properties) -> diff --git a/src/key-binding.coffee b/src/key-binding.coffee deleted file mode 100644 index 0dbc73f20..000000000 --- a/src/key-binding.coffee +++ /dev/null @@ -1,64 +0,0 @@ -_ = require 'underscore-plus' -fs = require 'fs-plus' -{specificity} = require 'clear-cut' - -module.exports = -class KeyBinding - @parser: null - @currentIndex: 1 - @specificities: null - - @calculateSpecificity: (selector) -> - @specificities ?= {} - value = @specificities[selector] - unless value? - value = specificity(selector) - @specificities[selector] = value - value - - @normalizeKeystroke: (keystroke) -> - normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => - keys = @parseKeystroke(keystroke) - modifiers = keys[0...-1] - modifiers.sort() - key = _.last(keys) - - modifiers.push 'shift' if /^[A-Z]$/.test(key) and 'shift' not in modifiers - key = key.toUpperCase() if /^[a-z]$/.test(key) and 'shift' in modifiers - - [modifiers..., key].join('-') - - normalizedKeystroke.join(' ') - - @parseKeystroke: (keystroke) -> - unless @parser? - try - @parser = require './keystroke-pattern' - catch - {allowUnsafeEval} = require 'loophole' - keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') - PEG = require 'pegjs' - allowUnsafeEval => @parser = PEG.buildParser(keystrokePattern) - - @parser.parse(keystroke) - - constructor: (source, command, keystroke, selector) -> - @source = source - @command = command - @keystroke = KeyBinding.normalizeKeystroke(keystroke) - @selector = selector.replace(/!important/g, '') - @specificity = KeyBinding.calculateSpecificity(selector) - @index = KeyBinding.currentIndex++ - - matches: (keystroke) -> - multiKeystroke = /\s/.test keystroke - if multiKeystroke - keystroke == @keystroke - else - keystroke.split(' ')[0] == @keystroke.split(' ')[0] - - compare: (keyBinding) -> - if keyBinding.specificity == @specificity - keyBinding.index - @index - else - keyBinding.specificity - @specificity diff --git a/src/keymap.coffee b/src/keymap.coffee index 4a8d72701..b99bfb95e 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -1,13 +1,7 @@ -{$} = require './space-pen-extensions' -_ = require 'underscore-plus' -fs = require 'fs-plus' path = require 'path' -CSON = require 'season' -KeyBinding = require './key-binding' -{File} = require 'pathwatcher' -{Emitter} = require 'emissary' - -Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd'] +AtomKeymap = require 'atom-keymap' +season = require 'season' +fs = require 'fs-plus' # Public: Associates keybindings with commands. # @@ -27,197 +21,21 @@ Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd'] # For that key, you define one or more key:value pairs, associating keystrokes # with a command to execute. module.exports = -class Keymap - Emitter.includeInto(this) - - constructor: ({@resourcePath, @configDirPath})-> - @keyBindings = [] - - destroy: -> - @unwatchUserKeymap() - - # Public: Returns an array of all {KeyBinding}s. - getKeyBindings: -> - _.clone(@keyBindings) - - # Public: Returns a array of {KeyBinding}s (sorted by selector specificity) - # that match a keystroke and element. - # - # keystroke - The {String} representing the keys pressed (e.g. ctrl-P). - # element - The DOM node that will match a {KeyBinding}'s selector. - keyBindingsForKeystrokeMatchingElement: (keystroke, element) -> - keyBindings = @keyBindingsForKeystroke(keystroke) - @keyBindingsMatchingElement(element, keyBindings) - - # Public: Returns a array of {KeyBinding}s (sorted by selector specificity) - # that match a command. - # - # command - The {String} representing the command (tree-view:toggle). - # element - The DOM node that will match a {KeyBinding}'s selector. - keyBindingsForCommandMatchingElement: (command, element) -> - keyBindings = @keyBindingsForCommand(command) - @keyBindingsMatchingElement(element, keyBindings) - - # Public: Returns an array of {KeyBinding}s that match a keystroke - # - # keystroke: The {String} representing the keys pressed (e.g. ctrl-P) - keyBindingsForKeystroke: (keystroke) -> - keystroke = KeyBinding.normalizeKeystroke(keystroke) - @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) - - # Public: Returns an array of {KeyBinding}s that match a command - # - # keystroke - The {String} representing the keys pressed (e.g. ctrl-P) - keyBindingsForCommand: (command) -> - @keyBindings.filter (keyBinding) -> keyBinding.command == command - - # Public: Returns a array of {KeyBinding}s (sorted by selector specificity) - # whos selector matches the element. - # - # element - The DOM node that will match a {KeyBinding}'s selector. - keyBindingsMatchingElement: (element, keyBindings=@keyBindings) -> - keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 - keyBindings.sort (a, b) -> a.compare(b) - - # Public: Returns a keystroke string derived from an event. - # - # event - A DOM or jQuery event. - # previousKeystroke - An optional string used for multiKeystrokes. - keystrokeStringForEvent: (event, previousKeystroke) -> - if event.originalEvent.keyIdentifier.indexOf('U+') == 0 - hexCharCode = event.originalEvent.keyIdentifier[2..] - charCode = parseInt(hexCharCode, 16) - charCode = event.which if !@isAscii(charCode) and @isAscii(event.which) - key = @keyFromCharCode(charCode) - else - key = event.originalEvent.keyIdentifier.toLowerCase() - - modifiers = [] - if event.altKey and key not in Modifiers - modifiers.push 'alt' - if event.metaKey and key not in Modifiers - modifiers.push 'cmd' - if event.ctrlKey and key not in Modifiers - modifiers.push 'ctrl' - if event.shiftKey and key not in Modifiers - # Don't push the shift modifier on single letter non-alpha keys (e.g. { or ') - modifiers.push 'shift' unless /^[^a-z]$/i.test(key) - - if 'shift' in modifiers and /^[a-z]$/i.test(key) - key = key.toUpperCase() - else - key = key.toLowerCase() - - keystroke = [modifiers..., key].join('-') - - if previousKeystroke - if keystroke in Modifiers - previousKeystroke - else - "#{previousKeystroke} #{keystroke}" - else - keystroke +class Keymap extends AtomKeymap + constructor: ({@resourcePath, @configDirPath}) -> + super loadBundledKeymaps: -> - @loadDirectory(path.join(@resourcePath, 'keymaps')) + @loadKeyBindings(path.join(@resourcePath, 'keymaps')) @emit('bundled-keymaps-loaded') getUserKeymapPath: -> - if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap')) + if userKeymapPath = season.resolve(path.join(@configDirPath, 'keymap')) userKeymapPath else path.join(@configDirPath, 'keymap.cson') - unwatchUserKeymap: -> - @userKeymapFile?.off() - @remove(@userKeymapPath) if @userKeymapPath? - loadUserKeymap: -> - @unwatchUserKeymap() userKeymapPath = @getUserKeymapPath() if fs.isFileSync(userKeymapPath) - @userKeymapPath = userKeymapPath - @userKeymapFile = new File(userKeymapPath) - @userKeymapFile.on 'contents-changed moved removed', => @loadUserKeymap() - @add(@userKeymapPath, @readUserKeymap()) - - readUserKeymap: -> - try - CSON.readFileSync(@userKeymapPath) ? {} - catch error - console.warn("Failed to load your keymap file: #{@userKeymapPath}", error.stack ? error) - {} - - loadDirectory: (directoryPath) -> - platforms = ['darwin', 'freebsd', 'linux', 'sunos', 'win32'] - otherPlatforms = platforms.filter (name) -> name != process.platform - - for filePath in fs.listSync(directoryPath, ['.cson', '.json']) - continue if path.basename(filePath, path.extname(filePath)) in otherPlatforms - @load(filePath) - - load: (path) -> - @add(path, CSON.readFileSync(path)) - - add: (source, keyMappingsBySelector) -> - for selector, keyMappings of keyMappingsBySelector - @bindKeys(source, selector, keyMappings) - - remove: (source) -> - @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source - - bindKeys: (source, selector, keyMappings) -> - for keystroke, command of keyMappings - keyBinding = new KeyBinding(source, command, keystroke, selector) - try - $(keyBinding.selector) # Verify selector is valid before registering - @keyBindings.push(keyBinding) - catch - console.warn("Keybinding '#{keystroke}': '#{command}' in #{source} has an invalid selector: '#{selector}'") - - handleKeyEvent: (event) -> - element = event.target - element = atom.workspaceView if element == document.body - keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - keyBindings = @keyBindingsForKeystrokeMatchingElement(keystroke, element) - - if keyBindings.length == 0 and @queuedKeystroke - @queuedKeystroke = null - return false - else - @queuedKeystroke = null - - for keyBinding in keyBindings - partialMatch = keyBinding.keystroke isnt keystroke - if partialMatch - @queuedKeystroke = keystroke - shouldBubble = false - else - if keyBinding.command is 'native!' - shouldBubble = true - else if @triggerCommandEvent(element, keyBinding.command, event) - shouldBubble = false - - break if shouldBubble? - - shouldBubble ? true - - triggerCommandEvent: (element, commandName, event) -> - commandEvent = $.Event(commandName) - commandEvent.originalEvent = event - commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() - $(element).trigger(commandEvent) - not commandEvent.isImmediatePropagationStopped() - - isAscii: (charCode) -> - 0 <= charCode <= 127 - - keyFromCharCode: (charCode) -> - switch charCode - when 8 then 'backspace' - when 9 then 'tab' - when 13 then 'enter' - when 27 then 'escape' - when 32 then 'space' - when 127 then 'delete' - else String.fromCharCode(charCode) + @loadKeyBindings(userKeymapPath, watch: true, suppressErrors: true) diff --git a/src/keystroke-pattern.pegjs b/src/keystroke-pattern.pegjs deleted file mode 100644 index 5c0283916..000000000 --- a/src/keystroke-pattern.pegjs +++ /dev/null @@ -1,3 +0,0 @@ -keystrokePattern = key:key additionalKeys:additionalKey* { return [key].concat(additionalKeys); } -additionalKey = '-' key:key { return key; } -key = '-' / chars:[^-]+ { return chars.join('') } diff --git a/src/space-pen-extensions.coffee b/src/space-pen-extensions.coffee index 04e411114..02850180a 100644 --- a/src/space-pen-extensions.coffee +++ b/src/space-pen-extensions.coffee @@ -69,4 +69,7 @@ jQuery(document.body).on 'show.bs.tooltip', ({target}) -> jQuery.fn.setTooltip.getKeystroke = getKeystroke jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes +jQuery.Event::abortKeyBinding = -> + @originalEvent?.abortKeyBinding?() + module.exports = spacePen From 4c0f1efec607d2625176275c34f6009c742e7f30 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:14:47 -0600 Subject: [PATCH 03/17] Set the keymap default target to atom.workspaceView's DOM element --- src/atom.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atom.coffee b/src/atom.coffee index 5905902cd..68de0ccf3 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -236,6 +236,7 @@ class Atom extends Model WorkspaceView = require './workspace-view' @workspace = Workspace.deserialize(@state.workspace) ? new Workspace @workspaceView = new WorkspaceView(@workspace) + @keymap.defaultTarget = @workspaceView[0] $(@workspaceViewParentSelector).append(@workspaceView) deserializePackageStates: -> From 9a488adbb990bd56af703f1a7ac9aef35c86f4a0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:14:57 -0600 Subject: [PATCH 04/17] Suggest people bind to .workspace instead of body --- dot-atom/keymap.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index bce80b757..43ec6951d 100644 --- a/dot-atom/keymap.cson +++ b/dot-atom/keymap.cson @@ -12,7 +12,7 @@ # '.editor': # 'enter': 'editor:newline' # -# 'body': +# '.workspace': # 'ctrl-P': 'core:move-up' # 'ctrl-p': 'core:move-down' # From ae324c13a6813d0cf42b5528f8888afb85ec6aea Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:36:22 -0600 Subject: [PATCH 05/17] Upgrade to atom-keymap@0.5.0 to support keybinding resolver --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ba91a9d3..65630e8e5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "atomShellVersion": "0.10.7", "dependencies": { "async": "0.2.6", - "atom-keymap": "^0.4.0", + "atom-keymap": "^0.5.0", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-script": "1.7.0", From 067f73da382dba1ca8cb6592d92a10787bcad649 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:46:56 -0600 Subject: [PATCH 06/17] Defend against jQuery-wrapped targets in keydownEvent spec helper --- spec/spec-helper.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 8341eaf72..1a5e26359 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -187,7 +187,7 @@ window.keydownEvent = (key, properties={}) -> nativeProperties.alt = properties.altKey nativeProperties.shift = properties.shiftKey nativeProperties.cmd = properties.metaKey - nativeProperties.target = properties.target + nativeProperties.target = properties.target?[0] ? properties.target nativeProperties.which = properties.which originalEvent = nativeKeydownEvent(key, nativeProperties) properties = $.extend({originalEvent}, properties) From 2dcbf7f7511e20ab1f79d1e19c983d5797e02aa0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 14:55:10 -0600 Subject: [PATCH 07/17] Replace Keymap subclass with keymap-extensions --- spec/keymap-spec.coffee | 12 ++++++++--- spec/spec-helper.coffee | 3 ++- src/atom.coffee | 7 ++++-- src/keymap-extensions.coffee | 19 +++++++++++++++++ src/keymap.coffee | 41 ------------------------------------ 5 files changed, 35 insertions(+), 47 deletions(-) create mode 100644 src/keymap-extensions.coffee delete mode 100644 src/keymap.coffee diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index f4c726f89..07d4282c4 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -1,7 +1,8 @@ fs = require 'fs-plus' path = require 'path' temp = require 'temp' -Keymap = require '../src/keymap' +Keymap = require 'atom-keymap' +require '../src/keymap-extensions' {$, $$, WorkspaceView} = require 'atom' describe "Keymap", -> @@ -12,7 +13,10 @@ describe "Keymap", -> beforeEach -> configDirPath = temp.mkdirSync('atom') - keymap = new Keymap({configDirPath, resourcePath}) + keymap = new Keymap + keymap.configDirPath = configDirPath + keymap.resourcePath = resourcePath + fragment = $ """
@@ -386,7 +390,9 @@ describe "Keymap", -> beforeEach -> resourcePath = temp.mkdirSync('atom') - customKeymap = new Keymap({configDirPath, resourcePath}) + customKeymap = new Keymap + customKeymap.configDirPath = configDirPath + customKeymap.resourcePath = resourcePath afterEach -> customKeymap.destroy() diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 1a5e26359..cc77bf244 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -6,8 +6,9 @@ require '../vendor/jasmine-jquery' path = require 'path' _ = require 'underscore-plus' fs = require 'fs-plus' +Keymap = require 'atom-keymap' {$, WorkspaceView} = require 'atom' -Keymap = require '../src/keymap' +require '../src/keymap-extensions' Config = require '../src/config' {Point} = require 'text-buffer' Project = require '../src/project' diff --git a/src/atom.coffee b/src/atom.coffee index 68de0ccf3..a785151dd 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -138,7 +138,8 @@ class Atom extends Model @loadTime = null Config = require './config' - Keymap = require './keymap' + Keymap = require 'atom-keymap' + require './keymap-extensions' PackageManager = require './package-manager' Clipboard = require './clipboard' Syntax = require './syntax' @@ -149,7 +150,9 @@ class Atom extends Model configDirPath = @getConfigDirPath() @config = new Config({configDirPath, resourcePath}) - @keymap = new Keymap({configDirPath, resourcePath}) + @keymap = new Keymap + @keymap.configDirPath = configDirPath + @keymap.resourcePath = resourcePath @packages = new PackageManager({devMode, configDirPath, resourcePath}) @themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath}) @contextMenu = new ContextMenuManager(devMode) diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee new file mode 100644 index 000000000..30d7cc1b9 --- /dev/null +++ b/src/keymap-extensions.coffee @@ -0,0 +1,19 @@ +fs = require 'fs-plus' +path = require 'path' +Keymap = require 'atom-keymap' +season = require 'season' + +Keymap::loadBundledKeymaps = -> + @loadKeyBindings(path.join(@resourcePath, 'keymaps')) + @emit('bundled-keymaps-loaded') + +Keymap::getUserKeymapPath = -> + if userKeymapPath = season.resolve(path.join(@configDirPath, 'keymap')) + userKeymapPath + else + path.join(@configDirPath, 'keymap.cson') + +Keymap::loadUserKeymap = -> + userKeymapPath = @getUserKeymapPath() + if fs.isFileSync(userKeymapPath) + @loadKeyBindings(userKeymapPath, watch: true, suppressErrors: true) diff --git a/src/keymap.coffee b/src/keymap.coffee deleted file mode 100644 index b99bfb95e..000000000 --- a/src/keymap.coffee +++ /dev/null @@ -1,41 +0,0 @@ -path = require 'path' -AtomKeymap = require 'atom-keymap' -season = require 'season' -fs = require 'fs-plus' - -# Public: Associates keybindings with commands. -# -# An instance of this class is always available as the `atom.keymap` global. -# -# Keymaps are defined in a CSON/JSON format. A typical keymap looks something -# like this: -# -# ```cson -# 'body': -# 'ctrl-l': 'package:do-something' -# '.someClass': -# 'enter': 'package:confirm' -# ``` -# -# As a key, you define the DOM element you want to work on, using CSS notation. -# For that key, you define one or more key:value pairs, associating keystrokes -# with a command to execute. -module.exports = -class Keymap extends AtomKeymap - constructor: ({@resourcePath, @configDirPath}) -> - super - - loadBundledKeymaps: -> - @loadKeyBindings(path.join(@resourcePath, 'keymaps')) - @emit('bundled-keymaps-loaded') - - getUserKeymapPath: -> - if userKeymapPath = season.resolve(path.join(@configDirPath, 'keymap')) - userKeymapPath - else - path.join(@configDirPath, 'keymap.cson') - - loadUserKeymap: -> - userKeymapPath = @getUserKeymapPath() - if fs.isFileSync(userKeymapPath) - @loadKeyBindings(userKeymapPath, watch: true, suppressErrors: true) From 1c964d05f8cc3d460550981a5893cd9f8f11f51f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 15:08:36 -0600 Subject: [PATCH 08/17] :lipstick: spec helper's keydownEvent --- spec/spec-helper.coffee | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index cc77bf244..77c899664 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -180,17 +180,15 @@ window.keyIdentifierForKey = (key) -> charCode = key.toUpperCase().charCodeAt(0) "U+00" + charCode.toString(16) -nativeKeydownEvent = require('atom-keymap').keydownEvent - window.keydownEvent = (key, properties={}) -> - nativeProperties = {} - nativeProperties.ctrl = properties.ctrlKey - nativeProperties.alt = properties.altKey - nativeProperties.shift = properties.shiftKey - nativeProperties.cmd = properties.metaKey - nativeProperties.target = properties.target?[0] ? properties.target - nativeProperties.which = properties.which - originalEvent = nativeKeydownEvent(key, nativeProperties) + originalEventProperties = {} + originalEventProperties.ctrl = properties.ctrlKey + originalEventProperties.alt = properties.altKey + originalEventProperties.shift = properties.shiftKey + originalEventProperties.cmd = properties.metaKey + originalEventProperties.target = properties.target?[0] ? properties.target + originalEventProperties.which = properties.which + originalEvent = Keymap.keydownEvent(key, originalEventProperties) properties = $.extend({originalEvent}, properties) $.Event("keydown", properties) From 8f2745c248529587e24b6a30501192e705dffbd9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 15:09:10 -0600 Subject: [PATCH 09/17] Remove old keymap-spec. It has been supplanted by the atom-keymap npm --- spec/keymap-spec.coffee | 464 ---------------------------------------- 1 file changed, 464 deletions(-) delete mode 100644 spec/keymap-spec.coffee diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee deleted file mode 100644 index 07d4282c4..000000000 --- a/spec/keymap-spec.coffee +++ /dev/null @@ -1,464 +0,0 @@ -fs = require 'fs-plus' -path = require 'path' -temp = require 'temp' -Keymap = require 'atom-keymap' -require '../src/keymap-extensions' -{$, $$, WorkspaceView} = require 'atom' - -describe "Keymap", -> - fragment = null - keymap = null - resourcePath = atom.getLoadSettings().resourcePath - configDirPath = null - - beforeEach -> - configDirPath = temp.mkdirSync('atom') - keymap = new Keymap - keymap.configDirPath = configDirPath - keymap.resourcePath = resourcePath - - fragment = $ """ -
-
-
-
-
- """ - - afterEach -> - keymap.destroy() - - describe ".handleKeyEvent(event)", -> - deleteCharHandler = null - insertCharHandler = null - commandZHandler = null - - beforeEach -> - keymap.bindKeys 'name', '.command-mode', 'x': 'deleteChar' - keymap.bindKeys 'name', '.insert-mode', 'x': 'insertChar' - keymap.bindKeys 'name', '.command-mode', 'cmd-z': 'commandZPressed' - - deleteCharHandler = jasmine.createSpy('deleteCharHandler') - insertCharHandler = jasmine.createSpy('insertCharHandler') - commandZHandler = jasmine.createSpy('commandZHandler') - fragment.on 'deleteChar', deleteCharHandler - fragment.on 'insertChar', insertCharHandler - fragment.on 'commandZPressed', commandZHandler - - describe "when no binding matches the event's keystroke", -> - it "does not return false so the event continues to propagate", -> - expect(keymap.handleKeyEvent(keydownEvent('0', target: fragment[0]))).not.toBe false - - describe "when a non-English keyboard language is used", -> - it "uses the physical character pressed instead of the character it maps to in the current language", -> - event = keydownEvent('U+03B6', metaKey: true, which: 122, target: fragment[0]) # This is the 'z' key using the Greek keyboard layout - result = keymap.handleKeyEvent(event) - - expect(result).toBe(false) - expect(commandZHandler).toHaveBeenCalled() - - describe "when at least one binding fully matches the event's keystroke", -> - describe "when the event's target node matches a selector with a matching binding", -> - it "triggers the command event associated with that binding on the target node and returns false", -> - result = keymap.handleKeyEvent(keydownEvent('x', target: fragment[0])) - expect(result).toBe(false) - expect(deleteCharHandler).toHaveBeenCalled() - expect(insertCharHandler).not.toHaveBeenCalled() - - deleteCharHandler.reset() - fragment.removeClass('command-mode').addClass('insert-mode') - - event = keydownEvent('x', target: fragment[0]) - keymap.handleKeyEvent(event) - expect(deleteCharHandler).not.toHaveBeenCalled() - expect(insertCharHandler).toHaveBeenCalled() - - describe "when the event's target node *descends* from a selector with a matching binding", -> - it "triggers the command event associated with that binding on the target node and returns false", -> - target = fragment.find('.child-node')[0] - result = keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(result).toBe(false) - expect(deleteCharHandler).toHaveBeenCalled() - expect(insertCharHandler).not.toHaveBeenCalled() - - deleteCharHandler.reset() - fragment.removeClass('command-mode').addClass('insert-mode') - - keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(deleteCharHandler).not.toHaveBeenCalled() - expect(insertCharHandler).toHaveBeenCalled() - - describe "when the event's target node descends from multiple nodes that match selectors with a binding", -> - beforeEach -> - keymap.bindKeys 'name', '.child-node', 'x': 'foo' - - it "only triggers bindings on selectors associated with the closest ancestor node", -> - fooHandler = jasmine.createSpy 'fooHandler' - fragment.on 'foo', fooHandler - - target = fragment.find('.grandchild-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(fooHandler).toHaveBeenCalled() - expect(deleteCharHandler).not.toHaveBeenCalled() - expect(insertCharHandler).not.toHaveBeenCalled() - - describe "when 'abortKeyBinding' is called on the triggered event", -> - [fooHandler1, fooHandler2] = [] - - beforeEach -> - fooHandler1 = jasmine.createSpy('fooHandler1').andCallFake (e) -> - expect(deleteCharHandler).not.toHaveBeenCalled() - e.abortKeyBinding() - fooHandler2 = jasmine.createSpy('fooHandler2') - - fragment.find('.child-node').on 'foo', fooHandler1 - fragment.on 'foo', fooHandler2 - - it "aborts the current event and tries again with the next-most-specific key binding", -> - target = fragment.find('.grandchild-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(fooHandler1).toHaveBeenCalled() - expect(fooHandler2).not.toHaveBeenCalled() - expect(deleteCharHandler).toHaveBeenCalled() - - it "does not throw an exception if the event was not triggered by the keymap", -> - fragment.find('.grandchild-node').trigger 'foo' - - describe "when the event bubbles to a node that matches multiple selectors", -> - describe "when the matching selectors differ in specificity", -> - it "triggers the binding for the most specific selector", -> - keymap.bindKeys 'name', 'div .child-node', 'x': 'foo' - keymap.bindKeys 'name', '.command-mode .child-node !important', 'x': 'baz' - keymap.bindKeys 'name', '.command-mode .child-node', 'x': 'quux' - keymap.bindKeys 'name', '.child-node', 'x': 'bar' - - fooHandler = jasmine.createSpy 'fooHandler' - barHandler = jasmine.createSpy 'barHandler' - bazHandler = jasmine.createSpy 'bazHandler' - fragment.on 'foo', fooHandler - fragment.on 'bar', barHandler - fragment.on 'baz', bazHandler - - target = fragment.find('.grandchild-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) - - expect(fooHandler).not.toHaveBeenCalled() - expect(barHandler).not.toHaveBeenCalled() - expect(bazHandler).toHaveBeenCalled() - - describe "when the matching selectors have the same specificity", -> - it "triggers the bindings for the most recently declared selector", -> - keymap.bindKeys 'name', '.child-node', 'x': 'foo', 'y': 'baz' - keymap.bindKeys 'name', '.child-node', 'x': 'bar' - - fooHandler = jasmine.createSpy 'fooHandler' - barHandler = jasmine.createSpy 'barHandler' - bazHandler = jasmine.createSpy 'bazHandler' - fragment.on 'foo', fooHandler - fragment.on 'bar', barHandler - fragment.on 'baz', bazHandler - - target = fragment.find('.grandchild-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) - - expect(barHandler).toHaveBeenCalled() - expect(fooHandler).not.toHaveBeenCalled() - - keymap.handleKeyEvent(keydownEvent('y', target: target)) - expect(bazHandler).toHaveBeenCalled() - - describe "when the event's target is the document body", -> - xit "triggers the mapped event on the workspaceView", -> - atom.workspaceView = new WorkspaceView - atom.workspaceView.attachToDom() - keymap.defaultTarget = atom.workspaceView[0] - keymap.bindKeys 'name', 'body', 'x': 'foo' - fooHandler = jasmine.createSpy("fooHandler") - atom.workspaceView.on 'foo', fooHandler - - result = keymap.handleKeyEvent(keydownEvent('x', target: document.body)) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - expect(deleteCharHandler).not.toHaveBeenCalled() - expect(insertCharHandler).not.toHaveBeenCalled() - - describe "when the event matches a 'native!' binding", -> - it "returns true, allowing the browser's native key handling to process the event", -> - keymap.bindKeys 'name', '.grandchild-node', 'x': 'native!' - nativeHandler = jasmine.createSpy("nativeHandler") - fragment.on 'native!', nativeHandler - expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment.find('.grandchild-node')[0]))).toBe true - expect(nativeHandler).not.toHaveBeenCalled() - - describe "when at least one binding partially matches the event's keystroke", -> - [quitHandler, closeOtherWindowsHandler] = [] - - beforeEach -> - keymap.bindKeys 'name', "*", - 'ctrl-x ctrl-c': 'quit' - 'ctrl-x 1': 'close-other-windows' - - quitHandler = jasmine.createSpy('quitHandler') - closeOtherWindowsHandler = jasmine.createSpy('closeOtherWindowsHandler') - fragment.on 'quit', quitHandler - fragment.on 'close-other-windows', closeOtherWindowsHandler - - it "only matches entire keystroke patterns", -> - expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0]))).not.toBe false - - describe "when the event's target node matches a selector with a partially matching multi-stroke binding", -> - describe "when a second keystroke added to the first to match a multi-stroke binding completely", -> - it "triggers the event associated with the matched multi-stroke binding", -> - expect(keymap.handleKeyEvent(keydownEvent('ctrl', target: fragment[0]))).toBeTruthy() # This simulates actual key event behavior - expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment[0], ctrlKey: true))).toBeFalsy() - expect(keymap.handleKeyEvent(keydownEvent('ctrl', target: fragment[0]))).toBeFalsy() # This simulates actual key event behavior - expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0], ctrlKey: true))).toBeFalsy() - - expect(quitHandler).toHaveBeenCalled() - - expect(closeOtherWindowsHandler).not.toHaveBeenCalled() - quitHandler.reset() - - expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment[0], ctrlKey: true))).toBeFalsy() - expect(keymap.handleKeyEvent(keydownEvent('1', target: fragment[0]))).toBeFalsy() - - expect(quitHandler).not.toHaveBeenCalled() - expect(closeOtherWindowsHandler).toHaveBeenCalled() - - describe "when a second keystroke added to the first doesn't match any bindings", -> - it "clears the queued keystroke without triggering any events", -> - expect(keymap.handleKeyEvent(keydownEvent('x', target: fragment[0], ctrlKey: true))).toBe false - expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0]))).toBe true - expect(quitHandler).not.toHaveBeenCalled() - expect(closeOtherWindowsHandler).not.toHaveBeenCalled() - - expect(keymap.handleKeyEvent(keydownEvent('c', target: fragment[0]))).not.toBe false - - describe "when the event's target node descends from multiple nodes that match selectors with a partial binding match", -> - it "allows any of the bindings to be triggered upon a second keystroke, favoring the most specific selector", -> - keymap.bindKeys 'name', ".grandchild-node", 'ctrl-x ctrl-c': 'more-specific-quit' - grandchildNode = fragment.find('.grandchild-node')[0] - moreSpecificQuitHandler = jasmine.createSpy('moreSpecificQuitHandler') - fragment.on 'more-specific-quit', moreSpecificQuitHandler - - expect(keymap.handleKeyEvent(keydownEvent('x', target: grandchildNode, ctrlKey: true))).toBeFalsy() - expect(keymap.handleKeyEvent(keydownEvent('1', target: grandchildNode))).toBeFalsy() - expect(quitHandler).not.toHaveBeenCalled() - expect(moreSpecificQuitHandler).not.toHaveBeenCalled() - expect(closeOtherWindowsHandler).toHaveBeenCalled() - closeOtherWindowsHandler.reset() - - expect(keymap.handleKeyEvent(keydownEvent('x', target: grandchildNode, ctrlKey: true))).toBeFalsy() - expect(keymap.handleKeyEvent(keydownEvent('c', target: grandchildNode, ctrlKey: true))).toBeFalsy() - expect(quitHandler).not.toHaveBeenCalled() - expect(closeOtherWindowsHandler).not.toHaveBeenCalled() - expect(moreSpecificQuitHandler).toHaveBeenCalled() - - describe "when there is a complete binding with a less specific selector", -> - it "favors the more specific partial match", -> - - describe "when there is a complete binding with a more specific selector", -> - it "favors the more specific complete match", -> - - describe ".bindKeys(name, selector, bindings)", -> - it "normalizes bindings that use shift with lower case alpha char", -> - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler - keymap.bindKeys 'name', '*', 'ctrl-shift-l': 'foo' - result = keymap.handleKeyEvent(keydownEvent('l', ctrlKey: true, altKey: false, shiftKey: true, target: fragment[0])) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - - it "normalizes bindings that use shift with upper case alpha char", -> - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler - keymap.bindKeys 'name', '*', 'ctrl-shift-L': 'foo' - result = keymap.handleKeyEvent(keydownEvent('l', ctrlKey: true, altKey: false, shiftKey: true, target: fragment[0])) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - - it "normalizes bindings that use an upper case alpha char without shift", -> - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler - keymap.bindKeys 'name', '*', 'ctrl-L': 'foo' - result = keymap.handleKeyEvent(keydownEvent('l', ctrlKey: true, altKey: false, shiftKey: true, target: fragment[0])) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - - it "normalizes the key patterns in the hash to put the modifiers in alphabetical order", -> - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler - keymap.bindKeys 'name', '*', 'ctrl-alt-delete': 'foo' - result = keymap.handleKeyEvent(keydownEvent('delete', ctrlKey: true, altKey: true, target: fragment[0])) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - - fooHandler.reset() - keymap.bindKeys 'name', '*', 'ctrl-alt--': 'foo' - result = keymap.handleKeyEvent(keydownEvent('-', ctrlKey: true, altKey: true, target: fragment[0])) - expect(result).toBe(false) - expect(fooHandler).toHaveBeenCalled() - - describe ".remove(name)", -> - it "removes the binding set with the given selector and bindings", -> - keymap.add 'nature', - '.green': - 'ctrl-c': 'cultivate' - '.brown': - 'ctrl-h': 'harvest' - - keymap.add 'medical', - '.green': - 'ctrl-v': 'vomit' - - expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 2 - expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 - - keymap.remove('nature') - - expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 - expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] - - describe ".keystrokeStringForEvent(event)", -> - describe "when no modifiers are pressed", -> - it "returns a string that identifies the key pressed", -> - expect(keymap.keystrokeStringForEvent(keydownEvent('a'))).toBe 'a' - expect(keymap.keystrokeStringForEvent(keydownEvent('['))).toBe '[' - expect(keymap.keystrokeStringForEvent(keydownEvent('*'))).toBe '*' - expect(keymap.keystrokeStringForEvent(keydownEvent('left'))).toBe 'left' - expect(keymap.keystrokeStringForEvent(keydownEvent('\b'))).toBe 'backspace' - - describe "when ctrl, alt or command is pressed with a non-modifier key", -> - it "returns a string that identifies the key pressed", -> - expect(keymap.keystrokeStringForEvent(keydownEvent('a', altKey: true))).toBe 'alt-a' - expect(keymap.keystrokeStringForEvent(keydownEvent('[', metaKey: true))).toBe 'cmd-[' - expect(keymap.keystrokeStringForEvent(keydownEvent('*', ctrlKey: true))).toBe 'ctrl-*' - expect(keymap.keystrokeStringForEvent(keydownEvent('left', ctrlKey: true, metaKey: true, altKey: true))).toBe 'ctrl-alt-cmd-left' - - describe "when shift is pressed when a non-modifer key", -> - it "returns a string that identifies the key pressed", -> - expect(keymap.keystrokeStringForEvent(keydownEvent('A', shiftKey: true))).toBe 'shift-A' - expect(keymap.keystrokeStringForEvent(keydownEvent('{', shiftKey: true))).toBe '{' - expect(keymap.keystrokeStringForEvent(keydownEvent('left', shiftKey: true))).toBe 'shift-left' - expect(keymap.keystrokeStringForEvent(keydownEvent('Left', shiftKey: true))).toBe 'shift-left' - - describe ".keyBindingsMatchingElement(element)", -> - it "returns the matching bindings for the element", -> - keymap.bindKeys 'name', '.command-mode', 'c': 'c' - keymap.bindKeys 'name', '.grandchild-node', 'g': 'g' - - bindings = keymap.keyBindingsMatchingElement(fragment.find('.grandchild-node')) - expect(bindings).toHaveLength 2 - expect(bindings[0].command).toEqual "g" - expect(bindings[1].command).toEqual "c" - - describe "when multiple bindings match a keystroke", -> - it "only returns bindings that match the most specific selector", -> - keymap.bindKeys 'name', '.command-mode', 'g': 'cmd-mode' - keymap.bindKeys 'name', '.command-mode .grandchild-node', 'g': 'cmd-and-grandchild-node' - keymap.bindKeys 'name', '.grandchild-node', 'g': 'grandchild-node' - - bindings = keymap.keyBindingsMatchingElement(fragment.find('.grandchild-node')) - expect(bindings).toHaveLength 3 - expect(bindings[0].command).toEqual "cmd-and-grandchild-node" - - describe ".keyBindingsForCommandMatchingElement(element)", -> - beforeEach -> - keymap.add 'nature', - '.green': - 'ctrl-c': 'cultivate' - '.green-2': - 'ctrl-o': 'cultivate' - '.brown': - 'ctrl-h': 'harvest' - '.blue': - 'ctrl-c': 'fly' - - it "finds a keymap for an element", -> - el = $$ -> @div class: 'green' - bindings = keymap.keyBindingsForCommandMatchingElement('cultivate', el) - expect(bindings).toHaveLength 1 - expect(bindings[0].keystroke).toEqual "ctrl-c" - - it "no keymap an element without that map", -> - el = $$ -> @div class: 'brown' - bindings = keymap.keyBindingsForCommandMatchingElement('cultivate', el) - expect(bindings).toHaveLength 0 - - describe "loading platform specific keybindings", -> - customKeymap = null - - beforeEach -> - resourcePath = temp.mkdirSync('atom') - customKeymap = new Keymap - customKeymap.configDirPath = configDirPath - customKeymap.resourcePath = resourcePath - - afterEach -> - customKeymap.destroy() - - it "doesn't load keybindings from other platforms", -> - win32FilePath = path.join(resourcePath, "keymaps", "win32.cson") - darwinFilePath = path.join(resourcePath, "keymaps", "darwin.cson") - fs.writeFileSync(win32FilePath, '"body": "ctrl-l": "core:win32-move-left"') - fs.writeFileSync(darwinFilePath, '"body": "ctrl-l": "core:darwin-move-left"') - - customKeymap.loadBundledKeymaps() - keyBindings = customKeymap.keyBindingsForKeystroke('ctrl-l') - expect(keyBindings).toHaveLength 1 - expect(keyBindings[0].command).toBe "core:#{process.platform}-move-left" - - describe "when the user keymap file is changed", -> - it "is reloaded", -> - jasmine.unspy(global, 'setTimeout') - keymapFilePath = path.join(configDirPath, "keymap.cson") - fs.writeFileSync(keymapFilePath, '"body": "ctrl-l": "core:move-left"') - keymap.loadUserKeymap() - - fs.writeFileSync(keymapFilePath, "'body': 'ctrl-l': 'core:move-right'") - - waitsFor 300, (done) -> - keymap.once 'reloaded-key-bindings', done - - runs -> - keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0] - expect(keyBinding.command).toBe 'core:move-right' - fs.removeSync(keymapFilePath) - - waitsFor 300, (done) -> - keymap.once 'unloaded-key-bindings', done - - runs -> - keyBinding = keymap.keyBindingsForKeystroke('ctrl-l')[0] - expect(keyBinding).toBeUndefined() - - it "logs a warning when it can't be parsed", -> - keymapFilePath = path.join(configDirPath, "keymap.json") - fs.writeFileSync(keymapFilePath, '') - spyOn(console, 'warn') - - keymap.loadUserKeymap() - - spyOn(keymap, 'loadUserKeymap').andCallThrough() - fs.writeFileSync(keymapFilePath, '}{') - console.warn.reset() - - waitsFor -> - console.warn.callCount > 0 - - runs -> - expect(console.warn.callCount).toBe 1 - expect(console.warn.argsForCall[0][0].length).toBeGreaterThan 0 - - describe "when adding a binding with an invalid selector", -> - it "logs a warning and does not add it", -> - spyOn(console, 'warn') - keybinding = - '##selector': - 'cmd-a': 'invalid-command' - keymap.add('test', keybinding) - - expect(console.warn.callCount).toBe 1 - expect(console.warn.argsForCall[0][0].length).toBeGreaterThan 0 - expect(-> keymap.keyBindingsMatchingElement(document.body)).not.toThrow() - expect(keymap.keyBindingsForCommand('invalid:command')).toEqual [] From 27a157702113c17cf17af4bb1c3e8d4298f2945d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Mar 2014 17:07:44 -0600 Subject: [PATCH 10/17] Upgrade to atom-keymap@0.6.0 for bug fixes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 65630e8e5..2b2bbb8c9 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "atomShellVersion": "0.10.7", "dependencies": { "async": "0.2.6", - "atom-keymap": "^0.5.0", + "atom-keymap": "^0.6.0", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-script": "1.7.0", From 596c584ef192d695736ea311d874f814f2e648c8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 15 Mar 2014 17:27:46 -0600 Subject: [PATCH 11/17] Add atom-keymap classes to the docs grunt task --- build/tasks/docs-task.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/tasks/docs-task.coffee b/build/tasks/docs-task.coffee index b32669242..fdc49ad68 100644 --- a/build/tasks/docs-task.coffee +++ b/build/tasks/docs-task.coffee @@ -143,6 +143,8 @@ downloadFileFromRepo = ({repo, file}, callback) -> downloadIncludes = (callback) -> includes = [ + {repo: 'atom-keymap', file: 'src/keymap.coffee'} + {repo: 'atom-keymap', file: 'src/key-binding.coffee'} {repo: 'first-mate', file: 'src/grammar.coffee'} {repo: 'first-mate', file: 'src/grammar-registry.coffee'} {repo: 'node-pathwatcher', file: 'src/directory.coffee'} From 371820fd59a36585d1a0ec20826bafdd0c085273 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 11:35:32 -0600 Subject: [PATCH 12/17] Use ^ prefix for pathwatcher dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b2bbb8c9..ce77349c0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "nslog": "0.5.0", "oniguruma": ">=1.0.3 <2.0", "optimist": "0.4.0", - "pathwatcher": "1.0.0", + "pathwatcher": "^1.0.0", "property-accessors": "1.x", "q": "^1.0.1", "random-words": "0.0.1", From 2eb2aa5bef665d0800dff5ddbfe7e42d742f57ed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 11:36:04 -0600 Subject: [PATCH 13/17] Rename season variable to CSON for consistency --- src/keymap-extensions.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index 30d7cc1b9..29734e965 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -1,14 +1,14 @@ fs = require 'fs-plus' path = require 'path' Keymap = require 'atom-keymap' -season = require 'season' +CSON = require 'season' Keymap::loadBundledKeymaps = -> @loadKeyBindings(path.join(@resourcePath, 'keymaps')) @emit('bundled-keymaps-loaded') Keymap::getUserKeymapPath = -> - if userKeymapPath = season.resolve(path.join(@configDirPath, 'keymap')) + if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap')) userKeymapPath else path.join(@configDirPath, 'keymap.cson') From 3d28f957c7a1c562b88d2f9a64d7099f7ae4b3fe Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 11:37:39 -0600 Subject: [PATCH 14/17] Export Keymap from keymap-extensions --- spec/spec-helper.coffee | 3 +-- src/atom.coffee | 3 +-- src/keymap-extensions.coffee | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 77c899664..3f68f9062 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -6,9 +6,8 @@ require '../vendor/jasmine-jquery' path = require 'path' _ = require 'underscore-plus' fs = require 'fs-plus' -Keymap = require 'atom-keymap' +Keymap = require '../src/keymap-extensions' {$, WorkspaceView} = require 'atom' -require '../src/keymap-extensions' Config = require '../src/config' {Point} = require 'text-buffer' Project = require '../src/project' diff --git a/src/atom.coffee b/src/atom.coffee index a785151dd..75c83fd49 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -138,8 +138,7 @@ class Atom extends Model @loadTime = null Config = require './config' - Keymap = require 'atom-keymap' - require './keymap-extensions' + Keymap = require './keymap-extensions' PackageManager = require './package-manager' Clipboard = require './clipboard' Syntax = require './syntax' diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index 29734e965..a6aa5524f 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -17,3 +17,5 @@ Keymap::loadUserKeymap = -> userKeymapPath = @getUserKeymapPath() if fs.isFileSync(userKeymapPath) @loadKeyBindings(userKeymapPath, watch: true, suppressErrors: true) + +module.exports = Keymap From dcdc9d6b907080fa388912f68265edcdbd73f3d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 11:45:24 -0600 Subject: [PATCH 15/17] Construct Keymap with options object --- package.json | 2 +- src/atom.coffee | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ce77349c0..a7aaec6e0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "atomShellVersion": "0.10.7", "dependencies": { "async": "0.2.6", - "atom-keymap": "^0.6.0", + "atom-keymap": "^0.7.0", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-script": "1.7.0", diff --git a/src/atom.coffee b/src/atom.coffee index 75c83fd49..5bf5b87d7 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -149,9 +149,7 @@ class Atom extends Model configDirPath = @getConfigDirPath() @config = new Config({configDirPath, resourcePath}) - @keymap = new Keymap - @keymap.configDirPath = configDirPath - @keymap.resourcePath = resourcePath + @keymap = new Keymap({configDirPath, resourcePath}) @packages = new PackageManager({devMode, configDirPath, resourcePath}) @themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath}) @contextMenu = new ContextMenuManager(devMode) From ff23f62c3e87c8a721537d3a875cff9ae612e2fd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 12:50:24 -0600 Subject: [PATCH 16/17] Move jQuery.Event::abortKeyBinding to keymap-extensions --- src/keymap-extensions.coffee | 6 ++++++ src/space-pen-extensions.coffee | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee index a6aa5524f..c86e82b11 100644 --- a/src/keymap-extensions.coffee +++ b/src/keymap-extensions.coffee @@ -2,6 +2,7 @@ fs = require 'fs-plus' path = require 'path' Keymap = require 'atom-keymap' CSON = require 'season' +{jQuery} = require 'space-pen' Keymap::loadBundledKeymaps = -> @loadKeyBindings(path.join(@resourcePath, 'keymaps')) @@ -18,4 +19,9 @@ Keymap::loadUserKeymap = -> if fs.isFileSync(userKeymapPath) @loadKeyBindings(userKeymapPath, watch: true, suppressErrors: true) +# This enables command handlers registered via jQuery to call +# `.abortKeyBinding()` on the `jQuery.Event` object passed to the handler. +jQuery.Event::abortKeyBinding = -> + @originalEvent?.abortKeyBinding?() + module.exports = Keymap diff --git a/src/space-pen-extensions.coffee b/src/space-pen-extensions.coffee index 02850180a..04e411114 100644 --- a/src/space-pen-extensions.coffee +++ b/src/space-pen-extensions.coffee @@ -69,7 +69,4 @@ jQuery(document.body).on 'show.bs.tooltip', ({target}) -> jQuery.fn.setTooltip.getKeystroke = getKeystroke jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes -jQuery.Event::abortKeyBinding = -> - @originalEvent?.abortKeyBinding?() - module.exports = spacePen From 8a333208a2db3316c1aacfd02cd78e22913ddcb1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 17 Mar 2014 13:51:25 -0600 Subject: [PATCH 17/17] Upgrade to atom-keymap@0.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7aaec6e0..ba3899500 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "atomShellVersion": "0.10.7", "dependencies": { "async": "0.2.6", - "atom-keymap": "^0.7.0", + "atom-keymap": "^0.8.0", "bootstrap": "git://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372", "clear-cut": "0.4.0", "coffee-script": "1.7.0",