diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 833fefd0f2cf24b5546c97661f0128bd7b08a02d..11893847ade8d51398974e557bb7052927d004b1 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -407,19 +407,16 @@ { "context": "Terminal", "bindings": { - "ctrl-c": "terminal::Sigint", - "escape": "terminal::Escape", - "ctrl-d": "terminal::Quit", - "backspace": "terminal::Del", - "enter": "terminal::Return", - "left": "terminal::Left", - "right": "terminal::Right", + // Overrides for global bindings, remove at your own risk: "up": "terminal::Up", "down": "terminal::Down", - "tab": "terminal::Tab", - "cmd-v": "terminal::Paste", + "escape": "terminal::Escape", + "enter": "terminal::Enter", + "ctrl-c": "terminal::CtrlC", + // Useful terminal actions: "cmd-c": "terminal::Copy", - "ctrl-l": "terminal::Clear" + "cmd-v": "terminal::Paste", + "cmd-k": "terminal::Clear" } }, { diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index e3a458327d4b5b323fff6e29edcc010edd986832..ee67e644b0bcc3b4e6c6f5434d6382b36ccd588d 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -28,5 +28,3 @@ gpui = { path = "../gpui", features = ["test-support"] } client = { path = "../client", features = ["test-support"]} project = { path = "../project", features = ["test-support"]} workspace = { path = "../workspace", features = ["test-support"] } - - diff --git a/crates/terminal/src/connection.rs b/crates/terminal/src/connection.rs index 800791370cdf63efee8be8ae7b5095b87da7a7aa..8f48e4ba8b4c73ffeb8d10abd39e86e678ec02bb 100644 --- a/crates/terminal/src/connection.rs +++ b/crates/terminal/src/connection.rs @@ -1,3 +1,5 @@ +mod keymappings; + use alacritty_terminal::{ ansi::{ClearMode, Handler}, config::{Config, PtyConfig}, @@ -13,13 +15,15 @@ use futures::{channel::mpsc::unbounded, StreamExt}; use settings::Settings; use std::{collections::HashMap, path::PathBuf, sync::Arc}; -use gpui::{ClipboardItem, CursorStyle, Entity, ModelContext}; +use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext}; use crate::{ color_translation::{get_color_at_index, to_alac_rgb}, ZedListener, }; +use self::keymappings::to_esc_str; + const DEFAULT_TITLE: &str = "Terminal"; ///Upward flowing events, for changing the title and such @@ -182,6 +186,19 @@ impl TerminalConnection { self.write_to_pty("\x0c".into()); self.term.lock().clear_screen(ClearMode::Saved); } + + pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { + let guard = self.term.lock(); + let mode = guard.mode(); + let esc = to_esc_str(keystroke, mode); + drop(guard); + if esc.is_some() { + self.write_to_pty(esc.unwrap()); + true + } else { + false + } + } } impl Drop for TerminalConnection { diff --git a/crates/terminal/src/connection/keymappings.rs b/crates/terminal/src/connection/keymappings.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4d429843bf17aa51760b69e3b00f8317b681d15 --- /dev/null +++ b/crates/terminal/src/connection/keymappings.rs @@ -0,0 +1,444 @@ +use alacritty_terminal::term::TermMode; +use gpui::keymap::Keystroke; + +/* +Connection events still to do: +- Reporting mouse events correctly. +- Reporting scrolls +- Correctly bracketing a paste +- Storing changed colors +- Focus change sequence +*/ + +#[derive(Debug)] +pub enum Modifiers { + None, + Alt, + Ctrl, + Shift, + CtrlShift, + Other, +} + +impl Modifiers { + fn new(ks: &Keystroke) -> Self { + match (ks.alt, ks.ctrl, ks.shift, ks.cmd) { + (false, false, false, false) => Modifiers::None, + (true, false, false, false) => Modifiers::Alt, + (false, true, false, false) => Modifiers::Ctrl, + (false, false, true, false) => Modifiers::Shift, + (false, true, true, false) => Modifiers::CtrlShift, + _ => 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) { + //Basic special keys + ("space", Modifiers::None) => Some(" ".to_string()), + ("tab", Modifiers::None) => Some("\x09".to_string()), + ("escape", Modifiers::None) => Some("\x1b".to_string()), + ("enter", Modifiers::None) => Some("\x0d".to_string()), + ("backspace", Modifiers::None) => Some("\x7f".to_string()), + //Interesting escape codes + ("tab", Modifiers::Shift) => Some("\x1b[Z".to_string()), + ("backspace", Modifiers::Alt) => Some("\x1b\x7f".to_string()), + ("backspace", Modifiers::Shift) => Some("\x7f".to_string()), + ("home", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[1;2H".to_string()) + } + ("end", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[1;2F".to_string()) + } + ("pageup", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[5;2~".to_string()) + } + ("pagedown", Modifiers::Shift) if mode.contains(TermMode::ALT_SCREEN) => { + Some("\x1b[6;2~".to_string()) + } + ("home", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOH".to_string()) + } + ("home", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[H".to_string()) + } + ("end", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOF".to_string()) + } + ("end", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[F".to_string()) + } + ("up", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOA".to_string()) + } + ("up", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[A".to_string()) + } + ("down", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOB".to_string()) + } + ("down", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[B".to_string()) + } + ("right", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOC".to_string()) + } + ("right", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[C".to_string()) + } + ("left", Modifiers::None) if mode.contains(TermMode::APP_CURSOR) => { + Some("\x1bOD".to_string()) + } + ("left", Modifiers::None) if !mode.contains(TermMode::APP_CURSOR) => { + Some("\x1b[D".to_string()) + } + ("back", Modifiers::None) => Some("\x7f".to_string()), + ("insert", Modifiers::None) => Some("\x1b[2~".to_string()), + ("delete", Modifiers::None) => Some("\x1b[3~".to_string()), + ("pageup", Modifiers::None) => Some("\x1b[5~".to_string()), + ("pagedown", Modifiers::None) => Some("\x1b[6~".to_string()), + ("f1", Modifiers::None) => Some("\x1bOP".to_string()), + ("f2", Modifiers::None) => Some("\x1bOQ".to_string()), + ("f3", Modifiers::None) => Some("\x1bOR".to_string()), + ("f4", Modifiers::None) => Some("\x1bOS".to_string()), + ("f5", Modifiers::None) => Some("\x1b[15~".to_string()), + ("f6", Modifiers::None) => Some("\x1b[17~".to_string()), + ("f7", Modifiers::None) => Some("\x1b[18~".to_string()), + ("f8", Modifiers::None) => Some("\x1b[19~".to_string()), + ("f9", Modifiers::None) => Some("\x1b[20~".to_string()), + ("f10", Modifiers::None) => Some("\x1b[21~".to_string()), + ("f11", Modifiers::None) => Some("\x1b[23~".to_string()), + ("f12", Modifiers::None) => Some("\x1b[24~".to_string()), + ("f13", Modifiers::None) => Some("\x1b[25~".to_string()), + ("f14", Modifiers::None) => Some("\x1b[26~".to_string()), + ("f15", Modifiers::None) => Some("\x1b[28~".to_string()), + ("f16", Modifiers::None) => Some("\x1b[29~".to_string()), + ("f17", Modifiers::None) => Some("\x1b[31~".to_string()), + ("f18", Modifiers::None) => Some("\x1b[32~".to_string()), + ("f19", Modifiers::None) => Some("\x1b[33~".to_string()), + ("f20", Modifiers::None) => Some("\x1b[34~".to_string()), + // NumpadEnter, Action::Esc("\n".into()); + //Mappings for caret notation keys + ("a", Modifiers::Ctrl) => Some("\x01".to_string()), //1 + ("A", Modifiers::CtrlShift) => Some("\x01".to_string()), //1 + ("b", Modifiers::Ctrl) => Some("\x02".to_string()), //2 + ("B", Modifiers::CtrlShift) => Some("\x02".to_string()), //2 + ("c", Modifiers::Ctrl) => Some("\x03".to_string()), //3 + ("C", Modifiers::CtrlShift) => Some("\x03".to_string()), //3 + ("d", Modifiers::Ctrl) => Some("\x04".to_string()), //4 + ("D", Modifiers::CtrlShift) => Some("\x04".to_string()), //4 + ("e", Modifiers::Ctrl) => Some("\x05".to_string()), //5 + ("E", Modifiers::CtrlShift) => Some("\x05".to_string()), //5 + ("f", Modifiers::Ctrl) => Some("\x06".to_string()), //6 + ("F", Modifiers::CtrlShift) => Some("\x06".to_string()), //6 + ("g", Modifiers::Ctrl) => Some("\x07".to_string()), //7 + ("G", Modifiers::CtrlShift) => Some("\x07".to_string()), //7 + ("h", Modifiers::Ctrl) => Some("\x08".to_string()), //8 + ("H", Modifiers::CtrlShift) => Some("\x08".to_string()), //8 + ("i", Modifiers::Ctrl) => Some("\x09".to_string()), //9 + ("I", Modifiers::CtrlShift) => Some("\x09".to_string()), //9 + ("j", Modifiers::Ctrl) => Some("\x0a".to_string()), //10 + ("J", Modifiers::CtrlShift) => Some("\x0a".to_string()), //10 + ("k", Modifiers::Ctrl) => Some("\x0b".to_string()), //11 + ("K", Modifiers::CtrlShift) => Some("\x0b".to_string()), //11 + ("l", Modifiers::Ctrl) => Some("\x0c".to_string()), //12 + ("L", Modifiers::CtrlShift) => Some("\x0c".to_string()), //12 + ("m", Modifiers::Ctrl) => Some("\x0d".to_string()), //13 + ("M", Modifiers::CtrlShift) => Some("\x0d".to_string()), //13 + ("n", Modifiers::Ctrl) => Some("\x0e".to_string()), //14 + ("N", Modifiers::CtrlShift) => Some("\x0e".to_string()), //14 + ("o", Modifiers::Ctrl) => Some("\x0f".to_string()), //15 + ("O", Modifiers::CtrlShift) => Some("\x0f".to_string()), //15 + ("p", Modifiers::Ctrl) => Some("\x10".to_string()), //16 + ("P", Modifiers::CtrlShift) => Some("\x10".to_string()), //16 + ("q", Modifiers::Ctrl) => Some("\x11".to_string()), //17 + ("Q", Modifiers::CtrlShift) => Some("\x11".to_string()), //17 + ("r", Modifiers::Ctrl) => Some("\x12".to_string()), //18 + ("R", Modifiers::CtrlShift) => Some("\x12".to_string()), //18 + ("s", Modifiers::Ctrl) => Some("\x13".to_string()), //19 + ("S", Modifiers::CtrlShift) => Some("\x13".to_string()), //19 + ("t", Modifiers::Ctrl) => Some("\x14".to_string()), //20 + ("T", Modifiers::CtrlShift) => Some("\x14".to_string()), //20 + ("u", Modifiers::Ctrl) => Some("\x15".to_string()), //21 + ("U", Modifiers::CtrlShift) => Some("\x15".to_string()), //21 + ("v", Modifiers::Ctrl) => Some("\x16".to_string()), //22 + ("V", Modifiers::CtrlShift) => Some("\x16".to_string()), //22 + ("w", Modifiers::Ctrl) => Some("\x17".to_string()), //23 + ("W", Modifiers::CtrlShift) => Some("\x17".to_string()), //23 + ("x", Modifiers::Ctrl) => Some("\x18".to_string()), //24 + ("X", Modifiers::CtrlShift) => Some("\x18".to_string()), //24 + ("y", Modifiers::Ctrl) => Some("\x19".to_string()), //25 + ("Y", Modifiers::CtrlShift) => Some("\x19".to_string()), //25 + ("z", Modifiers::Ctrl) => Some("\x1a".to_string()), //26 + ("Z", Modifiers::CtrlShift) => Some("\x1a".to_string()), //26 + ("@", Modifiers::Ctrl) => Some("\x00".to_string()), //0 + ("[", Modifiers::Ctrl) => Some("\x1b".to_string()), //27 + ("\\", Modifiers::Ctrl) => Some("\x1c".to_string()), //28 + ("]", Modifiers::Ctrl) => Some("\x1d".to_string()), //29 + ("^", Modifiers::Ctrl) => Some("\x1e".to_string()), //30 + ("_", Modifiers::Ctrl) => Some("\x1f".to_string()), //31 + ("?", Modifiers::Ctrl) => Some("\x7f".to_string()), //127 + _ => None, + }; + if manual_esc_str.is_some() { + return manual_esc_str; + } + + // Automated bindings applying modifiers + 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 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 +/// 3 | Alt +/// 4 | Shift + Alt +/// 5 | Control +/// 6 | Shift + Control +/// 7 | Alt + Control +/// 8 | Shift + Alt + Control +/// ---------+--------------------------- +/// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys +fn modifier_code(keystroke: &Keystroke) -> u32 { + let mut modifier_code = 0; + if keystroke.shift { + modifier_code |= 1; + } + if keystroke.alt { + modifier_code |= 1 << 1; + } + if keystroke.ctrl { + modifier_code |= 1 << 2; + } + modifier_code + 1 +} + +#[cfg(test)] +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; + let none = TermMode::NONE; + + let up = Keystroke::parse("up").unwrap(); + let down = Keystroke::parse("down").unwrap(); + 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, &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())); + } + + #[test] + fn test_ctrl_codes() { + let letters_lower = 'a'..='z'; + let letters_upper = 'A'..='Z'; + let mode = TermMode::ANY; + + for (lower, upper) in letters_lower.zip(letters_upper) { + assert_eq!( + to_esc_str( + &Keystroke::parse(&format!("ctrl-{}", lower)).unwrap(), + &mode + ), + to_esc_str( + &Keystroke::parse(&format!("ctrl-shift-{}", upper)).unwrap(), + &mode + ), + "On letter: {}/{}", + lower, + upper + ) + } + } + + #[test] + fn test_modifier_code_calc() { + // Code Modifiers + // ---------+--------------------------- + // 2 | Shift + // 3 | Alt + // 4 | Shift + Alt + // 5 | Control + // 6 | Shift + Control + // 7 | Alt + Control + // 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!(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())); + assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-A").unwrap())); + assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-A").unwrap())); + assert_eq!( + 8, + modifier_code(&Keystroke::parse("shift-ctrl-alt-A").unwrap()) + ); + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c2d8fc1bab9ed84492c1da7fbad85715cbd23e14..32c9d7a2be902069a7f9ca2a6a23ab554883ba45 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -5,7 +5,6 @@ pub mod terminal_element; use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener}, - grid::Scroll, term::SizeInfo, }; @@ -14,7 +13,7 @@ use dirs::home_dir; use editor::Input; use futures::channel::mpsc::UnboundedSender; use gpui::{ - actions, elements::*, impl_internal_actions, AppContext, ClipboardItem, Entity, ModelHandle, + actions, elements::*, keymap::Keystroke, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, }; use modal::deploy_modal; @@ -27,16 +26,6 @@ use workspace::{Item, Workspace}; use crate::terminal_element::TerminalEl; -//ASCII Control characters on a keyboard -const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c' -const TAB_CHAR: char = 9_u8 as char; -const CARRIAGE_RETURN_CHAR: char = 13_u8 as char; -const ESC_CHAR: char = 27_u8 as char; // == \x1b -const DEL_CHAR: char = 127_u8 as char; -const LEFT_SEQ: &str = "\x1b[D"; -const RIGHT_SEQ: &str = "\x1b[C"; -const UP_SEQ: &str = "\x1b[A"; -const DOWN_SEQ: &str = "\x1b[B"; const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space. const DEBUG_TERMINAL_HEIGHT: f32 = 200.; const DEBUG_CELL_WIDTH: f32 = 5.; @@ -52,44 +41,34 @@ pub struct ScrollTerminal(pub i32); actions!( terminal, [ - Sigint, - Escape, - Del, - Return, - Left, - Right, + Deploy, Up, Down, - Tab, + CtrlC, + Escape, + Enter, Clear, Copy, Paste, - Deploy, - Quit, - DeployModal, + DeployModal ] ); -impl_internal_actions!(terminal, [ScrollTerminal]); ///Initialize and register all of our action handlers pub fn init(cx: &mut MutableAppContext) { - cx.add_action(Terminal::deploy); - cx.add_action(Terminal::send_sigint); - cx.add_action(Terminal::escape); - cx.add_action(Terminal::quit); - cx.add_action(Terminal::del); - cx.add_action(Terminal::carriage_return); - cx.add_action(Terminal::left); - cx.add_action(Terminal::right); + //Global binding overrrides + cx.add_action(Terminal::ctrl_c); cx.add_action(Terminal::up); cx.add_action(Terminal::down); - cx.add_action(Terminal::tab); + cx.add_action(Terminal::escape); + cx.add_action(Terminal::enter); + //Useful terminal actions + cx.add_action(Terminal::deploy); + cx.add_action(deploy_modal); cx.add_action(Terminal::copy); cx.add_action(Terminal::paste); - cx.add_action(Terminal::scroll_terminal); cx.add_action(Terminal::input); cx.add_action(Terminal::clear); - cx.add_action(deploy_modal); } ///A translation struct for Alacritty to communicate with us from their event loop @@ -168,15 +147,6 @@ impl Terminal { } } - ///Scroll the terminal. This locks the terminal - fn scroll_terminal(&mut self, scroll: &ScrollTerminal, cx: &mut ViewContext) { - self.connection - .read(cx) - .term - .lock() - .scroll_display(Scroll::Delta(scroll.0)); - } - fn input(&mut self, Input(text): &Input, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { //TODO: This is probably not encoding UTF8 correctly (see alacritty/src/input.rs:L825-837) @@ -200,11 +170,6 @@ impl Terminal { workspace.add_item(Box::new(cx.add_view(|cx| Terminal::new(wd, false, cx))), cx); } - ///Tell Zed to close us - fn quit(&mut self, _: &Quit, cx: &mut ViewContext) { - cx.emit(Event::CloseTerminal); - } - ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { let term = self.connection.read(cx).term.lock(); @@ -224,66 +189,38 @@ impl Terminal { } } - ///Send the `up` key + ///Synthesize the keyboard event corresponding to 'up' fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(UP_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("up").unwrap()); }); } - ///Send the `down` key + ///Synthesize the keyboard event corresponding to 'down' fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(DOWN_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("down").unwrap()); }); } - ///Send the `tab` key - fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { + ///Synthesize the keyboard event corresponding to 'ctrl-c' + fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(TAB_CHAR.to_string()); + connection.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); }); } - ///Send `SIGINT` (`ctrl-c`) - fn send_sigint(&mut self, _: &Sigint, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(ETX_CHAR.to_string()); - }); - } - - ///Send the `escape` key + ///Synthesize the keyboard event corresponding to 'escape' fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(ESC_CHAR.to_string()); - }); - } - - ///Send the `delete` key. TODO: Difference between this and backspace? - fn del(&mut self, _: &Del, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(DEL_CHAR.to_string()); - }); - } - - ///Send a carriage return. TODO: May need to check the terminal mode. - fn carriage_return(&mut self, _: &Return, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(CARRIAGE_RETURN_CHAR.to_string()); - }); - } - - //Send the `left` key - fn left(&mut self, _: &Left, cx: &mut ViewContext) { - self.connection.update(cx, |connection, _| { - connection.write_to_pty(LEFT_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("escape").unwrap()); }); } - //Send the `right` key - fn right(&mut self, _: &Right, cx: &mut ViewContext) { + ///Synthesize the keyboard event corresponding to 'enter' + fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { self.connection.update(cx, |connection, _| { - connection.write_to_pty(RIGHT_SEQ.to_string()); + connection.try_keystroke(&Keystroke::parse("enter").unwrap()); }); } } @@ -481,7 +418,7 @@ mod tests { terminal.connection.update(cx, |connection, _| { connection.write_to_pty("expr 3 + 4".to_string()); }); - terminal.carriage_return(&Return, cx); + terminal.enter(&Enter, cx); }); cx.set_condition_duration(Some(Duration::from_secs(5))); @@ -506,7 +443,7 @@ mod tests { terminal.connection.update(cx, |connection, _| { connection.write_to_pty("expr 3 + 4".to_string()); }); - terminal.carriage_return(&Return, cx); + terminal.enter(&Enter, cx); }); terminal diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 09dce20f97d10af3d9e8d3a082485435eb1aa156..cfb881feb22fffe594a31de3783616775eb1a5d1 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - grid::{Dimensions, GridIterator, Indexed}, + grid::{Dimensions, GridIterator, Indexed, Scroll}, index::{Column as GridCol, Line as GridLine, Point, Side}, selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, @@ -9,7 +9,7 @@ use alacritty_terminal::{ }, Term, }; -use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine, Input}; +use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use gpui::{ color::Color, elements::*, @@ -31,9 +31,7 @@ use theme::TerminalStyle; use std::{cmp::min, ops::Range, rc::Rc, sync::Arc}; use std::{fmt::Debug, ops::Sub}; -use crate::{ - color_translation::convert_color, connection::TerminalConnection, ScrollTerminal, ZedListener, -}; +use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener}; ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I @@ -359,25 +357,6 @@ impl Element for TerminalEl { _paint: &mut Self::PaintState, cx: &mut gpui::EventContext, ) -> bool { - //The problem: - //Depending on the terminal mode, we either send an escape sequence - //OR update our own data structures. - //e.g. scrolling. If we do smooth scrolling, then we need to check if - //we own scrolling and then if so, do our scrolling thing. - //Ok, so the terminal connection should have APIs for querying it semantically - //something like `should_handle_scroll()`. This means we need a handle to the connection. - //Actually, this is the only time that this app needs to talk to the outer world. - //TODO for scrolling rework: need a way of intercepting Home/End/PageUp etc. - //Sometimes going to scroll our own internal buffer, sometimes going to send ESC - // - //Same goes for key events - //Actually, we don't use the terminal at all in dispatch_event code, the view - //Handles it all. Check how the editor implements scrolling, is it view-level - //or element level? - - //Question: Can we continue dispatching to the view, so it can talk to the connection - //Or should we instead add a connection into here? - match event { Event::ScrollWheel(ScrollWheelEvent { delta, position, .. @@ -386,17 +365,30 @@ impl Element for TerminalEl { .then(|| { let vertical_scroll = (delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER; - cx.dispatch_action(ScrollTerminal(vertical_scroll.round() as i32)); - }) - .is_some(), - Event::KeyDown(KeyDownEvent { - input: Some(input), .. - }) => cx - .is_parent_view_focused() - .then(|| { - cx.dispatch_action(Input(input.to_string())); + + if let Some(connection) = self.connection.upgrade(cx.app) { + connection.update(cx.app, |connection, _| { + connection + .term + .lock() + .scroll_display(Scroll::Delta(vertical_scroll.round() as i32)); + }) + } }) .is_some(), + Event::KeyDown(KeyDownEvent { keystroke, .. }) => { + if !cx.is_parent_view_focused() { + return false; + } + + self.connection + .upgrade(cx.app) + .map(|connection| { + connection + .update(cx.app, |connection, _| connection.try_keystroke(keystroke)) + }) + .unwrap_or(false) + } _ => false, } }