diff --git a/assets/settings/default.json b/assets/settings/default.json index 95ba08c9d56ba11a4107897a08be64a0e00ba98c..61af2bcaf93260c3448c3dec0fee96f6dea18457 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -122,6 +122,14 @@ // 2. Default alternate scroll mode to off // "alternate_scroll": "off", "alternate_scroll": "off", + // Set whether the option key behaves as the meta key. + // May take 2 values: + // 1. Rely on default platform handling of option key, on macOS + // this means generating certain unicode characters + // "option_to_meta": false, + // 2. Make the option keys behave as a 'meta' key, e.g. for emacs + // "option_to_meta": true, + "option_as_meta": false, // Any key-value pairs added to this list will be added to the terminal's // enviroment. Use `:` to seperate multiple values. "env": { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 1095f289ebb94382449ea346e6af827331fc0e32..9622387c208eeb42b8db7e1bd9e34c352d62cc57 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -98,6 +98,7 @@ pub struct TerminalSettings { pub env: Option>, pub blinking: Option, pub alternate_scroll: Option, + pub option_as_meta: Option, } #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 7e0c6e3d17ce8c3ca1d26859abf1217430f9be0f..199f42df65c26a9a32b3fbff239f49c7079b7b33 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -2,7 +2,7 @@ use alacritty_terminal::term::TermMode; use gpui::keymap::Keystroke; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Modifiers { None, Alt, @@ -45,10 +45,10 @@ impl Modifiers { ///that depend on terminal modes also have a mapping that doesn't depend on the terminal mode. ///This is fragile, but as these mappings are locked up in legacy compatibility, it's probably good enough pub fn might_convert(keystroke: &Keystroke) -> bool { - to_esc_str(keystroke, &TermMode::NONE).is_some() + to_esc_str(keystroke, &TermMode::NONE, false).is_some() } -pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { +pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) -> Option { let modifiers = Modifiers::new(keystroke); // Manual Bindings including modifiers @@ -244,6 +244,17 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { } } + let alt_meta_binding = if alt_is_meta && modifiers == Modifiers::Alt && keystroke.key.is_ascii() + { + Some(format!("\x1b{}", keystroke.key)) + } else { + None + }; + + if alt_meta_binding.is_some() { + return alt_meta_binding; + } + None } @@ -286,26 +297,26 @@ mod test { 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); + assert_eq!(to_esc_str(&shift_pageup, &none, false), None); + assert_eq!(to_esc_str(&shift_pagedown, &none, false), None); + assert_eq!(to_esc_str(&shift_home, &none, false), None); + assert_eq!(to_esc_str(&shift_end, &none, false), None); let alt_screen = TermMode::ALT_SCREEN; assert_eq!( - to_esc_str(&shift_pageup, &alt_screen), + to_esc_str(&shift_pageup, &alt_screen, false), Some("\x1b[5;2~".to_string()) ); assert_eq!( - to_esc_str(&shift_pagedown, &alt_screen), + to_esc_str(&shift_pagedown, &alt_screen, false), Some("\x1b[6;2~".to_string()) ); assert_eq!( - to_esc_str(&shift_home, &alt_screen), + to_esc_str(&shift_home, &alt_screen, false), Some("\x1b[1;2H".to_string()) ); assert_eq!( - to_esc_str(&shift_end, &alt_screen), + to_esc_str(&shift_end, &alt_screen, false), Some("\x1b[1;2F".to_string()) ); @@ -313,8 +324,14 @@ mod test { 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())); + assert_eq!( + to_esc_str(&pageup, &any, false), + Some("\x1b[5~".to_string()) + ); + assert_eq!( + to_esc_str(&pagedown, &any, false), + Some("\x1b[6~".to_string()) + ); } #[test] @@ -327,7 +344,7 @@ mod test { function: false, key: "🖖🏻".to_string(), //2 char string }; - assert_eq!(to_esc_str(&ks, &TermMode::NONE), None); + assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None); } #[test] @@ -340,15 +357,27 @@ mod test { let left = Keystroke::parse("left").unwrap(); let right = Keystroke::parse("right").unwrap(); - assert_eq!(to_esc_str(&up, &none), Some("\x1b[A".to_string())); - assert_eq!(to_esc_str(&down, &none), Some("\x1b[B".to_string())); - assert_eq!(to_esc_str(&right, &none), Some("\x1b[C".to_string())); - assert_eq!(to_esc_str(&left, &none), Some("\x1b[D".to_string())); + assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".to_string())); + assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".to_string())); + assert_eq!(to_esc_str(&right, &none, false), Some("\x1b[C".to_string())); + assert_eq!(to_esc_str(&left, &none, false), Some("\x1b[D".to_string())); - assert_eq!(to_esc_str(&up, &app_cursor), Some("\x1bOA".to_string())); - assert_eq!(to_esc_str(&down, &app_cursor), Some("\x1bOB".to_string())); - assert_eq!(to_esc_str(&right, &app_cursor), Some("\x1bOC".to_string())); - assert_eq!(to_esc_str(&left, &app_cursor), Some("\x1bOD".to_string())); + assert_eq!( + to_esc_str(&up, &app_cursor, false), + Some("\x1bOA".to_string()) + ); + assert_eq!( + to_esc_str(&down, &app_cursor, false), + Some("\x1bOB".to_string()) + ); + assert_eq!( + to_esc_str(&right, &app_cursor, false), + Some("\x1bOC".to_string()) + ); + assert_eq!( + to_esc_str(&left, &app_cursor, false), + Some("\x1bOD".to_string()) + ); } #[test] @@ -361,11 +390,13 @@ mod test { assert_eq!( to_esc_str( &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(), - &mode + &mode, + false ), to_esc_str( &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(), - &mode + &mode, + false ), "On letter: {}/{}", lower, @@ -374,6 +405,40 @@ mod test { } } + #[test] + fn alt_is_meta() { + let ascii_printable = ' '..='~'; + for character in ascii_printable { + assert_eq!( + to_esc_str( + &Keystroke::parse(&format!("alt-{}", character)).unwrap(), + &TermMode::NONE, + true + ) + .unwrap(), + format!("\x1b{}", character) + ); + } + + let gpui_keys = [ + "up", "down", "right", "left", "f1", "f2", "f3", "f4", "F5", "f6", "f7", "f8", "f9", + "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "insert", + "pageup", "pagedown", "end", "home", + ]; + + for key in gpui_keys { + assert_ne!( + to_esc_str( + &Keystroke::parse(&format!("alt-{}", key)).unwrap(), + &TermMode::NONE, + true + ) + .unwrap(), + format!("\x1b{}", key) + ); + } + } + #[test] fn test_modifier_code_calc() { // Code Modifiers diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d6c22ee6bce68013123028d17fe1e176f9229c66..a93dd096705d664df6021d37acf8d6902f6070f1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -677,8 +677,8 @@ impl Terminal { self.write_to_pty(input); } - pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { - let esc = to_esc_str(keystroke, &self.last_content.mode); + pub fn try_keystroke(&mut self, keystroke: &Keystroke, alt_is_meta: bool) -> bool { + let esc = to_esc_str(keystroke, &self.last_content.mode, alt_is_meta); if let Some(esc) = esc { self.input(esc); true diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index d88511cbce408acf9b14a34fc98b218193f8b75d..51ebf63e5e41dd3caccd397119bc881a5710613c 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -775,7 +775,15 @@ impl Element for TerminalElement { self.terminal .upgrade(cx.app) .map(|model_handle| { - model_handle.update(cx.app, |term, _| term.try_keystroke(keystroke)) + model_handle.update(cx.app, |term, cx| { + term.try_keystroke( + keystroke, + cx.global::() + .terminal_overrides + .option_as_meta + .unwrap_or(false), + ) + }) }) .unwrap_or(false) } diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index ec35ba0a5ce84a01994e788d6110152c96d37f80..9ba90d42dc922477caddf45bb420a0a700b2476e 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -155,8 +155,14 @@ impl TerminalView { { cx.show_character_palette(); } else { - self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap()) + self.terminal.update(cx, |term, cx| { + term.try_keystroke( + &Keystroke::parse("ctrl-cmd-space").unwrap(), + cx.global::() + .terminal_overrides + .option_as_meta + .unwrap_or(false), + ) }); } } @@ -280,7 +286,7 @@ impl TerminalView { fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.clear_bel(cx); self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("up").unwrap()) + term.try_keystroke(&Keystroke::parse("up").unwrap(), false) }); } @@ -288,7 +294,7 @@ impl TerminalView { fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.clear_bel(cx); self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("down").unwrap()) + term.try_keystroke(&Keystroke::parse("down").unwrap(), false) }); } @@ -296,7 +302,7 @@ impl TerminalView { fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.clear_bel(cx); self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()) + term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap(), false) }); } @@ -304,7 +310,7 @@ impl TerminalView { fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.clear_bel(cx); self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("escape").unwrap()) + term.try_keystroke(&Keystroke::parse("escape").unwrap(), false) }); } @@ -312,7 +318,7 @@ impl TerminalView { fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { self.clear_bel(cx); self.terminal.update(cx, |term, _| { - term.try_keystroke(&Keystroke::parse("enter").unwrap()) + term.try_keystroke(&Keystroke::parse("enter").unwrap(), false) }); } }