From 9914085ead23cf3173b6c621c4b984e203cf978c Mon Sep 17 00:00:00 2001 From: probablycorey Date: Mon, 4 Nov 2013 11:58:30 -0800 Subject: [PATCH 01/32] Simplify keymap selector matching --- src/binding-set.coffee | 10 ++--- src/keymap.coffee | 79 +++++++++++++-------------------- src/window-event-handler.coffee | 3 +- 3 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 691206dc2..0e616b5da 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -35,13 +35,13 @@ class BindingSet getCommandsByKeystrokes: -> @commandsByKeystrokes - commandForEvent: (event) -> - for keystrokes, command of @commandsByKeystrokes - return command if event.keystrokes == keystrokes + commandForKeystrokes: (keystrokes) -> + for commandKeystrokes, command of @commandsByKeystrokes + return command if commandKeystrokes == keystrokes null - matchesKeystrokePrefix: (event) -> - eventKeystrokes = event.keystrokes.split(' ') + matchesKeystrokePrefix: (keystrokes) -> + eventKeystrokes = keystrokes.split(' ') for keystrokes, command of @commandsByKeystrokes bindingKeystrokes = keystrokes.split(' ') continue unless eventKeystrokes.length < bindingKeystrokes.length diff --git a/src/keymap.coffee b/src/keymap.coffee index e09fa7aff..98225560d 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -115,65 +115,50 @@ class Keymap if bindingSet _.remove(@bindingSets, bindingSet) - bindingsForElement: (element) -> - keystrokeMap = {} - currentNode = $(element) - - while currentNode.length - bindingSets = @bindingSetsForNode(currentNode) - _.defaults(keystrokeMap, set.commandsByKeystrokes) for set in bindingSets - currentNode = currentNode.parent() - - keystrokeMap - - handleKeyEvent: (event) => - event.keystrokes = @multiKeystrokeStringForEvent(event) - isMultiKeystroke = @queuedKeystrokes? + handleKeyEvent: (event) -> + target = event.target + target = rootView[0] if target == document.body + keystrokes = @multiKeystrokeStringForEvent(event) + shouldBubble = undefined @queuedKeystrokes = null - firstKeystroke = event.keystrokes.split(' ')[0] - bindingSetsForFirstKeystroke = @bindingSetsByFirstKeystroke[firstKeystroke] - if bindingSetsForFirstKeystroke? - currentNode = $(event.target) - currentNode = rootView if currentNode is $('body')[0] - while currentNode.length - candidateBindingSets = @bindingSetsForNode(currentNode, bindingSetsForFirstKeystroke) - for bindingSet in candidateBindingSets - command = bindingSet.commandForEvent(event) - if command is 'native!' - return true - else if command - continue if @triggerCommandEvent(event, command) - return false - else if command == false - return false + for bindingSet in @sortedBindingSets(target, keystrokes) + if isMultiKeystroke = bindingSet.matchesKeystrokePrefix(keystrokes) + @queuedKeystrokes = keystrokes + shouldBubble = false + else + command = bindingSet.commandForKeystrokes(keystrokes) + console.log bindingSet + if command is 'native!' then shouldBubble = true + else if @triggerCommandEvent(target, command) then shouldBubble = false - if bindingSet.matchesKeystrokePrefix(event) - @queuedKeystrokes = event.keystrokes - return false - currentNode = currentNode.parent() + break if shouldBubble? - return false if isMultiKeystroke - return false if firstKeystroke is 'tab' + shouldBubble + + sortedBindingSets: (target, keystrokes) -> + firstKeystroke = keystrokes.split(' ')[0] + + bindingSets = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] + bindingSets = bindingSets.filter (bindingSet) -> + element = $(bindingSet.selector) + element.is(target) or element.has(target).length > 0 + + return [] unless bindingSets.length > 0 + for binding in bindingSets + console.log binding.selector, binding.specificity - bindingSetsForNode: (node, candidateBindingSets = @bindingSets) -> - bindingSets = candidateBindingSets.filter (set) -> node.is(set.selector) bindingSets.sort (a, b) -> if b.specificity == a.specificity b.index - a.index else b.specificity - a.specificity - triggerCommandEvent: (keyEvent, commandName) -> - keyEvent.target = rootView[0] if keyEvent.target == document.body and window.rootView + triggerCommandEvent: (target, commandName) -> commandEvent = $.Event(commandName) - commandEvent.keyEvent = keyEvent - aborted = false - commandEvent.abortKeyBinding = -> - @stopImmediatePropagation() - aborted = true - $(keyEvent.target).trigger(commandEvent) - aborted + commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() + $(target).trigger(commandEvent) + not commandEvent.isImmediatePropagationStopped() multiKeystrokeStringForEvent: (event) -> currentKeystroke = @keystrokeStringForEvent(event) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 22f7fc5f3..3c54510d8 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -54,7 +54,8 @@ class WindowEventHandler @subscribeToCommand $(document), 'core:focus-previous', @focusPrevious - @subscribe $(document), 'keydown', atom.keymap.handleKeyEvent + @subscribe $(document), 'keydown', (event) => + atom.keymap.handleKeyEvent(event) @subscribe $(document), 'drop', (e) -> e.preventDefault() From 94bc4ce7374edb6a44cc26eda7a47d6c1ea2c466 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Nov 2013 09:40:02 -0800 Subject: [PATCH 02/32] Update clear-cut --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1613af7f3..157095029 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "async": "0.2.6", "bootstrap": "git://github.com/twbs/bootstrap.git#v3.0.0", - "clear-cut": "0.1.0", + "clear-cut": "0.2.0", "coffee-script": "1.6.3", "coffeestack": "0.6.0", "emissary": "0.9.0", From 8c8f1bc048f3c6c8c3f287f2e57225c1e3381509 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Tue, 5 Nov 2013 10:31:43 -0800 Subject: [PATCH 03/32] Make keymap changes backwards compatible --- src/binding-set.coffee | 17 +++++------- src/keymap.coffee | 61 +++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 0e616b5da..90531ccc0 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -35,18 +35,13 @@ class BindingSet getCommandsByKeystrokes: -> @commandsByKeystrokes - commandForKeystrokes: (keystrokes) -> - for commandKeystrokes, command of @commandsByKeystrokes - return command if commandKeystrokes == keystrokes - null - - matchesKeystrokePrefix: (keystrokes) -> - eventKeystrokes = keystrokes.split(' ') + commandForKeystrokes: (keystrokesToMatch) -> + keyStrokesRegex = new RegExp("^" + _.escapeRegExp(keystrokesToMatch)) for keystrokes, command of @commandsByKeystrokes - bindingKeystrokes = keystrokes.split(' ') - continue unless eventKeystrokes.length < bindingKeystrokes.length - return true if _.isEqual(eventKeystrokes, bindingKeystrokes[0...eventKeystrokes.length]) - false + if keyStrokesRegex.test(keystrokes) + multiKeystrokes = keystrokesToMatch isnt keystrokes + return {command, multiKeystrokes} + null normalizeCommandsByKeystrokes: (commandsByKeystrokes) -> normalizedCommandsByKeystrokes = {} diff --git a/src/keymap.coffee b/src/keymap.coffee index 98225560d..b7ba51a0d 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -116,37 +116,42 @@ class Keymap _.remove(@bindingSets, bindingSet) handleKeyEvent: (event) -> - target = event.target - target = rootView[0] if target == document.body - keystrokes = @multiKeystrokeStringForEvent(event) - shouldBubble = undefined + element = event.target + element = rootView[0] if element == document.body + keystrokes = @keystrokeStringForEvent(event, @queuedKeystrokes) @queuedKeystrokes = null + shouldBubble = undefined - for bindingSet in @sortedBindingSets(target, keystrokes) - if isMultiKeystroke = bindingSet.matchesKeystrokePrefix(keystrokes) + for {command, multiKeystrokes} in @commandsForKeystrokes(keystrokes, element) + if multiKeystrokes @queuedKeystrokes = keystrokes shouldBubble = false else - command = bindingSet.commandForKeystrokes(keystrokes) - console.log bindingSet if command is 'native!' then shouldBubble = true - else if @triggerCommandEvent(target, command) then shouldBubble = false + else if @triggerCommandEvent(element, command) then shouldBubble = false break if shouldBubble? shouldBubble - sortedBindingSets: (target, keystrokes) -> - firstKeystroke = keystrokes.split(' ')[0] + bindingsForElement: (element) -> + keystrokesMap = {} + for bindingSet in @bindingSetsForElement(element) + _.defaults(keystrokesMap, bindingSet.commandsByKeystrokes) - bindingSets = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] + keystrokesMap + + commandsForKeystrokes: (keystrokes, element) -> + firstKeystroke = keystrokes.split(' ')[0] + bindingSetsForKeystroke = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] + @bindingSetsForElement(element, bindingSetsForKeystroke).map (bindingSet) -> + bindingSet.commandForKeystrokes(keystrokes) + + bindingSetsForElement: (element, bindingSets=@bindingSets) -> bindingSets = bindingSets.filter (bindingSet) -> - element = $(bindingSet.selector) - element.is(target) or element.has(target).length > 0 + $(element).closest(bindingSet.selector).length > 0 return [] unless bindingSets.length > 0 - for binding in bindingSets - console.log binding.selector, binding.specificity bindingSets.sort (a, b) -> if b.specificity == a.specificity @@ -154,23 +159,13 @@ class Keymap else b.specificity - a.specificity - triggerCommandEvent: (target, commandName) -> + triggerCommandEvent: (element, commandName) -> commandEvent = $.Event(commandName) commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() - $(target).trigger(commandEvent) + $(element).trigger(commandEvent) not commandEvent.isImmediatePropagationStopped() - multiKeystrokeStringForEvent: (event) -> - currentKeystroke = @keystrokeStringForEvent(event) - if @queuedKeystrokes - if currentKeystroke in Modifiers - @queuedKeystrokes - else - @queuedKeystrokes + ' ' + currentKeystroke - else - currentKeystroke - - keystrokeStringForEvent: (event) -> + keystrokeStringForEvent: (event, prefix) -> if event.originalEvent.keyIdentifier.indexOf('U+') == 0 hexCharCode = event.originalEvent.keyIdentifier[2..] charCode = parseInt(hexCharCode, 16) @@ -193,7 +188,13 @@ class Keymap else key = key.toLowerCase() - [modifiers..., key].join('-') + keystrokes = [modifiers..., key].join('-') + + if prefix + if keystrokes in Modifiers then prefix + else "#{prefix} #{keystroke}" + else + keystrokes keystrokesByCommandForSelector: (selector)-> keystrokesByCommand = {} From d71e58ec33132333c3b31cdd275ce608b7d868c7 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Mon, 11 Nov 2013 14:05:48 -0800 Subject: [PATCH 04/32] Use `keystroke` even when referring to multiKeystrokes The use of keystroke and keystrokes was becoming confusing. When I started thinking of a keystroke as a set of keys that trigger a command it became easier to think about. --- spec/keymap-spec.coffee | 20 +++---- src/binding-set.coffee | 47 ++++++++-------- src/browser/application-menu.coffee | 32 +++++------ src/browser/atom-application.coffee | 4 +- src/keymap.coffee | 84 ++++++++++++++--------------- src/menu-manager.coffee | 18 +++---- 6 files changed, 101 insertions(+), 104 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 6bb2cc837..48262c7dd 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -32,15 +32,15 @@ describe "Keymap", -> fragment.on 'deleteChar', deleteCharHandler fragment.on 'insertChar', insertCharHandler - it "adds a 'keystrokes' string to the event object", -> + it "adds a 'keystroke' string to the event object", -> event = keydownEvent('x', altKey: true, metaKey: true) keymap.handleKeyEvent(event) - expect(event.keystrokes).toBe 'alt-meta-x' + expect(event.keystroke).toBe 'alt-meta-x' event = keydownEvent(',', metaKey: true) event.which = 188 keymap.handleKeyEvent(event) - expect(event.keystrokes).toBe 'meta-,' + expect(event.keystroke).toBe 'meta-,' describe "when no binding matches the event's keystroke", -> it "does not return false so the event continues to propagate", -> @@ -51,7 +51,7 @@ describe "Keymap", -> event = keydownEvent('U+03B6', metaKey: true) # This is the 'z' key using the Greek keyboard layout event.which = 122 keymap.handleKeyEvent(event) - expect(event.keystrokes).toBe 'meta-z' + expect(event.keystroke).toBe 'meta-z' 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", -> @@ -70,7 +70,7 @@ describe "Keymap", -> expect(insertCharHandler).toHaveBeenCalled() commandEvent = insertCharHandler.argsForCall[0][0] expect(commandEvent.keyEvent).toBe event - expect(event.keystrokes).toBe 'x' + expect(event.keystroke).toBe 'x' 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", -> @@ -221,7 +221,7 @@ describe "Keymap", -> expect(closeOtherWindowsHandler).toHaveBeenCalled() describe "when a second keystroke added to the first doesn't match any bindings", -> - it "clears the queued keystrokes without triggering any events", -> + 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(quitHandler).not.toHaveBeenCalled() @@ -259,19 +259,19 @@ describe "Keymap", -> it "returns false to prevent the browser from transferring focus", -> expect(keymap.handleKeyEvent(keydownEvent('U+0009', target: fragment[0]))).toBe false - describe ".keystrokesByCommandForSelector(selector)", -> + describe ".keystrokeByCommandForSelector(selector)", -> it "returns a hash of all commands and their keybindings", -> keymap.bindKeys 'body', 'a': 'letter' keymap.bindKeys '.editor', 'b': 'letter' keymap.bindKeys '.editor', '1': 'number' keymap.bindKeys '.editor', 'meta-alt-1': 'number-with-modifiers' - expect(keymap.keystrokesByCommandForSelector()).toEqual + expect(keymap.keystrokeByCommandForSelector()).toEqual 'letter': ['b', 'a'] 'number': ['1'] 'number-with-modifiers': ['alt-meta-1'] - expect(keymap.keystrokesByCommandForSelector('.editor')).toEqual + expect(keymap.keystrokeByCommandForSelector('.editor')).toEqual 'letter': ['b'] 'number': ['1'] 'number-with-modifiers': ['alt-meta-1'] @@ -360,7 +360,7 @@ describe "Keymap", -> mappings = keymap.getAllKeyMappings() expect(mappings.length).toBe 1 expect(mappings[0].source).toEqual 'dummy' - expect(mappings[0].keystrokes).toEqual 'k' + expect(mappings[0].keystroke).toEqual 'k' expect(mappings[0].command).toEqual 'c' expect(mappings[0].selector).toEqual '.command-mode' diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 90531ccc0..565784233 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -12,16 +12,16 @@ class BindingSet @parser: null selector: null - commandsByKeystrokes: null + commandsByKeystroke: null parser: null name: null - constructor: (selector, commandsByKeystrokes, @index, @name) -> + constructor: (selector, commandsByKeystroke, @index, @name) -> keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') BindingSet.parser ?= PEG.buildParser(keystrokePattern) @specificity = specificity(selector) @selector = selector.replace(/!important/g, '') - @commandsByKeystrokes = @normalizeCommandsByKeystrokes(commandsByKeystrokes) + @commandsByKeystroke = @normalizeCommandsByKeystroke(commandsByKeystroke) # Private: getName: -> @@ -32,30 +32,27 @@ class BindingSet @selector # Private: - getCommandsByKeystrokes: -> - @commandsByKeystrokes + getCommandsByKeystroke: -> + @commandsByKeystroke - commandForKeystrokes: (keystrokesToMatch) -> - keyStrokesRegex = new RegExp("^" + _.escapeRegExp(keystrokesToMatch)) - for keystrokes, command of @commandsByKeystrokes - if keyStrokesRegex.test(keystrokes) - multiKeystrokes = keystrokesToMatch isnt keystrokes - return {command, multiKeystrokes} + commandForKeystroke: (keystrokeToMatch) -> + keyStrokeRegex = new RegExp("^" + _.escapeRegExp(keystrokeToMatch)) + for keystroke, command of @commandsByKeystroke + if keyStrokeRegex.test(keystroke) + isMultiKeystroke = keystrokeToMatch isnt keystroke + return {command, isMultiKeystroke} null - normalizeCommandsByKeystrokes: (commandsByKeystrokes) -> - normalizedCommandsByKeystrokes = {} - for keystrokes, command of commandsByKeystrokes - normalizedCommandsByKeystrokes[@normalizeKeystrokes(keystrokes)] = command - normalizedCommandsByKeystrokes - - normalizeKeystrokes: (keystrokes) -> - normalizedKeystrokes = keystrokes.split(/\s+/).map (keystroke) => - @normalizeKeystroke(keystroke) - normalizedKeystrokes.join(' ') + normalizeCommandsByKeystroke: (commandsByKeystroke) -> + normalizedCommandsByKeystroke = {} + for keystroke, command of commandsByKeystroke + normalizedCommandsByKeystroke[@normalizeKeystroke(keystroke)] = command + normalizedCommandsByKeystroke normalizeKeystroke: (keystroke) -> - keys = BindingSet.parser.parse(keystroke) - modifiers = keys[0...-1] - modifiers.sort() - [modifiers..., _.last(keys)].join('-') + normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => + keys = BindingSet.parser.parse(keystroke) + modifiers = keys[0...-1] + modifiers.sort() + [modifiers..., _.last(keys)].join('-') + normalizedKeystroke.join(' ') diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 57a0d7e81..a7b2f485f 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -20,11 +20,11 @@ class ApplicationMenu # # * template: # The Object which describes the menu to display. - # * keystrokesByCommand: + # * keystrokeByCommand: # An Object where the keys are commands and the values are Arrays containing - # the keystrokes. - update: (template, keystrokesByCommand) -> - @translateTemplate(template, keystrokesByCommand) + # the keystroke. + update: (template, keystrokeByCommand) -> + @translateTemplate(template, keystrokeByCommand) @substituteVersion(template) @menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(@menu) @@ -97,41 +97,41 @@ class ApplicationMenu ] ] - # Private: Combines a menu template with the appropriate keystrokes. + # Private: Combines a menu template with the appropriate keystroke. # # * template: # An Object conforming to atom-shell's menu api but lacking accelerator and # click properties. - # * keystrokesByCommand: + # * keystrokeByCommand: # An Object where the keys are commands and the values are Arrays containing - # the keystrokes. + # the keystroke. # # Returns a complete menu configuration object for atom-shell's menu API. - translateTemplate: (template, keystrokesByCommand) -> + translateTemplate: (template, keystrokeByCommand) -> template.forEach (item) => item.metadata = {} if item.command - item.accelerator = @acceleratorForCommand(item.command, keystrokesByCommand) + item.accelerator = @acceleratorForCommand(item.command, keystrokeByCommand) item.click = => global.atomApplication.sendCommand(item.command) item.metadata['windowSpecific'] = true unless /^application:/.test(item.command) - @translateTemplate(item.submenu, keystrokesByCommand) if item.submenu + @translateTemplate(item.submenu, keystrokeByCommand) if item.submenu template # Private: Determine the accelerator for a given command. # # * command: # The name of the command. - # * keystrokesByCommand: + # * keystrokeByCommand: # An Object where the keys are commands and the values are Arrays containing - # the keystrokes. + # the keystroke. # # Returns a String containing the keystroke in a format that can be interpreted # by atom shell to provide nice icons where available. - acceleratorForCommand: (command, keystrokesByCommand) -> - keystroke = keystrokesByCommand[command]?[0] - return null unless keystroke + acceleratorForCommand: (command, keystrokeByCommand) -> + firstKeystroke = keystrokeByCommand[command]?[0] + return null unless firstKeystroke - modifiers = keystroke.split('-') + modifiers = firstKeystroke.split('-') key = modifiers.pop() modifiers.push("Shift") if key != key.toLowerCase() diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 0bc6d0add..263a9a26f 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -176,8 +176,8 @@ class AtomApplication else @promptForPath() - ipc.on 'update-application-menu', (processId, routingId, template, keystrokesByCommand) => - @applicationMenu.update(template, keystrokesByCommand) + ipc.on 'update-application-menu', (processId, routingId, template, keystrokeByCommand) => + @applicationMenu.update(template, keystrokeByCommand) ipc.on 'run-package-specs', (processId, routingId, specDirectory) => @runSpecs({resourcePath: global.devResourcePath, specDirectory: specDirectory, exitWhenDone: false}) diff --git a/src/keymap.coffee b/src/keymap.coffee index b7ba51a0d..b4080bdd5 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -28,7 +28,7 @@ class Keymap bindingSets: null nextBindingSetIndex: 0 bindingSetsByFirstKeystroke: null - queuedKeystrokes: null + queuedKeystroke: null constructor: ({resourcePath, @configDirPath})-> @bundledKeymapsDirPath = path.join(resourcePath, "keymaps") @@ -58,22 +58,9 @@ class Keymap remove: (name) -> for bindingSet in @bindingSets.filter((bindingSet) -> bindingSet.name is name) _.remove(@bindingSets, bindingSet) - for keystrokes of bindingSet.commandsByKeystrokes - keystroke = keystrokes.split(' ')[0] - _.remove(@bindingSetsByFirstKeystroke[keystroke], bindingSet) - - # Public: Returns an array of objects that represent every keystroke to - # command mapping. Each object contains the following keys `source`, - # `selector`, `command`, `keystrokes`. - getAllKeyMappings: -> - mappings = [] - for bindingSet in @bindingSets - selector = bindingSet.getSelector() - source = @determineSource(bindingSet.getName()) - for keystrokes, command of bindingSet.getCommandsByKeystrokes() - mappings.push {keystrokes, command, selector, source} - - mappings + for keystroke of bindingSet.commandsByKeystroke + firstKeystroke = keystroke.split(' ')[0] + _.remove(@bindingSetsByFirstKeystroke[firstKeystroke], bindingSet) # Private: Returns a user friendly description of where a keybinding was # loaded from. @@ -103,8 +90,8 @@ class Keymap [selector, bindings] = args bindingSet = new BindingSet(selector, bindings, @nextBindingSetIndex++, name) @bindingSets.unshift(bindingSet) - for keystrokes of bindingSet.commandsByKeystrokes - keystroke = keystrokes.split(' ')[0] # only index by first keystroke + for keystroke of bindingSet.commandsByKeystroke + keystroke = keystroke.split(' ')[0] # only index by first keystroke @bindingSetsByFirstKeystroke[keystroke] ?= [] @bindingSetsByFirstKeystroke[keystroke].push(bindingSet) @@ -118,13 +105,13 @@ class Keymap handleKeyEvent: (event) -> element = event.target element = rootView[0] if element == document.body - keystrokes = @keystrokeStringForEvent(event, @queuedKeystrokes) - @queuedKeystrokes = null + keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) + @queuedKeystroke = null shouldBubble = undefined - for {command, multiKeystrokes} in @commandsForKeystrokes(keystrokes, element) - if multiKeystrokes - @queuedKeystrokes = keystrokes + for {command, isMultiKeystroke} in @commandsForKeystroke(keystroke, element) + if isMultiKeystroke + @queuedKeystroke = keystroke shouldBubble = false else if command is 'native!' then shouldBubble = true @@ -134,18 +121,31 @@ class Keymap shouldBubble + # Public: Returns an array of objects that represent every keystroke to + # command mapping. Each object contains the following keys `source`, + # `selector`, `command`, `keystroke`. + getAllKeyMappings: -> + mappings = [] + for bindingSet in @bindingSets + selector = bindingSet.getSelector() + source = @determineSource(bindingSet.getName()) + for keystroke, command of bindingSet.getCommandsByKeystroke() + mappings.push {keystroke, command, selector, source} + + mappings + bindingsForElement: (element) -> - keystrokesMap = {} + keystrokeMap = {} for bindingSet in @bindingSetsForElement(element) - _.defaults(keystrokesMap, bindingSet.commandsByKeystrokes) + _.defaults(keystrokeMap, bindingSet.commandsByKeystroke) - keystrokesMap + keystrokeMap - commandsForKeystrokes: (keystrokes, element) -> - firstKeystroke = keystrokes.split(' ')[0] + commandsForKeystroke: (keystroke, element) -> + firstKeystroke = keystroke.split(' ')[0] bindingSetsForKeystroke = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] @bindingSetsForElement(element, bindingSetsForKeystroke).map (bindingSet) -> - bindingSet.commandForKeystrokes(keystrokes) + bindingSet.commandForKeystroke(keystroke) bindingSetsForElement: (element, bindingSets=@bindingSets) -> bindingSets = bindingSets.filter (bindingSet) -> @@ -165,7 +165,7 @@ class Keymap $(element).trigger(commandEvent) not commandEvent.isImmediatePropagationStopped() - keystrokeStringForEvent: (event, prefix) -> + keystrokeStringForEvent: (event, previousKeystroke) -> if event.originalEvent.keyIdentifier.indexOf('U+') == 0 hexCharCode = event.originalEvent.keyIdentifier[2..] charCode = parseInt(hexCharCode, 16) @@ -188,22 +188,22 @@ class Keymap else key = key.toLowerCase() - keystrokes = [modifiers..., key].join('-') + keystroke = [modifiers..., key].join('-') - if prefix - if keystrokes in Modifiers then prefix - else "#{prefix} #{keystroke}" + if previousKeystroke + if keystroke in Modifiers then previousKeystroke + else "#{previousKeystroke} #{keystroke}" else - keystrokes + keystroke - keystrokesByCommandForSelector: (selector)-> - keystrokesByCommand = {} + keystrokeByCommandForSelector: (selector)-> + keystrokeByCommand = {} for bindingSet in @bindingSets - for keystroke, command of bindingSet.commandsByKeystrokes + for keystroke, command of bindingSet.commandsByKeystroke continue if selector? and selector != bindingSet.selector - keystrokesByCommand[command] ?= [] - keystrokesByCommand[command].push keystroke - keystrokesByCommand + keystrokeByCommand[command] ?= [] + keystrokeByCommand[command].push keystroke + keystrokeByCommand isAscii: (charCode) -> 0 <= charCode <= 127 diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index f14790d06..448ebc0eb 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -29,10 +29,10 @@ class MenuManager # Public: Refreshes the currently visible menu. update: -> - keystrokesByCommand = atom.keymap.keystrokesByCommandForSelector('body') - _.extend(keystrokesByCommand, atom.keymap.keystrokesByCommandForSelector('.editor')) - _.extend(keystrokesByCommand, atom.keymap.keystrokesByCommandForSelector('.editor:not(.mini)')) - @sendToBrowserProcess(@template, keystrokesByCommand) + keystrokeByCommand = atom.keymap.keystrokeByCommandForSelector('body') + _.extend(keystrokeByCommand, atom.keymap.keystrokeByCommandForSelector('.editor')) + _.extend(keystrokeByCommand, atom.keymap.keystrokeByCommandForSelector('.editor:not(.mini)')) + @sendToBrowserProcess(@template, keystrokeByCommand) # Private loadCoreItems: -> @@ -54,9 +54,9 @@ class MenuManager # Private: OSX can't handle displaying accelerators for multiple keystrokes. # If they are sent across, it will stop processing accelerators for the rest # of the menu items. - filterMultipleKeystrokes: (keystrokesByCommand) -> + filterMultipleKeystroke: (keystrokeByCommand) -> filtered = {} - for key, bindings of keystrokesByCommand + for key, bindings of keystrokeByCommand for binding in bindings continue if binding.indexOf(' ') != -1 @@ -65,6 +65,6 @@ class MenuManager filtered # Private - sendToBrowserProcess: (template, keystrokesByCommand) -> - keystrokesByCommand = @filterMultipleKeystrokes(keystrokesByCommand) - ipc.sendChannel 'update-application-menu', template, keystrokesByCommand + sendToBrowserProcess: (template, keystrokeByCommand) -> + keystrokeByCommand = @filterMultipleKeystroke(keystrokeByCommand) + ipc.sendChannel 'update-application-menu', template, keystrokeByCommand From 8f9f5ed0ed58d0d9e62b46af79d254b0564bbd93 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Mon, 11 Nov 2013 15:46:17 -0800 Subject: [PATCH 05/32] Update specs --- spec/keymap-spec.coffee | 31 ++++++++++--------------------- src/binding-set.coffee | 6 +++--- src/keymap.coffee | 19 +++++++++++++------ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 48262c7dd..be081e782 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -22,25 +22,19 @@ describe "Keymap", -> describe ".handleKeyEvent(event)", -> deleteCharHandler = null insertCharHandler = null + metaZHandler = null beforeEach -> keymap.bindKeys '.command-mode', 'x': 'deleteChar' keymap.bindKeys '.insert-mode', 'x': 'insertChar' + keymap.bindKeys '.command-mode', 'meta-z': 'metaZPressed' deleteCharHandler = jasmine.createSpy('deleteCharHandler') insertCharHandler = jasmine.createSpy('insertCharHandler') + metaZHandler = jasmine.createSpy('metaZHandler') fragment.on 'deleteChar', deleteCharHandler fragment.on 'insertChar', insertCharHandler - - it "adds a 'keystroke' string to the event object", -> - event = keydownEvent('x', altKey: true, metaKey: true) - keymap.handleKeyEvent(event) - expect(event.keystroke).toBe 'alt-meta-x' - - event = keydownEvent(',', metaKey: true) - event.which = 188 - keymap.handleKeyEvent(event) - expect(event.keystroke).toBe 'meta-,' + fragment.on 'metaZPressed', metaZHandler describe "when no binding matches the event's keystroke", -> it "does not return false so the event continues to propagate", -> @@ -48,10 +42,11 @@ describe "Keymap", -> 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) # This is the 'z' key using the Greek keyboard layout - event.which = 122 - keymap.handleKeyEvent(event) - expect(event.keystroke).toBe 'meta-z' + 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(metaZHandler).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", -> @@ -68,9 +63,6 @@ describe "Keymap", -> keymap.handleKeyEvent(event) expect(deleteCharHandler).not.toHaveBeenCalled() expect(insertCharHandler).toHaveBeenCalled() - commandEvent = insertCharHandler.argsForCall[0][0] - expect(commandEvent.keyEvent).toBe event - expect(event.keystroke).toBe 'x' 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", -> @@ -169,6 +161,7 @@ describe "Keymap", -> describe "when the event's target is the document body", -> it "triggers the mapped event on the rootView", -> window.rootView = new RootView + rootView.attachToDom() keymap.bindKeys 'body', 'x': 'foo' fooHandler = jasmine.createSpy("fooHandler") rootView.on 'foo', fooHandler @@ -255,10 +248,6 @@ describe "Keymap", -> describe "when there is a complete binding with a more specific selector", -> it "favors the more specific complete match", -> - describe "when a tab keystroke does not match any bindings", -> - it "returns false to prevent the browser from transferring focus", -> - expect(keymap.handleKeyEvent(keydownEvent('U+0009', target: fragment[0]))).toBe false - describe ".keystrokeByCommandForSelector(selector)", -> it "returns a hash of all commands and their keybindings", -> keymap.bindKeys 'body', 'a': 'letter' diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 565784233..9cb5146d1 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -36,11 +36,11 @@ class BindingSet @commandsByKeystroke commandForKeystroke: (keystrokeToMatch) -> - keyStrokeRegex = new RegExp("^" + _.escapeRegExp(keystrokeToMatch)) + keyStrokeRegex = new RegExp("^" + _.escapeRegExp(keystrokeToMatch) + "( |$)") for keystroke, command of @commandsByKeystroke if keyStrokeRegex.test(keystroke) - isMultiKeystroke = keystrokeToMatch isnt keystroke - return {command, isMultiKeystroke} + partialMatch = keystrokeToMatch isnt keystroke + return {command, partialMatch} null normalizeCommandsByKeystroke: (commandsByKeystroke) -> diff --git a/src/keymap.coffee b/src/keymap.coffee index b4080bdd5..49a6926eb 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -106,11 +106,16 @@ class Keymap element = event.target element = rootView[0] if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - @queuedKeystroke = null - shouldBubble = undefined + commands = @commandsForKeystroke(keystroke, element) - for {command, isMultiKeystroke} in @commandsForKeystroke(keystroke, element) - if isMultiKeystroke + if commands.length == 0 and @queuedKeystroke + @queuedKeystroke = null + return false + else + @queuedKeystroke = null + + for {command, partialMatch} in commands + if partialMatch @queuedKeystroke = keystroke shouldBubble = false else @@ -119,7 +124,7 @@ class Keymap break if shouldBubble? - shouldBubble + shouldBubble ? true # Public: Returns an array of objects that represent every keystroke to # command mapping. Each object contains the following keys `source`, @@ -144,9 +149,11 @@ class Keymap commandsForKeystroke: (keystroke, element) -> firstKeystroke = keystroke.split(' ')[0] bindingSetsForKeystroke = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] - @bindingSetsForElement(element, bindingSetsForKeystroke).map (bindingSet) -> + commands = @bindingSetsForElement(element, bindingSetsForKeystroke).map (bindingSet) -> bindingSet.commandForKeystroke(keystroke) + _.compact(commands) + bindingSetsForElement: (element, bindingSets=@bindingSets) -> bindingSets = bindingSets.filter (bindingSet) -> $(element).closest(bindingSet.selector).length > 0 From 21060ae85a8560b25a793f33b6eb2883b5fd1076 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 08:33:23 -0800 Subject: [PATCH 06/32] Update public keymap interface --- src/binding-set.coffee | 8 ---- src/keymap.coffee | 94 +++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 9cb5146d1..51326067b 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -35,14 +35,6 @@ class BindingSet getCommandsByKeystroke: -> @commandsByKeystroke - commandForKeystroke: (keystrokeToMatch) -> - keyStrokeRegex = new RegExp("^" + _.escapeRegExp(keystrokeToMatch) + "( |$)") - for keystroke, command of @commandsByKeystroke - if keyStrokeRegex.test(keystroke) - partialMatch = keystrokeToMatch isnt keystroke - return {command, partialMatch} - null - normalizeCommandsByKeystroke: (commandsByKeystroke) -> normalizedCommandsByKeystroke = {} for keystroke, command of commandsByKeystroke diff --git a/src/keymap.coffee b/src/keymap.coffee index 49a6926eb..bb10a65b5 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -106,66 +106,59 @@ class Keymap element = event.target element = rootView[0] if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - commands = @commandsForKeystroke(keystroke, element) + mappings = @mappingsForKeystroke(keystroke) + mappings = @mappingsMatchingElement(mappings, element) - if commands.length == 0 and @queuedKeystroke + if mappings.length == 0 and @queuedKeystroke @queuedKeystroke = null return false else @queuedKeystroke = null - for {command, partialMatch} in commands + for mapping in mappings + partialMatch = mapping.keystroke isnt keystroke if partialMatch @queuedKeystroke = keystroke shouldBubble = false else - if command is 'native!' then shouldBubble = true - else if @triggerCommandEvent(element, command) then shouldBubble = false + if mapping.command is 'native!' then shouldBubble = true + else if @triggerCommandEvent(element, mapping.command) then shouldBubble = false break if shouldBubble? shouldBubble ? true - # Public: Returns an array of objects that represent every keystroke to - # command mapping. Each object contains the following keys `source`, - # `selector`, `command`, `keystroke`. - getAllKeyMappings: -> + allMappings: -> mappings = [] for bindingSet in @bindingSets - selector = bindingSet.getSelector() - source = @determineSource(bindingSet.getName()) for keystroke, command of bindingSet.getCommandsByKeystroke() - mappings.push {keystroke, command, selector, source} + mappings.push @buildMapping(bindingSet, command, keystroke) mappings - bindingsForElement: (element) -> - keystrokeMap = {} - for bindingSet in @bindingSetsForElement(element) - _.defaults(keystrokeMap, bindingSet.commandsByKeystroke) + mappingsForKeystroke: (keystroke) -> + mappings = @allMappings().filter (mapping) -> + multiKeystroke = /\s/.test keystroke + if multiKeystroke + keystroke == mapping.keystroke + else + keystroke.split(' ')[0] == mapping.keystroke.split(' ')[0] - keystrokeMap - - commandsForKeystroke: (keystroke, element) -> - firstKeystroke = keystroke.split(' ')[0] - bindingSetsForKeystroke = @bindingSetsByFirstKeystroke[firstKeystroke] ? [] - commands = @bindingSetsForElement(element, bindingSetsForKeystroke).map (bindingSet) -> - bindingSet.commandForKeystroke(keystroke) - - _.compact(commands) - - bindingSetsForElement: (element, bindingSets=@bindingSets) -> - bindingSets = bindingSets.filter (bindingSet) -> - $(element).closest(bindingSet.selector).length > 0 - - return [] unless bindingSets.length > 0 - - bindingSets.sort (a, b) -> + mappingsMatchingElement: (mappings, element) -> + mappings = mappings.filter ({selector}) -> $(element).closest(selector).length > 0 + mappings.sort (a, b) -> if b.specificity == a.specificity b.index - a.index else b.specificity - a.specificity + buildMapping: (bindingSet, command, keystroke) -> + selector = bindingSet.selector + specificity = bindingSet.specificity + index = bindingSet.index + source = bindingSet.name + {command, keystroke, selector, specificity, source} + triggerCommandEvent: (element, commandName) -> commandEvent = $.Event(commandName) commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() @@ -203,15 +196,6 @@ class Keymap else keystroke - keystrokeByCommandForSelector: (selector)-> - keystrokeByCommand = {} - for bindingSet in @bindingSets - for keystroke, command of bindingSet.commandsByKeystroke - continue if selector? and selector != bindingSet.selector - keystrokeByCommand[command] ?= [] - keystrokeByCommand[command].push keystroke - keystrokeByCommand - isAscii: (charCode) -> 0 <= charCode <= 127 @@ -224,3 +208,29 @@ class Keymap when 32 then 'space' when 127 then 'delete' else String.fromCharCode(charCode) + + # + # Deprecated + # + + # Public: Returns an array of objects that represent every keystroke to + # command mapping. Each object contains the following keys `source`, + # `selector`, `command`, `keystroke`. + getAllKeyMappings: -> + @allMappings().map (mapping) => + mapping.source = @determineSource(mapping.source) + mapping + + bindingsForElement: (element) -> + keystrokeMap = {} + mappings = @mappingsMatchingElement(@allMappings(), element) + keystrokeMap[keystroke] ?= command for {command, keystroke} in mappings + keystrokeMap + + keystrokeByCommandForSelector: (selector)-> + keystrokeByCommand = {} + for mapping in @allMappings() + continue if selector? and selector != mapping.selector + keystrokeByCommand[mapping.command] ?= [] + keystrokeByCommand[mapping.command].push mapping.keystroke + keystrokeByCommand From c3aea1d1499c722e3d83a70c2440c99a3386cd81 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 14:18:54 -0800 Subject: [PATCH 07/32] Remove keymap.keystrokeByCommmandForSelector --- spec/keymap-spec.coffee | 18 ------------------ src/keymap.coffee | 21 +++++++++------------ src/menu-manager.coffee | 8 +++++--- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index be081e782..982a6a50e 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -248,24 +248,6 @@ describe "Keymap", -> describe "when there is a complete binding with a more specific selector", -> it "favors the more specific complete match", -> - describe ".keystrokeByCommandForSelector(selector)", -> - it "returns a hash of all commands and their keybindings", -> - keymap.bindKeys 'body', 'a': 'letter' - keymap.bindKeys '.editor', 'b': 'letter' - keymap.bindKeys '.editor', '1': 'number' - keymap.bindKeys '.editor', 'meta-alt-1': 'number-with-modifiers' - - expect(keymap.keystrokeByCommandForSelector()).toEqual - 'letter': ['b', 'a'] - 'number': ['1'] - 'number-with-modifiers': ['alt-meta-1'] - - expect(keymap.keystrokeByCommandForSelector('.editor')).toEqual - 'letter': ['b'] - 'number': ['1'] - 'number-with-modifiers': ['alt-meta-1'] - - describe ".bindKeys(selector, bindings)", -> it "normalizes the key patterns in the hash to put the modifiers in alphabetical order", -> fooHandler = jasmine.createSpy('fooHandler') diff --git a/src/keymap.coffee b/src/keymap.coffee index bb10a65b5..aaf44785a 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -121,8 +121,10 @@ class Keymap @queuedKeystroke = keystroke shouldBubble = false else - if mapping.command is 'native!' then shouldBubble = true - else if @triggerCommandEvent(element, mapping.command) then shouldBubble = false + if mapping.command is 'native!' + shouldBubble = true + else if @triggerCommandEvent(element, mapping.command) + shouldBubble = false break if shouldBubble? @@ -130,6 +132,7 @@ class Keymap allMappings: -> mappings = [] + for bindingSet in @bindingSets for keystroke, command of bindingSet.getCommandsByKeystroke() mappings.push @buildMapping(bindingSet, command, keystroke) @@ -191,8 +194,10 @@ class Keymap keystroke = [modifiers..., key].join('-') if previousKeystroke - if keystroke in Modifiers then previousKeystroke - else "#{previousKeystroke} #{keystroke}" + if keystroke in Modifiers + previousKeystroke + else + "#{previousKeystroke} #{keystroke}" else keystroke @@ -226,11 +231,3 @@ class Keymap mappings = @mappingsMatchingElement(@allMappings(), element) keystrokeMap[keystroke] ?= command for {command, keystroke} in mappings keystrokeMap - - keystrokeByCommandForSelector: (selector)-> - keystrokeByCommand = {} - for mapping in @allMappings() - continue if selector? and selector != mapping.selector - keystrokeByCommand[mapping.command] ?= [] - keystrokeByCommand[mapping.command].push mapping.keystroke - keystrokeByCommand diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index 448ebc0eb..e5f80c983 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -29,9 +29,11 @@ class MenuManager # Public: Refreshes the currently visible menu. update: -> - keystrokeByCommand = atom.keymap.keystrokeByCommandForSelector('body') - _.extend(keystrokeByCommand, atom.keymap.keystrokeByCommandForSelector('.editor')) - _.extend(keystrokeByCommand, atom.keymap.keystrokeByCommandForSelector('.editor:not(.mini)')) + keystrokesByCommand = {} + selectors = ['body', '.editor', '.editor:not(.mini)'] + for mapping in atom.keymap.allMappings() when mapping.selector in selectors + keystrokesByCommand[mapping.command] ?= [] + keystrokesByCommand[mapping.command].push mapping.keystroke @sendToBrowserProcess(@template, keystrokeByCommand) # Private From e90f19da978c547af69cb7274b71b10666e9fa1e Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 14:26:25 -0800 Subject: [PATCH 08/32] Rename keystrokeByCommmand to keystrokesByCommmand --- src/browser/application-menu.coffee | 20 ++++++++++---------- src/browser/atom-application.coffee | 4 ++-- src/menu-manager.coffee | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index a7b2f485f..7b8844bad 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -20,11 +20,11 @@ class ApplicationMenu # # * template: # The Object which describes the menu to display. - # * keystrokeByCommand: + # * keystrokesByCommand: # An Object where the keys are commands and the values are Arrays containing # the keystroke. - update: (template, keystrokeByCommand) -> - @translateTemplate(template, keystrokeByCommand) + update: (template, keystrokesByCommand) -> + @translateTemplate(template, keystrokesByCommand) @substituteVersion(template) @menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(@menu) @@ -102,33 +102,33 @@ class ApplicationMenu # * template: # An Object conforming to atom-shell's menu api but lacking accelerator and # click properties. - # * keystrokeByCommand: + # * keystrokesByCommand: # An Object where the keys are commands and the values are Arrays containing # the keystroke. # # Returns a complete menu configuration object for atom-shell's menu API. - translateTemplate: (template, keystrokeByCommand) -> + translateTemplate: (template, keystrokesByCommand) -> template.forEach (item) => item.metadata = {} if item.command - item.accelerator = @acceleratorForCommand(item.command, keystrokeByCommand) + item.accelerator = @acceleratorForCommand(item.command, keystrokesByCommand) item.click = => global.atomApplication.sendCommand(item.command) item.metadata['windowSpecific'] = true unless /^application:/.test(item.command) - @translateTemplate(item.submenu, keystrokeByCommand) if item.submenu + @translateTemplate(item.submenu, keystrokesByCommand) if item.submenu template # Private: Determine the accelerator for a given command. # # * command: # The name of the command. - # * keystrokeByCommand: + # * keystrokesByCommand: # An Object where the keys are commands and the values are Arrays containing # the keystroke. # # Returns a String containing the keystroke in a format that can be interpreted # by atom shell to provide nice icons where available. - acceleratorForCommand: (command, keystrokeByCommand) -> - firstKeystroke = keystrokeByCommand[command]?[0] + acceleratorForCommand: (command, keystrokesByCommand) -> + firstKeystroke = keystrokesByCommand[command]?[0] return null unless firstKeystroke modifiers = firstKeystroke.split('-') diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 263a9a26f..0bc6d0add 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -176,8 +176,8 @@ class AtomApplication else @promptForPath() - ipc.on 'update-application-menu', (processId, routingId, template, keystrokeByCommand) => - @applicationMenu.update(template, keystrokeByCommand) + ipc.on 'update-application-menu', (processId, routingId, template, keystrokesByCommand) => + @applicationMenu.update(template, keystrokesByCommand) ipc.on 'run-package-specs', (processId, routingId, specDirectory) => @runSpecs({resourcePath: global.devResourcePath, specDirectory: specDirectory, exitWhenDone: false}) diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index e5f80c983..9d730122f 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -34,7 +34,7 @@ class MenuManager for mapping in atom.keymap.allMappings() when mapping.selector in selectors keystrokesByCommand[mapping.command] ?= [] keystrokesByCommand[mapping.command].push mapping.keystroke - @sendToBrowserProcess(@template, keystrokeByCommand) + @sendToBrowserProcess(@template, keystrokesByCommand) # Private loadCoreItems: -> @@ -56,9 +56,9 @@ class MenuManager # Private: OSX can't handle displaying accelerators for multiple keystrokes. # If they are sent across, it will stop processing accelerators for the rest # of the menu items. - filterMultipleKeystroke: (keystrokeByCommand) -> + filterMultipleKeystroke: (keystrokesByCommand) -> filtered = {} - for key, bindings of keystrokeByCommand + for key, bindings of keystrokesByCommand for binding in bindings continue if binding.indexOf(' ') != -1 @@ -67,6 +67,6 @@ class MenuManager filtered # Private - sendToBrowserProcess: (template, keystrokeByCommand) -> - keystrokeByCommand = @filterMultipleKeystroke(keystrokeByCommand) - ipc.sendChannel 'update-application-menu', template, keystrokeByCommand + sendToBrowserProcess: (template, keystrokesByCommand) -> + keystrokesByCommand = @filterMultipleKeystroke(keystrokesByCommand) + ipc.sendChannel 'update-application-menu', template, keystrokesByCommand From c43f277c5b6ef14f1813ec13bdb89b9cf6a6fb1d Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 14:46:21 -0800 Subject: [PATCH 09/32] Remove getAllKeyMappings --- spec/keymap-spec.coffee | 11 ----------- src/keymap.coffee | 34 +++------------------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 044797d9c..b942547ff 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -323,17 +323,6 @@ describe "Keymap", -> expect(Object.keys(bindings).length).toBe 1 expect(bindings['g']).toEqual "command-and-grandchild-node" - describe ".getAllKeyMappings", -> - it "returns the all bindings", -> - keymap.bindKeys path.join('~', '.atom', 'packages', 'dummy', 'keymaps', 'a.cson'), '.command-mode', 'k': 'c' - - mappings = keymap.getAllKeyMappings() - expect(mappings.length).toBe 1 - expect(mappings[0].source).toEqual 'dummy' - expect(mappings[0].keystroke).toEqual 'k' - expect(mappings[0].command).toEqual 'c' - expect(mappings[0].selector).toEqual '.command-mode' - describe ".determineSource", -> describe "for a package", -> it "returns ''", -> diff --git a/src/keymap.coffee b/src/keymap.coffee index bb7694038..bc5e6cdd4 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -61,29 +61,6 @@ class Keymap firstKeystroke = keystroke.split(' ')[0] _.remove(@bindingSetsByFirstKeystroke[firstKeystroke], bindingSet) - # Private: Returns a user friendly description of where a keybinding was - # loaded from. - # - # * filePath: - # The absolute path from which the keymap was loaded - # - # Returns one of: - # * `Core` indicates it comes from a bundled package. - # * `User` indicates that it was defined by a user. - # * `` the package which defined it. - # * `Unknown` if an invalid path was passed in. - determineSource: (filePath) -> - return 'Unknown' unless filePath - - pathParts = filePath.split(path.sep) - if _.contains(pathParts, 'node_modules') or _.contains(pathParts, 'atom') or _.contains(pathParts, 'src') - 'Core' - else if _.contains(pathParts, '.atom') and _.contains(pathParts, 'keymaps') and !_.contains(pathParts, 'packages') - 'User' - else - packageNameIndex = pathParts.length - 3 - pathParts[packageNameIndex] - bindKeys: (args...) -> name = args.shift() if args.length > 2 [selector, bindings] = args @@ -129,6 +106,9 @@ class Keymap shouldBubble ? true + # Public: Returns an array of objects that represent every keystroke to + # command mapping. Each object contains the following keys `source`, + # `selector`, `command`, `keystroke`, `index`, `specificity`. allMappings: -> mappings = [] @@ -217,14 +197,6 @@ class Keymap # Deprecated # - # Public: Returns an array of objects that represent every keystroke to - # command mapping. Each object contains the following keys `source`, - # `selector`, `command`, `keystroke`. - getAllKeyMappings: -> - @allMappings().map (mapping) => - mapping.source = @determineSource(mapping.source) - mapping - bindingsForElement: (element) -> keystrokeMap = {} mappings = @mappingsMatchingElement(@allMappings(), element) From fd443a8b6819b99400667879d76bfb135ad11c08 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:01:51 -0800 Subject: [PATCH 10/32] Reorder args --- src/keymap.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index bc5e6cdd4..9c2d489bf 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -126,7 +126,7 @@ class Keymap else keystroke.split(' ')[0] == mapping.keystroke.split(' ')[0] - mappingsMatchingElement: (mappings, element) -> + mappingsMatchingElement: (element, mappings=@allMappings()) -> mappings = mappings.filter ({selector}) -> $(element).closest(selector).length > 0 mappings.sort (a, b) -> if b.specificity == a.specificity From fc0a46d6b2efd16fa69e4432baaca522c103eab8 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:05:10 -0800 Subject: [PATCH 11/32] Add mappingsForKeystrokeMatchingElement --- src/keymap.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index 9c2d489bf..c066572b9 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -82,8 +82,7 @@ class Keymap element = event.target element = rootView[0] if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - mappings = @mappingsForKeystroke(keystroke) - mappings = @mappingsMatchingElement(mappings, element) + mappings = @mappingsForKeystrokeMatchingElement(keystroke, element) if mappings.length == 0 and @queuedKeystroke @queuedKeystroke = null @@ -118,6 +117,10 @@ class Keymap mappings + mappingsForKeystrokeMatchingElement: (keystroke, element) -> + mappings = @mappingsForKeystroke(keystroke) + @mappingsMatchingElement(element, mappings) + mappingsForKeystroke: (keystroke) -> mappings = @allMappings().filter (mapping) -> multiKeystroke = /\s/.test keystroke From 94a8d16664d8a5138b4865cf037887fa472521ba Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:05:17 -0800 Subject: [PATCH 12/32] Update specs --- spec/atom-spec.coffee | 24 +++++++++++----------- spec/keymap-spec.coffee | 45 +++++++++++------------------------------ 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index e00bb024a..8ecd9123c 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -137,28 +137,28 @@ describe "the `atom` global", -> element2 = $$ -> @div class: 'test-2' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.bindingsForElement(element1)['ctrl-z']).toBeUndefined() - expect(atom.keymap.bindingsForElement(element2)['ctrl-z']).toBeUndefined() - expect(atom.keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined() + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0 + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 atom.activatePackage("package-with-keymaps") - expect(atom.keymap.bindingsForElement(element1)['ctrl-z']).toBe "test-1" - expect(atom.keymap.bindingsForElement(element2)['ctrl-z']).toBe "test-2" - expect(atom.keymap.bindingsForElement(element3)['ctrl-z']).toBeUndefined() + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1" + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2" + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 describe "when the metadata contains a 'keymaps' manifest", -> it "loads only the keymaps specified by the manifest, in the specified order", -> element1 = $$ -> @div class: 'test-1' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.bindingsForElement(element1)['ctrl-z']).toBeUndefined() + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 atom.activatePackage("package-with-keymaps-manifest") - expect(atom.keymap.bindingsForElement(element1)['ctrl-z']).toBe 'keymap-1' - expect(atom.keymap.bindingsForElement(element1)['ctrl-n']).toBe 'keymap-2' - expect(atom.keymap.bindingsForElement(element3)['ctrl-y']).toBeUndefined() + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1' + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2' + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0 describe "menu loading", -> beforeEach -> @@ -317,8 +317,8 @@ describe "the `atom` global", -> it "removes the package's keymaps", -> atom.activatePackage('package-with-keymaps') atom.deactivatePackage('package-with-keymaps') - expect(atom.keymap.bindingsForElement($$ -> @div class: 'test-1')['ctrl-z']).toBeUndefined() - expect(atom.keymap.bindingsForElement($$ -> @div class: 'test-2')['ctrl-z']).toBeUndefined() + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 + expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 it "removes the package's stylesheets", -> atom.activatePackage('package-with-stylesheets') diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index b942547ff..7d4d5ad06 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -270,13 +270,13 @@ describe "Keymap", -> '.brown': 'ctrl-h': 'harvest' - expect(keymap.bindingsForElement($$ -> @div class: 'green')).toEqual { 'ctrl-c': 'cultivate' } - expect(keymap.bindingsForElement($$ -> @div class: 'brown')).toEqual { 'ctrl-h': 'harvest' } + expect(keymap.mappingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 + expect(keymap.mappingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 keymap.remove('nature') - expect(keymap.bindingsForElement($$ -> @div class: 'green')).toEqual {} - expect(keymap.bindingsForElement($$ -> @div class: 'brown')).toEqual {} + expect(keymap.mappingsMatchingElement($$ -> @div class: 'green')).toEqual [] + expect(keymap.mappingsMatchingElement($$ -> @div class: 'brown')).toEqual [] expect(keymap.bindingSetsByFirstKeystroke['ctrl-c']).toEqual [] expect(keymap.bindingSetsByFirstKeystroke['ctrl-h']).toEqual [] @@ -303,15 +303,15 @@ describe "Keymap", -> expect(keymap.keystrokeStringForEvent(keydownEvent('left', shiftKey: true))).toBe 'shift-left' expect(keymap.keystrokeStringForEvent(keydownEvent('Left', shiftKey: true))).toBe 'shift-left' - describe ".bindingsForElement(element)", -> + describe ".mappingsMatchingElement(element)", -> it "returns the matching bindings for the element", -> keymap.bindKeys '.command-mode', 'c': 'c' keymap.bindKeys '.grandchild-node', 'g': 'g' - bindings = keymap.bindingsForElement(fragment.find('.grandchild-node')) - expect(Object.keys(bindings).length).toBe 2 - expect(bindings['c']).toEqual "c" - expect(bindings['g']).toEqual "g" + mappings = keymap.mappingsMatchingElement(fragment.find('.grandchild-node')) + expect(mappings).toHaveLength 2 + expect(mappings[0].command).toEqual "g" + expect(mappings[1].command).toEqual "c" describe "when multiple bindings match a keystroke", -> it "only returns bindings that match the most specific selector", -> @@ -319,27 +319,6 @@ describe "Keymap", -> keymap.bindKeys '.command-mode .grandchild-node', 'g': 'command-and-grandchild-node' keymap.bindKeys '.grandchild-node', 'g': 'grandchild-node' - bindings = keymap.bindingsForElement(fragment.find('.grandchild-node')) - expect(Object.keys(bindings).length).toBe 1 - expect(bindings['g']).toEqual "command-and-grandchild-node" - - describe ".determineSource", -> - describe "for a package", -> - it "returns ''", -> - expect(keymap.determineSource(path.join('~', '.atom', 'packages', 'dummy', 'keymaps', 'a.cson'))).toEqual 'dummy' - - describe "for a linked package", -> - it "returns ''", -> - expect(keymap.determineSource(path.join('Users', 'john', 'github', 'dummy', 'keymaps', 'a.cson'))).toEqual 'dummy' - - describe "for a user defined keymap", -> - it "returns 'User'", -> - expect(keymap.determineSource(path.join('~', '.atom', 'keymaps', 'a.cson'))).toEqual 'User' - - describe "for a core keymap", -> - it "returns 'Core'", -> - expect(keymap.determineSource(path.join('Applications', 'Atom.app', '..', 'node_modules', 'dummy', 'keymaps', 'a.cson'))).toEqual 'Core' - - describe "for a linked core keymap", -> - it "returns 'Core'", -> - expect(keymap.determineSource(path.join('Users', 'john', 'github', 'atom', 'keymaps', 'a.cson'))).toEqual 'Core' + mappings = keymap.mappingsMatchingElement(fragment.find('.grandchild-node')) + expect(mappings).toHaveLength 3 + expect(mappings[0].command).toEqual "command-and-grandchild-node" From 2fb00af2559116b327100058926c8e46ecc425b1 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:20:12 -0800 Subject: [PATCH 13/32] Rename keyMapping to keyBinding --- spec/atom-spec.coffee | 24 ++++++++++----------- spec/keymap-spec.coffee | 24 ++++++++++----------- src/keymap.coffee | 48 ++++++++++++++++++++--------------------- src/menu-manager.coffee | 6 +++--- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 8ecd9123c..649306c60 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -137,28 +137,28 @@ describe "the `atom` global", -> element2 = $$ -> @div class: 'test-2' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0 - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 atom.activatePackage("package-with-keymaps") - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1" - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2" - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1" + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2" + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 describe "when the metadata contains a 'keymaps' manifest", -> it "loads only the keymaps specified by the manifest, in the specified order", -> element1 = $$ -> @div class: 'test-1' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 atom.activatePackage("package-with-keymaps-manifest") - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1' - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2' - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1' + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2' + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0 describe "menu loading", -> beforeEach -> @@ -317,8 +317,8 @@ describe "the `atom` global", -> it "removes the package's keymaps", -> atom.activatePackage('package-with-keymaps') atom.deactivatePackage('package-with-keymaps') - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 - expect(atom.keymap.mappingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 + expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 it "removes the package's stylesheets", -> atom.activatePackage('package-with-stylesheets') diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 7d4d5ad06..b38410a62 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -270,13 +270,13 @@ describe "Keymap", -> '.brown': 'ctrl-h': 'harvest' - expect(keymap.mappingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 - expect(keymap.mappingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 + expect(keymap.bindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 + expect(keymap.bindingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 keymap.remove('nature') - expect(keymap.mappingsMatchingElement($$ -> @div class: 'green')).toEqual [] - expect(keymap.mappingsMatchingElement($$ -> @div class: 'brown')).toEqual [] + expect(keymap.bindingsMatchingElement($$ -> @div class: 'green')).toEqual [] + expect(keymap.bindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] expect(keymap.bindingSetsByFirstKeystroke['ctrl-c']).toEqual [] expect(keymap.bindingSetsByFirstKeystroke['ctrl-h']).toEqual [] @@ -303,15 +303,15 @@ describe "Keymap", -> expect(keymap.keystrokeStringForEvent(keydownEvent('left', shiftKey: true))).toBe 'shift-left' expect(keymap.keystrokeStringForEvent(keydownEvent('Left', shiftKey: true))).toBe 'shift-left' - describe ".mappingsMatchingElement(element)", -> + describe ".bindingsMatchingElement(element)", -> it "returns the matching bindings for the element", -> keymap.bindKeys '.command-mode', 'c': 'c' keymap.bindKeys '.grandchild-node', 'g': 'g' - mappings = keymap.mappingsMatchingElement(fragment.find('.grandchild-node')) - expect(mappings).toHaveLength 2 - expect(mappings[0].command).toEqual "g" - expect(mappings[1].command).toEqual "c" + bindings = keymap.bindingsMatchingElement(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", -> @@ -319,6 +319,6 @@ describe "Keymap", -> keymap.bindKeys '.command-mode .grandchild-node', 'g': 'command-and-grandchild-node' keymap.bindKeys '.grandchild-node', 'g': 'grandchild-node' - mappings = keymap.mappingsMatchingElement(fragment.find('.grandchild-node')) - expect(mappings).toHaveLength 3 - expect(mappings[0].command).toEqual "command-and-grandchild-node" + bindings = keymap.bindingsMatchingElement(fragment.find('.grandchild-node')) + expect(bindings).toHaveLength 3 + expect(bindings[0].command).toEqual "command-and-grandchild-node" diff --git a/src/keymap.coffee b/src/keymap.coffee index c066572b9..37992d0e0 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -82,62 +82,62 @@ class Keymap element = event.target element = rootView[0] if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - mappings = @mappingsForKeystrokeMatchingElement(keystroke, element) + bindings = @bindingsForKeystrokeMatchingElement(keystroke, element) - if mappings.length == 0 and @queuedKeystroke + if bindings.length == 0 and @queuedKeystroke @queuedKeystroke = null return false else @queuedKeystroke = null - for mapping in mappings - partialMatch = mapping.keystroke isnt keystroke + for binding in bindings + partialMatch = binding.keystroke isnt keystroke if partialMatch @queuedKeystroke = keystroke shouldBubble = false else - if mapping.command is 'native!' + if binding.command is 'native!' shouldBubble = true - else if @triggerCommandEvent(element, mapping.command) + else if @triggerCommandEvent(element, binding.command) shouldBubble = false break if shouldBubble? shouldBubble ? true - # Public: Returns an array of objects that represent every keystroke to - # command mapping. Each object contains the following keys `source`, - # `selector`, `command`, `keystroke`, `index`, `specificity`. - allMappings: -> - mappings = [] + # Public: Returns an array of objects that represent every keybinding. Each + # object contains the following keys `source`, `selector`, `command`, + # `keystroke`, `index`, `specificity`. + allBindings: -> + bindings = [] for bindingSet in @bindingSets for keystroke, command of bindingSet.getCommandsByKeystroke() - mappings.push @buildMapping(bindingSet, command, keystroke) + bindings.push @buildBinding(bindingSet, command, keystroke) - mappings + bindings - mappingsForKeystrokeMatchingElement: (keystroke, element) -> - mappings = @mappingsForKeystroke(keystroke) - @mappingsMatchingElement(element, mappings) + bindingsForKeystrokeMatchingElement: (keystroke, element) -> + bindings = @bindingsForKeystroke(keystroke) + @bindingsMatchingElement(element, bindings) - mappingsForKeystroke: (keystroke) -> - mappings = @allMappings().filter (mapping) -> + bindingsForKeystroke: (keystroke) -> + bindings = @allBindings().filter (binding) -> multiKeystroke = /\s/.test keystroke if multiKeystroke - keystroke == mapping.keystroke + keystroke == binding.keystroke else - keystroke.split(' ')[0] == mapping.keystroke.split(' ')[0] + keystroke.split(' ')[0] == binding.keystroke.split(' ')[0] - mappingsMatchingElement: (element, mappings=@allMappings()) -> - mappings = mappings.filter ({selector}) -> $(element).closest(selector).length > 0 - mappings.sort (a, b) -> + bindingsMatchingElement: (element, bindings=@allBindings()) -> + bindings = bindings.filter ({selector}) -> $(element).closest(selector).length > 0 + bindings.sort (a, b) -> if b.specificity == a.specificity b.index - a.index else b.specificity - a.specificity - buildMapping: (bindingSet, command, keystroke) -> + buildBinding: (bindingSet, command, keystroke) -> selector = bindingSet.selector specificity = bindingSet.specificity index = bindingSet.index diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index 174812aae..e857bd433 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -31,9 +31,9 @@ class MenuManager update: -> keystrokesByCommand = {} selectors = ['body', '.editor', '.editor:not(.mini)'] - for mapping in atom.keymap.allMappings() when mapping.selector in selectors - keystrokesByCommand[mapping.command] ?= [] - keystrokesByCommand[mapping.command].push mapping.keystroke + for binding in atom.keymap.allBindings() when binding.selector in selectors + keystrokesByCommand[binding.command] ?= [] + keystrokesByCommand[binding.command].push binding.keystroke @sendToBrowserProcess(@template, keystrokesByCommand) # Private From ca8ae9ad6169ca490ec920e55424ee35e18e02d8 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:45:09 -0800 Subject: [PATCH 14/32] Remove bindingsForElement --- src/keymap.coffee | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index 37992d0e0..ccb521506 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -195,13 +195,3 @@ class Keymap when 32 then 'space' when 127 then 'delete' else String.fromCharCode(charCode) - - # - # Deprecated - # - - bindingsForElement: (element) -> - keystrokeMap = {} - mappings = @mappingsMatchingElement(@allMappings(), element) - keystrokeMap[keystroke] ?= command for {command, keystroke} in mappings - keystrokeMap From 8788b2a51c6d7ead06f7c1a17188552a22e51809 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Thu, 14 Nov 2013 16:49:09 -0800 Subject: [PATCH 15/32] Add keybinding-resolver to default package list --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 27526058e..6672f1067 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "go-to-line": "0.8.0", "grammar-selector": "0.8.0", "image-view": "0.7.0", + "keybinding-resolver": "0.1.0", "link": "0.7.0", "markdown-preview": "0.15.0", "metrics": "0.11.0", From c7a1205ca660cdaa4ef19bc1d80e90074752a7e3 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 09:39:26 -0800 Subject: [PATCH 16/32] Remove Editor:bindToKeyedEvent --- src/editor.coffee | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/editor.coffee b/src/editor.coffee index db11812fe..680b8fe00 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -1807,13 +1807,6 @@ class Editor extends View else ' ' - bindToKeyedEvent: (key, event, callback) -> - binding = {} - binding[key] = event - atom.keymap.bindKeys '.editor', binding - @on event, => - callback(this, event) - replaceSelectedText: (replaceFn) -> selection = @getSelection() return false if selection.isEmpty() From 4852ba6d9517818df886228b195c985a8ad8dfbe Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 10:21:38 -0800 Subject: [PATCH 17/32] Don't store binding sets, instead store a keyBinding array --- spec/keymap-spec.coffee | 46 +++++++++--------- spec/spec-helper.coffee | 8 ++-- src/binding-set.coffee | 6 ++- src/keymap.coffee | 103 ++++++++++++++++------------------------ 4 files changed, 71 insertions(+), 92 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index b38410a62..d49d4f24d 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -24,9 +24,9 @@ describe "Keymap", -> metaZHandler = null beforeEach -> - keymap.bindKeys '.command-mode', 'x': 'deleteChar' - keymap.bindKeys '.insert-mode', 'x': 'insertChar' - keymap.bindKeys '.command-mode', 'meta-z': 'metaZPressed' + keymap.bindKeys 'name', '.command-mode', 'x': 'deleteChar' + keymap.bindKeys 'name', '.insert-mode', 'x': 'insertChar' + keymap.bindKeys 'name', '.command-mode', 'meta-z': 'metaZPressed' deleteCharHandler = jasmine.createSpy('deleteCharHandler') insertCharHandler = jasmine.createSpy('insertCharHandler') @@ -80,7 +80,7 @@ describe "Keymap", -> describe "when the event's target node descends from multiple nodes that match selectors with a binding", -> beforeEach -> - keymap.bindKeys '.child-node', 'x': 'foo' + keymap.bindKeys 'name', '.child-node', 'x': 'foo' it "only triggers bindings on selectors associated with the closest ancestor node", -> fooHandler = jasmine.createSpy 'fooHandler' @@ -117,10 +117,10 @@ describe "Keymap", -> 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 'div .child-node', 'x': 'foo' - keymap.bindKeys '.command-mode .child-node !important', 'x': 'baz' - keymap.bindKeys '.command-mode .child-node', 'x': 'quux' - keymap.bindKeys '.child-node', 'x': 'bar' + 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' @@ -138,8 +138,8 @@ describe "Keymap", -> describe "when the matching selectors have the same specificity", -> it "triggers the bindings for the most recently declared selector", -> - keymap.bindKeys '.child-node', 'x': 'foo', 'y': 'baz' - keymap.bindKeys '.child-node', 'x': 'bar' + keymap.bindKeys 'name', '.child-node', 'x': 'foo', 'y': 'baz' + keymap.bindKeys 'name', '.child-node', 'x': 'bar' fooHandler = jasmine.createSpy 'fooHandler' barHandler = jasmine.createSpy 'barHandler' @@ -161,7 +161,7 @@ describe "Keymap", -> it "triggers the mapped event on the rootView", -> window.rootView = new RootView rootView.attachToDom() - keymap.bindKeys 'body', 'x': 'foo' + keymap.bindKeys 'name', 'body', 'x': 'foo' fooHandler = jasmine.createSpy("fooHandler") rootView.on 'foo', fooHandler @@ -173,7 +173,7 @@ describe "Keymap", -> describe "when the event matches a 'native!' binding", -> it "returns true, allowing the browser's native key handling to process the event", -> - keymap.bindKeys '.grandchild-node', 'x': 'native!' + 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 @@ -183,7 +183,7 @@ describe "Keymap", -> [quitHandler, closeOtherWindowsHandler] = [] beforeEach -> - keymap.bindKeys "*", + keymap.bindKeys 'name', "*", 'ctrl-x ctrl-c': 'quit' 'ctrl-x 1': 'close-other-windows' @@ -223,7 +223,7 @@ describe "Keymap", -> 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 ".grandchild-node", 'ctrl-x ctrl-c': 'more-specific-quit' + 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 @@ -247,17 +247,17 @@ describe "Keymap", -> describe "when there is a complete binding with a more specific selector", -> it "favors the more specific complete match", -> - describe ".bindKeys(selector, bindings)", -> + describe ".bindKeys(name, selector, bindings)", -> 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 '*', 'ctrl-alt-delete': 'foo' + 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 '*', 'ctrl-alt--': 'foo' + keymap.bindKeys 'name', '*', 'ctrl-alt--': 'foo' result = keymap.handleKeyEvent(keydownEvent('-', ctrlKey: true, altKey: true, target: fragment[0])) expect(result).toBe(false) expect(fooHandler).toHaveBeenCalled() @@ -277,8 +277,6 @@ describe "Keymap", -> expect(keymap.bindingsMatchingElement($$ -> @div class: 'green')).toEqual [] expect(keymap.bindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] - expect(keymap.bindingSetsByFirstKeystroke['ctrl-c']).toEqual [] - expect(keymap.bindingSetsByFirstKeystroke['ctrl-h']).toEqual [] describe ".keystrokeStringForEvent(event)", -> describe "when no modifiers are pressed", -> @@ -305,8 +303,8 @@ describe "Keymap", -> describe ".bindingsMatchingElement(element)", -> it "returns the matching bindings for the element", -> - keymap.bindKeys '.command-mode', 'c': 'c' - keymap.bindKeys '.grandchild-node', 'g': 'g' + keymap.bindKeys 'name', '.command-mode', 'c': 'c' + keymap.bindKeys 'name', '.grandchild-node', 'g': 'g' bindings = keymap.bindingsMatchingElement(fragment.find('.grandchild-node')) expect(bindings).toHaveLength 2 @@ -315,9 +313,9 @@ describe "Keymap", -> describe "when multiple bindings match a keystroke", -> it "only returns bindings that match the most specific selector", -> - keymap.bindKeys '.command-mode', 'g': 'command-mode' - keymap.bindKeys '.command-mode .grandchild-node', 'g': 'command-and-grandchild-node' - keymap.bindKeys '.grandchild-node', 'g': 'grandchild-node' + keymap.bindKeys 'name', '.command-mode', 'g': 'command-mode' + keymap.bindKeys 'name', '.command-mode .grandchild-node', 'g': 'command-and-grandchild-node' + keymap.bindKeys 'name', '.grandchild-node', 'g': 'grandchild-node' bindings = keymap.bindingsMatchingElement(fragment.find('.grandchild-node')) expect(bindings).toHaveLength 3 diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index ffc2bdba8..bd35ae174 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -23,7 +23,7 @@ atom.themes.requireStylesheet '../static/jasmine' fixturePackagesPath = path.resolve(__dirname, './fixtures/packages') atom.packages.packageDirPaths.unshift(fixturePackagesPath) atom.keymap.loadBundledKeymaps() -[bindingSetsToRestore, bindingSetsByFirstKeystrokeToRestore] = [] +keyBindingsToRestore = null $(window).on 'core:close', -> window.close() $(window).on 'unload', -> @@ -67,8 +67,7 @@ beforeEach -> resolvePackagePath = _.bind(spy.originalValue, atom.packages) # used to reset keymap after each spec - bindingSetsToRestore = _.clone(atom.keymap.bindingSets) - bindingSetsByFirstKeystrokeToRestore = _.clone(atom.keymap.bindingSetsByFirstKeystroke) + keyBindingsToRestore = _.clone(atom.keymap.allBindings()) # prevent specs from modifying Atom's menus spyOn(atom.menu, 'sendToBrowserProcess') @@ -108,8 +107,7 @@ beforeEach -> addCustomMatchers(this) afterEach -> - atom.keymap.bindingSets = bindingSetsToRestore - atom.keymap.bindingSetsByFirstKeystroke = bindingSetsByFirstKeystrokeToRestore + atom.keymap.keyBindings = keyBindingsToRestore atom.deactivatePackages() atom.menu.template = [] diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 51326067b..6661e35da 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -4,11 +4,12 @@ fs = require 'fs-plus' {specificity} = require 'clear-cut' PEG = require 'pegjs' +nextBindingSetIndex = 0 + ### Internal ### module.exports = class BindingSet - @parser: null selector: null @@ -16,7 +17,8 @@ class BindingSet parser: null name: null - constructor: (selector, commandsByKeystroke, @index, @name) -> + constructor: (selector, commandsByKeystroke, @name) -> + @index = nextBindingSetIndex++ keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') BindingSet.parser ?= PEG.buildParser(keystrokePattern) @specificity = specificity(selector) diff --git a/src/keymap.coffee b/src/keymap.coffee index ccb521506..599064078 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -25,14 +25,8 @@ module.exports = class Keymap Emitter.includeInto(this) - bindingSets: null - nextBindingSetIndex: 0 - bindingSetsByFirstKeystroke: null - queuedKeystroke: null - constructor: ({@resourcePath, @configDirPath})-> - @bindingSets = [] - @bindingSetsByFirstKeystroke = {} + @keyBindings = [] loadBundledKeymaps: -> @loadDirectory(path.join(@resourcePath, 'keymaps')) @@ -48,102 +42,81 @@ class Keymap load: (path) -> @add(path, CSON.readFileSync(path)) - add: (args...) -> - name = args.shift() if args.length > 1 - keymap = args.shift() - for selector, bindings of keymap - @bindKeys(name, selector, bindings) + add: (name, keyMappingsBySelector) -> + for selector, keyMappings of keyMappingsBySelector + @bindKeys(name, selector, keyMappings) remove: (name) -> - for bindingSet in @bindingSets.filter((bindingSet) -> bindingSet.name is name) - _.remove(@bindingSets, bindingSet) - for keystroke of bindingSet.commandsByKeystroke - firstKeystroke = keystroke.split(' ')[0] - _.remove(@bindingSetsByFirstKeystroke[firstKeystroke], bindingSet) + @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.name is name - bindKeys: (args...) -> - name = args.shift() if args.length > 2 - [selector, bindings] = args - bindingSet = new BindingSet(selector, bindings, @nextBindingSetIndex++, name) - @bindingSets.unshift(bindingSet) - for keystroke of bindingSet.commandsByKeystroke - keystroke = keystroke.split(' ')[0] # only index by first keystroke - @bindingSetsByFirstKeystroke[keystroke] ?= [] - @bindingSetsByFirstKeystroke[keystroke].push(bindingSet) + bindKeys: (name, selector, keyMappings) -> + bindingSet = new BindingSet(selector, keyMappings, name) + for keystroke, command of keyMappings + @keyBindings.push @buildBinding(bindingSet, command, keystroke) - unbindKeys: (selector, bindings) -> - bindingSet = _.detect @bindingSets, (bindingSet) -> - bindingSet.selector is selector and bindingSet.bindings is bindings - - if bindingSet - _.remove(@bindingSets, bindingSet) + buildBinding: (bindingSet, command, keystroke) -> + keystroke = @normalizeKeystroke(keystroke) + selector = bindingSet.selector + specificity = bindingSet.specificity + index = bindingSet.index + source = bindingSet.name + {command, keystroke, selector, specificity, source, index} handleKeyEvent: (event) -> element = event.target element = rootView[0] if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - bindings = @bindingsForKeystrokeMatchingElement(keystroke, element) + keyBindings = @bindingsForKeystrokeMatchingElement(keystroke, element) - if bindings.length == 0 and @queuedKeystroke + if keyBindings.length == 0 and @queuedKeystroke @queuedKeystroke = null return false else @queuedKeystroke = null - for binding in bindings - partialMatch = binding.keystroke isnt keystroke + for keyBinding in keyBindings + partialMatch = keyBinding.keystroke isnt keystroke if partialMatch @queuedKeystroke = keystroke shouldBubble = false else - if binding.command is 'native!' + if keyBinding.command is 'native!' shouldBubble = true - else if @triggerCommandEvent(element, binding.command) + else if @triggerCommandEvent(element, keyBinding.command) shouldBubble = false break if shouldBubble? shouldBubble ? true - # Public: Returns an array of objects that represent every keybinding. Each + # Public: Returns an array of objects that represent every keyBinding. Each # object contains the following keys `source`, `selector`, `command`, # `keystroke`, `index`, `specificity`. allBindings: -> - bindings = [] - - for bindingSet in @bindingSets - for keystroke, command of bindingSet.getCommandsByKeystroke() - bindings.push @buildBinding(bindingSet, command, keystroke) - - bindings + @keyBindings bindingsForKeystrokeMatchingElement: (keystroke, element) -> - bindings = @bindingsForKeystroke(keystroke) - @bindingsMatchingElement(element, bindings) + keyBindings = @bindingsForKeystroke(keystroke) + @bindingsMatchingElement(element, keyBindings) bindingsForKeystroke: (keystroke) -> - bindings = @allBindings().filter (binding) -> + keystroke = @normalizeKeystroke(keystroke) + + keyBindings = @allBindings().filter (keyBinding) -> multiKeystroke = /\s/.test keystroke if multiKeystroke - keystroke == binding.keystroke + keystroke == keyBinding.keystroke else - keystroke.split(' ')[0] == binding.keystroke.split(' ')[0] + keystroke.split(' ')[0] == keyBinding.keystroke.split(' ')[0] - bindingsMatchingElement: (element, bindings=@allBindings()) -> - bindings = bindings.filter ({selector}) -> $(element).closest(selector).length > 0 - bindings.sort (a, b) -> + bindingsMatchingElement: (element, keyBindings=@allBindings()) -> + keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 + keyBindings.sort (a, b) -> if b.specificity == a.specificity b.index - a.index else b.specificity - a.specificity - buildBinding: (bindingSet, command, keystroke) -> - selector = bindingSet.selector - specificity = bindingSet.specificity - index = bindingSet.index - source = bindingSet.name - {command, keystroke, selector, specificity, source} - triggerCommandEvent: (element, commandName) -> commandEvent = $.Event(commandName) commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() @@ -195,3 +168,11 @@ class Keymap when 32 then 'space' when 127 then 'delete' else String.fromCharCode(charCode) + + normalizeKeystroke: (keystroke) -> + normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => + keys = BindingSet.parser.parse(keystroke) + modifiers = keys[0...-1] + modifiers.sort() + [modifiers..., _.last(keys)].join('-') + normalizedKeystroke.join(' ') From bd8e19bce78bacb57448305c25f9ed24a09bd966 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 10:43:20 -0800 Subject: [PATCH 18/32] Update keymap in spec-helper --- spec/spec-helper.coffee | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index bd35ae174..311d7923e 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -23,7 +23,7 @@ atom.themes.requireStylesheet '../static/jasmine' fixturePackagesPath = path.resolve(__dirname, './fixtures/packages') atom.packages.packageDirPaths.unshift(fixturePackagesPath) atom.keymap.loadBundledKeymaps() -keyBindingsToRestore = null +keyBindingsToRestore = _.clone(atom.keymap.allBindings()) $(window).on 'core:close', -> window.close() $(window).on 'unload', -> @@ -52,6 +52,7 @@ beforeEach -> else atom.project = new Project(path.join(@specDirectory, 'fixtures')) window.project = atom.project + atom.keymap.keyBindings = keyBindingsToRestore window.resetTimeouts() atom.packages.packageStates = {} @@ -66,9 +67,6 @@ beforeEach -> resolvePackagePath(packageName) resolvePackagePath = _.bind(spy.originalValue, atom.packages) - # used to reset keymap after each spec - keyBindingsToRestore = _.clone(atom.keymap.allBindings()) - # prevent specs from modifying Atom's menus spyOn(atom.menu, 'sendToBrowserProcess') @@ -107,7 +105,6 @@ beforeEach -> addCustomMatchers(this) afterEach -> - atom.keymap.keyBindings = keyBindingsToRestore atom.deactivatePackages() atom.menu.template = [] From 0878d7ab6a93c933f1869d8a4bea661cc81b029d Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 10:45:21 -0800 Subject: [PATCH 19/32] Update specs --- spec/editor-spec.coffee | 2 +- spec/root-view-spec.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/editor-spec.coffee b/spec/editor-spec.coffee index ac8bb3614..e2c8f5817 100644 --- a/spec/editor-spec.coffee +++ b/spec/editor-spec.coffee @@ -2719,7 +2719,7 @@ describe "Editor", -> describe "when the escape key is pressed on the editor", -> it "clears multiple selections if there are any, and otherwise allows other bindings to be handled", -> - keymap.bindKeys '.editor', 'escape': 'test-event' + keymap.bindKeys 'name', '.editor', 'escape': 'test-event' testEventHandler = jasmine.createSpy("testEventHandler") editor.on 'test-event', testEventHandler diff --git a/spec/root-view-spec.coffee b/spec/root-view-spec.coffee index 65222bd84..e026f60de 100644 --- a/spec/root-view-spec.coffee +++ b/spec/root-view-spec.coffee @@ -136,7 +136,7 @@ describe "RootView", -> commandHandler = jasmine.createSpy('commandHandler') rootView.on('foo-command', commandHandler) - atom.keymap.bindKeys('*', 'x': 'foo-command') + atom.keymap.bindKeys('name', '*', 'x': 'foo-command') describe "when a keydown event is triggered in the RootView", -> it "triggers matching keybindings for that event", -> From b9902cb6f2afb14bed3698a7fe73ba242a058b87 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 11:50:55 -0800 Subject: [PATCH 20/32] Add specs --- src/binding-set.coffee | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/src/binding-set.coffee b/src/binding-set.coffee index 6661e35da..1b93c1c66 100644 --- a/src/binding-set.coffee +++ b/src/binding-set.coffee @@ -4,49 +4,15 @@ fs = require 'fs-plus' {specificity} = require 'clear-cut' PEG = require 'pegjs' -nextBindingSetIndex = 0 - ### Internal ### module.exports = class BindingSet - @parser: null - - selector: null - commandsByKeystroke: null - parser: null - name: null + @nextBindingSetIndex: 0 constructor: (selector, commandsByKeystroke, @name) -> - @index = nextBindingSetIndex++ + @index = BindingSet.nextBindingSetIndex++ keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') BindingSet.parser ?= PEG.buildParser(keystrokePattern) @specificity = specificity(selector) @selector = selector.replace(/!important/g, '') - @commandsByKeystroke = @normalizeCommandsByKeystroke(commandsByKeystroke) - - # Private: - getName: -> - @name - - # Private: - getSelector: -> - @selector - - # Private: - getCommandsByKeystroke: -> - @commandsByKeystroke - - normalizeCommandsByKeystroke: (commandsByKeystroke) -> - normalizedCommandsByKeystroke = {} - for keystroke, command of commandsByKeystroke - normalizedCommandsByKeystroke[@normalizeKeystroke(keystroke)] = command - normalizedCommandsByKeystroke - - normalizeKeystroke: (keystroke) -> - normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => - keys = BindingSet.parser.parse(keystroke) - modifiers = keys[0...-1] - modifiers.sort() - [modifiers..., _.last(keys)].join('-') - normalizedKeystroke.join(' ') From 809a02ca10bdcd85f2fef8961f0d523a3f47b7c4 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 11:54:09 -0800 Subject: [PATCH 21/32] Clone keyBindings by default --- spec/spec-helper.coffee | 2 +- src/keymap.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 311d7923e..b6d2112ee 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -23,7 +23,7 @@ atom.themes.requireStylesheet '../static/jasmine' fixturePackagesPath = path.resolve(__dirname, './fixtures/packages') atom.packages.packageDirPaths.unshift(fixturePackagesPath) atom.keymap.loadBundledKeymaps() -keyBindingsToRestore = _.clone(atom.keymap.allBindings()) +keyBindingsToRestore = atom.keymap.allBindings() $(window).on 'core:close', -> window.close() $(window).on 'unload', -> diff --git a/src/keymap.coffee b/src/keymap.coffee index 599064078..d629cee6c 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -93,7 +93,7 @@ class Keymap # object contains the following keys `source`, `selector`, `command`, # `keystroke`, `index`, `specificity`. allBindings: -> - @keyBindings + _.clone(@keyBindings) bindingsForKeystrokeMatchingElement: (keystroke, element) -> keyBindings = @bindingsForKeystroke(keystroke) From 529c829438a8269679b08089833ca2aa92640fca Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 11:54:18 -0800 Subject: [PATCH 22/32] :lipstick: --- src/keymap.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index d629cee6c..d771ebe9e 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -64,7 +64,7 @@ class Keymap handleKeyEvent: (event) -> element = event.target - element = rootView[0] if element == document.body + element = rootView if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) keyBindings = @bindingsForKeystrokeMatchingElement(keystroke, element) From 8ed4923e58e18c12d5c294870b082b908843a3cc Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 13:32:09 -0800 Subject: [PATCH 23/32] Replace binding-set with key-binding --- src/binding-set.coffee | 18 ------------------ src/key-binding.coffee | 35 +++++++++++++++++++++++++++++++++++ src/keymap.coffee | 25 ++++--------------------- 3 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 src/binding-set.coffee create mode 100644 src/key-binding.coffee diff --git a/src/binding-set.coffee b/src/binding-set.coffee deleted file mode 100644 index 1b93c1c66..000000000 --- a/src/binding-set.coffee +++ /dev/null @@ -1,18 +0,0 @@ -{$} = require './space-pen-extensions' -_ = require 'underscore-plus' -fs = require 'fs-plus' -{specificity} = require 'clear-cut' -PEG = require 'pegjs' - -### Internal ### - -module.exports = -class BindingSet - @nextBindingSetIndex: 0 - - constructor: (selector, commandsByKeystroke, @name) -> - @index = BindingSet.nextBindingSetIndex++ - keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') - BindingSet.parser ?= PEG.buildParser(keystrokePattern) - @specificity = specificity(selector) - @selector = selector.replace(/!important/g, '') diff --git a/src/key-binding.coffee b/src/key-binding.coffee new file mode 100644 index 000000000..0ad595238 --- /dev/null +++ b/src/key-binding.coffee @@ -0,0 +1,35 @@ +{$} = require './space-pen-extensions' +_ = require 'underscore-plus' +fs = require 'fs-plus' +{specificity} = require 'clear-cut' +PEG = require 'pegjs' + +### Internal ### + +module.exports = +class KeyBinding + @parser: null + @currentIndex: 1 + + @normalizeKeystroke: (keystroke) -> + normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => + keys = @getParser().parse(keystroke) + modifiers = keys[0...-1] + modifiers.sort() + [modifiers..., _.last(keys)].join('-') + normalizedKeystroke.join(' ') + + @getParser: -> + if not KeyBinding.parser + keystrokePattern = fs.readFileSync(require.resolve('./keystroke-pattern.pegjs'), 'utf8') + KeyBinding.parser = PEG.buildParser(keystrokePattern) + + KeyBinding.parser + + constructor: (source, command, keystroke, selector) -> + @source = source + @command = command + @keystroke = KeyBinding.normalizeKeystroke(keystroke) + @selector = selector.replace(/!important/g, '') + @specificity = specificity(selector) + @index = KeyBinding.currentIndex++ diff --git a/src/keymap.coffee b/src/keymap.coffee index d771ebe9e..3483543f4 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -3,7 +3,7 @@ _ = require 'underscore-plus' fs = require 'fs-plus' path = require 'path' CSON = require 'season' -BindingSet = require './binding-set' +KeyBinding = require './key-binding' {Emitter} = require 'emissary' Modifiers = ['alt', 'control', 'ctrl', 'shift', 'meta'] @@ -49,18 +49,9 @@ class Keymap remove: (name) -> @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.name is name - bindKeys: (name, selector, keyMappings) -> - bindingSet = new BindingSet(selector, keyMappings, name) + bindKeys: (source, selector, keyMappings) -> for keystroke, command of keyMappings - @keyBindings.push @buildBinding(bindingSet, command, keystroke) - - buildBinding: (bindingSet, command, keystroke) -> - keystroke = @normalizeKeystroke(keystroke) - selector = bindingSet.selector - specificity = bindingSet.specificity - index = bindingSet.index - source = bindingSet.name - {command, keystroke, selector, specificity, source, index} + @keyBindings.push new KeyBinding(source, command, keystroke, selector) handleKeyEvent: (event) -> element = event.target @@ -100,7 +91,7 @@ class Keymap @bindingsMatchingElement(element, keyBindings) bindingsForKeystroke: (keystroke) -> - keystroke = @normalizeKeystroke(keystroke) + keystroke = KeyBinding.normalizeKeystroke(keystroke) keyBindings = @allBindings().filter (keyBinding) -> multiKeystroke = /\s/.test keystroke @@ -168,11 +159,3 @@ class Keymap when 32 then 'space' when 127 then 'delete' else String.fromCharCode(charCode) - - normalizeKeystroke: (keystroke) -> - normalizedKeystroke = keystroke.split(/\s+/).map (keystroke) => - keys = BindingSet.parser.parse(keystroke) - modifiers = keys[0...-1] - modifiers.sort() - [modifiers..., _.last(keys)].join('-') - normalizedKeystroke.join(' ') From dcccde8f3f4a7cee4f27239eab5f87bf6f55ea38 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 13:32:31 -0800 Subject: [PATCH 24/32] Clone default keybindings in specs --- 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 b6d2112ee..681bbd307 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -52,7 +52,7 @@ beforeEach -> else atom.project = new Project(path.join(@specDirectory, 'fixtures')) window.project = atom.project - atom.keymap.keyBindings = keyBindingsToRestore + atom.keymap.keyBindings = _.clone(keyBindingsToRestore) window.resetTimeouts() atom.packages.packageStates = {} From cb8e378af6ee3215f427aed18a4f33a16a056c8c Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 13:49:17 -0800 Subject: [PATCH 25/32] Use keyBinding in place of binding in method names --- spec/atom-spec.coffee | 24 ++++++++++++------------ spec/keymap-spec.coffee | 14 +++++++------- spec/spec-helper.coffee | 2 +- src/keymap.coffee | 16 ++++++++-------- src/menu-manager.coffee | 2 +- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 649306c60..1e8c5dda8 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -137,28 +137,28 @@ describe "the `atom` global", -> element2 = $$ -> @div class: 'test-2' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0 - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 atom.activatePackage("package-with-keymaps") - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1" - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2" - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1" + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2" + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0 describe "when the metadata contains a 'keymaps' manifest", -> it "loads only the keymaps specified by the manifest, in the specified order", -> element1 = $$ -> @div class: 'test-1' element3 = $$ -> @div class: 'test-3' - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0 atom.activatePackage("package-with-keymaps-manifest") - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1' - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2' - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1' + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2' + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0 describe "menu loading", -> beforeEach -> @@ -317,8 +317,8 @@ describe "the `atom` global", -> it "removes the package's keymaps", -> atom.activatePackage('package-with-keymaps') atom.deactivatePackage('package-with-keymaps') - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 - expect(atom.keymap.bindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0 + expect(atom.keymap.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0 it "removes the package's stylesheets", -> atom.activatePackage('package-with-stylesheets') diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index d49d4f24d..57c0f1b0f 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -270,13 +270,13 @@ describe "Keymap", -> '.brown': 'ctrl-h': 'harvest' - expect(keymap.bindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 - expect(keymap.bindingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 + expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 + expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'brown')).toHaveLength 1 keymap.remove('nature') - expect(keymap.bindingsMatchingElement($$ -> @div class: 'green')).toEqual [] - expect(keymap.bindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] + expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toEqual [] + expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] describe ".keystrokeStringForEvent(event)", -> describe "when no modifiers are pressed", -> @@ -301,12 +301,12 @@ describe "Keymap", -> expect(keymap.keystrokeStringForEvent(keydownEvent('left', shiftKey: true))).toBe 'shift-left' expect(keymap.keystrokeStringForEvent(keydownEvent('Left', shiftKey: true))).toBe 'shift-left' - describe ".bindingsMatchingElement(element)", -> + 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.bindingsMatchingElement(fragment.find('.grandchild-node')) + bindings = keymap.keyBindingsMatchingElement(fragment.find('.grandchild-node')) expect(bindings).toHaveLength 2 expect(bindings[0].command).toEqual "g" expect(bindings[1].command).toEqual "c" @@ -317,6 +317,6 @@ describe "Keymap", -> keymap.bindKeys 'name', '.command-mode .grandchild-node', 'g': 'command-and-grandchild-node' keymap.bindKeys 'name', '.grandchild-node', 'g': 'grandchild-node' - bindings = keymap.bindingsMatchingElement(fragment.find('.grandchild-node')) + bindings = keymap.keyBindingsMatchingElement(fragment.find('.grandchild-node')) expect(bindings).toHaveLength 3 expect(bindings[0].command).toEqual "command-and-grandchild-node" diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 681bbd307..09276fe13 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -23,7 +23,7 @@ atom.themes.requireStylesheet '../static/jasmine' fixturePackagesPath = path.resolve(__dirname, './fixtures/packages') atom.packages.packageDirPaths.unshift(fixturePackagesPath) atom.keymap.loadBundledKeymaps() -keyBindingsToRestore = atom.keymap.allBindings() +keyBindingsToRestore = atom.keymap.getKeyBindings() $(window).on 'core:close', -> window.close() $(window).on 'unload', -> diff --git a/src/keymap.coffee b/src/keymap.coffee index 3483543f4..89d5bf2b9 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -57,7 +57,7 @@ class Keymap element = event.target element = rootView if element == document.body keystroke = @keystrokeStringForEvent(event, @queuedKeystroke) - keyBindings = @bindingsForKeystrokeMatchingElement(keystroke, element) + keyBindings = @keyBindingsForKeystrokeMatchingElement(keystroke, element) if keyBindings.length == 0 and @queuedKeystroke @queuedKeystroke = null @@ -83,24 +83,24 @@ class Keymap # Public: Returns an array of objects that represent every keyBinding. Each # object contains the following keys `source`, `selector`, `command`, # `keystroke`, `index`, `specificity`. - allBindings: -> + getKeyBindings: -> _.clone(@keyBindings) - bindingsForKeystrokeMatchingElement: (keystroke, element) -> - keyBindings = @bindingsForKeystroke(keystroke) - @bindingsMatchingElement(element, keyBindings) + keyBindingsForKeystrokeMatchingElement: (keystroke, element) -> + keyBindings = @keyBindingsForKeystroke(keystroke) + @keyBindingsMatchingElement(element, keyBindings) - bindingsForKeystroke: (keystroke) -> + keyBindingsForKeystroke: (keystroke) -> keystroke = KeyBinding.normalizeKeystroke(keystroke) - keyBindings = @allBindings().filter (keyBinding) -> + keyBindings = @getKeyBindings().filter (keyBinding) -> multiKeystroke = /\s/.test keystroke if multiKeystroke keystroke == keyBinding.keystroke else keystroke.split(' ')[0] == keyBinding.keystroke.split(' ')[0] - bindingsMatchingElement: (element, keyBindings=@allBindings()) -> + keyBindingsMatchingElement: (element, keyBindings=@getKeyBindings()) -> keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 keyBindings.sort (a, b) -> if b.specificity == a.specificity diff --git a/src/menu-manager.coffee b/src/menu-manager.coffee index e857bd433..15e47e24f 100644 --- a/src/menu-manager.coffee +++ b/src/menu-manager.coffee @@ -31,7 +31,7 @@ class MenuManager update: -> keystrokesByCommand = {} selectors = ['body', '.editor', '.editor:not(.mini)'] - for binding in atom.keymap.allBindings() when binding.selector in selectors + for binding in atom.keymap.getKeyBindings() when binding.selector in selectors keystrokesByCommand[binding.command] ?= [] keystrokesByCommand[binding.command].push binding.keystroke @sendToBrowserProcess(@template, keystrokesByCommand) From 98b509441c74598ba43c0f221a25317604e57606 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:05:35 -0800 Subject: [PATCH 26/32] Move methods into KeyBinding class --- src/key-binding.coffee | 13 +++++++++++++ src/keymap.coffee | 13 ++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/key-binding.coffee b/src/key-binding.coffee index 0ad595238..11b4505d0 100644 --- a/src/key-binding.coffee +++ b/src/key-binding.coffee @@ -33,3 +33,16 @@ class KeyBinding @selector = selector.replace(/!important/g, '') @specificity = specificity(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 89d5bf2b9..8bb299110 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -92,21 +92,12 @@ class Keymap keyBindingsForKeystroke: (keystroke) -> keystroke = KeyBinding.normalizeKeystroke(keystroke) + keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) - keyBindings = @getKeyBindings().filter (keyBinding) -> - multiKeystroke = /\s/.test keystroke - if multiKeystroke - keystroke == keyBinding.keystroke - else - keystroke.split(' ')[0] == keyBinding.keystroke.split(' ')[0] keyBindingsMatchingElement: (element, keyBindings=@getKeyBindings()) -> keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 - keyBindings.sort (a, b) -> - if b.specificity == a.specificity - b.index - a.index - else - b.specificity - a.specificity + keyBindings.sort (a, b) -> a.compare(b) triggerCommandEvent: (element, commandName) -> commandEvent = $.Event(commandName) From 1ae3806c696648a976b4a757dcd0bc29e6b433dd Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:06:03 -0800 Subject: [PATCH 27/32] Use `source` instead of `name` --- src/keymap.coffee | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index 8bb299110..f3d20854f 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -42,12 +42,12 @@ class Keymap load: (path) -> @add(path, CSON.readFileSync(path)) - add: (name, keyMappingsBySelector) -> + add: (source, keyMappingsBySelector) -> for selector, keyMappings of keyMappingsBySelector - @bindKeys(name, selector, keyMappings) + @bindKeys(source, selector, keyMappings) - remove: (name) -> - @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.name is name + remove: (source) -> + @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source is source bindKeys: (source, selector, keyMappings) -> for keystroke, command of keyMappings @@ -94,8 +94,7 @@ class Keymap keystroke = KeyBinding.normalizeKeystroke(keystroke) keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) - - keyBindingsMatchingElement: (element, keyBindings=@getKeyBindings()) -> + keyBindingsMatchingElement: (element, keyBindings=@keyBindings) -> keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 keyBindings.sort (a, b) -> a.compare(b) From 29c3fadb6f1141752dd6baae9560d54679676476 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:07:01 -0800 Subject: [PATCH 28/32] Move public methods to top of file --- src/keymap.coffee | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index f3d20854f..ed0d7b6b6 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -49,6 +49,22 @@ class Keymap remove: (source) -> @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source is source + # Public: Returns an array of all {KeyBinding}s + getKeyBindings: -> + _.clone(@keyBindings) + + keyBindingsForKeystrokeMatchingElement: (keystroke, element) -> + keyBindings = @keyBindingsForKeystroke(keystroke) + @keyBindingsMatchingElement(element, keyBindings) + + keyBindingsForKeystroke: (keystroke) -> + keystroke = KeyBinding.normalizeKeystroke(keystroke) + keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) + + keyBindingsMatchingElement: (element, keyBindings=@keyBindings) -> + keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 + keyBindings.sort (a, b) -> a.compare(b) + bindKeys: (source, selector, keyMappings) -> for keystroke, command of keyMappings @keyBindings.push new KeyBinding(source, command, keystroke, selector) @@ -80,24 +96,6 @@ class Keymap shouldBubble ? true - # Public: Returns an array of objects that represent every keyBinding. Each - # object contains the following keys `source`, `selector`, `command`, - # `keystroke`, `index`, `specificity`. - getKeyBindings: -> - _.clone(@keyBindings) - - keyBindingsForKeystrokeMatchingElement: (keystroke, element) -> - keyBindings = @keyBindingsForKeystroke(keystroke) - @keyBindingsMatchingElement(element, keyBindings) - - keyBindingsForKeystroke: (keystroke) -> - keystroke = KeyBinding.normalizeKeystroke(keystroke) - keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) - - keyBindingsMatchingElement: (element, keyBindings=@keyBindings) -> - keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 - keyBindings.sort (a, b) -> a.compare(b) - triggerCommandEvent: (element, commandName) -> commandEvent = $.Event(commandName) commandEvent.abortKeyBinding = -> commandEvent.stopImmediatePropagation() From 80cdf61fa4e7afadcebf203b535564442f325e90 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:09:24 -0800 Subject: [PATCH 29/32] Update remove spec --- spec/keymap-spec.coffee | 8 ++++++-- src/keymap.coffee | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/spec/keymap-spec.coffee b/spec/keymap-spec.coffee index 57c0f1b0f..41b3dda09 100644 --- a/spec/keymap-spec.coffee +++ b/spec/keymap-spec.coffee @@ -270,12 +270,16 @@ describe "Keymap", -> '.brown': 'ctrl-h': 'harvest' - expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 + 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')).toEqual [] + expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'green')).toHaveLength 1 expect(keymap.keyBindingsMatchingElement($$ -> @div class: 'brown')).toEqual [] describe ".keystrokeStringForEvent(event)", -> diff --git a/src/keymap.coffee b/src/keymap.coffee index ed0d7b6b6..118c6cf85 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -47,7 +47,7 @@ class Keymap @bindKeys(source, selector, keyMappings) remove: (source) -> - @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source is source + @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source # Public: Returns an array of all {KeyBinding}s getKeyBindings: -> From a0c6a94409bce7e3e14ae88a259b5820c676dc80 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:23:52 -0800 Subject: [PATCH 30/32] Added documentation for Keymap's public classes --- src/keymap.coffee | 118 +++++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 49 deletions(-) diff --git a/src/keymap.coffee b/src/keymap.coffee index 118c6cf85..7f7869813 100644 --- a/src/keymap.coffee +++ b/src/keymap.coffee @@ -28,6 +28,75 @@ class Keymap constructor: ({@resourcePath, @configDirPath})-> @keyBindings = [] + # 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 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 = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) + + # 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.ctrlKey and key not in Modifiers + modifiers.push 'ctrl' + if event.metaKey and key not in Modifiers + modifiers.push 'meta' + + if event.shiftKey and key not in Modifiers + isNamedKey = key.length > 1 + modifiers.push 'shift' if isNamedKey + else + key = key.toLowerCase() + + keystroke = [modifiers..., key].join('-') + + if previousKeystroke + if keystroke in Modifiers + previousKeystroke + else + "#{previousKeystroke} #{keystroke}" + else + keystroke + loadBundledKeymaps: -> @loadDirectory(path.join(@resourcePath, 'keymaps')) @emit('bundled-keymaps-loaded') @@ -49,22 +118,6 @@ class Keymap remove: (source) -> @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source - # Public: Returns an array of all {KeyBinding}s - getKeyBindings: -> - _.clone(@keyBindings) - - keyBindingsForKeystrokeMatchingElement: (keystroke, element) -> - keyBindings = @keyBindingsForKeystroke(keystroke) - @keyBindingsMatchingElement(element, keyBindings) - - keyBindingsForKeystroke: (keystroke) -> - keystroke = KeyBinding.normalizeKeystroke(keystroke) - keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke) - - keyBindingsMatchingElement: (element, keyBindings=@keyBindings) -> - keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0 - keyBindings.sort (a, b) -> a.compare(b) - bindKeys: (source, selector, keyMappings) -> for keystroke, command of keyMappings @keyBindings.push new KeyBinding(source, command, keystroke, selector) @@ -102,39 +155,6 @@ class Keymap $(element).trigger(commandEvent) not commandEvent.isImmediatePropagationStopped() - 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.ctrlKey and key not in Modifiers - modifiers.push 'ctrl' - if event.metaKey and key not in Modifiers - modifiers.push 'meta' - - if event.shiftKey and key not in Modifiers - isNamedKey = key.length > 1 - modifiers.push 'shift' if isNamedKey - else - key = key.toLowerCase() - - keystroke = [modifiers..., key].join('-') - - if previousKeystroke - if keystroke in Modifiers - previousKeystroke - else - "#{previousKeystroke} #{keystroke}" - else - keystroke - isAscii: (charCode) -> 0 <= charCode <= 127 From 7c348ee4785fe0152fc00db8e9a49a973eeba784 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:25:48 -0800 Subject: [PATCH 31/32] Update packages --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 01dc16a37..6f11896e2 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "bookmarks": "0.10.0", "bracket-matcher": "0.11.0", "command-logger": "0.6.0", - "command-palette": "0.7.0", + "command-palette": "0.8.0", "dev-live-reload": "0.15.0", "editor-stats": "0.5.0", "exception-reporting": "0.7.0", @@ -90,13 +90,13 @@ "go-to-line": "0.8.0", "grammar-selector": "0.8.0", "image-view": "0.7.0", - "keybinding-resolver": "0.1.0", + "keybinding-resolver": "0.2.0", "link": "0.7.0", "markdown-preview": "0.15.0", "metrics": "0.11.0", "package-generator": "0.19.0", "release-notes": "0.11.0", - "settings-view": "0.39.0", + "settings-view": "0.41.0", "snippets": "0.13.0", "spell-check": "0.13.0", "status-bar": "0.16.0", From 084bbb1578ae1bb3012c26b526e77fe847f1355f Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 15 Nov 2013 14:35:02 -0800 Subject: [PATCH 32/32] Use skinny arrow --- src/window-event-handler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index 3c54510d8..6549073b3 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -54,7 +54,7 @@ class WindowEventHandler @subscribeToCommand $(document), 'core:focus-previous', @focusPrevious - @subscribe $(document), 'keydown', (event) => + @subscribe $(document), 'keydown', (event) -> atom.keymap.handleKeyEvent(event) @subscribe $(document), 'drop', (e) ->