Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Improve reliability of catching modifier key-up events #156

Merged
merged 18 commits into from
Nov 15, 2016

Conversation

iolsen
Copy link
Contributor

@iolsen iolsen commented Oct 17, 2016

For MRU tab switching, these key bindings were added:

'ctrl-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'
'ctrl-shift-tab ^ctrl': 'pane:move-active-item-to-top-of-stack'

The ^ctrl is a key-up event on the ctrl key. Catching it is necessary to correctly push the current item to the top of the MRU stack. But we fail to catch the ctrl key-up in any of these cases:

  1. The ctrl key is released beyond the pending partial match timeout of 1 second. If you release ctrl more than one second after last pressing tab, we don't catch it.
  2. The ctrl key is released in concert with another modifier key, e.g. shift. Depending on the timing this could be treated like ^ctrl-shift, ^shift ^ctrl, or ^ctrl ^shift and only the last will successfully match the binding.
  3. Typing any other keys between the key-down and key-up will result in the key-up not resolving to the bound command.

This PR addresses this bug as follows:

  1. Persist pending keyup matches beyond the partial match timeout.
  2. Fix a bug in the way modifier keyup events get translated to Atom keystroke strings.

Should fix atom/tabs#314 and is a prerequisite for adding the MRU list UI (atom/tabs#388) that fixes atom/atom#11035.

still very WIP, lots of pieces still on the floor
Copy link
Contributor

@nathansobo nathansobo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good.

Overall the approach of maintaining a separate collection of pending matches that you don't clear on the timeout seems sound. It's always hard for me to look at something this complex and say "yep, no regressions", but the approach makes sense.

Biggest style nitpick is you gotta convert all the snake_case to camelCase because that's how things are in JS/CoffeeScript land.

I made one suggestion that may or may not work out when explored, which is to store the pending keydown-keyup style bindings in a separate array always rather than puling them out of the partial matches. They really are a different class of match, and I think expressing that in the way the essential state is maintained might lead to more clarity. I could be wrong about this though and you'd have to dig in to find out.

assert(isModifierKeyup('^cmd'))
assert(isModifierKeyup('^ctrl-shift'))
assert(isModifierKeyup('^alt-cmd'))
it "returns false for modifier keydowns", ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Should insert an empty line above this second it.

KeyBinding = require '../src/key-binding'

describe "KeyBinding", ->

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: 🔥 this newline.


describe "KeyBinding", ->

describe "is_matched_modifer_keydown_keyup", ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snake_case ▶️ camelCase

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method name is also quite a mouthful. I'm not sure I can come up with anything better though.


describe "is_matched_modifer_keydown_keyup", ->

describe "returns false when the binding...", ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this ... approach. Cute!

@@ -0,0 +1,54 @@
KeyBinding = require '../src/key-binding'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea unit testing this class.

exports.keydownEvent = (key, options) ->
return buildKeyboardEvent(key, 'keydown', options)

exports.keyupEvent = (key, options) ->
return buildKeyboardEvent(key, 'keyup', options)

exports.getModKeys = (keystroke) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally spell stuff out fully for maximal clarity throughout the Atom codebase. getModifierKeys or getModifiers would be better.

@@ -95,6 +95,10 @@ class KeymapManager
pendingStateTimeoutHandle: null
dvorakQwertyWorkaroundEnabled: false

# Pending matches to bindings that begin with a modifier keydown and and with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and and ➡️ and end?

keystroke = @keystrokeForKeyboardEvent(event)
console.log(keystroke)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

@@ -513,6 +519,15 @@ class KeymapManager
dispatchedExactMatch = null
partialMatches = @findPartialMatches(partialMatchCandidates, target)

if @pendingPartialMatchedModifierKeystrokes? and isModifierKeyup(keystroke)
for binding in @pendingPartialMatchedModifierKeystrokes
binding_mod_keyups = getModKeys(binding.keystrokeArray[binding.keystrokeArray.length-1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be encapsulated in a method in the binding that takes the keystroke string?

@pendingPartialMatches = pendingPartialMatches
if enableTimeout
@pendingStateTimeoutHandle = setTimeout(@terminatePendingState.bind(this, true), @partialMatchTimeout)

cancelPendingState: ->
buildPendingPartialMatchedModiferKeystrokes: ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would make more sense to store the matched-keydown-keyup style bindings in a separate array all the time rather than filtering them out of the partial matches array later. You could potentially do this in the findMatchCandidates method.

@iolsen iolsen changed the title [WIP] Improve reliability of catching modifier key-up events Improve reliability of catching modifier key-up events Oct 25, 2016
@iolsen iolsen mentioned this pull request Oct 28, 2016
@iolsen
Copy link
Contributor Author

iolsen commented Oct 28, 2016

This will get merged after the next release, meaning it should land in 1.14.

@iolsen iolsen merged commit eeaee76 into master Nov 15, 2016
@nathansobo
Copy link
Contributor

🎉

@iolsen iolsen deleted the io-modifier-keyups branch November 15, 2016 19:21
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MRU order gets randomly corrupted Add config option for MRU tabs
2 participants