diff options
| author | Stephen Blott | 2016-03-05 05:43:42 +0000 |
|---|---|---|
| committer | Stephen Blott | 2016-03-05 05:43:42 +0000 |
| commit | 62359adda7bc38917de38e3fc794d37817fa05ed (patch) | |
| tree | 816d696b785f8a58b06ddd01560fee05ca0299e7 /content_scripts/mode_key_handler.coffee | |
| parent | 27d3d0087c86a6effd25049cbf0d9273eb0af9db (diff) | |
| parent | fb67cfdd2ca8c09453cc896fd02d08ed5a74a8a4 (diff) | |
| download | vimium-62359adda7bc38917de38e3fc794d37817fa05ed.tar.bz2 | |
Merge pull request #2022 from smblott-github/generalised-key-bindings
Key handling in content scripts.
Diffstat (limited to 'content_scripts/mode_key_handler.coffee')
| -rw-r--r-- | content_scripts/mode_key_handler.coffee | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/content_scripts/mode_key_handler.coffee b/content_scripts/mode_key_handler.coffee new file mode 100644 index 00000000..fdcac430 --- /dev/null +++ b/content_scripts/mode_key_handler.coffee @@ -0,0 +1,101 @@ +# 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 ? {} + + super extend options, + keydown: @onKeydown.bind this + keypress: @onKeypress.bind this + keyup: @onKeyup.bind this + # We cannot track keyup events if we lose the focus. + blur: (event) => @alwaysContinueBubbling => @keydownEvents = {} if event.target == window + + onKeydown: (event) -> + keyChar = KeyboardUtils.getKeyCharString event + 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 @isMappedKey keyChar + @keydownEvents[event.keyCode] = true + @handleKeyChar keyChar + else if not keyChar and (keyChar = KeyboardUtils.getKeyChar event) and + (@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 + DomUtils.suppressPropagation event + @stopBubblingAndTrue + else + @continueBubbling + + onKeypress: (event) -> + keyChar = KeyboardUtils.getKeyCharString event + if @isMappedKey keyChar + @handleKeyChar 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 + delete @keydownEvents[event.keyCode] + DomUtils.suppressPropagation event + @stopBubblingAndTrue + + # This tests whether there is a mapping of keyChar in the current key state (and accounts for pass keys). + isMappedKey: (keyChar) -> + (mapping for mapping in @keyState when keyChar of mapping)[0]? and not @isPassKey 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)..., @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. + +root = exports ? window +root.KeyHandlerMode = KeyHandlerMode |
