diff --git a/nebula_app/src/gpui_shell/terminal/keymap.rs b/nebula_app/src/gpui_shell/terminal/keymap.rs index d119c0f..01c1db7 100644 --- a/nebula_app/src/gpui_shell/terminal/keymap.rs +++ b/nebula_app/src/gpui_shell/terminal/keymap.rs @@ -589,17 +589,45 @@ mod tests { /// 当按键已处理、不再 TranslateMessage,中文组字起不来。 #[cfg(windows)] #[test] - fn unmodified_letters_stay_on_the_ime_path_in_win32_mode() { + fn win32_mode_preserves_text_input_and_modified_slash() { let mode = TermMode::WIN32_INPUT_MODE; assert_eq!(encode(&keystroke("n"), &mode), None); assert_eq!(encode(&keystroke("a"), &mode), None); assert_eq!(encode(&keystroke("1"), &mode), None); assert_eq!(encode(&keystroke("space"), &mode), None); + assert_eq!(encode(&keystroke("/"), &mode), None); + assert_eq!(encode(&keystroke("?"), &mode), None); + assert_eq!(encode(&Keystroke::parse("shift-/").unwrap(), &mode), None); // Ctrl+C 仍走记录,不能为了 IME 把快捷键也放掉。 let mut ctrl_c = keystroke("c"); ctrl_c.modifiers.control = true; assert!(encode(&ctrl_c, &mode).is_some()); assert!(encode(&keystroke("escape"), &mode).is_some()); + + use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ + MAPVK_VK_TO_VSC, MapVirtualKeyW, VkKeyScanW, + }; + + let key = Keystroke::parse("ctrl-/").unwrap(); + let mapping = unsafe { VkKeyScanW(b'/' as u16) } as u16; + let vk = mapping & 0xff; + let scan = unsafe { MapVirtualKeyW(u32::from(vk), MAPVK_VK_TO_VSC) }; + let modifiers = 8 | ((mapping >> 8 & 1) << 4) | ((mapping >> 8 & 4) >> 1); + assert_eq!( + encode(&key, &TermMode::WIN32_INPUT_MODE), + Some( + format!("\x1b[{vk};{scan};0;1;{modifiers};1_\x1b[{vk};{scan};0;0;{modifiers};1_") + .into_bytes() + ) + ); + } + + #[test] + fn ctrl_slash_keeps_legacy_and_kitty_encodings() { + let key = Keystroke::parse("ctrl-/").unwrap(); + assert_eq!(encode(&key, &TermMode::default()), Some(b"\x1f".to_vec())); + let mode = TermMode::WIN32_INPUT_MODE | TermMode::DISAMBIGUATE_ESC_CODES; + assert_eq!(encode(&key, &mode), Some(b"\x1b[47;5u".to_vec())); } /// 普通空格与字母使用同一文本输入路径:英语布局最终提交 `" "`,IME diff --git a/nebula_app/src/gpui_shell/terminal/keymap/win32.rs b/nebula_app/src/gpui_shell/terminal/keymap/win32.rs index f9a73dd..771870b 100644 --- a/nebula_app/src/gpui_shell/terminal/keymap/win32.rs +++ b/nebula_app/src/gpui_shell/terminal/keymap/win32.rs @@ -2,7 +2,7 @@ use gpui::Keystroke; -/// 无修饰的字母/数字/空格必须交给 IME / `TranslateMessage`,不能编进 PTY。 +/// 普通可打印字符必须交给 IME / `TranslateMessage`,不能编进 PTY。 /// /// GPUI 的 Windows 后端:`on_key_down` 一旦 `stop_propagation`,就不会再 /// `TranslateMessage`。IME 组字(微软拼音)是 TranslateMessage 喂进去的; @@ -18,20 +18,19 @@ pub(super) fn win32_encodes_keystroke(ks: &Keystroke) -> bool { } let mut chars = key.chars(); match (chars.next(), chars.next()) { - (Some(c), None) if c.is_ascii_alphanumeric() => false, + (Some(c), None) if c.is_ascii_graphic() => false, _ => true, } } -/// GPUI 键名 → Win32 虚拟键码。 +/// GPUI 键名 → Win32 虚拟键码及布局所需的修饰键。 /// /// 旧壳从 winit fork 的 `RawKeyEventInfo` 直接拿到系统报的 VK;GPUI 的 -/// `Keystroke` 只有键名,所以这里按名字反查。表只覆盖**编码器会处理的键** -/// (控制键、方向、功能键)——可打印字符在 GPUI 走 IME 管道,不经这里。 -pub(super) fn virtual_key_of(key: &str) -> Option { +/// `Keystroke` 只有键名,所以这里按名字反查;普通文本仍走 IME 管道。 +pub(super) fn virtual_key_of(key: &str) -> Option<(u16, u16)> { use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ VK_BACK, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_HOME, VK_INSERT, VK_LEFT, - VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SPACE, VK_TAB, VK_UP, + VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SPACE, VK_TAB, VK_UP, VkKeyScanW, }; let vk = match key { @@ -50,6 +49,11 @@ pub(super) fn virtual_key_of(key: &str) -> Option { "delete" => VK_DELETE, "pageup" => VK_PRIOR, "pagedown" => VK_NEXT, + "/" => { + // GPUI 将布局所需的 Shift 合入键名,不能写死美式布局的 VK_OEM_2。 + let mapping = unsafe { VkKeyScanW(b'/' as u16) }; + return (mapping != -1).then_some((mapping as u16 & 0xff, mapping as u16 >> 8)); + }, // F1..F24 在 VK 表里连号。 key if key.starts_with('f') => { let index: u16 = key[1..].parse().ok()?; @@ -68,7 +72,7 @@ pub(super) fn virtual_key_of(key: &str) -> Option { c.to_ascii_uppercase() as u16 }, }; - Some(vk) + Some((vk, 0)) } /// 控制键必须携带真实 `KEY_EVENT_RECORD` 的字符值(Esc=0x1B、Enter=0x0D、 @@ -121,17 +125,17 @@ pub(super) fn win32_input_record(ks: &Keystroke, key_down: bool) -> Option