on master help sign in
Add bracketed paste (#3233)
f38ede8Charlie Groves
helix-term/src/application.rs+14−8
@@ -29,7 +29,10 @@ use std::{
29use anyhow::{Context, Error};
29use anyhow::{Context, Error};
30
30
31use crossterm::{
31use crossterm::{
32+ event::{
32− event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent},
33+ DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
34+ Event as CrosstermEvent,
35+ },
33 execute, terminal,
36 execute, terminal,
34 tty::IsTty,
37 tty::IsTty,
35};
38};
@@ -425,14 +428,13 @@ impl Application {
425 scroll: None,
428 scroll: None,
426 };
429 };
427 // Handle key events
430 // Handle key events
428− let should_redraw = match event {
431+ let should_redraw = match event.unwrap() {
429− Ok(CrosstermEvent::Resize(width, height)) => {
432+ CrosstermEvent::Resize(width, height) => {
430 self.compositor.resize(width, height);
433 self.compositor.resize(width, height);
431 self.compositor
434 self.compositor
432− .handle_event(Event::Resize(width, height), &mut cx)
435+ .handle_event(&Event::Resize(width, height), &mut cx)
433 }
436 }
434− Ok(event) => self.compositor.handle_event(event.into(), &mut cx),
437+ event => self.compositor.handle_event(&event.into(), &mut cx),
435− Err(x) => panic!("{}", x),
436 };
438 };
437
439
438 if should_redraw && !self.editor.should_close() {
440 if should_redraw && !self.editor.should_close() {
@@ -788,7 +790,7 @@ impl Application {
788 async fn claim_term(&mut self) -> Result<(), Error> {
790 async fn claim_term(&mut self) -> Result<(), Error> {
789 terminal::enable_raw_mode()?;
791 terminal::enable_raw_mode()?;
790 let mut stdout = stdout();
792 let mut stdout = stdout();
791− execute!(stdout, terminal::EnterAlternateScreen)?;
793+ execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?;
792 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
794 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
793 if self.config.load().editor.mouse {
795 if self.config.load().editor.mouse {
794 execute!(stdout, EnableMouseCapture)?;
796 execute!(stdout, EnableMouseCapture)?;
@@ -821,7 +823,11 @@ impl Application {
821 // probably not a good idea to `unwrap()` inside a panic handler.
823 // probably not a good idea to `unwrap()` inside a panic handler.
822 // So we just ignore the `Result`s.
824 // So we just ignore the `Result`s.
823 let _ = execute!(std::io::stdout(), DisableMouseCapture);
825 let _ = execute!(std::io::stdout(), DisableMouseCapture);
826+ let _ = execute!(
827+ std::io::stdout(),
824− let _ = execute!(std::io::stdout(), terminal::LeaveAlternateScreen);
828+ terminal::LeaveAlternateScreen,
829+ DisableBracketedPaste
830+ );
825 let _ = terminal::disable_raw_mode();
831 let _ = terminal::disable_raw_mode();
826 hook(info);
832 hook(info);
827 }));
833 }));
helix-term/src/commands.rs+17−24
@@ -3369,13 +3369,7 @@ enum Paste {
3369 Cursor,
3369 Cursor,
3370}
3370}
3371
3371
3372−fn paste_impl(
3372+fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste, count: usize) {
3373− values: &[String],
3374− doc: &mut Document,
3375− view: &View,
3376− action: Paste,
3377− count: usize,
3378−) -> Option<Transaction> {
3379 let repeat = std::iter::repeat(
3373 let repeat = std::iter::repeat(
3380 values
3374 values
3381 .last()
3375 .last()
@@ -3418,8 +3412,17 @@ fn paste_impl(
3418 };
3412 };
3419 (pos, pos, values.next())
3413 (pos, pos, values.next())
3420 });
3414 });
3415+ doc.apply(&transaction, view.id);
3416+}
3421
3417
3422− Some(transaction)
3418+pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
3419+ let count = cx.count();
3420+ let (view, doc) = current!(cx.editor);
3421+ let paste = match doc.mode {
3422+ Mode::Insert | Mode::Select => Paste::Cursor,
3423+ Mode::Normal => Paste::Before,
3424+ };
3425+ paste_impl(&[contents], doc, view, paste, count);
3423}
3426}
3424
3427
3425fn paste_clipboard_impl(
3428fn paste_clipboard_impl(
@@ -3429,18 +3432,11 @@ fn paste_clipboard_impl(
3429 count: usize,
3432 count: usize,
3430) -> anyhow::Result<()> {
3433) -> anyhow::Result<()> {
3431 let (view, doc) = current!(editor);
3434 let (view, doc) = current!(editor);
3432−
3433− match editor
3434− .clipboard_provider
3435− .get_contents(clipboard_type)
3435+ match editor.clipboard_provider.get_contents(clipboard_type) {
3436+ Ok(contents) => {
3436− .map(|contents| paste_impl(&[contents], doc, view, action, count))
3437+ paste_impl(&[contents], doc, view, action, count);
3437− {
3438− Ok(Some(transaction)) => {
3439− doc.apply(&transaction, view.id);
3440− doc.append_changes_to_history(view.id);
3441 Ok(())
3438 Ok(())
3442 }
3439 }
3443− Ok(None) => Ok(()),
3444 Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3440 Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3445 }
3441 }
3446}
3442}
@@ -3553,11 +3549,8 @@ fn paste(cx: &mut Context, pos: Paste) {
3553 let (view, doc) = current!(cx.editor);
3549 let (view, doc) = current!(cx.editor);
3554 let registers = &mut cx.editor.registers;
3550 let registers = &mut cx.editor.registers;
3555
3551
3556− if let Some(transaction) = registers
3552+ if let Some(values) = registers.read(reg_name) {
3557− .read(reg_name)
3558− .and_then(|values| paste_impl(values, doc, view, pos, count))
3553+ paste_impl(values, doc, view, pos, count);
3559− {
3560− doc.apply(&transaction, view.id);
3561 }
3554 }
3562}
3555}
3563
3556
@@ -4849,7 +4842,7 @@ fn replay_macro(cx: &mut Context) {
4849 cx.callback = Some(Box::new(move |compositor, cx| {
4842 cx.callback = Some(Box::new(move |compositor, cx| {
4850 for _ in 0..count {
4843 for _ in 0..count {
4851 for &key in keys.iter() {
4844 for &key in keys.iter() {
4852− compositor.handle_event(compositor::Event::Key(key), cx);
4845+ compositor.handle_event(&compositor::Event::Key(key), cx);
4853 }
4846 }
4854 }
4847 }
4855 // The macro under replay is cleared at the end of the callback, not in the
4848 // The macro under replay is cleared at the end of the callback, not in the
helix-term/src/compositor.rs+3−3
@@ -29,7 +29,7 @@ pub struct Context<'a> {
29
29
30pub trait Component: Any + AnyComponent {
30pub trait Component: Any + AnyComponent {
31 /// Process input events, return true if handled.
31 /// Process input events, return true if handled.
32− fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
32+ fn handle_event(&mut self, _event: &Event, _ctx: &mut Context) -> EventResult {
33 EventResult::Ignored(None)
33 EventResult::Ignored(None)
34 }
34 }
35 // , args: ()
35 // , args: ()
@@ -157,10 +157,10 @@ impl Compositor {
157 Some(self.layers.remove(idx))
157 Some(self.layers.remove(idx))
158 }
158 }
159
159
160− pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool {
160+ pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool {
161 // If it is a key event and a macro is being recorded, push the key event to the recording.
161 // If it is a key event and a macro is being recorded, push the key event to the recording.
162 if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) {
162 if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) {
163− keys.push(key);
163+ keys.push(*key);
164 }
164 }
165
165
166 let mut callbacks = Vec::new();
166 let mut callbacks = Vec::new();
helix-term/src/ui/completion.rs+1−1
@@ -298,7 +298,7 @@ impl Completion {
298}
298}
299
299
300impl Component for Completion {
300impl Component for Completion {
301− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
301+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
302 // let the Editor handle Esc instead
302 // let the Editor handle Esc instead
303 if let Event::Key(KeyEvent {
303 if let Event::Key(KeyEvent {
304 code: KeyCode::Esc, ..
304 code: KeyCode::Esc, ..
helix-term/src/ui/editor.rs+20−3
@@ -936,7 +936,7 @@ impl EditorView {
936impl EditorView {
936impl EditorView {
937 fn handle_mouse_event(
937 fn handle_mouse_event(
938 &mut self,
938 &mut self,
939− event: MouseEvent,
939+ event: &MouseEvent,
940 cxt: &mut commands::Context,
940 cxt: &mut commands::Context,
941 ) -> EventResult {
941 ) -> EventResult {
942 let config = cxt.editor.config();
942 let config = cxt.editor.config();
@@ -946,7 +946,7 @@ impl EditorView {
946 column,
946 column,
947 modifiers,
947 modifiers,
948 ..
948 ..
949− } = event;
949+ } = *event;
950
950
951 let pos_and_view = |editor: &Editor, row, column| {
951 let pos_and_view = |editor: &Editor, row, column| {
952 editor.tree.views().find_map(|(view, _focus)| {
952 editor.tree.views().find_map(|(view, _focus)| {
@@ -1115,7 +1115,7 @@ impl EditorView {
1115impl Component for EditorView {
1115impl Component for EditorView {
1116 fn handle_event(
1116 fn handle_event(
1117 &mut self,
1117 &mut self,
1118− event: Event,
1118+ event: &Event,
1119 context: &mut crate::compositor::Context,
1119 context: &mut crate::compositor::Context,
1120 ) -> EventResult {
1120 ) -> EventResult {
1121 let mut cx = commands::Context {
1121 let mut cx = commands::Context {
@@ -1128,6 +1128,23 @@ impl Component for EditorView {
1128 };
1128 };
1129
1129
1130 match event {
1130 match event {
1131+ Event::Paste(contents) => {
1132+ cx.count = cx.editor.count;
1133+ commands::paste_bracketed_value(&mut cx, contents.clone());
1134+ cx.editor.count = None;
1135+
1136+ let config = cx.editor.config();
1137+ let (view, doc) = current!(cx.editor);
1138+ view.ensure_cursor_in_view(doc, config.scrolloff);
1139+
1140+ // Store a history state if not in insert mode. Otherwise wait till we exit insert
1141+ // to include any edits to the paste in the history state.
1142+ if doc.mode() != Mode::Insert {
1143+ doc.append_changes_to_history(view.id);
1144+ }
1145+
1146+ EventResult::Consumed(None)
1147+ }
1131 Event::Resize(_width, _height) => {
1148 Event::Resize(_width, _height) => {
1132 // Ignore this event, we handle resizing just before rendering to screen.
1149 // Ignore this event, we handle resizing just before rendering to screen.
1133 // Handling it here but not re-rendering will cause flashing
1150 // Handling it here but not re-rendering will cause flashing
helix-term/src/ui/menu.rs+2−2
@@ -225,9 +225,9 @@ impl<T: Item> Menu<T> {
225use super::PromptEvent as MenuEvent;
225use super::PromptEvent as MenuEvent;
226
226
227impl<T: Item + 'static> Component for Menu<T> {
227impl<T: Item + 'static> Component for Menu<T> {
228− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
228+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
229 let event = match event {
229 let event = match event {
230− Event::Key(event) => event,
230+ Event::Key(event) => *event,
231 _ => return EventResult::Ignored(None),
231 _ => return EventResult::Ignored(None),
232 };
232 };
233
233
helix-term/src/ui/overlay.rs+1−1
@@ -61,7 +61,7 @@ impl<T: Component + 'static> Component for Overlay<T> {
61 Some((width, height))
61 Some((width, height))
62 }
62 }
63
63
64− fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
64+ fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
65 self.content.handle_event(event, ctx)
65 self.content.handle_event(event, ctx)
66 }
66 }
67
67
helix-term/src/ui/picker.rs+13−7
@@ -260,7 +260,7 @@ impl<T: Item + 'static> Component for FilePicker<T> {
260 }
260 }
261 }
261 }
262
262
263− fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult {
263+ fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
264 // TODO: keybinds for scrolling preview
264 // TODO: keybinds for scrolling preview
265 self.picker.handle_event(event, ctx)
265 self.picker.handle_event(event, ctx)
266 }
266 }
@@ -476,6 +476,14 @@ impl<T: Item> Picker<T> {
476 pub fn toggle_preview(&mut self) {
476 pub fn toggle_preview(&mut self) {
477 self.show_preview = !self.show_preview;
477 self.show_preview = !self.show_preview;
478 }
478 }
479+
480+ fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
481+ if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
482+ // TODO: recalculate only if pattern changed
483+ self.score();
484+ }
485+ EventResult::Consumed(None)
486+ }
479}
487}
480
488
481// process:
489// process:
@@ -489,9 +497,10 @@ impl<T: Item + 'static> Component for Picker<T> {
489 Some(viewport)
497 Some(viewport)
490 }
498 }
491
499
492− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
500+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
493 let key_event = match event {
501 let key_event = match event {
494− Event::Key(event) => event,
502+ Event::Key(event) => *event,
503+ Event::Paste(..) => return self.prompt_handle_event(event, cx),
495 Event::Resize(..) => return EventResult::Consumed(None),
504 Event::Resize(..) => return EventResult::Consumed(None),
496 _ => return EventResult::Ignored(None),
505 _ => return EventResult::Ignored(None),
497 };
506 };
@@ -548,10 +557,7 @@ impl<T: Item + 'static> Component for Picker<T> {
548 self.toggle_preview();
557 self.toggle_preview();
549 }
558 }
550 _ => {
559 _ => {
551− if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
560+ self.prompt_handle_event(event, cx);
552− // TODO: recalculate only if pattern changed
553− self.score();
554− }
555 }
561 }
556 }
562 }
557
563
helix-term/src/ui/popup.rs+2−2
@@ -138,9 +138,9 @@ impl<T: Component> Popup<T> {
138}
138}
139
139
140impl<T: Component> Component for Popup<T> {
140impl<T: Component> Component for Popup<T> {
141− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
141+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
142 let key = match event {
142 let key = match event {
143− Event::Key(event) => event,
143+ Event::Key(event) => *event,
144 Event::Resize(_, _) => {
144 Event::Resize(_, _) => {
145 // TODO: calculate inner area, call component's handle_event with that area
145 // TODO: calculate inner area, call component's handle_event with that area
146 return EventResult::Ignored(None);
146 return EventResult::Ignored(None);
helix-term/src/ui/prompt.rs+6−2
@@ -466,9 +466,13 @@ impl Prompt {
466}
466}
467
467
468impl Component for Prompt {
468impl Component for Prompt {
469− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
469+ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
470 let event = match event {
470 let event = match event {
471+ Event::Paste(data) => {
472+ self.insert_str(data);
473+ return EventResult::Consumed(None);
474+ }
471− Event::Key(event) => event,
475+ Event::Key(event) => *event,
472 Event::Resize(..) => return EventResult::Consumed(None),
476 Event::Resize(..) => return EventResult::Consumed(None),
473 _ => return EventResult::Ignored(None),
477 _ => return EventResult::Ignored(None),
474 };
478 };
helix-view/src/input.rs+3−4
@@ -6,12 +6,13 @@ use std::fmt;
6
6
7pub use crate::keyboard::{KeyCode, KeyModifiers};
7pub use crate::keyboard::{KeyCode, KeyModifiers};
8
8
9−#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
9+#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
10pub enum Event {
10pub enum Event {
11 FocusGained,
11 FocusGained,
12 FocusLost,
12 FocusLost,
13 Key(KeyEvent),
13 Key(KeyEvent),
14 Mouse(MouseEvent),
14 Mouse(MouseEvent),
15+ Paste(String),
15 Resize(u16, u16),
16 Resize(u16, u16),
16}
17}
17
18
@@ -276,9 +277,7 @@ impl From<crossterm::event::Event> for Event {
276 crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
277 crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
277 crossterm::event::Event::FocusGained => Self::FocusGained,
278 crossterm::event::Event::FocusGained => Self::FocusGained,
278 crossterm::event::Event::FocusLost => Self::FocusLost,
279 crossterm::event::Event::FocusLost => Self::FocusLost,
279− crossterm::event::Event::Paste(_) => {
280+ crossterm::event::Event::Paste(s) => Self::Paste(s),
280− unreachable!("crossterm shouldn't emit Paste events without them being enabled")
281− }
282 }
281 }
283 }
282 }
284}
283}