From f38ede8631b083c1c74d31b7658ad162d31c3972 Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Mon, 29 Aug 2022 00:48:49 +0000 Subject: [PATCH] Add bracketed paste (#3233) --- helix-term/src/application.rs | 22 +++++++++++------- helix-term/src/commands.rs | 41 ++++++++++++++------------------- helix-term/src/compositor.rs | 6 ++--- helix-term/src/ui/completion.rs | 2 +- helix-term/src/ui/editor.rs | 23 +++++++++++++++--- helix-term/src/ui/menu.rs | 4 ++-- helix-term/src/ui/overlay.rs | 2 +- helix-term/src/ui/picker.rs | 20 ++++++++++------ helix-term/src/ui/popup.rs | 4 ++-- helix-term/src/ui/prompt.rs | 8 +++++-- helix-view/src/input.rs | 7 +++--- 11 files changed, 82 insertions(+), 57 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 21be7db07..3124beb08 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -29,7 +29,10 @@ use std::{ use anyhow::{Context, Error}; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent}, + event::{ + DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, + Event as CrosstermEvent, + }, execute, terminal, tty::IsTty, }; @@ -425,14 +428,13 @@ impl Application { scroll: None, }; // Handle key events - let should_redraw = match event { - Ok(CrosstermEvent::Resize(width, height)) => { + let should_redraw = match event.unwrap() { + CrosstermEvent::Resize(width, height) => { self.compositor.resize(width, height); self.compositor - .handle_event(Event::Resize(width, height), &mut cx) + .handle_event(&Event::Resize(width, height), &mut cx) } - Ok(event) => self.compositor.handle_event(event.into(), &mut cx), - Err(x) => panic!("{}", x), + event => self.compositor.handle_event(&event.into(), &mut cx), }; if should_redraw && !self.editor.should_close() { @@ -788,7 +790,7 @@ impl Application { async fn claim_term(&mut self) -> Result<(), Error> { terminal::enable_raw_mode()?; let mut stdout = stdout(); - execute!(stdout, terminal::EnterAlternateScreen)?; + execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?; execute!(stdout, terminal::Clear(terminal::ClearType::All))?; if self.config.load().editor.mouse { execute!(stdout, EnableMouseCapture)?; @@ -821,7 +823,11 @@ impl Application { // probably not a good idea to `unwrap()` inside a panic handler. // So we just ignore the `Result`s. let _ = execute!(std::io::stdout(), DisableMouseCapture); - let _ = execute!(std::io::stdout(), terminal::LeaveAlternateScreen); + let _ = execute!( + std::io::stdout(), + terminal::LeaveAlternateScreen, + DisableBracketedPaste + ); let _ = terminal::disable_raw_mode(); hook(info); })); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 16f7e6012..ffcccec3f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3369,13 +3369,7 @@ enum Paste { Cursor, } -fn paste_impl( - values: &[String], - doc: &mut Document, - view: &View, - action: Paste, - count: usize, -) -> Option { +fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste, count: usize) { let repeat = std::iter::repeat( values .last() @@ -3418,8 +3412,17 @@ fn paste_impl( }; (pos, pos, values.next()) }); + doc.apply(&transaction, view.id); +} - Some(transaction) +pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + let paste = match doc.mode { + Mode::Insert | Mode::Select => Paste::Cursor, + Mode::Normal => Paste::Before, + }; + paste_impl(&[contents], doc, view, paste, count); } fn paste_clipboard_impl( @@ -3429,18 +3432,11 @@ fn paste_clipboard_impl( count: usize, ) -> anyhow::Result<()> { let (view, doc) = current!(editor); - - match editor - .clipboard_provider - .get_contents(clipboard_type) - .map(|contents| paste_impl(&[contents], doc, view, action, count)) - { - Ok(Some(transaction)) => { - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view.id); + match editor.clipboard_provider.get_contents(clipboard_type) { + Ok(contents) => { + paste_impl(&[contents], doc, view, action, count); Ok(()) } - Ok(None) => Ok(()), Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } @@ -3553,11 +3549,8 @@ fn paste(cx: &mut Context, pos: Paste) { let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; - if let Some(transaction) = registers - .read(reg_name) - .and_then(|values| paste_impl(values, doc, view, pos, count)) - { - doc.apply(&transaction, view.id); + if let Some(values) = registers.read(reg_name) { + paste_impl(values, doc, view, pos, count); } } @@ -4849,7 +4842,7 @@ fn replay_macro(cx: &mut Context) { cx.callback = Some(Box::new(move |compositor, cx| { for _ in 0..count { for &key in keys.iter() { - compositor.handle_event(compositor::Event::Key(key), cx); + compositor.handle_event(&compositor::Event::Key(key), cx); } } // The macro under replay is cleared at the end of the callback, not in the diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index bda38c598..c0898dae2 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -29,7 +29,7 @@ pub struct Context<'a> { pub trait Component: Any + AnyComponent { /// Process input events, return true if handled. - fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, _event: &Event, _ctx: &mut Context) -> EventResult { EventResult::Ignored(None) } // , args: () @@ -157,10 +157,10 @@ impl Compositor { Some(self.layers.remove(idx)) } - pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { + pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool { // If it is a key event and a macro is being recorded, push the key event to the recording. if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { - keys.push(key); + keys.push(*key); } let mut callbacks = Vec::new(); diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 6a743632a..87913a8c0 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -298,7 +298,7 @@ impl Completion { } impl Component for Completion { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { // let the Editor handle Esc instead if let Event::Key(KeyEvent { code: KeyCode::Esc, .. diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 60cab905c..7326b70d0 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -936,7 +936,7 @@ impl EditorView { impl EditorView { fn handle_mouse_event( &mut self, - event: MouseEvent, + event: &MouseEvent, cxt: &mut commands::Context, ) -> EventResult { let config = cxt.editor.config(); @@ -946,7 +946,7 @@ impl EditorView { column, modifiers, .. - } = event; + } = *event; let pos_and_view = |editor: &Editor, row, column| { editor.tree.views().find_map(|(view, _focus)| { @@ -1115,7 +1115,7 @@ impl EditorView { impl Component for EditorView { fn handle_event( &mut self, - event: Event, + event: &Event, context: &mut crate::compositor::Context, ) -> EventResult { let mut cx = commands::Context { @@ -1128,6 +1128,23 @@ impl Component for EditorView { }; match event { + Event::Paste(contents) => { + cx.count = cx.editor.count; + commands::paste_bracketed_value(&mut cx, contents.clone()); + cx.editor.count = None; + + let config = cx.editor.config(); + let (view, doc) = current!(cx.editor); + view.ensure_cursor_in_view(doc, config.scrolloff); + + // Store a history state if not in insert mode. Otherwise wait till we exit insert + // to include any edits to the paste in the history state. + if doc.mode() != Mode::Insert { + doc.append_changes_to_history(view.id); + } + + EventResult::Consumed(None) + } Event::Resize(_width, _height) => { // Ignore this event, we handle resizing just before rendering to screen. // Handling it here but not re-rendering will cause flashing diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index ce51ecbc2..1d247b1ad 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -225,9 +225,9 @@ impl Menu { use super::PromptEvent as MenuEvent; impl Component for Menu { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let event = match event { - Event::Key(event) => event, + Event::Key(event) => *event, _ => return EventResult::Ignored(None), }; diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index 1cd60be51..0b8a93ae8 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -61,7 +61,7 @@ impl Component for Overlay { Some((width, height)) } - fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult { self.content.handle_event(event, ctx) } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 169aeaddf..2878fc904 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -260,7 +260,7 @@ impl Component for FilePicker { } } - fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult { // TODO: keybinds for scrolling preview self.picker.handle_event(event, ctx) } @@ -476,6 +476,14 @@ impl Picker { pub fn toggle_preview(&mut self) { self.show_preview = !self.show_preview; } + + fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { + if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { + // TODO: recalculate only if pattern changed + self.score(); + } + EventResult::Consumed(None) + } } // process: @@ -489,9 +497,10 @@ impl Component for Picker { Some(viewport) } - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key_event = match event { - Event::Key(event) => event, + Event::Key(event) => *event, + Event::Paste(..) => return self.prompt_handle_event(event, cx), Event::Resize(..) => return EventResult::Consumed(None), _ => return EventResult::Ignored(None), }; @@ -548,10 +557,7 @@ impl Component for Picker { self.toggle_preview(); } _ => { - if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { - // TODO: recalculate only if pattern changed - self.score(); - } + self.prompt_handle_event(event, cx); } } diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index af8e53c58..3c140da40 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -138,9 +138,9 @@ impl Popup { } impl Component for Popup { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key = match event { - Event::Key(event) => event, + Event::Key(event) => *event, Event::Resize(_, _) => { // TODO: calculate inner area, call component's handle_event with that area return EventResult::Ignored(None); diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 4cb38fb0b..5e8cd1f5c 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -466,9 +466,13 @@ impl Prompt { } impl Component for Prompt { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let event = match event { - Event::Key(event) => event, + Event::Paste(data) => { + self.insert_str(data); + return EventResult::Consumed(None); + } + Event::Key(event) => *event, Event::Resize(..) => return EventResult::Consumed(None), _ => return EventResult::Ignored(None), }; diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 3b03087db..9fa679dff 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -6,12 +6,13 @@ use std::fmt; pub use crate::keyboard::{KeyCode, KeyModifiers}; -#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] +#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)] pub enum Event { FocusGained, FocusLost, Key(KeyEvent), Mouse(MouseEvent), + Paste(String), Resize(u16, u16), } @@ -276,9 +277,7 @@ impl From for Event { crossterm::event::Event::Resize(w, h) => Self::Resize(w, h), crossterm::event::Event::FocusGained => Self::FocusGained, crossterm::event::Event::FocusLost => Self::FocusLost, - crossterm::event::Event::Paste(_) => { - unreachable!("crossterm shouldn't emit Paste events without them being enabled") - } + crossterm::event::Event::Paste(s) => Self::Paste(s), } } }