Support "Key Chords" in keybindings #1767

Open
opened 2026-01-30 22:35:59 +00:00 by claunia · 15 comments
Owner

Originally created by @zadjii-msft on GitHub (Jun 19, 2019).

Currently, we only support pressing a single key+modifiers to activate a keybinding action.

However, many text editors support "key chords" where the chord is a combination of multiple keys pressed in sequence. For example, in Visual Studio, the default keybinding for "Comment Code" is the chord [ctrl+c, ctrl+k].

We've already prepared for serializing these chords in the array of keybindings, but we don't support more than one key at a time. We'd have somehow check if a key is the start of a chord, and only dispatch the action when all keys for the chord have been pressed.

Lots of questions:

  • What happens if an action is bound to [ctrl+c, ctrl+k] and another is bound to [ctrl+c]?
  • If the first key of a chord is pressed, but the second key isn't bound to an action, should we write both keys to the input?
  • If the first key of a chord is pressed, but then nothing is pressed for a long time, should we time out?
  • What's the best way of finding the right keybinding for a chord?
    • Iterating over all of them when a key is pressed, until the chord is dispatched seems awful (but trivial to do).
    • Maybe we could build a tree of keychords, or a list of trees?
    • My CS542 senses are tingling and suggesting that this might be the case to use a state machine.
      • Is a state machine really different than a tree in this case?
Originally created by @zadjii-msft on GitHub (Jun 19, 2019). Currently, we only support pressing a single key+modifiers to activate a keybinding action. However, many text editors support "key chords" where the chord is a combination of multiple keys pressed in sequence. For example, in Visual Studio, the default keybinding for "Comment Code" is the chord `[ctrl+c, ctrl+k]`. We've already prepared for serializing these chords in the array of keybindings, but we don't support more than one key at a time. We'd have somehow check if a key is the start of a chord, and only dispatch the action when all keys for the chord have been pressed. Lots of questions: * [ ] What happens if an action is bound to `[ctrl+c, ctrl+k]` and another is bound to `[ctrl+c]`? * [ ] If the first key of a chord is pressed, but the second key _isn't_ bound to an action, should we write both keys to the input? * [ ] If the first key of a chord is pressed, but then nothing is pressed for a long time, should we time out? * [ ] What's the best way of finding the right keybinding for a chord? - Iterating over all of them when a key is pressed, until the chord is dispatched seems awful (but trivial to do). - Maybe we could build a tree of keychords, or a list of trees? - My CS542 senses are tingling and suggesting that this might be the case to use a state machine. - Is a state machine really different than a tree in this case?
claunia added the Issue-FeatureHelp WantedArea-SettingsProduct-Terminal labels 2026-01-30 22:35:59 +00:00
Author
Owner

@carlos-zamora commented on GitHub (Jun 20, 2019):

Here’s a thought. What if we execute the keybinding when “CTRL” is released? So “CTRL-K, CTRL-D” is really just you hold CTRL, then press K, then press D. And it executes when you release CTRL.

We could use a Trie data structure. Traverse the Trie as you press more keys. If you end on a leaf, execute the code. If an internal node has a command linked to it, execute ONLY when the first key pressed (in this case, CTRL) is released.

@carlos-zamora commented on GitHub (Jun 20, 2019): Here’s a thought. What if we execute the keybinding when “CTRL” is released? So “CTRL-K, CTRL-D” is really just you hold CTRL, then press K, then press D. And it executes when you release CTRL. We could use a Trie data structure. Traverse the Trie as you press more keys. If you end on a leaf, execute the code. If an internal node has a command linked to it, execute ONLY when the first key pressed (in this case, CTRL) is released.
Author
Owner

@dnagl commented on GitHub (Jul 1, 2019):

Do you have already some idea how this could be structured in the profiles.json file?

@dnagl commented on GitHub (Jul 1, 2019): Do you have already some idea how this could be structured in the `profiles.json` file?
Author
Owner

@zadjii-msft commented on GitHub (Jul 3, 2019):

@dnagl Yep 😄

I actually made the "keys" property an array when I first implemented keybindings, despite there only ever being one key combo, because I knew that this is something we'd want eventually.

@zadjii-msft commented on GitHub (Jul 3, 2019): @dnagl Yep 😄 I actually made the "keys" property an array when I first implemented keybindings, despite there only ever being one key combo, because I knew that this is something we'd want eventually.
Author
Owner

@dnagl commented on GitHub (Jul 3, 2019):

@zadjii-msft Seems obvious. The property contains an array. 😄

@dnagl commented on GitHub (Jul 3, 2019): @zadjii-msft Seems obvious. The property contains an array. 😄
Author
Owner

@0xabu commented on GitHub (Sep 19, 2019):

IMHO: chorded key binding shortcuts are generally useful when there are many possible actions/bindings, so you need a bigger space of possible inputs. A text editor / IDE is obviously one such scenario. It seems unlikely that a terminal would ever have so many shortcuts... does anyone know of another non-IDE terminal that implemented this?

Per https://github.com/microsoft/terminal/issues/2388#issuecomment-533281021 the array of "keys" might be a logical syntax to repurpose for multiple bindings to the same action.

@0xabu commented on GitHub (Sep 19, 2019): IMHO: chorded key binding shortcuts are generally useful when there are many possible actions/bindings, so you need a bigger space of possible inputs. A text editor / IDE is obviously one such scenario. It seems unlikely that a terminal would ever have so many shortcuts... does anyone know of another non-IDE terminal that implemented this? Per https://github.com/microsoft/terminal/issues/2388#issuecomment-533281021 the array of "keys" might be a logical syntax to repurpose for multiple bindings to the same action.
Author
Owner

@DHowett-MSFT commented on GitHub (Sep 19, 2019):

tmux and screen are both terminal emulators that have implemented chorded keys. 😄

@DHowett-MSFT commented on GitHub (Sep 19, 2019): **tmux** and **screen** are both terminal emulators that have implemented chorded keys. :smile:
Author
Owner

@0xabu commented on GitHub (Sep 19, 2019):

tmux and screen are both terminal emulators that have implemented chorded keys. 😄

Clearly I've massively underestimated your ambitions for this app!

@0xabu commented on GitHub (Sep 19, 2019): > tmux and screen are both terminal emulators that have implemented chorded keys. 😄 Clearly I've massively underestimated your ambitions for this app!
Author
Owner

@DHowett-MSFT commented on GitHub (Sep 19, 2019):

If you consider that iTerm2 can act as a tmux controller and turn tmux panes into physical terminal panes, the lines between the multiplexer and terminal emulator are already a bit blurry. We support panes, and we probably eventually want some higher-level concepts like pane selection, grouping, and movement that I don't think we should bind over the fairly limited set of level-1 keyboard characters.

More to the point, though, I personally hold all the things that could be serialized in VT as sacred and "under the purview of the receiving application", so chording gives us an escape hatch to add a terminal control plane keyset. Ya know?

@DHowett-MSFT commented on GitHub (Sep 19, 2019): If you consider that iTerm2 can act as a tmux controller and turn tmux panes into physical terminal panes, the lines between the multiplexer and terminal emulator are already a bit blurry. We support panes, and we probably eventually want some higher-level concepts like pane selection, grouping, and movement that I don't think we should bind over the fairly limited set of level-1 keyboard characters. More to the point, though, I personally hold all the things that could be serialized in VT as sacred and "under the purview of the receiving application", so chording gives us an escape hatch to add a terminal control plane keyset. Ya know?
Author
Owner

@jonschlinkert commented on GitHub (Sep 14, 2021):

does anyone know of another non-IDE terminal that implemented this?

FWIW, we're also implementing chords in enquirer, a prompt system, and this has turned out to be useful for not just traditional chords, but also multi-digit numbers. In general, one of the things that makes this challenging in the terminal (or at least limited in node.js) is there is no way to get key up/down events, so we can't determine when someone is holding down the ctrl key, etc.

If the first key of a chord is pressed, but the second key isn't bound to an action, should we write both keys to the input?

This is effectively what we're doing, and it seems to work well. Basically we store the first key if there is a non-chord key binding that should be used if the second key in the chord isn't pressed. If the second key is either a) not pressed before the timeout expires, or b) another key that does not match the second key in the chord is pressed, then we re-dispatch the stored key as a non-chord, then we dispatch the new (second) key.

@jonschlinkert commented on GitHub (Sep 14, 2021): > does anyone know of another non-IDE terminal that implemented this? FWIW, we're also implementing chords in [enquirer](https://github.com/enquirer/enquirer), a prompt system, and this has turned out to be useful for not just traditional chords, but also multi-digit numbers. In general, one of the things that makes this challenging in the terminal (or at least limited in node.js) is there is no way to get key up/down events, so we can't determine when someone is holding down the `ctrl` key, etc. > If the first key of a chord is pressed, but the second key isn't bound to an action, should we write both keys to the input? This is effectively what we're doing, and it seems to work well. Basically we store the first key if there is a non-chord key binding that should be used if the second key in the chord isn't pressed. If the second key is either a) not pressed before the timeout expires, or b) another key that does not match the second key in the chord is pressed, then we re-dispatch the stored key as a non-chord, then we dispatch the new (second) key.
Author
Owner

@SnirBroshi commented on GitHub (Oct 21, 2021):

IMHO: chorded key binding shortcuts are generally useful when there are many possible actions/bindings, so you need a bigger space of possible inputs. A text editor / IDE is obviously one such scenario. It seems unlikely that a terminal would ever have so many shortcuts...

A terminal runs another application inside it, which also has keybindings of it's own. For example, this is why Ctrl+Shift+W closes tabs instead of Ctrl+W, since Ctrl+W is used in most shells to delete a word. Since the terminal can even run editors such as emacs/vim inside it, the need for more obscure keybindings arises very quickly.

@SnirBroshi commented on GitHub (Oct 21, 2021): > IMHO: chorded key binding shortcuts are generally useful when there are many possible actions/bindings, so you need a bigger space of possible inputs. A text editor / IDE is obviously one such scenario. It seems unlikely that a terminal would ever have so many shortcuts... A terminal runs another application inside it, which also has keybindings of it's own. For example, this is why Ctrl+Shift+W closes tabs instead of Ctrl+W, since Ctrl+W is used in most shells to delete a word. Since the terminal can even run editors such as emacs/vim inside it, the need for more obscure keybindings arises very quickly.
Author
Owner

@jcrben commented on GitHub (Jun 12, 2022):

@0xabu terminal apps which support this feature already are https://github.com/Eugeny/tabby (formerly terminus)
image

and https://github.com/vercel/hyper/ as well

I'm not able to use these at work so looking forward to support with this terminal

@jcrben commented on GitHub (Jun 12, 2022): @0xabu terminal apps which support this feature already are https://github.com/Eugeny/tabby (formerly terminus) <img width="433" alt="image" src="https://user-images.githubusercontent.com/5614134/173217372-242c4ae1-974d-49a8-aaa5-df95e6f87e6d.png"> and https://github.com/vercel/hyper/ as well I'm not able to use these at work so looking forward to support with this terminal
Author
Owner

@coffeebe4code commented on GitHub (Feb 12, 2023):

What is needed here to get this going/finished?

@coffeebe4code commented on GitHub (Feb 12, 2023): What is needed here to get this going/finished?
Author
Owner

@zadjii-msft commented on GitHub (Feb 12, 2023):

Someone would need to figure out how to mechanically get a chorded keybinding to work with our TermControl right now. I left notes in the OP for things I would look into. I'd think the trick would be finding the way for KeyBindings to "stash" a key if it was the start of a chord, but the next key wasn't, then also sending the original stashed key if the second key doesn't match.

Alas, I don't think this is something I'll have time to get to anytime soon, but if someone else would like to take a look I'd be happy to help in whatever way I can ☺️

@zadjii-msft commented on GitHub (Feb 12, 2023): Someone would need to figure out how to mechanically get a chorded keybinding to work with our TermControl right now. I left notes in the OP for things I would look into. I'd think the trick would be finding the way for `KeyBindings` to "stash" a key if it was the start of a chord, but the next key wasn't, then also sending the original stashed key if the second key doesn't match. Alas, I don't think this is something I'll have time to get to anytime soon, but if someone else would like to take a look I'd be happy to help in whatever way I can ☺️
Author
Owner

@zadjii-msft commented on GitHub (Jan 23, 2025):

Dropping a note in here:

Lib/_pyrepl/windows_eventqueue.py

that's pretty clever, the key parsing is a tree of maps like, map<char, char|map> and then each keystroke takes you deeper into the tree, till you find a leaf or nothing. gotta keep that in mind for key chords in the future

@zadjii-msft commented on GitHub (Jan 23, 2025): Dropping a note in here: [Lib/_pyrepl/windows_eventqueue.py](https://github.com/python/cpython/pull/124119/files) > that's pretty clever, the key parsing is a tree of maps like, map<char, char|map> and then each keystroke takes you deeper into the tree, till you find a leaf or nothing. gotta keep that in mind for key chords in the future
Author
Owner

@Greg-T8 commented on GitHub (Jan 12, 2026):

Would love for this feature to be implemented. I use chords to create new windows in tmux and VSCode, but I don't have that capability in Windows Terminal.

@Greg-T8 commented on GitHub (Jan 12, 2026): Would love for this feature to be implemented. I use chords to create new windows in tmux and VSCode, but I don't have that capability in Windows Terminal.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#1767