From 6dd9dd0aa85f27e1d28a3c8ce548f4fae579fa63 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 10 Apr 2023 00:44:57 +0200 Subject: [PATCH] Use paste for ESC action and IME commit Route string terminal input through 'ActionContext::paste' instead of char by char write improving performance by utilizing bracketed paste mode when it's reasonable. Suggested-by: Kirill Chibisov --- alacritty/src/event.rs | 82 +++++++++++------------------------------- alacritty/src/input.rs | 66 +++++++++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 71 deletions(-) diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index d4d0d968..3248a1e7 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -218,56 +218,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.notifier.notify(val); } - fn received_char(&mut self, c: char) { - // Don't insert chars when we have IME running. - if self.display().ime.preedit().is_some() { - return; - } - - // Handle hint selection over anything else. - if self.display().hint_state.active() && !*self.suppress_chars { - self.hint_input(c); - return; - } - - // Pass keys to search and ignore them during `suppress_chars`. - let search_active = self.search_active(); - if *self.suppress_chars || search_active || self.terminal().mode().contains(TermMode::VI) { - if search_active && !*self.suppress_chars { - self.search_input(c); - } - - return; - } - - self.on_typing_start(); - - if self.terminal().grid().display_offset() != 0 { - self.scroll(Scroll::Bottom); - } - self.clear_selection(); - - let utf8_len = c.len_utf8(); - let mut bytes = vec![0; utf8_len]; - c.encode_utf8(&mut bytes[..]); - - #[cfg(not(target_os = "macos"))] - let alt_send_esc = true; - - // Don't send ESC when `OptionAsAlt` is used. This doesn't handle - // `Only{Left,Right}` variants due to inability to distinguish them. - #[cfg(target_os = "macos")] - let alt_send_esc = self.config().window.option_as_alt != OptionAsAlt::None; - - if alt_send_esc && *self.received_count() == 0 && self.modifiers().alt() && utf8_len == 1 { - bytes.insert(0, b'\x1b'); - } - - self.write_to_pty(bytes); - - *self.received_count() += 1; - } - /// Request a redraw. #[inline] fn mark_dirty(&mut self) { @@ -774,9 +724,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.clipboard.store(ClipboardType::Clipboard, text); }, // Write the text to the PTY/search. - HintAction::Action(HintInternalAction::Paste) => { - self.paste(&text); - }, + HintAction::Action(HintInternalAction::Paste) => self.paste(&text, true), // Select the text. HintAction::Action(HintInternalAction::Select) => { self.start_selection(SelectionType::Simple, *hint_bounds.start(), Side::Left); @@ -832,13 +780,25 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } } + /// Handle beginning of terminal text input. + fn on_terminal_input_start(&mut self) { + self.on_typing_start(); + self.clear_selection(); + + if self.terminal().grid().display_offset() != 0 { + self.scroll(Scroll::Bottom); + } + } + /// Paste a text into the terminal. - fn paste(&mut self, text: &str) { + fn paste(&mut self, text: &str, bracketed: bool) { if self.search_active() { for c in text.chars() { self.search_input(c); } - } else if self.terminal().mode().contains(TermMode::BRACKETED_PASTE) { + } else if bracketed && self.terminal().mode().contains(TermMode::BRACKETED_PASTE) { + self.on_terminal_input_start(); + self.write_to_pty(&b"\x1b[200~"[..]); // Write filtered escape sequences. @@ -851,6 +811,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.write_to_pty(&b"\x1b[201~"[..]); } else { + self.on_terminal_input_start(); + // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish // pasted data from keystrokes. // In theory, we should construct the keystrokes needed to produce the data we are @@ -1338,7 +1300,7 @@ impl input::Processor> { self.key_input(input); }, WindowEvent::ModifiersChanged(modifiers) => self.modifiers_input(modifiers), - WindowEvent::ReceivedCharacter(c) => self.ctx.received_char(c), + WindowEvent::ReceivedCharacter(c) => self.received_char(c), WindowEvent::MouseInput { state, button, .. } => { self.ctx.window().set_mouse_visible(true); self.mouse_input(state, button); @@ -1372,7 +1334,7 @@ impl input::Processor> { }, WindowEvent::DroppedFile(path) => { let path: String = path.to_string_lossy().into(); - self.ctx.paste(&(path + " ")); + self.ctx.paste(&(path + " "), true); }, WindowEvent::CursorLeft { .. } => { self.ctx.mouse.inside_text_area = false; @@ -1384,11 +1346,7 @@ impl input::Processor> { WindowEvent::Ime(ime) => match ime { Ime::Commit(text) => { *self.ctx.dirty = true; - - for ch in text.chars() { - self.ctx.received_char(ch); - } - + self.ctx.paste(&text, true); self.ctx.update_cursor_blinking(); }, Ime::Preedit(text, cursor_offset) => { diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 5fe7da98..992b01df 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -73,7 +73,6 @@ pub struct Processor> { pub trait ActionContext { fn write_to_pty>>(&self, _data: B) {} - fn received_char(&mut self, _c: char) {} fn mark_dirty(&mut self) {} fn size_info(&self) -> SizeInfo; fn copy_selection(&mut self, _ty: ClipboardType) {} @@ -120,7 +119,8 @@ pub trait ActionContext { fn hint_input(&mut self, _character: char) {} fn trigger_hint(&mut self, _hint: &HintMatch) {} fn expand_selection(&mut self) {} - fn paste(&mut self, _text: &str) {} + fn on_terminal_input_start(&mut self) {} + fn paste(&mut self, _text: &str, _bracketed: bool) {} fn spawn_daemon(&self, _program: &str, _args: I) where I: IntoIterator + Debug + Copy, @@ -152,11 +152,7 @@ impl Execute for Action { #[inline] fn execute>(&self, ctx: &mut A) { match self { - Action::Esc(s) => { - for c in s.chars() { - ctx.received_char(c); - } - }, + Action::Esc(s) => ctx.paste(s, false), Action::Command(program) => ctx.spawn_daemon(program.program(), program.args()), Action::Hint(hint) => { ctx.display().hint_state.start(hint.clone()); @@ -276,11 +272,11 @@ impl Execute for Action { Action::ClearSelection => ctx.clear_selection(), Action::Paste => { let text = ctx.clipboard_mut().load(ClipboardType::Clipboard); - ctx.paste(&text); + ctx.paste(&text, true); }, Action::PasteSelection => { let text = ctx.clipboard_mut().load(ClipboardType::Selection); - ctx.paste(&text); + ctx.paste(&text, true); }, Action::ToggleFullscreen => ctx.window().toggle_fullscreen(), Action::ToggleMaximized => ctx.window().toggle_maximized(), @@ -964,6 +960,58 @@ impl> Processor { self.ctx.window().set_mouse_cursor(mouse_state); } + /// Process a received character. + pub fn received_char(&mut self, c: char) { + let suppress_chars = *self.ctx.suppress_chars(); + + // Don't insert chars when we have IME running. + if self.ctx.display().ime.preedit().is_some() { + return; + } + + // Handle hint selection over anything else. + if self.ctx.display().hint_state.active() && !suppress_chars { + self.ctx.hint_input(c); + return; + } + + // Pass keys to search and ignore them during `suppress_chars`. + let search_active = self.ctx.search_active(); + if suppress_chars || search_active || self.ctx.terminal().mode().contains(TermMode::VI) { + if search_active && !suppress_chars { + self.ctx.search_input(c); + } + + return; + } + + self.ctx.on_terminal_input_start(); + + let utf8_len = c.len_utf8(); + let mut bytes = vec![0; utf8_len]; + c.encode_utf8(&mut bytes[..]); + + #[cfg(not(target_os = "macos"))] + let alt_send_esc = true; + + // Don't send ESC when `OptionAsAlt` is used. This doesn't handle + // `Only{Left,Right}` variants due to inability to distinguish them. + #[cfg(target_os = "macos")] + let alt_send_esc = self.ctx.config().window.option_as_alt != OptionAsAlt::None; + + if alt_send_esc + && *self.ctx.received_count() == 0 + && self.ctx.modifiers().alt() + && utf8_len == 1 + { + bytes.insert(0, b'\x1b'); + } + + self.ctx.write_to_pty(bytes); + + *self.ctx.received_count() += 1; + } + /// Attempt to find a binding and execute its action. /// /// The provided mode, mods, and key must match what is allowed by a binding