Skip to content

gpui: Only time out multi-stroke bindings when current prefix matches#42659

Merged
ConradIrwin merged 5 commits intozed-industries:mainfrom
xipeng-jin:feat/keybind-timer
Nov 21, 2025
Merged

gpui: Only time out multi-stroke bindings when current prefix matches#42659
ConradIrwin merged 5 commits intozed-industries:mainfrom
xipeng-jin:feat/keybind-timer

Conversation

@xipeng-jin
Copy link
Contributor

@xipeng-jin xipeng-jin commented Nov 13, 2025

Part One for Resolving #10910

Summary

Typing prefix (partial keybinding) will behave like Vim. No timeout until you either finish the sequence or hit Escape, while ambiguous sequences still auto-resolve after 1s.

Description

This follow-up tweaks the which-key system first part groundwork so our timeout behavior matches Vim’s expectations. Then we can implement the UI part in the next step (reference latest comments in #34798)

  • DispatchResult now reports when the current keystrokes are already a complete binding in the active context stack (pending_has_binding). We only start the 1s flush timer in that case. Pure prefixes or sequences that only match in other contexts—stay pending indefinitely, so leader-style combos like space f g no longer evaporate after a second.
  • Window::dispatch_key_event cancels any prior timer before scheduling a new one and only spawns the background flush task when pending_has_binding is true. If there’s no matching binding, we keep the pending keystrokes and rely on an explicit Escape or more typing to resolve them.

Release Notes:

  • Multi-stroke keybindings (like cmd-k cmd-s, or g d in vim mode) will no-longer time out waiting for input. Before this change Zed would wait at most 1s for the second keystroke. In the case where the start of the multi-stroke binding is itself a valid key action (or input text) then the 1s timeout will still apply.

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Nov 13, 2025
@maxdeviant maxdeviant changed the title GPUI: only time out multi-stroke bindings when current prefix matches gpui: Only time out multi-stroke bindings when current prefix matches Nov 13, 2025
@xipeng-jin
Copy link
Contributor Author

xipeng-jin commented Nov 13, 2025

A few comments to be considered:

  1. Do we want to consider to create a setting for customizing the default timeout duration? It is currently hard coded value with 1 second. For example, the user want to change to 5 second to match VSCode. If answer is yes, we maybe also consider to push this settings in a second PR which will be included in the which-key own module.
  2. We might want to think more ahead about dealing with leader key. For example, the default leader key is <space> in Vim. And in Zed we currently bound <space> to a action vim::WrappingRight at default, which would cause a conflict with the leader key since people will expect no timeout setting when they type the leader key <space>, but in zed we already have a matching keybinding with it so that it will trigger the action vim::WrappingRight after 1 second. And also at default it will automatically bring up the which-key menu after typing the leader key in Neovim, so it shouldn't use the timeout setting in this case.

cc.
@ConradIrwin . Please review as your last comment from that thread #34798 (comment). Thank you!
@Cretezy . If you still work on this feature, I am happy to have your feedback here regarding to this timer configuration. If you want to pair on this I am also happy to do it! Really appreciate your previous work!

@xipeng-jin
Copy link
Contributor Author

There is one integration test collab tests::integration_tests::test_pane_split_left we have to modify to adapt the new behavior from this adaptive timeout settings for which-key system. To be more specific, we need to adjust the test so it now document the new “no timeout for unmatched prefixes” behavior which means after triggering cmd-k and waiting two seconds, the follow-up left keystroke is still treated as part of the multi-stroke binding, so the workspace ends up with three items instead of two. Reference the commit 8684055

@ConradIrwin
Copy link
Member

Thanks for this!

I'm worried about this condition:

                .filter(|key_down| {
                    key_down.keystroke.key_char.is_some()
                        && key_down
                            .keystroke
                            .modifiers
                            .is_subset_of(&Modifiers::shift())
                })

because I think keys like option-x on macOS should still be treated as character input. Do we need the additional filter on the shift modifier? In theory (though maybe not in practice) key_char should be enough.

@xipeng-jin
Copy link
Contributor Author

xipeng-jin commented Nov 20, 2025

Yes, thank you for correcting me. I think you are right. I was missing the default functionalities for typing option-x (macOS) or alt-x (Windows/Linux) to entering the special characters, since I rarely use those characters. And I checked keystroke.key_char has covered all the cases for input text in insert-ish mode so we should be good to only use key_char in the filter. No need for filtering on the shift modifier.

I removed that filter in the new commit and I noticed there is minor difference when you type option-x or option plus any characters compared with the normal characters or using shift to enter capital case. Please check the following recording.

Screen.Recording.2025-11-20.at.7.30.08.AM.mov

As you can see, normally we will have a pending effect for the input text when we didn't enter the next key_char immediately (in this example is character j or J). However for special characters we miss this pending effect and it simply didn't show the special character when we type option-x, but after the 1 second timer if you didn't type the next one k it will eventually show up. I didn't have a chance to dig into this difference yet, but I would like to align with you first @ConradIrwin this also seems a issue to you?

@xipeng-jin
Copy link
Contributor Author

Okay, I found the root cause for this inconsistent behavior. Since observe_pending_input only collected pending text when modifiers were in the subset of Shift modifier, Option/Alt keystrokes had a key_char but also an Alt modifier, so no pending text was inserted/highlighted even though the timeout stayed active. Therefore, it was dropping Alt-generated characters and leaving them invisible until the timeout fired. 51798db

As a result, we need to change the pending text/highlight respecting any keystroke that produces a key_char, so Option/Alt characters render immediately while the 1s timeout still runs for ambiguous bindings.

@ConradIrwin
Copy link
Member

Great, thank you!

@ConradIrwin ConradIrwin merged commit b3ebcef into zed-industries:main Nov 21, 2025
24 checks passed
11happy pushed a commit to 11happy/zed that referenced this pull request Dec 1, 2025
…zed-industries#42659)

Part One for Resolving zed-industries#10910

### Summary
Typing prefix (partial keybinding) will behave like Vim. No timeout
until you either finish the sequence or hit Escape, while ambiguous
sequences still auto-resolve after 1s.

### Description
This follow-up tweaks the which-key system first part groundwork so our
timeout behavior matches Vim’s expectations. Then we can implement the
UI part in the next step (reference latest comments in
zed-industries#34798)
- `DispatchResult` now reports when the current keystrokes are already a
complete binding in the active context stack (`pending_has_binding`). We
only start the 1s flush timer in that case. Pure prefixes or sequences
that only match in other contexts—stay pending indefinitely, so
leader-style combos like `space f g` no longer evaporate after a second.
- `Window::dispatch_key_event` cancels any prior timer before scheduling
a new one and only spawns the background flush task when
`pending_has_binding` is true. If there’s no matching binding, we keep
the pending keystrokes and rely on an explicit Escape or more typing to
resolve them.

Release Notes:

- Fixed multi-stroke keybindings so only ambiguous prefixes auto-trigger
after 1 s; unmatched prefixes now stay pending until canceled, matching
Vim-style leader behavior.
ConradIrwin added a commit that referenced this pull request Dec 17, 2025
Closes #10910

Follow up work continuing from the last PR
#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
HactarCE pushed a commit that referenced this pull request Dec 17, 2025
Closes #10910

Follow up work continuing from the last PR
#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
rtfeldman pushed a commit that referenced this pull request Jan 5, 2026
Closes #10910

Follow up work continuing from the last PR
#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Closes zed-industries#10910

Follow up work continuing from the last PR
zed-industries#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Jan 20, 2026
Closes zed-industries#10910

Follow up work continuing from the last PR
zed-industries#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
LivioGama pushed a commit to LivioGama/zed that referenced this pull request Feb 15, 2026
Closes zed-industries#10910

Follow up work continuing from the last PR
zed-industries#42659. Add the UI element for
displaying vim like which-key menu.




https://github.com/user-attachments/assets/3dc5f0c9-5a2f-459e-a3db-859169aeba26


Release Notes:

- Added a which-key like modal with a compact, single-column panel
anchored to the bottom-right. You can enable with `{"which_key":
{"enabled": true}}` in your settings.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants