T44: round 2 fix: on Windows a repeat goes as a record where its press did, one that types after its modifiers came up too, so its release still goes as a record and herdr frees the key; the choice is one function, records, tested on every system
CI / platforms (windows-latest) (push) Failing after 11s
CI / platforms (ubuntu-24.04) (push) Successful in 10m38s
CI / macos (push) Successful in 14m37s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-18 04:28:55 -04:00
1 parent 6f8bfaeff8
commit 3f47134928
2 files changed
+46 -12

No files matched your search

+45 -11
View File
@@ -98,9 +98,10 @@ impl TerminalView {
} else {
Action::Press
};
let went = self.send(&event.keystroke, action, facts, false, cx);
self.held
.insert(held(&event.keystroke, facts), went == Some(true));
let id = held(&event.keystroke, facts);
let pressed = self.held.get(&id) == Some(&true);
let went = self.send(&event.keystroke, action, facts, pressed, cx);
self.held.insert(id, went == Some(true));
if went.is_some() {
cx.stop_propagation();
}
@@ -120,13 +121,13 @@ impl TerminalView {
}
}
/// Sends what `keystroke` means to the program: none when it is text the input method types, else whether it went as Windows' record; `record` says that of its press.
/// Sends what `keystroke` means to the program: none when it is text the input method types, else whether it went as Windows' record; `pressed` says that of a held key's press.
fn send(
&mut self,
keystroke: &Keystroke,
action: Action,
facts: Facts,
record: bool,
pressed: bool,
cx: &mut Context<Self>,
) -> Option<bool> {
let modifiers = keystroke.modifiers;
@@ -154,12 +155,8 @@ impl TerminalView {
all: mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC),
text: mode.contains(TermMode::REPORT_ASSOCIATED_TEXT),
};
// As in Windows Terminal: kitty's flags first, then Windows' records; text stays text.
#[cfg(windows)]
if self.win32
&& kitty == Kitty::default()
&& (record || action != Action::Release && !keys::types(&key))
{
if self.win32 && records(kitty, action, keys::types(&key), pressed) {
match super::win32::record(keystroke, action) {
Some(bytes) => {
log::trace!("terminal {}: {key:?} sends {bytes:?}", self.pty.pid);
@@ -174,7 +171,7 @@ impl TerminalView {
}
}
#[cfg(not(windows))]
debug_assert!(!record, "only Windows sends records");
debug_assert!(!pressed, "only Windows sends records");
let bytes = keys::encode(&key, mode.contains(TermMode::APP_CURSOR), kitty)?;
log::trace!("terminal {}: {key:?} sends {bytes:?}", self.pty.pid);
self.input(bytes, cx);
@@ -255,6 +252,17 @@ pub(super) enum Held {
Name(String),
}
/// Whether a key goes as Windows' record, as in Windows Terminal: kitty's flags go first, text stays text, and a repeat or release goes as its press did.
#[cfg(any(windows, test))]
fn records(kitty: Kitty, action: Action, types: bool, pressed: bool) -> bool {
kitty == Kitty::default()
&& match action {
Action::Press => !types,
Action::Repeat => pressed || !types,
Action::Release => pressed,
}
}
fn held(keystroke: &Keystroke, facts: Facts) -> Held {
facts
.code
@@ -347,3 +355,29 @@ impl EntityInputHandler for TerminalView {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_record_press_takes_its_repeats_and_release_with_it() {
let plain = Kitty::default();
// Ctrl-Shift-] goes as a record; Ctrl and Shift come up while ] repeats and types.
assert!(records(plain, Action::Press, false, false));
assert!(records(plain, Action::Repeat, true, true));
assert!(records(plain, Action::Release, true, true));
// A key that types goes as text, and so does its release.
assert!(!records(plain, Action::Press, true, false));
assert!(!records(plain, Action::Repeat, true, false));
assert!(!records(plain, Action::Release, true, false));
let kitty = Kitty {
disambiguate: true,
..plain
};
assert!(
!records(kitty, Action::Press, false, false),
"kitty's flags go first"
);
}
}