From 64bd26a392ba1298902945bef6f4624253e71e14 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 29 May 2012 14:15:54 -0700 Subject: [PATCH 1/7] Add Keymap.bindingsForElement method --- spec/app/keymap-spec.coffee | 33 +++++++++++++++++++++------------ src/app/binding-set.coffee | 2 ++ src/app/keymap.coffee | 22 +++++++++++++++++----- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/spec/app/keymap-spec.coffee b/spec/app/keymap-spec.coffee index 5cdb9978c..b341debd4 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", -> + fit "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" \ No newline at end of file diff --git a/src/app/binding-set.coffee b/src/app/binding-set.coffee index 1f2dc4ca8..29dccb011 100644 --- a/src/app/binding-set.coffee +++ b/src/app/binding-set.coffee @@ -8,6 +8,7 @@ PEG = require 'pegjs' module.exports = class BindingSet selector: null + keystrokeMap: null commandForEvent: null keystrokePatternParser: null @@ -15,6 +16,7 @@ class BindingSet @parser = PEG.buildParser(fs.read(require.resolve 'keystroke-pattern.pegjs')) @specificity = Specificity(@selector) @commandForEvent = @buildEventHandler(mapOrFunction) + @keystrokeMap = if not _.isFunction(mapOrFunction) then mapOrFunction else {} buildEventHandler: (mapOrFunction) -> if _.isFunction(mapOrFunction) diff --git a/src/app/keymap.coffee b/src/app/keymap.coffee index 9d02c540d..d729083a0 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -6,7 +6,7 @@ $ = require 'jquery' module.exports = class Keymap - bindingSetsBySelector: null + bindingSets: null constructor: -> @bindingSets = [] @@ -26,10 +26,22 @@ class Keymap bindKeys: (selector, bindings) -> @bindingSets.unshift(new BindingSet(selector, bindings)) - bindKey: (selector, pattern, eventName) -> - bindings = {} - bindings[pattern] = eventName - @bindKeys(selector, bindings) + bindingsForElement: (element) -> + currentNode = $(element) + keystrokeMap = {} + + while currentNode.length + bindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) + console.log @bindingSets, currentNode + + bindingSets.sort (a, b) -> b.specificity - a.specificity + for bindingSet in bindingSets + for keystroke, command of bindingSet.keystrokeMap + keystrokeMap[keystroke] ?= command + + currentNode = currentNode.parent() + + keystrokeMap handleKeyEvent: (event) -> event.keystroke = @keystrokeStringForEvent(event) From 893564945e5384e92e7ac20058df7676f1144834 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 29 May 2012 14:27:22 -0700 Subject: [PATCH 2/7] Add rootView.activeKeybindings(). This will return all active keybindings available for the focused element. --- spec/app/keymap-spec.coffee | 4 ++-- spec/app/root-view-spec.coffee | 26 ++++++++++++++++++++++++++ src/app/keymap.coffee | 1 - src/app/root-view.coffee | 3 +++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/spec/app/keymap-spec.coffee b/spec/app/keymap-spec.coffee index b341debd4..5199c9509 100644 --- a/spec/app/keymap-spec.coffee +++ b/spec/app/keymap-spec.coffee @@ -225,11 +225,11 @@ describe "Keymap", -> expect(bindings['g']).toEqual "g" describe "when multiple bindings match a keystroke", -> - fit "only returns bindings that match the most specific selector", -> + 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" \ No newline at end of file + 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/keymap.coffee b/src/app/keymap.coffee index d729083a0..75778a40e 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -32,7 +32,6 @@ class Keymap while currentNode.length bindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) - console.log @bindingSets, currentNode bindingSets.sort (a, b) -> b.specificity - a.specificity for bindingSet in bindingSets 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 From cbf5c5d16cac8e8ae68b6327907d247bbe632a00 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 29 May 2012 17:59:00 -0700 Subject: [PATCH 3/7] ctrl-? brings up keybindings for focused view. it is very ugly. --- src/app/keymaps/atom.coffee | 7 +++++++ src/app/keymaps/editor.coffee | 15 +++------------ src/app/root-view.coffee | 18 ++++++++++++++++++ static/atom.css | 19 +++++++++++++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 src/app/keymaps/atom.coffee diff --git a/src/app/keymaps/atom.coffee b/src/app/keymaps/atom.coffee new file mode 100644 index 000000000..a277a1035 --- /dev/null +++ b/src/app/keymaps/atom.coffee @@ -0,0 +1,7 @@ +window.keymap.bindKeys '*' + 'alt-meta-i': 'show-console' + right: 'move-right' + left: 'move-left' + down: 'move-down' + up: 'move-up' + 'ctrl-?': 'toggle-keybindings-view' \ No newline at end of file diff --git a/src/app/keymaps/editor.coffee b/src/app/keymaps/editor.coffee index fe392b3dd..c74d12931 100644 --- a/src/app/keymaps/editor.coffee +++ b/src/app/keymaps/editor.coffee @@ -1,17 +1,6 @@ -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' + 'meta-w': 'close' 'shift-right': 'select-right' 'shift-left': 'select-left' 'shift-up': 'select-up' @@ -38,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/root-view.coffee b/src/app/root-view.coffee index 34aaa5113..e3362d7b1 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -29,6 +29,7 @@ class RootView extends View extensions: null extensionStates: null fontSize: 18 + keybindingsView: null initialize: (pathToOpen) -> @extensions = {} @@ -59,6 +60,7 @@ class RootView extends View @on 'increase-font-size', => @setFontSize(@getFontSize() + 1) @on 'decrease-font-size', => @setFontSize(@getFontSize() - 1) + @on 'toggle-keybindings-view', => @toggleKeybindingsView() afterAttach: (onDom) -> @focus() if onDom @@ -114,6 +116,22 @@ class RootView extends View if not previousActiveEditor or editor.buffer.path != previousActiveEditor.buffer.path @trigger 'active-editor-path-change', editor.buffer.path + toggleKeybindingsView: -> + if @keybindingsView? + @keybindingsView.remove() + @keybindingsView = null + else + keybindings = @activeKeybindings() + @keybindingsView = $$ -> + @div class: 'keybindings-view', => + @ul => + for keystroke, command of keybindings + @li => + @span class: 'keystroke', "#{keystroke}" + @span "#{command}" + + @append(@keybindingsView) + activeKeybindings: -> keymap.bindingsForElement(document.activeElement) diff --git a/static/atom.css b/static/atom.css index c4258d885..e774c3fc5 100644 --- a/static/atom.css +++ b/static/atom.css @@ -63,3 +63,22 @@ body { -webkit-transition: background 200ms ease-out; } +.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: 100px; +} + From 46e94b33d430dcb9a0513bc85c0c174f9483a27d Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 30 May 2012 10:20:20 -0700 Subject: [PATCH 4/7] :lipstick: --- src/app/binding-set.coffee | 34 ++++++++++++++++------------------ src/app/keymap.coffee | 16 +++++----------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/app/binding-set.coffee b/src/app/binding-set.coffee index 29dccb011..0d179a412 100644 --- a/src/app/binding-set.coffee +++ b/src/app/binding-set.coffee @@ -1,8 +1,8 @@ $ = require 'jquery' _ = require 'underscore' -Specificity = require 'specificity' fs = require 'fs' +Specificity = require 'specificity' PEG = require 'pegjs' module.exports = @@ -10,33 +10,31 @@ 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 = if not _.isFunction(mapOrFunction) then mapOrFunction else {} + @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/keymap.coffee b/src/app/keymap.coffee index 75778a40e..8fc71fa54 100644 --- a/src/app/keymap.coffee +++ b/src/app/keymap.coffee @@ -1,9 +1,10 @@ +$ = require 'jquery' +_ = require 'underscore' fs = require 'fs' + BindingSet = require 'binding-set' Specificity = require 'specificity' -$ = require 'jquery' - module.exports = class Keymap bindingSets: null @@ -27,17 +28,13 @@ class Keymap @bindingSets.unshift(new BindingSet(selector, bindings)) bindingsForElement: (element) -> - currentNode = $(element) keystrokeMap = {} + currentNode = $(element) while currentNode.length bindingSets = @bindingSets.filter (set) -> currentNode.is(set.selector) - bindingSets.sort (a, b) -> b.specificity - a.specificity - for bindingSet in bindingSets - for keystroke, command of bindingSet.keystrokeMap - keystrokeMap[keystroke] ?= command - + _.defaults(keystrokeMap, set.keystrokeMap) for set in bindingSets currentNode = currentNode.parent() keystrokeMap @@ -58,9 +55,6 @@ class Keymap currentNode = currentNode.parent() true - reset: -> - @bindingSets = [] - triggerCommandEvent: (keyEvent, commandName) -> commandEvent = $.Event(commandName) commandEvent.keyEvent = keyEvent From 1d1eeb5c099dd5863fd7a47f23032c48749bb90c Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 31 May 2012 15:26:59 -0700 Subject: [PATCH 5/7] meta-w is a global keybinding --- src/app/keymaps/atom.coffee | 1 + src/app/keymaps/editor.coffee | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/keymaps/atom.coffee b/src/app/keymaps/atom.coffee index a277a1035..028588691 100644 --- a/src/app/keymaps/atom.coffee +++ b/src/app/keymaps/atom.coffee @@ -1,4 +1,5 @@ window.keymap.bindKeys '*' + 'meta-w': 'close' 'alt-meta-i': 'show-console' right: 'move-right' left: 'move-left' diff --git a/src/app/keymaps/editor.coffee b/src/app/keymaps/editor.coffee index c74d12931..55aa221b3 100644 --- a/src/app/keymaps/editor.coffee +++ b/src/app/keymaps/editor.coffee @@ -1,6 +1,5 @@ window.keymap.bindKeys '.editor', 'meta-s': 'save' - 'meta-w': 'close' 'shift-right': 'select-right' 'shift-left': 'select-left' 'shift-up': 'select-up' From 130361fc1c52c7d41cd85a3f33814edebd82eee3 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 31 May 2012 15:54:40 -0700 Subject: [PATCH 6/7] Keybinding view is an extension --- src/app/keymaps/atom.coffee | 3 +- src/app/keymaps/keybindings-view.coffee | 5 +++ src/app/root-view.coffee | 18 ----------- src/extensions/keybindings-view/index.coffee | 1 + .../keybindings-view/keybindings-view.coffee | 31 +++++++++++++++++++ static/atom.css | 22 +------------ static/keybinding-view.css | 18 +++++++++++ 7 files changed, 57 insertions(+), 41 deletions(-) create mode 100644 src/app/keymaps/keybindings-view.coffee create mode 100644 src/extensions/keybindings-view/index.coffee create mode 100644 src/extensions/keybindings-view/keybindings-view.coffee create mode 100644 static/keybinding-view.css diff --git a/src/app/keymaps/atom.coffee b/src/app/keymaps/atom.coffee index 028588691..ea88bbb3a 100644 --- a/src/app/keymaps/atom.coffee +++ b/src/app/keymaps/atom.coffee @@ -4,5 +4,4 @@ window.keymap.bindKeys '*' right: 'move-right' left: 'move-left' down: 'move-down' - up: 'move-up' - 'ctrl-?': 'toggle-keybindings-view' \ No newline at end of file + up: 'move-up' \ No newline at end of file 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 e3362d7b1..34aaa5113 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -29,7 +29,6 @@ class RootView extends View extensions: null extensionStates: null fontSize: 18 - keybindingsView: null initialize: (pathToOpen) -> @extensions = {} @@ -60,7 +59,6 @@ class RootView extends View @on 'increase-font-size', => @setFontSize(@getFontSize() + 1) @on 'decrease-font-size', => @setFontSize(@getFontSize() - 1) - @on 'toggle-keybindings-view', => @toggleKeybindingsView() afterAttach: (onDom) -> @focus() if onDom @@ -116,22 +114,6 @@ class RootView extends View if not previousActiveEditor or editor.buffer.path != previousActiveEditor.buffer.path @trigger 'active-editor-path-change', editor.buffer.path - toggleKeybindingsView: -> - if @keybindingsView? - @keybindingsView.remove() - @keybindingsView = null - else - keybindings = @activeKeybindings() - @keybindingsView = $$ -> - @div class: 'keybindings-view', => - @ul => - for keystroke, command of keybindings - @li => - @span class: 'keystroke', "#{keystroke}" - @span "#{command}" - - @append(@keybindingsView) - activeKeybindings: -> keymap.bindingsForElement(document.activeElement) 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 e774c3fc5..dd65fe811 100644 --- a/static/atom.css +++ b/static/atom.css @@ -61,24 +61,4 @@ body { .error { background: #991212; -webkit-transition: background 200ms ease-out; -} - -.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: 100px; -} - +} \ No newline at end of file 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; +} From 89ac1f50af60850d54e6f322b989e6d49efff40f Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 31 May 2012 16:23:38 -0700 Subject: [PATCH 7/7] Replace @getLastVisibleScreenRow with @getFirstVisibleScreenRow My :lipstick: commit awhile back was more than skin deep. --- src/app/editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/editor.coffee b/src/app/editor.coffee index b5302e9b4..e46c6158c 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -453,7 +453,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()