diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index d17752d395..8f48e4ba8b 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -1,4 +1,4 @@ -mod events; +mod keymappings; use alacritty_terminal::{ ansi::{ClearMode, Handler}, @@ -22,7 +22,7 @@ use crate::{ ZedListener, }; -use self::events::to_esc_str; +use self::keymappings::to_esc_str; const DEFAULT_TITLE: &str = "Terminal"; diff --git a/crates/terminal/src/connection/events.rs b/crates/terminal/src/connection/keymappings.rs similarity index 67% rename from crates/terminal/src/connection/events.rs rename to crates/terminal/src/connection/keymappings.rs index ae14e296b5..a4d429843b 100644 --- a/crates/terminal/src/connection/events.rs +++ b/crates/terminal/src/connection/keymappings.rs @@ -2,12 +2,9 @@ use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; /* -Design notes: -I would like terminal mode checking to be concealed behind the TerminalConnection in as many ways as possible. -Alacritty has a lot of stuff intermixed for it's input handling. TerminalConnection should be in charge -of anything that needs to conform to a standard that isn't handled by Term, e.g.: +Connection events still to do: - Reporting mouse events correctly. -- Reporting scrolls -> Depends on MOUSE_MODE, ALT_SCREEN, and ALTERNATE_SCROLL, etc. +- Reporting scrolls - Correctly bracketing a paste - Storing changed colors - Focus change sequence @@ -34,13 +31,24 @@ impl Modifiers { _ => Modifiers::Other, } } + + fn any(&self) -> bool { + match &self { + Modifiers::None => false, + Modifiers::Alt => true, + Modifiers::Ctrl => true, + Modifiers::Shift => true, + Modifiers::CtrlShift => true, + Modifiers::Other => true, + } + } } pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { let modifiers = Modifiers::new(&keystroke); // Manual Bindings including modifiers - let manual_esc_str = match (keystroke.key.as_ref(), modifiers) { + let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) { //Basic special keys ("space", Modifiers::None) => Some(" ".to_string()), ("tab", Modifiers::None) => Some("\x09".to_string()), @@ -192,53 +200,95 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } // Automated bindings applying modifiers - let modifier_code = modifier_code(&keystroke); - let modified_esc_str = match keystroke.key.as_ref() { - "up" => Some(format!("\x1b[1;{}A", modifier_code)), - "down" => Some(format!("\x1b[1;{}B", modifier_code)), - "right" => Some(format!("\x1b[1;{}C", modifier_code)), - "left" => Some(format!("\x1b[1;{}D", modifier_code)), - "f1" => Some(format!("\x1b[1;{}P", modifier_code)), - "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), - "f3" => Some(format!("\x1b[1;{}R", modifier_code)), - "f4" => Some(format!("\x1b[1;{}S", modifier_code)), - "F5" => Some(format!("\x1b[15;{}~", modifier_code)), - "f6" => Some(format!("\x1b[17;{}~", modifier_code)), - "f7" => Some(format!("\x1b[18;{}~", modifier_code)), - "f8" => Some(format!("\x1b[19;{}~", modifier_code)), - "f9" => Some(format!("\x1b[20;{}~", modifier_code)), - "f10" => Some(format!("\x1b[21;{}~", modifier_code)), - "f11" => Some(format!("\x1b[23;{}~", modifier_code)), - "f12" => Some(format!("\x1b[24;{}~", modifier_code)), - "f13" => Some(format!("\x1b[25;{}~", modifier_code)), - "f14" => Some(format!("\x1b[26;{}~", modifier_code)), - "f15" => Some(format!("\x1b[28;{}~", modifier_code)), - "f16" => Some(format!("\x1b[29;{}~", modifier_code)), - "f17" => Some(format!("\x1b[31;{}~", modifier_code)), - "f18" => Some(format!("\x1b[32;{}~", modifier_code)), - "f19" => Some(format!("\x1b[33;{}~", modifier_code)), - "f20" => Some(format!("\x1b[34;{}~", modifier_code)), - _ if modifier_code == 2 => None, - "insert" => Some(format!("\x1b[2;{}~", modifier_code)), - "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), - "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), - "end" => Some(format!("\x1b[1;{}F", modifier_code)), - "home" => Some(format!("\x1b[1;{}H", modifier_code)), - _ => None, - }; - if modified_esc_str.is_some() { - return modified_esc_str; + if modifiers.any() { + let modifier_code = modifier_code(&keystroke); + let modified_esc_str = match keystroke.key.as_ref() { + "up" => Some(format!("\x1b[1;{}A", modifier_code)), + "down" => Some(format!("\x1b[1;{}B", modifier_code)), + "right" => Some(format!("\x1b[1;{}C", modifier_code)), + "left" => Some(format!("\x1b[1;{}D", modifier_code)), + "f1" => Some(format!("\x1b[1;{}P", modifier_code)), + "f2" => Some(format!("\x1b[1;{}Q", modifier_code)), + "f3" => Some(format!("\x1b[1;{}R", modifier_code)), + "f4" => Some(format!("\x1b[1;{}S", modifier_code)), + "F5" => Some(format!("\x1b[15;{}~", modifier_code)), + "f6" => Some(format!("\x1b[17;{}~", modifier_code)), + "f7" => Some(format!("\x1b[18;{}~", modifier_code)), + "f8" => Some(format!("\x1b[19;{}~", modifier_code)), + "f9" => Some(format!("\x1b[20;{}~", modifier_code)), + "f10" => Some(format!("\x1b[21;{}~", modifier_code)), + "f11" => Some(format!("\x1b[23;{}~", modifier_code)), + "f12" => Some(format!("\x1b[24;{}~", modifier_code)), + "f13" => Some(format!("\x1b[25;{}~", modifier_code)), + "f14" => Some(format!("\x1b[26;{}~", modifier_code)), + "f15" => Some(format!("\x1b[28;{}~", modifier_code)), + "f16" => Some(format!("\x1b[29;{}~", modifier_code)), + "f17" => Some(format!("\x1b[31;{}~", modifier_code)), + "f18" => Some(format!("\x1b[32;{}~", modifier_code)), + "f19" => Some(format!("\x1b[33;{}~", modifier_code)), + "f20" => Some(format!("\x1b[34;{}~", modifier_code)), + _ if modifier_code == 2 => None, + "insert" => Some(format!("\x1b[2;{}~", modifier_code)), + "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), + "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), + "end" => Some(format!("\x1b[1;{}F", modifier_code)), + "home" => Some(format!("\x1b[1;{}H", modifier_code)), + _ => None, + }; + if modified_esc_str.is_some() { + return modified_esc_str; + } } - // Fallback to keystroke input sent directly - if keystroke.key.chars().count() == 1 { - //TODO this might fail on unicode during internationalization + //Fallback to sending the keystroke input directly + //Skin colors in utf8 are implemented as a seperate, invisible character + //that modifies the associated emoji. Some languages may have similarly + //implemented modifiers, e.g. certain diacritics that can be typed as a single character. + //This means that we need to assume some user input can result in multi-byte, + //multi-char strings. This is somewhat difficult, as GPUI normalizes all + //keys into a string representation. Hence, the check here to filter out GPUI + //keys that weren't captured above. + if !matches_gpui_key_str(&keystroke.key) { return Some(keystroke.key.clone()); } else { None } } +///Checks if the given string matches a GPUI key string. +///Table made from reading the source at gpui/src/platform/mac/event.rs +fn matches_gpui_key_str(str: &str) -> bool { + match str { + "backspace" => true, + "up" => true, + "down" => true, + "left" => true, + "right" => true, + "pageup" => true, + "pagedown" => true, + "home" => true, + "end" => true, + "delete" => true, + "enter" => true, + "escape" => true, + "tab" => true, + "f1" => true, + "f2" => true, + "f3" => true, + "f4" => true, + "f5" => true, + "f6" => true, + "f7" => true, + "f8" => true, + "f9" => true, + "f10" => true, + "f11" => true, + "f12" => true, + "space" => true, + _ => false, + } +} + /// Code Modifiers /// ---------+--------------------------- /// 2 | Shift @@ -268,6 +318,61 @@ fn modifier_code(keystroke: &Keystroke) -> u32 { mod test { use super::*; + #[test] + fn test_scroll_keys() { + //These keys should be handled by the scrolling element directly + //Need to signify this by returning 'None' + let shift_pageup = Keystroke::parse("shift-pageup").unwrap(); + let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap(); + let shift_home = Keystroke::parse("shift-home").unwrap(); + let shift_end = Keystroke::parse("shift-end").unwrap(); + + let none = TermMode::NONE; + assert_eq!(to_esc_str(&shift_pageup, &none), None); + assert_eq!(to_esc_str(&shift_pagedown, &none), None); + assert_eq!(to_esc_str(&shift_home, &none), None); + assert_eq!(to_esc_str(&shift_end, &none), None); + + let alt_screen = TermMode::ALT_SCREEN; + assert_eq!( + to_esc_str(&shift_pageup, &alt_screen), + Some("\x1b[5;2~".to_string()) + ); + assert_eq!( + to_esc_str(&shift_pagedown, &alt_screen), + Some("\x1b[6;2~".to_string()) + ); + assert_eq!( + to_esc_str(&shift_home, &alt_screen), + Some("\x1b[1;2H".to_string()) + ); + assert_eq!( + to_esc_str(&shift_end, &alt_screen), + Some("\x1b[1;2F".to_string()) + ); + + let pageup = Keystroke::parse("pageup").unwrap(); + let pagedown = Keystroke::parse("pagedown").unwrap(); + let any = TermMode::ANY; + + assert_eq!(to_esc_str(&pageup, &any), Some("\x1b[5~".to_string())); + assert_eq!(to_esc_str(&pagedown, &any), Some("\x1b[6~".to_string())); + } + + #[test] + fn test_multi_char_fallthrough() { + let ks = Keystroke { + ctrl: false, + alt: false, + shift: false, + cmd: false, + + key: "🖖🏻".to_string(), //2 char string + }; + + assert_eq!(to_esc_str(&ks, &TermMode::NONE), Some("🖖🏻".to_string())); + } + #[test] fn test_application_mode() { let app_cursor = TermMode::APP_CURSOR; @@ -325,7 +430,7 @@ mod test { // 8 | Shift + Alt + Control // ---------+--------------------------- // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys - // assert_eq!(2, modifier_code(Keystroke::parse("shift-A").unwrap())); + assert_eq!(2, modifier_code(&Keystroke::parse("shift-A").unwrap())); assert_eq!(3, modifier_code(&Keystroke::parse("alt-A").unwrap())); assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-A").unwrap())); assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-A").unwrap()));