diff --git a/spec/app/keymap-spec.coffee b/spec/app/keymap-spec.coffee index 5cdb9978c..5199c9509 100644 --- a/spec/app/keymap-spec.coffee +++ b/spec/app/keymap-spec.coffee @@ -190,18 +190,6 @@ describe "Keymap", -> expect(fooHandler).toHaveBeenCalled() - describe ".bindKey(selector, pattern, eventName)", -> - it "binds a single key", -> - keymap.bindKey '.child-node', 'z', 'foo' - - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler - - target = fragment.find('.child-node')[0] - keymap.handleKeyEvent(keydownEvent('z', target: target)) - - expect(fooHandler).toHaveBeenCalled() - describe ".keystrokeStringForEvent(event)", -> describe "when no modifiers are pressed", -> it "returns a string that identifies the key pressed", -> @@ -224,3 +212,24 @@ describe "Keymap", -> expect(keymap.keystrokeStringForEvent(keydownEvent('{', shiftKey: true))).toBe '{' expect(keymap.keystrokeStringForEvent(keydownEvent('left', shiftKey: true))).toBe 'shift-left' expect(keymap.keystrokeStringForEvent(keydownEvent('Left', shiftKey: true))).toBe 'shift-left' + + + describe ".bindingsForElement(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" + + 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' + + bindings = keymap.bindingsForElement(fragment.find('.grandchild-node')) + expect(Object.keys(bindings).length).toBe 1 + expect(bindings['g']).toEqual "command-and-grandchild-node" diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index 46cd37506..e3a1f401f 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -356,6 +356,32 @@ describe "RootView", -> rootView.trigger(event) expect(commandHandler).toHaveBeenCalled() + describe ".activeKeybindings()", -> + originalKeymap = null + keymap = null + editor = null + + beforeEach -> + rootView.attachToDom() + editor = rootView.activeEditor() + keymap = new (require 'keymap') + originalKeymap = window.keymap + window.keymap = keymap + + afterEach -> + window.keymap = originalKeymap + + it "returns all keybindings available for focused element", -> + editor.on 'test-event-a', => # nothing + + keymap.bindKeys ".editor", + "meta-a": "test-event-a" + "meta-b": "test-event-b" + + keybindings = rootView.activeKeybindings() + expect(Object.keys(keybindings).length).toBe 2 + expect(keybindings["meta-a"]).toEqual "test-event-a" + describe "when the path of the focused editor's buffer changes", -> it "changes the document.title and emits an active-editor-path-change event", -> pathChangeHandler = jasmine.createSpy 'pathChangeHandler' diff --git a/src/app/binding-set.coffee b/src/app/binding-set.coffee index 1f2dc4ca8..0d179a412 100644 --- a/src/app/binding-set.coffee +++ b/src/app/binding-set.coffee @@ -1,40 +1,40 @@ $ = require 'jquery' _ = require 'underscore' -Specificity = require 'specificity' fs = require 'fs' +Specificity = require 'specificity' PEG = require 'pegjs' module.exports = class BindingSet selector: null + keystrokeMap: null commandForEvent: null - keystrokePatternParser: null + parser: null constructor: (@selector, mapOrFunction) -> @parser = PEG.buildParser(fs.read(require.resolve 'keystroke-pattern.pegjs')) @specificity = Specificity(@selector) - @commandForEvent = @buildEventHandler(mapOrFunction) + @keystrokeMap = {} - buildEventHandler: (mapOrFunction) -> if _.isFunction(mapOrFunction) - mapOrFunction + @commandForEvent = mapOrFunction else - mapOrFunction = @normalizeKeystrokePatterns(mapOrFunction) - (event) => - for pattern, command of mapOrFunction - return command if event.keystroke == pattern + @keystrokeMap = @normalizeKeystrokeMap(mapOrFunction) + @commandForEvent = (event) => + for keystroke, command of @keystrokeMap + return command if event.keystroke == keystroke null - normalizeKeystrokePatterns: (map) -> - normalizedMap = {} - for pattern, event of map - normalizedMap[@normalizeKeystrokePattern(pattern)] = event - normalizedMap + normalizeKeystrokeMap: (keystrokeMap) -> + normalizeKeystrokeMap = {} + for keystroke, command of keystrokeMap + normalizeKeystrokeMap[@normalizeKeystroke(keystroke)] = command - normalizeKeystrokePattern: (pattern) -> - keys = @parser.parse(pattern) + normalizeKeystrokeMap + + normalizeKeystroke: (keystroke) -> + keys = @parser.parse(keystroke) modifiers = keys[0...-1] modifiers.sort() [modifiers..., _.last(keys)].join('-') - diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 1605bddd1..5a41f734b 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -454,7 +454,7 @@ class Editor extends View return if oldScreenRange.start.row > @lastRenderedScreenRow - maxEndRow = Math.max(@getLastVisibleScreenRow() + @lineOverdraw, @lastRenderedScreenRow) + maxEndRow = Math.max(@getFirstVisibleScreenRow() + @lineOverdraw, @lastRenderedScreenRow) @gutter.renderLineNumbers(@firstRenderedScreenRow, maxEndRow) if e.lineNumbersChanged newScreenRange = newScreenRange.copy() diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 9d02c540d..8fc71fa54 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -1,12 +1,13 @@ +$ = require 'jquery' +_ = require 'underscore' fs = require 'fs' + BindingSet = require 'binding-set' Specificity = require 'specificity' -$ = require 'jquery' - module.exports = class Keymap - bindingSetsBySelector: null + bindingSets: null constructor: -> @bindingSets = [] @@ -26,10 +27,17 @@ class Keymap bindKeys: (selector, bindings) -> @bindingSets.unshift(new BindingSet(selector, bindings)) - bindKey: (selector, pattern, eventName) -> - bindings = {} - bindings[pattern] = eventName - @bindKeys(selector, bindings) + bindingsForElement: (element) -> + keystrokeMap = {} + currentNode = $(element) + + while currentNode.length + bindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) + bindingSets.sort (a, b) -> b.specificity - a.specificity + _.defaults(keystrokeMap, set.keystrokeMap) for set in bindingSets + currentNode = currentNode.parent() + + keystrokeMap handleKeyEvent: (event) -> event.keystroke = @keystrokeStringForEvent(event) @@ -47,9 +55,6 @@ class Keymap currentNode = currentNode.parent() true - reset: -> - @bindingSets = [] - triggerCommandEvent: (keyEvent, commandName) -> commandEvent = $.Event(commandName) commandEvent.keyEvent = keyEvent diff --git a/src/app/keymaps/atom.coffee b/src/app/keymaps/atom.coffee new file mode 100644 index 000000000..ea88bbb3a --- /dev/null +++ b/src/app/keymaps/atom.coffee @@ -0,0 +1,7 @@ +window.keymap.bindKeys '*' + 'meta-w': 'close' + 'alt-meta-i': 'show-console' + right: 'move-right' + left: 'move-left' + down: 'move-down' + up: 'move-up' \ No newline at end of file diff --git a/src/app/keymaps/editor.coffee b/src/app/keymaps/editor.coffee index fb17f104f..6252869c4 100644 --- a/src/app/keymaps/editor.coffee +++ b/src/app/keymaps/editor.coffee @@ -1,15 +1,3 @@ -window.keymap.bindKeys '*' - 'meta-s': 'save' - 'meta-w': 'close' - 'alt-meta-i': 'show-console' - 'meta-+': 'increase-font-size' - 'meta--': 'decrease-font-size' - - right: 'move-right' - left: 'move-left' - down: 'move-down' - up: 'move-up' - window.keymap.bindKeys '.editor', 'meta-s': 'save' 'shift-right': 'select-right' @@ -39,3 +27,5 @@ window.keymap.bindKeys '.editor', 'meta-]': 'indent-selected-rows' 'meta-{': 'show-previous-buffer' 'meta-}': 'show-next-buffer' + 'meta-+': 'increase-font-size' + 'meta--': 'decrease-font-size' diff --git a/src/app/keymaps/keybindings-view.coffee b/src/app/keymaps/keybindings-view.coffee new file mode 100644 index 000000000..99c2344cd --- /dev/null +++ b/src/app/keymaps/keybindings-view.coffee @@ -0,0 +1,5 @@ +window.keymap.bindKeys '*', + 'ctrl-?': 'keybindings-view:attach' + +window.keymap.bindKeys ".keybindings-view", + 'escape': 'keybindings-view:detach' \ No newline at end of file diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 0109e3391..34aaa5113 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -114,6 +114,9 @@ class RootView extends View if not previousActiveEditor or editor.buffer.path != previousActiveEditor.buffer.path @trigger 'active-editor-path-change', editor.buffer.path + activeKeybindings: -> + keymap.bindingsForElement(document.activeElement) + setTitle: (title='untitled') -> document.title = title diff --git a/src/extensions/keybindings-view/index.coffee b/src/extensions/keybindings-view/index.coffee new file mode 100644 index 000000000..d1bc7b87c --- /dev/null +++ b/src/extensions/keybindings-view/index.coffee @@ -0,0 +1 @@ +module.exports = require 'keybindings-view/keybindings-view' \ No newline at end of file diff --git a/src/extensions/keybindings-view/keybindings-view.coffee b/src/extensions/keybindings-view/keybindings-view.coffee new file mode 100644 index 000000000..e2a641369 --- /dev/null +++ b/src/extensions/keybindings-view/keybindings-view.coffee @@ -0,0 +1,31 @@ +{View, $$} = require 'space-pen' + +module.exports = +class KeybindingsView extends View + @activate: (rootView, state) -> + requireStylesheet 'keybinding-view.css' + @instance = new this(rootView) + + @content: (rootView) -> + @div class: 'keybindings-view', tabindex: -1, => + @ul outlet: 'keybindingList' + + initialize: (@rootView) -> + @rootView.on 'keybindings-view:attach', => @attach() + @on 'keybindings-view:detach', => @detach() + + attach: -> + @keybindingList.empty() + @keybindingList.append $$ -> + for keystroke, command of rootView.activeKeybindings() + @li => + @span class: 'keystroke', "#{keystroke}" + @span ":" + @span "#{command}" + + @rootView.append(this) + @focus() + + detach: -> + super() + @rootView.focus() diff --git a/static/atom.css b/static/atom.css index 4fbffb9cd..fd6f8583c 100644 --- a/static/atom.css +++ b/static/atom.css @@ -61,4 +61,3 @@ body { background: #991212; -webkit-transition: background 200ms ease-out; } - diff --git a/static/keybinding-view.css b/static/keybinding-view.css new file mode 100644 index 000000000..9e3312e02 --- /dev/null +++ b/static/keybinding-view.css @@ -0,0 +1,18 @@ +.keybindings-view { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + background-color: white; + overflow: scroll; + opacity: 0.9; +} + +.keybindings-view li .keystroke { + font-weight: bold; + float: left; + text-align: right; + padding-right: 10px; + width: 200px; +}