From 7c5fb2c312b9140c2dd091f792535ae8f592ecdb Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 26 Feb 2016 16:47:17 +0000 Subject: Key bindings; initial "generic" class. This implements a generic front-end class for key handling (a la normal mode). Also: - supports count prefixes (or not) - supports multi-key mappings (longer than two) Also included is a very poor-man's demo. See the bottom of mode_key_handler.coffee for some hard-wired key bindings. IMPORTANT: This does not actually work as Vimium. It's just a demo. --- content_scripts/mode_key_handler.coffee | 158 ++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 content_scripts/mode_key_handler.coffee (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee new file mode 100644 index 00000000..c79f1991 --- /dev/null +++ b/content_scripts/mode_key_handler.coffee @@ -0,0 +1,158 @@ + +# The important data structure here is the "keyState". The key state is a non-empty list of objects, the keys +# of which are key names, and the values are other key-mapping objects or commands (strings). Key-mapping +# objects can be arbitrarily nested; so we support any length of multi-key mapping. +# +# Whenever we consume a key, we append a new copy of the global key mapping to the key state (hence, the +# global mappings are always available, and the key state is always non-empty). + +class KeyHandlerMode extends Mode + useCount: true + countPrefix: 0 + keydownEvents: {} + keyState: [] + + constructor: (options) -> + # A function accepting a command name and a count; required. + @commandHandler = options.commandHandler ? (->) + # A Key mapping structure; required. + @keyMapping = options.keyMapping ? {} + @useCount = false if options.noCount + @reset() + + # We don't pass these options on to super(). + options = Utils.copyObjectOmittingProperties options, "commandHandler", "keyMapping", "noCount" + + super extend options, + keydown: @onKeydown.bind this + keypress: @onKeypress.bind this + keyup: @onKeyup.bind this + # We cannot track matching keydown/keyup events if we lose the focus. + blur: (event) => @alwaysContinueBubbling => + @keydownEvents = {} if event.target == window + + onKeydown: (event) -> + keyChar = KeyboardUtils.getKeyCharString event + + if KeyboardUtils.isEscape event + if @isInResetState() + @continueBubbling + else + @reset() + DomUtils.suppressKeyupAfterEscape handlerStack + false # Suppress event. + + else if keyChar and @keyCharIsKeyStatePrefix keyChar + @advanceKeyState keyChar + commands = @keyState.filter (entry) -> "string" == typeof entry + @invokeCommand commands[0] if 0 < commands.length + false # Suppress event. + + else + # We did not handle the event, but we might handle the subsequent keypress event. If we *will* be + # handling that event, then we need to suppress propagation of this keydown event to prevent triggering + # page features like Google instant search. + keyChar = KeyboardUtils.getKeyChar event + if keyChar and (@keyCharIsKeyStatePrefix(keyChar) or @isCountKey keyChar) + DomUtils.suppressPropagation event + @keydownEvents[@getEventCode event] = true + @stopBubblingAndTrue + else + @countPrefix = 0 if keyChar + @continueBubbling + + onKeypress: (event) -> + keyChar = KeyboardUtils.getKeyCharString event + if keyChar and @keyCharIsKeyStatePrefix keyChar + @advanceKeyState keyChar + commands = @keyState.filter (entry) -> "string" == typeof entry + @invokeCommand commands[0] if 0 < commands.length + false # Suppress event. + else if keyChar and @isCountKey keyChar + @countPrefix = @countPrefix * 10 + parseInt keyChar + false # Suppress event. + else + @continueBubbling + + onKeyup: (event) -> + eventCode = @getEventCode event + if eventCode of @keydownEvents + delete @keydownEvents[eventCode] + DomUtils.suppressPropagation event + @stopBubblingAndTrue + else + @continueBubbling + + # This tests whether keyChar is a prefix of any current mapping in the key state. + keyCharIsKeyStatePrefix: (keyChar) -> + for mapping in @keyState + return true if keyChar of mapping + false + + # This is called whenever a keyChar is matched. We keep any existing entries matching keyChar, and append a + # new copy of the global key mappings. + advanceKeyState: (keyChar) -> + newKeyState = + for mapping in @keyState + continue unless keyChar of mapping + mapping[keyChar] + @keyState = [newKeyState..., @keyMapping] + + # This is called to invoke a command and reset the key state. + invokeCommand: (command) -> + countPrefix = if 0 < @countPrefix then @countPrefix else 1 + @reset() + @commandHandler command, countPrefix + + # Reset the state (as if no keys had been handled). + reset: -> + @countPrefix = 0 + @keyState = [@keyMapping] + + # This tests whether we are in the reset state. It is used to check whether we should be using escape to + # reset the key state, or passing it to the page. + isInResetState: -> + @countPrefix == 0 and @keyState.length == 1 + + # This tests whether keyChar should be treated as a count key. + isCountKey: (keyChar) -> + return false unless @useCount and keyChar.length == 1 + if 0 < @countPrefix + '0' <= keyChar <= '9' + else + '1' <= keyChar <= '9' + + getEventCode: (event) -> event.keyCode + +# Demo/test code. +# A (very) poor-man's normal mode. + +demoKeyMapping = + j: "scrollDown" + k: "scrollUp" + i: "enterInsertMode" + g: + g: "scrollToTop" + a: "scrollToTop" + z: "scrollToBottom" + i: "focusInput" + # A three-key binding. + a: + b: + c: "enterInsertMode" + # And this should override "j" on its own. + j: "enterInsertMode" + +demoCommandHandler = (command, count) -> + switch command + when "scrollDown" then scrollDown() + when "scrollUp" then scrollUp() + when "scrollToTop" then scrollToTop count + when "scrollToBottom" then scrollToBottom() + when "enterInsertMode" then enterInsertMode() + when "focusInput" then focusInput count + +root = exports ? window +root.KeyHandlerMode = KeyHandlerMode +root.demoKeyMapping = demoKeyMapping +root.demoCommandHandler = demoCommandHandler -- cgit v1.2.3 From 34b1fb0f4e2ce1696c17e703d0bc43463355d6ba Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 27 Feb 2016 14:20:12 +0000 Subject: Key bindings; initial partially-functioning version. --- content_scripts/mode_key_handler.coffee | 43 +++------------------------------ 1 file changed, 3 insertions(+), 40 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index c79f1991..6e04addb 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -1,11 +1,4 @@ -# The important data structure here is the "keyState". The key state is a non-empty list of objects, the keys -# of which are key names, and the values are other key-mapping objects or commands (strings). Key-mapping -# objects can be arbitrarily nested; so we support any length of multi-key mapping. -# -# Whenever we consume a key, we append a new copy of the global key mapping to the key state (hence, the -# global mappings are always available, and the key state is always non-empty). - class KeyHandlerMode extends Mode useCount: true countPrefix: 0 @@ -15,8 +8,6 @@ class KeyHandlerMode extends Mode constructor: (options) -> # A function accepting a command name and a count; required. @commandHandler = options.commandHandler ? (->) - # A Key mapping structure; required. - @keyMapping = options.keyMapping ? {} @useCount = false if options.noCount @reset() @@ -31,6 +22,8 @@ class KeyHandlerMode extends Mode blur: (event) => @alwaysContinueBubbling => @keydownEvents = {} if event.target == window + setKeyMapping: (@keyMapping) -> @reset() + onKeydown: (event) -> keyChar = KeyboardUtils.getKeyCharString event @@ -65,7 +58,7 @@ class KeyHandlerMode extends Mode keyChar = KeyboardUtils.getKeyCharString event if keyChar and @keyCharIsKeyStatePrefix keyChar @advanceKeyState keyChar - commands = @keyState.filter (entry) -> "string" == typeof entry + commands = @keyState.filter (entry) -> entry.command @invokeCommand commands[0] if 0 < commands.length false # Suppress event. else if keyChar and @isCountKey keyChar @@ -124,35 +117,5 @@ class KeyHandlerMode extends Mode getEventCode: (event) -> event.keyCode -# Demo/test code. -# A (very) poor-man's normal mode. - -demoKeyMapping = - j: "scrollDown" - k: "scrollUp" - i: "enterInsertMode" - g: - g: "scrollToTop" - a: "scrollToTop" - z: "scrollToBottom" - i: "focusInput" - # A three-key binding. - a: - b: - c: "enterInsertMode" - # And this should override "j" on its own. - j: "enterInsertMode" - -demoCommandHandler = (command, count) -> - switch command - when "scrollDown" then scrollDown() - when "scrollUp" then scrollUp() - when "scrollToTop" then scrollToTop count - when "scrollToBottom" then scrollToBottom() - when "enterInsertMode" then enterInsertMode() - when "focusInput" then focusInput count - root = exports ? window root.KeyHandlerMode = KeyHandlerMode -root.demoKeyMapping = demoKeyMapping -root.demoCommandHandler = demoCommandHandler -- cgit v1.2.3 From 47de80f2fcb03c8741ab46308ce982209f74f6ac Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 27 Feb 2016 15:03:09 +0000 Subject: Key bindings; fix passkeys. --- content_scripts/mode_key_handler.coffee | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 6e04addb..7f03dd64 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -35,7 +35,7 @@ class KeyHandlerMode extends Mode DomUtils.suppressKeyupAfterEscape handlerStack false # Suppress event. - else if keyChar and @keyCharIsKeyStatePrefix keyChar + else if keyChar and @mappingForKeyChar keyChar @advanceKeyState keyChar commands = @keyState.filter (entry) -> "string" == typeof entry @invokeCommand commands[0] if 0 < commands.length @@ -46,7 +46,7 @@ class KeyHandlerMode extends Mode # handling that event, then we need to suppress propagation of this keydown event to prevent triggering # page features like Google instant search. keyChar = KeyboardUtils.getKeyChar event - if keyChar and (@keyCharIsKeyStatePrefix(keyChar) or @isCountKey keyChar) + if keyChar and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) DomUtils.suppressPropagation event @keydownEvents[@getEventCode event] = true @stopBubblingAndTrue @@ -56,7 +56,7 @@ class KeyHandlerMode extends Mode onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event - if keyChar and @keyCharIsKeyStatePrefix keyChar + if keyChar and @mappingForKeyChar keyChar @advanceKeyState keyChar commands = @keyState.filter (entry) -> entry.command @invokeCommand commands[0] if 0 < commands.length @@ -76,11 +76,12 @@ class KeyHandlerMode extends Mode else @continueBubbling - # This tests whether keyChar is a prefix of any current mapping in the key state. - keyCharIsKeyStatePrefix: (keyChar) -> + # This returns the first mapping for which keyChar is mapped. The return value is truthy if a match is found + # and falsy otherwise. + mappingForKeyChar: (keyChar) -> for mapping in @keyState - return true if keyChar of mapping - false + return mapping if keyChar of mapping + null # This is called whenever a keyChar is matched. We keep any existing entries matching keyChar, and append a # new copy of the global key mappings. @@ -115,6 +116,11 @@ class KeyHandlerMode extends Mode else '1' <= keyChar <= '9' + # True if keyChar would be the first character of a command mapping. This is used by passKeys to decide + # whether keyChar is a continuation of a command which the user has already begin entering. + isFirstKeyChar: (keyChar) -> + @countPrefix == 0 and @keyMapping == @mappingForKeyChar keyChar + getEventCode: (event) -> event.keyCode root = exports ? window -- cgit v1.2.3 From 18b8db13fa5184e0cc3ac5fd6645620fdb9d5cef Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 27 Feb 2016 17:33:05 +0000 Subject: Key bindings; tweaks and fixes. --- content_scripts/mode_key_handler.coffee | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 7f03dd64..9b044923 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -36,10 +36,7 @@ class KeyHandlerMode extends Mode false # Suppress event. else if keyChar and @mappingForKeyChar keyChar - @advanceKeyState keyChar - commands = @keyState.filter (entry) -> "string" == typeof entry - @invokeCommand commands[0] if 0 < commands.length - false # Suppress event. + @handleKeyChar event, keyChar else # We did not handle the event, but we might handle the subsequent keypress event. If we *will* be @@ -57,12 +54,9 @@ class KeyHandlerMode extends Mode onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event if keyChar and @mappingForKeyChar keyChar - @advanceKeyState keyChar - commands = @keyState.filter (entry) -> entry.command - @invokeCommand commands[0] if 0 < commands.length - false # Suppress event. + @handleKeyChar event, keyChar else if keyChar and @isCountKey keyChar - @countPrefix = @countPrefix * 10 + parseInt keyChar + @reset @countPrefix * 10 + parseInt keyChar false # Suppress event. else @continueBubbling @@ -76,6 +70,12 @@ class KeyHandlerMode extends Mode else @continueBubbling + handleKeyChar: (event, keyChar) -> + @advanceKeyState keyChar + commands = @keyState.filter (entry) -> entry.command + @invokeCommand commands[0] if 0 < commands.length + false # Suppress event. + # This returns the first mapping for which keyChar is mapped. The return value is truthy if a match is found # and falsy otherwise. mappingForKeyChar: (keyChar) -> @@ -98,9 +98,9 @@ class KeyHandlerMode extends Mode @reset() @commandHandler command, countPrefix - # Reset the state (as if no keys had been handled). - reset: -> - @countPrefix = 0 + # Reset the state (as if no keys had been handled), but retaining the count - if one is provided. + reset: (count = 0) -> + @countPrefix = count @keyState = [@keyMapping] # This tests whether we are in the reset state. It is used to check whether we should be using escape to -- cgit v1.2.3 From f89b58f04af3eae05de9e999c39b2fe047fe6f4a Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 06:48:03 +0000 Subject: Key bindings; more tweaks and fixes. Miscellaneous fixes and tweaks, including: - Reinstate key logging. - Fix count handling in line with expected behaviour in #2024. - Remove `noCount` option; we don't need it. - Simplify logic in various places. Fixes #2024. --- content_scripts/mode_key_handler.coffee | 72 +++++++++++++-------------------- 1 file changed, 28 insertions(+), 44 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 9b044923..c9cab244 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -1,32 +1,25 @@ class KeyHandlerMode extends Mode - useCount: true countPrefix: 0 keydownEvents: {} keyState: [] constructor: (options) -> - # A function accepting a command name and a count; required. @commandHandler = options.commandHandler ? (->) - @useCount = false if options.noCount - @reset() - - # We don't pass these options on to super(). - options = Utils.copyObjectOmittingProperties options, "commandHandler", "keyMapping", "noCount" + @setKeyMapping options.commandHandler ? {} + delete options[option] for option in ["commandHandler", "keyMapping"] super extend options, keydown: @onKeydown.bind this keypress: @onKeypress.bind this keyup: @onKeyup.bind this # We cannot track matching keydown/keyup events if we lose the focus. - blur: (event) => @alwaysContinueBubbling => - @keydownEvents = {} if event.target == window + blur: (event) => @alwaysContinueBubbling => @keydownEvents = {} if event.target == window setKeyMapping: (@keyMapping) -> @reset() onKeydown: (event) -> keyChar = KeyboardUtils.getKeyCharString event - if KeyboardUtils.isEscape event if @isInResetState() @continueBubbling @@ -39,16 +32,14 @@ class KeyHandlerMode extends Mode @handleKeyChar event, keyChar else - # We did not handle the event, but we might handle the subsequent keypress event. If we *will* be - # handling that event, then we need to suppress propagation of this keydown event to prevent triggering - # page features like Google instant search. + # We did not handle the event, but we might handle a subsequent keypress. If we will be handling that + # event, then we suppress propagation of this keydown to prevent triggering page events. keyChar = KeyboardUtils.getKeyChar event if keyChar and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) DomUtils.suppressPropagation event - @keydownEvents[@getEventCode event] = true + @keydownEvents[event.keyCode] = true @stopBubblingAndTrue else - @countPrefix = 0 if keyChar @continueBubbling onKeypress: (event) -> @@ -56,50 +47,46 @@ class KeyHandlerMode extends Mode if keyChar and @mappingForKeyChar keyChar @handleKeyChar event, keyChar else if keyChar and @isCountKey keyChar - @reset @countPrefix * 10 + parseInt keyChar + digit = parseInt keyChar + @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit false # Suppress event. else + @reset() @continueBubbling onKeyup: (event) -> - eventCode = @getEventCode event - if eventCode of @keydownEvents - delete @keydownEvents[eventCode] + if event.keyCode of @keydownEvents + delete @keydownEvents[event.keyCode] DomUtils.suppressPropagation event @stopBubblingAndTrue else @continueBubbling handleKeyChar: (event, keyChar) -> + bgLog "Handling key #{keyChar}, mode=#{@name}." @advanceKeyState keyChar commands = @keyState.filter (entry) -> entry.command - @invokeCommand commands[0] if 0 < commands.length + if 0 < commands.length + countPrefix = if 0 < @countPrefix then @countPrefix else 1 + @reset() + bgLog "Calling mode=#{@name}, command=#{commands[0].command}, count=#{countPrefix}." + @commandHandler commands[0], countPrefix false # Suppress event. - # This returns the first mapping for which keyChar is mapped. The return value is truthy if a match is found - # and falsy otherwise. + # This returns the first key-state entry for which keyChar is mapped. The return value is truthy if a match + # is found and falsy otherwise. mappingForKeyChar: (keyChar) -> - for mapping in @keyState - return mapping if keyChar of mapping - null + (mapping for mapping in @keyState when keyChar of mapping)[0] - # This is called whenever a keyChar is matched. We keep any existing entries matching keyChar, and append a - # new copy of the global key mappings. + # This is called whenever a keyChar is matched. We keep any existing mappings matching keyChar, and append + # a new copy of the mode's global key mappings. advanceKeyState: (keyChar) -> - newKeyState = - for mapping in @keyState - continue unless keyChar of mapping - mapping[keyChar] - @keyState = [newKeyState..., @keyMapping] - - # This is called to invoke a command and reset the key state. - invokeCommand: (command) -> - countPrefix = if 0 < @countPrefix then @countPrefix else 1 - @reset() - @commandHandler command, countPrefix + newMappings = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) + @keyState = [newMappings..., @keyMapping] # Reset the state (as if no keys had been handled), but retaining the count - if one is provided. reset: (count = 0) -> + bgLog "Clearing key queue, set count=#{count}." @countPrefix = count @keyState = [@keyMapping] @@ -110,18 +97,15 @@ class KeyHandlerMode extends Mode # This tests whether keyChar should be treated as a count key. isCountKey: (keyChar) -> - return false unless @useCount and keyChar.length == 1 + return false unless keyChar.length == 1 if 0 < @countPrefix '0' <= keyChar <= '9' else '1' <= keyChar <= '9' - # True if keyChar would be the first character of a command mapping. This is used by passKeys to decide - # whether keyChar is a continuation of a command which the user has already begin entering. + # Test whether keyChar would be the very first character of a command mapping. isFirstKeyChar: (keyChar) -> - @countPrefix == 0 and @keyMapping == @mappingForKeyChar keyChar - - getEventCode: (event) -> event.keyCode + keyChar and @countPrefix == 0 and (@mappingForKeyChar(keyChar) == @keyMapping or @isCountKey keyChar) root = exports ? window root.KeyHandlerMode = KeyHandlerMode -- cgit v1.2.3 From 6e37f605fe45ee5eca03153c35c55c231e703c95 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 11:07:21 +0000 Subject: Key bindings; minor tweaks. --- content_scripts/mode_key_handler.coffee | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index c9cab244..e38a7051 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -21,16 +21,14 @@ class KeyHandlerMode extends Mode onKeydown: (event) -> keyChar = KeyboardUtils.getKeyCharString event if KeyboardUtils.isEscape event - if @isInResetState() + if @countPrefix == 0 and @keyState.length == 1 @continueBubbling else @reset() DomUtils.suppressKeyupAfterEscape handlerStack false # Suppress event. - else if keyChar and @mappingForKeyChar keyChar @handleKeyChar event, keyChar - else # We did not handle the event, but we might handle a subsequent keypress. If we will be handling that # event, then we suppress propagation of this keydown to prevent triggering page events. @@ -84,26 +82,15 @@ class KeyHandlerMode extends Mode newMappings = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) @keyState = [newMappings..., @keyMapping] - # Reset the state (as if no keys had been handled), but retaining the count - if one is provided. - reset: (count = 0) -> - bgLog "Clearing key queue, set count=#{count}." - @countPrefix = count + # Reset the state (as if no keys had been handled), but optionally retaining the count provided. + reset: (@countPrefix = 0) -> + bgLog "Clearing key queue, set count=#{@countPrefix}." @keyState = [@keyMapping] - # This tests whether we are in the reset state. It is used to check whether we should be using escape to - # reset the key state, or passing it to the page. - isInResetState: -> - @countPrefix == 0 and @keyState.length == 1 - - # This tests whether keyChar should be treated as a count key. isCountKey: (keyChar) -> - return false unless keyChar.length == 1 - if 0 < @countPrefix - '0' <= keyChar <= '9' - else - '1' <= keyChar <= '9' + keyChar.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' - # Test whether keyChar would be the very first character of a command mapping. + # This tests whether keyChar would be the very first character of a command mapping. isFirstKeyChar: (keyChar) -> keyChar and @countPrefix == 0 and (@mappingForKeyChar(keyChar) == @keyMapping or @isCountKey keyChar) -- cgit v1.2.3 From 520b63cb1d64fb5a293988122007bd05bacc49db Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 14:01:16 +0000 Subject: Key bindings; fix tests... ... and fix two bugs: - not suppressing keyup event after keyChar matched in keydown. - we cannot check the passKeys keyChar in keyup because the key state has changed; so we track what the next keyup response should be. --- content_scripts/mode_key_handler.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index e38a7051..f7be977b 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -6,7 +6,7 @@ class KeyHandlerMode extends Mode constructor: (options) -> @commandHandler = options.commandHandler ? (->) - @setKeyMapping options.commandHandler ? {} + @setKeyMapping options.keyMapping ? {} delete options[option] for option in ["commandHandler", "keyMapping"] super extend options, @@ -28,7 +28,10 @@ class KeyHandlerMode extends Mode DomUtils.suppressKeyupAfterEscape handlerStack false # Suppress event. else if keyChar and @mappingForKeyChar keyChar + @keydownEvents[event.keyCode] = true @handleKeyChar event, keyChar + else if keyChar + @continueBubbling else # We did not handle the event, but we might handle a subsequent keypress. If we will be handling that # event, then we suppress propagation of this keydown to prevent triggering page events. -- cgit v1.2.3 From b63683a1026e12bfd7bff6b8745d18e3b858bf92 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 14:13:52 +0000 Subject: Key bindings; yet more minor tweaks. --- content_scripts/mode_key_handler.coffee | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index f7be977b..265ceed1 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -32,16 +32,14 @@ class KeyHandlerMode extends Mode @handleKeyChar event, keyChar else if keyChar @continueBubbling - else + else if (keyChar = KeyboardUtils.getKeyChar event) and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) # We did not handle the event, but we might handle a subsequent keypress. If we will be handling that # event, then we suppress propagation of this keydown to prevent triggering page events. - keyChar = KeyboardUtils.getKeyChar event - if keyChar and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) - DomUtils.suppressPropagation event - @keydownEvents[event.keyCode] = true - @stopBubblingAndTrue - else - @continueBubbling + DomUtils.suppressPropagation event + @keydownEvents[event.keyCode] = true + @stopBubblingAndTrue + else + @continueBubbling onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event -- cgit v1.2.3 From b0d2f3bfbc36232c235f913131c78a5bb76b59c3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 15:35:10 +0000 Subject: Key bindings; and yet more minor tweaks. --- content_scripts/mode_key_handler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 265ceed1..97966644 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -93,7 +93,7 @@ class KeyHandlerMode extends Mode # This tests whether keyChar would be the very first character of a command mapping. isFirstKeyChar: (keyChar) -> - keyChar and @countPrefix == 0 and (@mappingForKeyChar(keyChar) == @keyMapping or @isCountKey keyChar) + @countPrefix == 0 and (@mappingForKeyChar(keyChar) == @keyMapping or @isCountKey keyChar) root = exports ? window root.KeyHandlerMode = KeyHandlerMode -- cgit v1.2.3 From f2bced459457dcc962d4bafe2fdf2e6245506ee3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 28 Feb 2016 16:07:25 +0000 Subject: Key bindings; tweaks. --- content_scripts/mode_key_handler.coffee | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 97966644..567552c0 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -1,31 +1,27 @@ class KeyHandlerMode extends Mode - countPrefix: 0 keydownEvents: {} - keyState: [] + setKeyMapping: (@keyMapping) -> @reset() constructor: (options) -> @commandHandler = options.commandHandler ? (->) @setKeyMapping options.keyMapping ? {} - delete options[option] for option in ["commandHandler", "keyMapping"] super extend options, keydown: @onKeydown.bind this keypress: @onKeypress.bind this keyup: @onKeyup.bind this - # We cannot track matching keydown/keyup events if we lose the focus. + # We cannot track keyup events if we lose the focus. blur: (event) => @alwaysContinueBubbling => @keydownEvents = {} if event.target == window - setKeyMapping: (@keyMapping) -> @reset() - onKeydown: (event) -> keyChar = KeyboardUtils.getKeyCharString event if KeyboardUtils.isEscape event if @countPrefix == 0 and @keyState.length == 1 @continueBubbling else + @keydownEvents[event.keyCode] = true @reset() - DomUtils.suppressKeyupAfterEscape handlerStack false # Suppress event. else if keyChar and @mappingForKeyChar keyChar @keydownEvents[event.keyCode] = true @@ -33,10 +29,10 @@ class KeyHandlerMode extends Mode else if keyChar @continueBubbling else if (keyChar = KeyboardUtils.getKeyChar event) and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) - # We did not handle the event, but we might handle a subsequent keypress. If we will be handling that - # event, then we suppress propagation of this keydown to prevent triggering page events. - DomUtils.suppressPropagation event + # We will probably be handling a subsequent keypress event, so suppress propagation of this event to + # prevent triggering page event listeners (e.g. Google instant Search). @keydownEvents[event.keyCode] = true + DomUtils.suppressPropagation event @stopBubblingAndTrue else @continueBubbling @@ -69,7 +65,7 @@ class KeyHandlerMode extends Mode countPrefix = if 0 < @countPrefix then @countPrefix else 1 @reset() bgLog "Calling mode=#{@name}, command=#{commands[0].command}, count=#{countPrefix}." - @commandHandler commands[0], countPrefix + @commandHandler command: commands[0], count: countPrefix, event: event false # Suppress event. # This returns the first key-state entry for which keyChar is mapped. The return value is truthy if a match -- cgit v1.2.3 From 9cfa00bc7db3e07c3abbeb09e483d9fdf20bbc17 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Feb 2016 06:07:01 +0000 Subject: Key bindings; refactor passKeys. Previously, the key-handling logic (keyQueue, etc) was and the backend whereas passKeys were handled in the content scripts - so they were a long way apart. Now that they're in the same place, it makes more sense to integrate passKey handling into the regular key handling, because they depend upon the same data structures. --- content_scripts/mode_key_handler.coffee | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 567552c0..60633895 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -24,30 +24,33 @@ class KeyHandlerMode extends Mode @reset() false # Suppress event. else if keyChar and @mappingForKeyChar keyChar - @keydownEvents[event.keyCode] = true - @handleKeyChar event, keyChar + @unlessKeyCharIsPassKey keyChar, => + @keydownEvents[event.keyCode] = true + @handleKeyChar event, keyChar else if keyChar @continueBubbling else if (keyChar = KeyboardUtils.getKeyChar event) and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) # We will probably be handling a subsequent keypress event, so suppress propagation of this event to # prevent triggering page event listeners (e.g. Google instant Search). - @keydownEvents[event.keyCode] = true - DomUtils.suppressPropagation event - @stopBubblingAndTrue + @unlessKeyCharIsPassKey keyChar, => + @keydownEvents[event.keyCode] = true + DomUtils.suppressPropagation event + @stopBubblingAndTrue else @continueBubbling onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event - if keyChar and @mappingForKeyChar keyChar - @handleKeyChar event, keyChar - else if keyChar and @isCountKey keyChar - digit = parseInt keyChar - @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit - false # Suppress event. - else - @reset() - @continueBubbling + @unlessKeyCharIsPassKey keyChar, => + if keyChar and @mappingForKeyChar keyChar + @handleKeyChar event, keyChar + else if keyChar and @isCountKey keyChar + digit = parseInt keyChar + @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit + false # Suppress event. + else + @reset() + @continueBubbling onKeyup: (event) -> if event.keyCode of @keydownEvents @@ -87,9 +90,14 @@ class KeyHandlerMode extends Mode isCountKey: (keyChar) -> keyChar.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' - # This tests whether keyChar would be the very first character of a command mapping. - isFirstKeyChar: (keyChar) -> - @countPrefix == 0 and (@mappingForKeyChar(keyChar) == @keyMapping or @isCountKey keyChar) + # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if + # 't' is a passKey, then 'gt' and '99t' are neverthless handled as regular keys. + unlessKeyCharIsPassKey: (keyChar, nonPassKeyCallback) -> + if (@passKeys? and keyChar?.length == 1 and 0 <= @passKeys.indexOf(keyChar) and + @countPrefix == 0 and @keyState.length == 1) + @stopBubblingAndTrue + else + nonPassKeyCallback() root = exports ? window root.KeyHandlerMode = KeyHandlerMode -- cgit v1.2.3 From e4193e2752ee7132ff16a7ba977857f70df2946b Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Feb 2016 06:31:43 +0000 Subject: Key bindings; tweaks. --- content_scripts/mode_key_handler.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 60633895..8c4ded43 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -63,12 +63,12 @@ class KeyHandlerMode extends Mode handleKeyChar: (event, keyChar) -> bgLog "Handling key #{keyChar}, mode=#{@name}." @advanceKeyState keyChar - commands = @keyState.filter (entry) -> entry.command - if 0 < commands.length - countPrefix = if 0 < @countPrefix then @countPrefix else 1 + command = (@keyState.filter (entry) -> entry.command)[0] + if command? + count = if 0 < @countPrefix then @countPrefix else 1 @reset() - bgLog "Calling mode=#{@name}, command=#{commands[0].command}, count=#{countPrefix}." - @commandHandler command: commands[0], count: countPrefix, event: event + bgLog "Calling mode=#{@name}, command=#{command.command}, count=#{count}." + @commandHandler {command, count} false # Suppress event. # This returns the first key-state entry for which keyChar is mapped. The return value is truthy if a match -- cgit v1.2.3 From 70151ea99cb86bbbf268ecb737337aa7cc266e2d Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Feb 2016 07:23:31 +0000 Subject: Key bindings; and yet more tweaks. --- content_scripts/mode_key_handler.coffee | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 8c4ded43..ecfcacd6 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -2,6 +2,7 @@ class KeyHandlerMode extends Mode keydownEvents: {} setKeyMapping: (@keyMapping) -> @reset() + setPassKeys: (@passKeys) -> constructor: (options) -> @commandHandler = options.commandHandler ? (->) @@ -23,15 +24,15 @@ class KeyHandlerMode extends Mode @keydownEvents[event.keyCode] = true @reset() false # Suppress event. - else if keyChar and @mappingForKeyChar keyChar + else if keyChar and @keyCharIsMapped keyChar @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true @handleKeyChar event, keyChar else if keyChar @continueBubbling - else if (keyChar = KeyboardUtils.getKeyChar event) and (@mappingForKeyChar(keyChar) or @isCountKey keyChar) - # We will probably be handling a subsequent keypress event, so suppress propagation of this event to - # prevent triggering page event listeners (e.g. Google instant Search). + else if (keyChar = KeyboardUtils.getKeyChar event) and (@keyCharIsMapped(keyChar) or @isCountKey keyChar) + # It looks like we will be handling a subsequent keypress event, so suppress propagation of this event + # to prevent triggering page event listeners (e.g. Google instant Search). @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true DomUtils.suppressPropagation event @@ -42,9 +43,9 @@ class KeyHandlerMode extends Mode onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event @unlessKeyCharIsPassKey keyChar, => - if keyChar and @mappingForKeyChar keyChar + if keyChar and @keyCharIsMapped keyChar @handleKeyChar event, keyChar - else if keyChar and @isCountKey keyChar + else if @isCountKey keyChar digit = parseInt keyChar @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit false # Suppress event. @@ -71,13 +72,10 @@ class KeyHandlerMode extends Mode @commandHandler {command, count} false # Suppress event. - # This returns the first key-state entry for which keyChar is mapped. The return value is truthy if a match - # is found and falsy otherwise. - mappingForKeyChar: (keyChar) -> - (mapping for mapping in @keyState when keyChar of mapping)[0] + keyCharIsMapped: (keyChar) -> + (mapping for mapping in @keyState when keyChar of mapping)[0]? - # This is called whenever a keyChar is matched. We keep any existing mappings matching keyChar, and append - # a new copy of the mode's global key mappings. + # The next key state is the current mappings matching keyChar plus @keyMapping. advanceKeyState: (keyChar) -> newMappings = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) @keyState = [newMappings..., @keyMapping] @@ -88,13 +86,13 @@ class KeyHandlerMode extends Mode @keyState = [@keyMapping] isCountKey: (keyChar) -> - keyChar.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' + keyChar?.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if # 't' is a passKey, then 'gt' and '99t' are neverthless handled as regular keys. unlessKeyCharIsPassKey: (keyChar, nonPassKeyCallback) -> - if (@passKeys? and keyChar?.length == 1 and 0 <= @passKeys.indexOf(keyChar) and - @countPrefix == 0 and @keyState.length == 1) + if @passKeys and @countPrefix == 0 and @keyState.length == 1 and + keyChar?.length == 1 and 0 <= @passKeys.indexOf keyChar @stopBubblingAndTrue else nonPassKeyCallback() -- cgit v1.2.3 From 7bf8cb11db08c61417a4d72b95c2905a4869cf67 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Feb 2016 11:53:05 +0000 Subject: Key bindings; small tweaks... - simplify pass key condition - don't keep key-parsing Regexp in memory - we should reset the key state when the pass keys change --- content_scripts/mode_key_handler.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index ecfcacd6..78ce3680 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -2,7 +2,7 @@ class KeyHandlerMode extends Mode keydownEvents: {} setKeyMapping: (@keyMapping) -> @reset() - setPassKeys: (@passKeys) -> + setPassKeys: (@passKeys) -> @reset() constructor: (options) -> @commandHandler = options.commandHandler ? (->) @@ -91,8 +91,7 @@ class KeyHandlerMode extends Mode # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if # 't' is a passKey, then 'gt' and '99t' are neverthless handled as regular keys. unlessKeyCharIsPassKey: (keyChar, nonPassKeyCallback) -> - if @passKeys and @countPrefix == 0 and @keyState.length == 1 and - keyChar?.length == 1 and 0 <= @passKeys.indexOf keyChar + if @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") @stopBubblingAndTrue else nonPassKeyCallback() -- cgit v1.2.3 From ff6d4b92df924b93de4366b1f0e3cc7411ac8065 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Feb 2016 15:43:26 +0000 Subject: Key bindings; more small tweaks. --- content_scripts/mode_key_handler.coffee | 66 +++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 28 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 78ce3680..df177c4a 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -1,9 +1,26 @@ +# Example key mapping (@keyMapping): +# i: +# command: "enterInsertMode", ... # This is a registryEntry object (as too are the other commands). +# g: +# g: +# command: "scrollToTop", ... +# t: +# command: "nextTab", ... +# +# This key-mapping structure is generated by Commands.generateKeyStateMapping() and may be arbitrarily deep. +# Observe that @keyMapping["g"] is itself also a valid key mapping. At any point, the key state (@keyState) +# consists of a (non-empty) list of such mappings. class KeyHandlerMode extends Mode keydownEvents: {} setKeyMapping: (@keyMapping) -> @reset() setPassKeys: (@passKeys) -> @reset() + # Reset the key state, optionally retaining the count provided. + reset: (@countPrefix = 0) -> + bgLog "Clearing key state, set count=#{@countPrefix}." + @keyState = [@keyMapping] + constructor: (options) -> @commandHandler = options.commandHandler ? (->) @setKeyMapping options.keyMapping ? {} @@ -17,22 +34,22 @@ class KeyHandlerMode extends Mode onKeydown: (event) -> keyChar = KeyboardUtils.getKeyCharString event - if KeyboardUtils.isEscape event - if @countPrefix == 0 and @keyState.length == 1 - @continueBubbling - else - @keydownEvents[event.keyCode] = true - @reset() - false # Suppress event. - else if keyChar and @keyCharIsMapped keyChar + isEscape = KeyboardUtils.isEscape event + if isEscape and @countPrefix == 0 and @keyState.length == 1 + @continueBubbling + else if isEscape + @keydownEvents[event.keyCode] = true + @reset() + false # Suppress event. + else if @keyCharIsMapped keyChar @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true @handleKeyChar event, keyChar else if keyChar @continueBubbling else if (keyChar = KeyboardUtils.getKeyChar event) and (@keyCharIsMapped(keyChar) or @isCountKey keyChar) - # It looks like we will be handling a subsequent keypress event, so suppress propagation of this event - # to prevent triggering page event listeners (e.g. Google instant Search). + # It looks like we will possibly be handling a subsequent keypress event, so suppress propagation of + # this event to prevent triggering page event listeners (e.g. Google instant Search). @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true DomUtils.suppressPropagation event @@ -43,7 +60,7 @@ class KeyHandlerMode extends Mode onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event @unlessKeyCharIsPassKey keyChar, => - if keyChar and @keyCharIsMapped keyChar + if @keyCharIsMapped keyChar @handleKeyChar event, keyChar else if @isCountKey keyChar digit = parseInt keyChar @@ -61,35 +78,28 @@ class KeyHandlerMode extends Mode else @continueBubbling + # This tests whether there is a mapping of keyChar in the current key state. + keyCharIsMapped: (keyChar) -> + (mapping for mapping in @keyState when keyChar of mapping)[0]? + handleKeyChar: (event, keyChar) -> bgLog "Handling key #{keyChar}, mode=#{@name}." - @advanceKeyState keyChar - command = (@keyState.filter (entry) -> entry.command)[0] + # Advance the key state. The new key state is the current mappings of keyChar, plus @keyMapping. + @keyState = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) + @keyState.push @keyMapping + command = (mapping for mapping in @keyState when mapping.command)[0] if command? count = if 0 < @countPrefix then @countPrefix else 1 - @reset() bgLog "Calling mode=#{@name}, command=#{command.command}, count=#{count}." + @reset() @commandHandler {command, count} false # Suppress event. - keyCharIsMapped: (keyChar) -> - (mapping for mapping in @keyState when keyChar of mapping)[0]? - - # The next key state is the current mappings matching keyChar plus @keyMapping. - advanceKeyState: (keyChar) -> - newMappings = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) - @keyState = [newMappings..., @keyMapping] - - # Reset the state (as if no keys had been handled), but optionally retaining the count provided. - reset: (@countPrefix = 0) -> - bgLog "Clearing key queue, set count=#{@countPrefix}." - @keyState = [@keyMapping] - isCountKey: (keyChar) -> keyChar?.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if - # 't' is a passKey, then 'gt' and '99t' are neverthless handled as regular keys. + # 't' is a passKey, then the "t"-s of 'gt' and '99t' are neverthless handled as regular keys. unlessKeyCharIsPassKey: (keyChar, nonPassKeyCallback) -> if @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") @stopBubblingAndTrue -- cgit v1.2.3 From 91498d457f4398ab467fda4ec67b7444b1335cd3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 1 Mar 2016 13:26:35 +0000 Subject: Key bindings; and yet more tiny tweaks. --- content_scripts/mode_key_handler.coffee | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index df177c4a..397e64c8 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -45,11 +45,10 @@ class KeyHandlerMode extends Mode @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true @handleKeyChar event, keyChar - else if keyChar - @continueBubbling - else if (keyChar = KeyboardUtils.getKeyChar event) and (@keyCharIsMapped(keyChar) or @isCountKey keyChar) - # It looks like we will possibly be handling a subsequent keypress event, so suppress propagation of - # this event to prevent triggering page event listeners (e.g. Google instant Search). + else if not keyChar and (keyChar = KeyboardUtils.getKeyChar event) and + (@keyCharIsMapped(keyChar) or @isCountKey keyChar) + # We will possibly be handling a subsequent keypress event, so suppress propagation of this event to + # prevent triggering page event listeners (e.g. Google instant Search). @unlessKeyCharIsPassKey keyChar, => @keydownEvents[event.keyCode] = true DomUtils.suppressPropagation event @@ -71,12 +70,10 @@ class KeyHandlerMode extends Mode @continueBubbling onKeyup: (event) -> - if event.keyCode of @keydownEvents - delete @keydownEvents[event.keyCode] - DomUtils.suppressPropagation event - @stopBubblingAndTrue - else - @continueBubbling + return @continueBubbling unless event.keyCode of @keydownEvents + delete @keydownEvents[event.keyCode] + DomUtils.suppressPropagation event + @stopBubblingAndTrue # This tests whether there is a mapping of keyChar in the current key state. keyCharIsMapped: (keyChar) -> -- cgit v1.2.3 From 91bcedbf4aebbd2691ef68ca96094206a942a1b8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 2 Mar 2016 08:44:42 +0000 Subject: Key bindings; simplify pass-key logic. --- content_scripts/mode_key_handler.coffee | 45 +++++++++++++++------------------ 1 file changed, 20 insertions(+), 25 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 397e64c8..fc4d485e 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -42,32 +42,29 @@ class KeyHandlerMode extends Mode @reset() false # Suppress event. else if @keyCharIsMapped keyChar - @unlessKeyCharIsPassKey keyChar, => - @keydownEvents[event.keyCode] = true - @handleKeyChar event, keyChar + @keydownEvents[event.keyCode] = true + @handleKeyChar event, keyChar else if not keyChar and (keyChar = KeyboardUtils.getKeyChar event) and (@keyCharIsMapped(keyChar) or @isCountKey keyChar) # We will possibly be handling a subsequent keypress event, so suppress propagation of this event to # prevent triggering page event listeners (e.g. Google instant Search). - @unlessKeyCharIsPassKey keyChar, => - @keydownEvents[event.keyCode] = true - DomUtils.suppressPropagation event - @stopBubblingAndTrue + @keydownEvents[event.keyCode] = true + DomUtils.suppressPropagation event + @stopBubblingAndTrue else @continueBubbling onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event - @unlessKeyCharIsPassKey keyChar, => - if @keyCharIsMapped keyChar - @handleKeyChar event, keyChar - else if @isCountKey keyChar - digit = parseInt keyChar - @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit - false # Suppress event. - else - @reset() - @continueBubbling + if @keyCharIsMapped keyChar + @handleKeyChar event, keyChar + else if @isCountKey keyChar + digit = parseInt keyChar + @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit + false # Suppress event. + else + @reset() + @continueBubbling onKeyup: (event) -> return @continueBubbling unless event.keyCode of @keydownEvents @@ -75,9 +72,9 @@ class KeyHandlerMode extends Mode DomUtils.suppressPropagation event @stopBubblingAndTrue - # This tests whether there is a mapping of keyChar in the current key state. + # This tests whether there is a mapping of keyChar in the current key state (and accounts for pass keys). keyCharIsMapped: (keyChar) -> - (mapping for mapping in @keyState when keyChar of mapping)[0]? + (mapping for mapping in @keyState when keyChar of mapping)[0]? and not @isPassKey keyChar handleKeyChar: (event, keyChar) -> bgLog "Handling key #{keyChar}, mode=#{@name}." @@ -93,15 +90,13 @@ class KeyHandlerMode extends Mode false # Suppress event. isCountKey: (keyChar) -> - keyChar?.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' + keyChar?.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' and + not @isPassKey keyChar # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if # 't' is a passKey, then the "t"-s of 'gt' and '99t' are neverthless handled as regular keys. - unlessKeyCharIsPassKey: (keyChar, nonPassKeyCallback) -> - if @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") - @stopBubblingAndTrue - else - nonPassKeyCallback() + isPassKey: (keyChar) -> + @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") root = exports ? window root.KeyHandlerMode = KeyHandlerMode -- cgit v1.2.3 From a049281ea12e2d31813277ef7777815175881f6b Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 2 Mar 2016 11:26:07 +0000 Subject: Key bindings; miscellaneous... - remove unused "event" parameter - move methods around to put like with like - simplify some expressions - one better method name --- content_scripts/mode_key_handler.coffee | 39 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index fc4d485e..fdcac430 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -41,11 +41,11 @@ class KeyHandlerMode extends Mode @keydownEvents[event.keyCode] = true @reset() false # Suppress event. - else if @keyCharIsMapped keyChar + else if @isMappedKey keyChar @keydownEvents[event.keyCode] = true - @handleKeyChar event, keyChar + @handleKeyChar keyChar else if not keyChar and (keyChar = KeyboardUtils.getKeyChar event) and - (@keyCharIsMapped(keyChar) or @isCountKey keyChar) + (@isMappedKey(keyChar) or @isCountKey keyChar) # We will possibly be handling a subsequent keypress event, so suppress propagation of this event to # prevent triggering page event listeners (e.g. Google instant Search). @keydownEvents[event.keyCode] = true @@ -56,8 +56,8 @@ class KeyHandlerMode extends Mode onKeypress: (event) -> keyChar = KeyboardUtils.getKeyCharString event - if @keyCharIsMapped keyChar - @handleKeyChar event, keyChar + if @isMappedKey keyChar + @handleKeyChar keyChar else if @isCountKey keyChar digit = parseInt keyChar @reset if @keyState.length == 1 then @countPrefix * 10 + digit else digit @@ -73,30 +73,29 @@ class KeyHandlerMode extends Mode @stopBubblingAndTrue # This tests whether there is a mapping of keyChar in the current key state (and accounts for pass keys). - keyCharIsMapped: (keyChar) -> + isMappedKey: (keyChar) -> (mapping for mapping in @keyState when keyChar of mapping)[0]? and not @isPassKey keyChar - handleKeyChar: (event, keyChar) -> + # This tests whether keyChar is a digit (and accounts for pass keys). + isCountKey: (keyChar) -> + keyChar and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' and not @isPassKey keyChar + + # Keystrokes are *never* considered pass keys if the user has begun entering a command. So, for example, if + # 't' is a passKey, then the "t"-s of 'gt' and '99t' are neverthless handled as regular keys. + isPassKey: (keyChar) -> + @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") + + handleKeyChar: (keyChar) -> bgLog "Handling key #{keyChar}, mode=#{@name}." # Advance the key state. The new key state is the current mappings of keyChar, plus @keyMapping. - @keyState = (mapping[keyChar] for mapping in @keyState when keyChar of mapping) - @keyState.push @keyMapping - command = (mapping for mapping in @keyState when mapping.command)[0] - if command? + @keyState = [(mapping[keyChar] for mapping in @keyState when keyChar of mapping)..., @keyMapping] + command = (mapping for mapping in @keyState when "command" of mapping)[0] + if command count = if 0 < @countPrefix then @countPrefix else 1 bgLog "Calling mode=#{@name}, command=#{command.command}, count=#{count}." @reset() @commandHandler {command, count} false # Suppress event. - isCountKey: (keyChar) -> - keyChar?.length == 1 and (if 0 < @countPrefix then '0' else '1') <= keyChar <= '9' and - not @isPassKey keyChar - - # Keystrokes are *never* considered passKeys if the user has begun entering a command. So, for example, if - # 't' is a passKey, then the "t"-s of 'gt' and '99t' are neverthless handled as regular keys. - isPassKey: (keyChar) -> - @countPrefix == 0 and @keyState.length == 1 and keyChar in (@passKeys ? "") - root = exports ? window root.KeyHandlerMode = KeyHandlerMode -- cgit v1.2.3 From 9a22be04cb0b5cef3dcffea84cbbd21565906bd9 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 2 Mar 2016 11:44:18 +0000 Subject: Key bindings; do not reset on passKeys change. This reinstates the legacy behaviour in the following case: - `g` - change tab - change back to the original tab - `g` - ..... which scrolls to top. It is not obvious that this is the best behaviour, but it is the legacy behaviour, and it certainly isn't unreasonable. --- content_scripts/mode_key_handler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index fdcac430..118cf70d 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -14,7 +14,7 @@ class KeyHandlerMode extends Mode keydownEvents: {} setKeyMapping: (@keyMapping) -> @reset() - setPassKeys: (@passKeys) -> @reset() + setPassKeys: (@passKeys) -> # Reset the key state, optionally retaining the count provided. reset: (@countPrefix = 0) -> -- cgit v1.2.3 From 632c97eb3485f05c7f813deea788512156116fbb Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 3 Mar 2016 05:54:58 +0000 Subject: Key bindings; reset on passKeys change. Normal mode updates the pass keys every time the frame changes (so, also every time we change tab). Here, we reset the key state too. Resetting the key state makes sense when, for example, the user has changed the pass keys. However, it also changes a status quo/master behaviour: - `g`, change-tab-with-mouse, change-back, `g` -- previously this scrolled to top; now it does not. --- content_scripts/mode_key_handler.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts/mode_key_handler.coffee') diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee index 118cf70d..fdcac430 100644 --- a/content_scripts/mode_key_handler.coffee +++ b/content_scripts/mode_key_handler.coffee @@ -14,7 +14,7 @@ class KeyHandlerMode extends Mode keydownEvents: {} setKeyMapping: (@keyMapping) -> @reset() - setPassKeys: (@passKeys) -> + setPassKeys: (@passKeys) -> @reset() # Reset the key state, optionally retaining the count provided. reset: (@countPrefix = 0) -> -- cgit v1.2.3