Skip to content

fix: restore key auto-repeat in copy mode and other non-terminal modes (PoC) - #2

Closed
shibayu36 wants to merge 3 commits into
masterfrom
copy-mode-repeat-context-poc
Closed

fix: restore key auto-repeat in copy mode and other non-terminal modes (PoC)#2
shibayu36 wants to merge 3 commits into
masterfrom
copy-mode-repeat-context-poc

Conversation

@shibayu36

Copy link
Copy Markdown
Owner

Summary

PoC fix for key auto-repeat not working while holding keys in copy mode, the navigator, and similar screens. Instead of maintaining a hand-written list of repeatable keys, it decides whether a repeat is allowed by looking at what the key press actually did: whether the input context changed.

Upstream issue: herdrdev#2371
Upstream PR taking the key-list approach: herdrdev#2372

Approach

The existing input lease ledger (src/app/input/lease.rs) already has the machinery: repeats are reprocessed only when the context at repeat time matches the context captured at press time. Copy mode simply had no context (None), which meant its repeats were always suppressed.

This PoC adds a NonTerminal variant to the input context and lets copy mode participate. That alone yields:

  • Movement keys (arrows, hjkl, ...): the mode does not change, so contexts match and repeats are reprocessed
  • Confirm/exit keys (Enter, y, q, Esc, ...): the mode changes, so contexts differ and repeats stay suppressed as before

so there is no repeatable-key list to maintain (no hand-written exclusions like is_prefix_key).

Prepare the input-context enum for a non-terminal variant: it is about
to model the input context of a key press, terminal-bound or not, so
the "terminal input" name would no longer describe it.
TerminalInputTarget keeps its name because it is genuinely
terminal-only.

refs herdrdev#2371
Track copy mode as a non-terminal input context so held semantic keys
(arrows, etc.) pass the press/repeat context comparison instead of
being suppressed, and route reprocessed repeats through the
non-terminal key handler in the headless pipeline.

refs herdrdev#2371
…modes

Map Mode::Navigator, Mode::Navigate, and Mode::KeybindHelp to
non-terminal input contexts so held movement keys repeat in the quick
jump modal, the workspaces sidebar, and the keybind help screen. All
three were reported in the issue comments. Their confirm and exit keys
change modes, so those repeats stay suppressed by the context
comparison without extra handling.

refs herdrdev#2371
Comment thread src/app/mod.rs

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TerminalInputContext {
pub(crate) enum InputContext {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Renamed TerminalInputContext to InputContext: the enum now has a NonTerminal variant, so the name "terminal input" no longer matches what it holds. TerminalInputTarget keeps its name because it is truly terminal-only. The rename is a mechanical replace, split into the first commit.

Comment thread src/app/mod.rs
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum NonTerminalInputContext {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This enum lists only the modes where we want key repeat. Modes not listed here get None from input_context(), so their repeats stay suppressed as before. I added only the four modes needed so far, but listing all modes is also possible if that is preferred.

Comment thread src/app/mod.rs
Some(TerminalInputContext::Pane)
Some(InputContext::Pane)
} else if self.state.mode == Mode::Copy {
Some(InputContext::NonTerminal(NonTerminalInputContext::Copy))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This line is the direct cause of the broken repeat in copy mode. Whether repeats are allowed is decided right after a press is handled, by comparing the context before the press with the context after it (complete_press). If both are the same Some, later repeats are reprocessed; if either is None, they are all dropped. Copy mode always had None here, so its repeats were always dropped. Returning Some makes this comparison succeed.

Keys that leave the mode (Enter/y/q/Esc, ...) change the context at press time, so they still do not repeat. Every repeat also re-checks that the context is still the same, so if the mode changes while the key is held, the repeat stops there.

Comment thread src/app/mod.rs
let initial_context = self.input_context();
let target = if matches!(
initial_context,
Some(InputContext::Pane | InputContext::Popup(_))

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This used to be is_some(), which meant "if there is a context, forward the key to the terminal". That is no longer true once NonTerminal contexts exist, so the terminal variants are matched explicitly now. The reprocess loop had the same pattern; there it is handled by the added NonTerminal branch (a hunk above in this file).

@shibayu36 shibayu36 closed this Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant