fix(input): preserve shift in kitty alternate reports (#2479)
refs #2435
This commit is contained in:
parent
374d3eddc5
commit
b0723b7906
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
### Fixed
|
||||
- macOS `herdr --remote` clients now keep the accepted bridge socket blocking, preventing an immediate disconnect after the protocol handshake. (#2478, thanks @mathijshenquet)
|
||||
- Prefix keybindings now preserve Shift in WezTerm Kitty keyboard mode, so commands such as config reload no longer trigger their unshifted action. (#2435)
|
||||
- Stable direct installs, self-updates, and remote helper downloads now require and verify the SHA-256 digest published for each GitHub release asset.
|
||||
- Configs containing the retired Herdr-written `ui.agent_panel_scope` setting no longer report it as an unknown key after upgrades. (#2292)
|
||||
- Claude Code confirmation prompts using `Enter to confirm · Esc to cancel` now report `blocked` instead of `idle`. (#2268)
|
||||
|
|
|
|||
|
|
@ -1901,7 +1901,12 @@ mod tests {
|
|||
use super::super::{state_with_workspaces, unique_temp_path};
|
||||
use super::*;
|
||||
use crate::{
|
||||
app::App, config::Config, input::TerminalKey, terminal::TerminalState, workspace::Workspace,
|
||||
app::App,
|
||||
config::Config,
|
||||
input::TerminalKey,
|
||||
raw_input::{parse_raw_input_bytes_sync, RawInputEvent},
|
||||
terminal::TerminalState,
|
||||
workspace::Workspace,
|
||||
};
|
||||
|
||||
fn mark_worktree_space_member(state: &mut AppState, ws_idx: usize, key: &str) {
|
||||
|
|
@ -2840,6 +2845,35 @@ command = "printf literal > '{}'"
|
|||
assert_eq!(app.state.mode, Mode::RenameWorkspace);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn kitty_shifted_alternate_without_modifier_prefers_reload_over_resize() {
|
||||
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let mut app = App::new(
|
||||
&Config::default(),
|
||||
true,
|
||||
None,
|
||||
api_rx,
|
||||
crate::api::EventHub::default(),
|
||||
);
|
||||
app.state.workspaces = vec![Workspace::test_new("test")];
|
||||
app.state.active = Some(0);
|
||||
app.state.selected = 0;
|
||||
app.state.mode = Mode::Prefix;
|
||||
|
||||
let mut events = parse_raw_input_bytes_sync(b"\x1b[114:82;1u");
|
||||
assert_eq!(events.len(), 1);
|
||||
let RawInputEvent::Key(key) = events.remove(0) else {
|
||||
panic!("expected key event");
|
||||
};
|
||||
assert_eq!(
|
||||
action_for_key(&app.state, key.clone(), BindingDispatch::Prefix),
|
||||
Some(NavigateAction::ReloadConfig)
|
||||
);
|
||||
app.handle_prefix_key(key);
|
||||
|
||||
assert_eq!(app.state.mode, Mode::Terminal);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_uppercase_prefers_shifted_reload_binding_over_unshifted() {
|
||||
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
|
|
|||
|
|
@ -39,8 +39,17 @@ fn parse_kitty_key_sequence(data: &str) -> Option<TerminalKey> {
|
|||
|
||||
let code = kitty_codepoint_to_keycode(codepoint)?;
|
||||
let kind = parse_kitty_event_type(event_type)?;
|
||||
let mut modifiers = key_modifiers_from_u8(modifier);
|
||||
// Kitty permits the shifted alternate only while Shift is active. Normalize
|
||||
// contradictory reports here so they cannot dispatch an unshifted command.
|
||||
if matches!(code, KeyCode::Char(_))
|
||||
&& shifted_codepoint
|
||||
.is_some_and(|shifted| shifted != codepoint && char::from_u32(shifted).is_some())
|
||||
{
|
||||
modifiers |= KeyModifiers::SHIFT;
|
||||
}
|
||||
|
||||
let mut key = TerminalKey::new(code, key_modifiers_from_u8(modifier)).with_kind(kind);
|
||||
let mut key = TerminalKey::new(code, modifiers).with_kind(kind);
|
||||
if let Some(shifted_codepoint) = shifted_codepoint {
|
||||
key = key.with_shifted_codepoint(shifted_codepoint);
|
||||
}
|
||||
|
|
@ -598,6 +607,30 @@ mod tests {
|
|||
assert_eq!(key.shifted_codepoint, Some('L' as u32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_kitty_sequence_recovers_omitted_shift_modifier() {
|
||||
for (sequence, kind) in [
|
||||
("\x1b[114:82;1u", crossterm::event::KeyEventKind::Press),
|
||||
("\x1b[114:82;1:2u", crossterm::event::KeyEventKind::Repeat),
|
||||
("\x1b[114:82;1:3u", crossterm::event::KeyEventKind::Release),
|
||||
] {
|
||||
let key = parse_terminal_key_sequence(sequence).unwrap();
|
||||
assert_eq!(key.code, KeyCode::Char('r'));
|
||||
assert_eq!(key.modifiers, KeyModifiers::SHIFT);
|
||||
assert_eq!(key.kind, kind);
|
||||
assert_eq!(key.shifted_codepoint, Some('R' as u32));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_kitty_sequence_does_not_infer_shift_without_distinct_shifted_alternate() {
|
||||
for sequence in ["\x1b[114;1u", "\x1b[114:114;1u", "\x1b[114::113;1u"] {
|
||||
let key = parse_terminal_key_sequence(sequence).unwrap();
|
||||
assert_eq!(key.code, KeyCode::Char('r'));
|
||||
assert_eq!(key.modifiers, KeyModifiers::empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_kitty_sequence_preserves_non_us_shift_pairs() {
|
||||
for (sequence, base, shifted) in [
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ modify_other_keys_ctrl_shift_l 1b5b32373b363b3130387e char:l control+shift press
|
|||
kitty_alt_backspace 1b5b3132373b3375 backspace alt press
|
||||
kitty_shift_letter 1b5b3130383a37363b323a3175 char:l shift press 76
|
||||
kitty_shift_symbol 1b5b34393a33333b323a3175 char:1 shift press 33
|
||||
kitty_omitted_shift_modifier 1b5b3131343a38323b3175 char:r shift press 82
|
||||
kitty_release_letter 1b5b3130383a37363b323a3375 char:l shift release 76
|
||||
kitty_keypad_0 1b5b35373339393b3175 char:0 - press
|
||||
kitty_keypad_1 1b5b35373430303b3175 char:1 - press
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 7.
|
Loading…
Reference in New Issue