From 75fed681c9647e78d9f05332134afca9ea368472 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 08:47:32 +0300 Subject: [PATCH 01/28] clipboard-none: add in-memory fallback buffer Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 401c0459c009..c06bdc9ce0b5 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; pub trait ClipboardProvider: std::fmt::Debug { fn name(&self) -> Cow; fn get_contents(&self) -> Result; - fn set_contents(&self, contents: String) -> Result<()>; + fn set_contents(&mut self, contents: String) -> Result<()>; } macro_rules! command_provider { @@ -81,7 +81,7 @@ pub fn get_clipboard_provider() -> Box { return Box::new(provider::WindowsProvider); #[cfg(not(target_os = "windows"))] - return Box::new(provider::NopProvider); + return Box::new(provider::NopProvider{ buf: String::new() }); } } @@ -108,7 +108,9 @@ mod provider { use std::borrow::Cow; #[derive(Debug)] - pub struct NopProvider; + pub struct NopProvider { + pub buf: String, + } impl ClipboardProvider for NopProvider { fn name(&self) -> Cow { @@ -116,10 +118,11 @@ mod provider { } fn get_contents(&self) -> Result { - Ok(String::new()) + Ok(self.buf.clone()) } - fn set_contents(&self, _: String) -> Result<()> { + fn set_contents(&mut self, content: String) -> Result<()> { + self.buf = content; Ok(()) } } @@ -139,7 +142,7 @@ mod provider { Ok(contents) } - fn set_contents(&self, contents: String) -> Result<()> { + fn set_contents(&mut self, contents: String) -> Result<()> { clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?; Ok(()) } @@ -211,7 +214,7 @@ mod provider { Ok(output) } - fn set_contents(&self, value: String) -> Result<()> { + fn set_contents(&mut self, value: String) -> Result<()> { self.set_cmd.execute(Some(&value), false).map(|_| ()) } } From 2c20d357407859c05bb68538053096251b16e41a Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 09:09:17 +0300 Subject: [PATCH 02/28] view: add Wayland primary clipboard Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 2 +- helix-view/src/clipboard.rs | 14 ++++++++++++++ helix-view/src/editor.rs | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ceb01ef3c78f..adfef0bf91aa 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1687,7 +1687,7 @@ mod cmd { _event: PromptEvent, ) -> anyhow::Result<()> { cx.editor - .set_status(cx.editor.clipboard_provider.name().into()); + .set_status(format!("Clipboard: {}; Primary selection: {}", cx.editor.clipboard_provider.name(), cx.editor.primary_selection_provider.name())); Ok(()) } diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index c06bdc9ce0b5..b4569ddd7211 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -85,6 +85,20 @@ pub fn get_clipboard_provider() -> Box { } } +pub fn get_primary_selection_provider() -> Box { + // TODO: support for user-defined provider, probably when we have plugin support by setting a + // variable? + + if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { + command_provider! { + paste => "wl-paste", "-p", "--no-newline"; + copy => "wl-copy", "-p", "--type", "text/plain"; + } + } else { + Box::new(provider::NopProvider{ buf: String::new() }) + } +} + fn exists(executable_name: &str) -> bool { which::which(executable_name).is_ok() } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7e8548e73180..72a33a544c55 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,5 @@ use crate::{ - clipboard::{get_clipboard_provider, ClipboardProvider}, + clipboard::{get_clipboard_provider, get_primary_selection_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::Tree, @@ -28,6 +28,7 @@ pub struct Editor { pub theme: Theme, pub language_servers: helix_lsp::Registry, pub clipboard_provider: Box, + pub primary_selection_provider: Box, pub syn_loader: Arc, pub theme_loader: Arc, @@ -65,6 +66,7 @@ impl Editor { theme_loader: themes, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), + primary_selection_provider: get_primary_selection_provider(), status_msg: None, } } From 5b8815148be24e451950088547153ef5f05673e7 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 09:11:20 +0300 Subject: [PATCH 03/28] Format Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 7 +++++-- helix-view/src/clipboard.rs | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index adfef0bf91aa..033991179b89 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1686,8 +1686,11 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor - .set_status(format!("Clipboard: {}; Primary selection: {}", cx.editor.clipboard_provider.name(), cx.editor.primary_selection_provider.name())); + cx.editor.set_status(format!( + "Clipboard: {}; Primary selection: {}", + cx.editor.clipboard_provider.name(), + cx.editor.primary_selection_provider.name() + )); Ok(()) } diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index b4569ddd7211..2282e8e16d74 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -81,7 +81,7 @@ pub fn get_clipboard_provider() -> Box { return Box::new(provider::WindowsProvider); #[cfg(not(target_os = "windows"))] - return Box::new(provider::NopProvider{ buf: String::new() }); + return Box::new(provider::NopProvider { buf: String::new() }); } } @@ -95,7 +95,7 @@ pub fn get_primary_selection_provider() -> Box { copy => "wl-copy", "-p", "--type", "text/plain"; } } else { - Box::new(provider::NopProvider{ buf: String::new() }) + Box::new(provider::NopProvider { buf: String::new() }) } } From 8ccbe360fcd16e45528c114e71508f2f9262dd9a Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 09:45:06 +0300 Subject: [PATCH 04/28] helix-term: copy to primary selection after mouse move stops Signed-off-by: Dmitry Sharshakov --- helix-term/src/ui/editor.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index a2b169ed4198..76dca135b305 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -856,6 +856,23 @@ impl Component for EditorView { doc.set_selection(view.id, selection); EventResult::Consumed(None) } + + Event::Mouse(MouseEvent { + kind: MouseEventKind::Up(MouseButton::Left), + .. + }) => { + let (view, doc) = current!(cx.editor); + + cx.editor.primary_selection_provider.set_contents( + doc.selection(view.id) + .primary() + .fragment(doc.text().slice(..)) + .to_string(), + ); + + EventResult::Consumed(None) + } + Event::Mouse(_) => EventResult::Ignored, } } From c938a7b22e9029b97ad8ff7f731a7b7b5a5d168e Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 09:50:59 +0300 Subject: [PATCH 05/28] helix-term: don't update primary selection if it is a single character Signed-off-by: Dmitry Sharshakov --- helix-term/src/ui/editor.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 76dca135b305..e7a6f1446aea 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -862,13 +862,15 @@ impl Component for EditorView { .. }) => { let (view, doc) = current!(cx.editor); + let range = doc.selection(view.id).primary(); - cx.editor.primary_selection_provider.set_contents( - doc.selection(view.id) - .primary() - .fragment(doc.text().slice(..)) - .to_string(), - ); + if range.to() - range.from() <= 1 { + return EventResult::Ignored; + } + + cx.editor + .primary_selection_provider + .set_contents(range.fragment(doc.text().slice(..)).to_string()); EventResult::Consumed(None) } From 684b8dcc4beb98e71cfdabba7d7c3fe7abc2d586 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 09:53:51 +0300 Subject: [PATCH 06/28] helix-term: discard result of setting primary selection Signed-off-by: Dmitry Sharshakov --- helix-term/src/ui/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index e7a6f1446aea..1d5f0098caea 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -868,7 +868,8 @@ impl Component for EditorView { return EventResult::Ignored; } - cx.editor + let _ = cx + .editor .primary_selection_provider .set_contents(range.fragment(doc.text().slice(..)).to_string()); From 71eb635444d45d367c9e04a71f4aa20b6a8b595c Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 12:11:50 +0300 Subject: [PATCH 07/28] helix-term: add commands for interaction with primary clipboard Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 198 +++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 033991179b89..5fe94c84d74c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1642,6 +1642,26 @@ mod cmd { yank_joined_to_clipboard_impl(&mut cx.editor, separator) } + fn yank_main_selection_to_primary_selection( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + yank_main_selection_to_primary_selection_impl(&mut cx.editor) + } + + fn yank_joined_to_primary_selection( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let (_, doc) = current!(cx.editor); + let separator = args + .first() + .copied() + .unwrap_or_else(|| doc.line_ending.as_str()); + yank_joined_to_primary_selection_impl(&mut cx.editor, separator) + } fn paste_clipboard_after( cx: &mut compositor::Context, _args: &[&str], @@ -1681,6 +1701,45 @@ mod cmd { } } + fn paste_primary_selection_after( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_primary_selection_impl(&mut cx.editor, Paste::After) + } + + fn paste_primary_selection_before( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_primary_selection_impl(&mut cx.editor, Paste::After) + } + + fn replace_selections_with_primary_selection( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let (view, doc) = current!(cx.editor); + + match cx.editor.primary_selection_provider.get_contents() { + Ok(contents) => { + let selection = doc.selection(view.id); + let transaction = + Transaction::change_by_selection(doc.text(), selection, |range| { + (range.from(), range.to(), Some(contents.as_str().into())) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); + Ok(()) + } + Err(e) => Err(e.context("Couldn't get system primary selection contents")), + } + } + fn show_clipboard_provider( cx: &mut compositor::Context, _args: &[&str], @@ -1891,6 +1950,20 @@ mod cmd { fun: yank_joined_to_clipboard, completer: None, }, + TypableCommand { + name: "primary-selection-yank", + alias: None, + doc: "Yank main selection into system primary selection.", + fun: yank_main_selection_to_primary_selection, + completer: None, + }, + TypableCommand { + name: "primary-selection-yank-join", + alias: None, + doc: "Yank joined selections into system primary selection. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc. + fun: yank_joined_to_primary_selection, + completer: None, + }, TypableCommand { name: "clipboard-paste-after", alias: None, @@ -1912,6 +1985,27 @@ mod cmd { fun: replace_selections_with_clipboard, completer: None, }, + TypableCommand { + name: "primary-selection-paste-after", + alias: None, + doc: "Paste primary selection after selections.", + fun: paste_primary_selection_after, + completer: None, + }, + TypableCommand { + name: "primary-selection-paste-before", + alias: None, + doc: "Paste primary selection before selections.", + fun: paste_primary_selection_before, + completer: None, + }, + TypableCommand { + name: "primary-selection-paste-replace", + alias: None, + doc: "Replace selections with content of system primary selection.", + fun: replace_selections_with_primary_selection, + completer: None, + }, TypableCommand { name: "show-clipboard-provider", alias: None, @@ -3184,6 +3278,62 @@ fn yank_main_selection_to_clipboard(cx: &mut Context) { let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor); } +fn yank_joined_to_primary_selection_impl( + editor: &mut Editor, + separator: &str, +) -> anyhow::Result<()> { + let (view, doc) = current!(editor); + let text = doc.text().slice(..); + + let values: Vec = doc + .selection(view.id) + .fragments(text) + .map(Cow::into_owned) + .collect(); + + let msg = format!( + "joined and yanked {} selection(s) to system primary selection", + values.len(), + ); + + let joined = values.join(separator); + + editor + .primary_selection_provider + .set_contents(joined) + .context("Couldn't set system primary selection content")?; + + editor.set_status(msg); + + Ok(()) +} + +fn yank_joined_to_primary_selection(cx: &mut Context) { + let line_ending = current!(cx.editor).1.line_ending; + let _ = yank_joined_to_primary_selection_impl(&mut cx.editor, line_ending.as_str()); +} + +fn yank_main_selection_to_primary_selection_impl(editor: &mut Editor) -> anyhow::Result<()> { + let (view, doc) = current!(editor); + let text = doc.text().slice(..); + + let value = doc.selection(view.id).primary().fragment(text); + + if let Err(e) = editor + .primary_selection_provider + .set_contents(value.into_owned()) + { + bail!("Couldn't set system primary selection content: {:?}", e); + } + + editor.set_status("yanked main selection to system primary selection".to_owned()); + Ok(()) +} + +fn yank_main_selection_to_primary_selection(cx: &mut Context) { + let _ = yank_main_selection_to_primary_selection_impl(&mut cx.editor); +} + #[derive(Copy, Clone)] enum Paste { Before, @@ -3259,6 +3409,32 @@ fn paste_clipboard_before(cx: &mut Context) { let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before); } +fn paste_primary_selection_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { + let (view, doc) = current!(editor); + + match editor + .primary_selection_provider + .get_contents() + .map(|contents| paste_impl(&[contents], doc, view, action)) + { + Ok(Some(transaction)) => { + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); + Ok(()) + } + Ok(None) => Ok(()), + Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), + } +} + +fn paste_primary_selection_after(cx: &mut Context) { + let _ = paste_primary_selection_impl(&mut cx.editor, Paste::After); +} + +fn paste_primary_selection_before(cx: &mut Context) { + let _ = paste_primary_selection_impl(&mut cx.editor, Paste::Before); +} + fn replace_with_yanked(cx: &mut Context) { let reg_name = cx.selected_register.name(); let (view, doc) = current!(cx.editor); @@ -3303,6 +3479,28 @@ fn replace_selections_with_clipboard(cx: &mut Context) { let _ = replace_selections_with_clipboard_impl(&mut cx.editor); } +fn replace_selections_with_primary_selection_impl(editor: &mut Editor) -> anyhow::Result<()> { + let (view, doc) = current!(editor); + + match editor.primary_selection_provider.get_contents() { + Ok(contents) => { + let selection = doc.selection(view.id); + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + (range.from(), range.to(), Some(contents.as_str().into())) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); + Ok(()) + } + Err(e) => Err(e.context("Couldn't get system primary selection contents")), + } +} + +fn replace_selections_with_primary_selection(cx: &mut Context) { + let _ = replace_selections_with_primary_selection_impl(&mut cx.editor); +} + fn paste_after(cx: &mut Context) { let reg_name = cx.selected_register.name(); let (view, doc) = current!(cx.editor); From fb06c20a82769ee7b1ec6d3db75e9e728667a7c2 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 12:41:17 +0300 Subject: [PATCH 08/28] editor: implement primary selection copy/paste using commands Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 5 ++++ helix-term/src/ui/editor.rs | 51 ++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5fe94c84d74c..80c4f812506b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -258,12 +258,17 @@ impl Command { yank, "Yank selection", yank_joined_to_clipboard, "Join and yank selections to clipboard", yank_main_selection_to_clipboard, "Yank main selection to clipboard", + yank_joined_to_primary_selection, "Join and yank selections to primary selection", + yank_main_selection_to_primary_selection, "Yank main selection to primary selection", replace_with_yanked, "Replace with yanked text", replace_selections_with_clipboard, "Replace selections by clipboard content", + replace_selections_with_primary_selection, "Replace selections by primary selection content", paste_after, "Paste after selection", paste_before, "Paste before selection", paste_clipboard_after, "Paste clipboard after selections", paste_clipboard_before, "Paste clipboard before selections", + paste_primary_selection_after, "Paste primary selection after selections", + paste_primary_selection_before, "Paste primary selection before selections", indent, "Indent selection", unindent, "Unindent selection", format_selections, "Format selection", diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1d5f0098caea..43a3648a6fc6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -868,14 +868,57 @@ impl Component for EditorView { return EventResult::Ignored; } - let _ = cx - .editor - .primary_selection_provider - .set_contents(range.fragment(doc.text().slice(..)).to_string()); + let mut cxt = commands::Context { + selected_register: helix_view::RegisterSelection::default(), + editor: &mut cx.editor, + count: None, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + commands::Command::yank_main_selection_to_primary_selection.execute(&mut cxt); EventResult::Consumed(None) } + Event::Mouse(MouseEvent { + kind: MouseEventKind::Up(MouseButton::Middle), + row, + column, + .. + }) => { + let editor = &mut cx.editor; + + let result = editor.tree.views().find_map(|(view, _focus)| { + view.pos_at_screen_coords(&editor.documents[view.doc], row, column) + .map(|pos| (pos, view.id)) + }); + + if let Some((pos, view_id)) = result { + let doc = &mut editor.documents[editor.tree.get(view_id).doc]; + + doc.set_selection(view_id, Selection::point(pos)); + + editor.tree.focus = view_id; + + let mut cxt = commands::Context { + selected_register: helix_view::RegisterSelection::default(), + editor: &mut cx.editor, + count: None, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + commands::Command::paste_primary_selection_before.execute(&mut cxt); + + return EventResult::Consumed(None); + } + + EventResult::Ignored + } + Event::Mouse(_) => EventResult::Ignored, } } From 74681bddf53d718b6200fc1ea5f41283004a5a12 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 12:47:28 +0300 Subject: [PATCH 09/28] clipboard: support xsel for primary selection Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 2282e8e16d74..fb99d22f518f 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -89,7 +89,13 @@ pub fn get_primary_selection_provider() -> Box { // TODO: support for user-defined provider, probably when we have plugin support by setting a // variable? - if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { + if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o"]) { + // FIXME: check performance of is_exit_success + command_provider! { + paste => "xsel", "-o"; + copy => "xsel", "-i"; + } + } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { command_provider! { paste => "wl-paste", "-p", "--no-newline"; copy => "wl-copy", "-p", "--type", "text/plain"; From f898fada7edfb8069f61101867a6a5270cfe9fa0 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 13:10:15 +0300 Subject: [PATCH 10/28] clipboard: support xclip for primary selection Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index fb99d22f518f..26a10249b138 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -95,6 +95,11 @@ pub fn get_primary_selection_provider() -> Box { paste => "xsel", "-o"; copy => "xsel", "-i"; } + } else if env_var_is_set("DISPLAY") && exists("xclip") { + command_provider! { + paste => "xclip", "-o"; + copy => "xclip", "-i"; + } } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { command_provider! { paste => "wl-paste", "-p", "--no-newline"; From 19f6c946037b52c6739af1869cdaf988fb0736dd Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 14:05:25 +0300 Subject: [PATCH 11/28] helix-term: multiple cursor support for middle click paste Signed-off-by: Dmitry Sharshakov --- helix-term/src/ui/editor.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 43a3648a6fc6..bfa1972308c0 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -886,10 +886,26 @@ impl Component for EditorView { kind: MouseEventKind::Up(MouseButton::Middle), row, column, + modifiers, .. }) => { let editor = &mut cx.editor; + if modifiers == crossterm::event::KeyModifiers::ALT { + let mut cxt = commands::Context { + selected_register: helix_view::RegisterSelection::default(), + editor: &mut cx.editor, + count: None, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + commands::Command::replace_selections_with_primary_selection.execute(&mut cxt); + + return EventResult::Consumed(None); + } + let result = editor.tree.views().find_map(|(view, _focus)| { view.pos_at_screen_coords(&editor.documents[view.doc], row, column) .map(|pos| (pos, view.id)) From 05ae6505f3775c5e1ebae9838d4fb5203933c25a Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 14:28:51 +0300 Subject: [PATCH 12/28] rename primary selection to primary clipboard in scope of PR Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 110 ++++++++++++++++++------------------ helix-term/src/ui/editor.rs | 6 +- helix-view/src/clipboard.rs | 2 +- helix-view/src/editor.rs | 6 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 80c4f812506b..84bf9683462a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -258,17 +258,17 @@ impl Command { yank, "Yank selection", yank_joined_to_clipboard, "Join and yank selections to clipboard", yank_main_selection_to_clipboard, "Yank main selection to clipboard", - yank_joined_to_primary_selection, "Join and yank selections to primary selection", - yank_main_selection_to_primary_selection, "Yank main selection to primary selection", + yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard", + yank_main_selection_to_primary_clipboard, "Yank main selection to primary clipboard", replace_with_yanked, "Replace with yanked text", replace_selections_with_clipboard, "Replace selections by clipboard content", - replace_selections_with_primary_selection, "Replace selections by primary selection content", + replace_selections_with_primary_clipboard, "Replace selections by primary clipboard content", paste_after, "Paste after selection", paste_before, "Paste before selection", paste_clipboard_after, "Paste clipboard after selections", paste_clipboard_before, "Paste clipboard before selections", - paste_primary_selection_after, "Paste primary selection after selections", - paste_primary_selection_before, "Paste primary selection before selections", + paste_primary_clipboard_after, "Paste primary clipboard after selections", + paste_primary_clipboard_before, "Paste primary clipboard before selections", indent, "Indent selection", unindent, "Unindent selection", format_selections, "Format selection", @@ -1647,15 +1647,15 @@ mod cmd { yank_joined_to_clipboard_impl(&mut cx.editor, separator) } - fn yank_main_selection_to_primary_selection( + fn yank_main_selection_to_primary_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - yank_main_selection_to_primary_selection_impl(&mut cx.editor) + yank_main_selection_to_primary_clipboard_impl(&mut cx.editor) } - fn yank_joined_to_primary_selection( + fn yank_joined_to_primary_clipboard( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, @@ -1665,7 +1665,7 @@ mod cmd { .first() .copied() .unwrap_or_else(|| doc.line_ending.as_str()); - yank_joined_to_primary_selection_impl(&mut cx.editor, separator) + yank_joined_to_primary_clipboard_impl(&mut cx.editor, separator) } fn paste_clipboard_after( cx: &mut compositor::Context, @@ -1706,30 +1706,30 @@ mod cmd { } } - fn paste_primary_selection_after( + fn paste_primary_clipboard_after( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_primary_selection_impl(&mut cx.editor, Paste::After) + paste_primary_clipboard_impl(&mut cx.editor, Paste::After) } - fn paste_primary_selection_before( + fn paste_primary_clipboard_before( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_primary_selection_impl(&mut cx.editor, Paste::After) + paste_primary_clipboard_impl(&mut cx.editor, Paste::After) } - fn replace_selections_with_primary_selection( + fn replace_selections_with_primary_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - match cx.editor.primary_selection_provider.get_contents() { + match cx.editor.primary_clipboard_provider.get_contents() { Ok(contents) => { let selection = doc.selection(view.id); let transaction = @@ -1741,7 +1741,7 @@ mod cmd { doc.append_changes_to_history(view.id); Ok(()) } - Err(e) => Err(e.context("Couldn't get system primary selection contents")), + Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), } } @@ -1753,7 +1753,7 @@ mod cmd { cx.editor.set_status(format!( "Clipboard: {}; Primary selection: {}", cx.editor.clipboard_provider.name(), - cx.editor.primary_selection_provider.name() + cx.editor.primary_clipboard_provider.name() )); Ok(()) } @@ -1956,17 +1956,17 @@ mod cmd { completer: None, }, TypableCommand { - name: "primary-selection-yank", + name: "primary-clipboard-yank", alias: None, - doc: "Yank main selection into system primary selection.", - fun: yank_main_selection_to_primary_selection, + doc: "Yank main selection into system primary clipboard.", + fun: yank_main_selection_to_primary_clipboard, completer: None, }, TypableCommand { - name: "primary-selection-yank-join", + name: "primary-clipboard-yank-join", alias: None, - doc: "Yank joined selections into system primary selection. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc. - fun: yank_joined_to_primary_selection, + doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc. + fun: yank_joined_to_primary_clipboard, completer: None, }, TypableCommand { @@ -1991,24 +1991,24 @@ mod cmd { completer: None, }, TypableCommand { - name: "primary-selection-paste-after", + name: "primary-clipboard-paste-after", alias: None, - doc: "Paste primary selection after selections.", - fun: paste_primary_selection_after, + doc: "Paste primary clipboard after selections.", + fun: paste_primary_clipboard_after, completer: None, }, TypableCommand { - name: "primary-selection-paste-before", + name: "primary-clipboard-paste-before", alias: None, - doc: "Paste primary selection before selections.", - fun: paste_primary_selection_before, + doc: "Paste primary clipboard before selections.", + fun: paste_primary_clipboard_before, completer: None, }, TypableCommand { - name: "primary-selection-paste-replace", + name: "primary-clipboard-paste-replace", alias: None, - doc: "Replace selections with content of system primary selection.", - fun: replace_selections_with_primary_selection, + doc: "Replace selections with content of system primary clipboard.", + fun: replace_selections_with_primary_clipboard, completer: None, }, TypableCommand { @@ -3283,7 +3283,7 @@ fn yank_main_selection_to_clipboard(cx: &mut Context) { let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor); } -fn yank_joined_to_primary_selection_impl( +fn yank_joined_to_primary_clipboard_impl( editor: &mut Editor, separator: &str, ) -> anyhow::Result<()> { @@ -3297,46 +3297,46 @@ fn yank_joined_to_primary_selection_impl( .collect(); let msg = format!( - "joined and yanked {} selection(s) to system primary selection", + "joined and yanked {} selection(s) to system primary clipboard", values.len(), ); let joined = values.join(separator); editor - .primary_selection_provider + .primary_clipboard_provider .set_contents(joined) - .context("Couldn't set system primary selection content")?; + .context("Couldn't set system primary clipboard content")?; editor.set_status(msg); Ok(()) } -fn yank_joined_to_primary_selection(cx: &mut Context) { +fn yank_joined_to_primary_clipboard(cx: &mut Context) { let line_ending = current!(cx.editor).1.line_ending; - let _ = yank_joined_to_primary_selection_impl(&mut cx.editor, line_ending.as_str()); + let _ = yank_joined_to_primary_clipboard_impl(&mut cx.editor, line_ending.as_str()); } -fn yank_main_selection_to_primary_selection_impl(editor: &mut Editor) -> anyhow::Result<()> { +fn yank_main_selection_to_primary_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); let text = doc.text().slice(..); let value = doc.selection(view.id).primary().fragment(text); if let Err(e) = editor - .primary_selection_provider + .primary_clipboard_provider .set_contents(value.into_owned()) { - bail!("Couldn't set system primary selection content: {:?}", e); + bail!("Couldn't set system primary clipboard content: {:?}", e); } - editor.set_status("yanked main selection to system primary selection".to_owned()); + editor.set_status("yanked main selection to system primary clipboard".to_owned()); Ok(()) } -fn yank_main_selection_to_primary_selection(cx: &mut Context) { - let _ = yank_main_selection_to_primary_selection_impl(&mut cx.editor); +fn yank_main_selection_to_primary_clipboard(cx: &mut Context) { + let _ = yank_main_selection_to_primary_clipboard_impl(&mut cx.editor); } #[derive(Copy, Clone)] @@ -3414,11 +3414,11 @@ fn paste_clipboard_before(cx: &mut Context) { let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before); } -fn paste_primary_selection_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { +fn paste_primary_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor - .primary_selection_provider + .primary_clipboard_provider .get_contents() .map(|contents| paste_impl(&[contents], doc, view, action)) { @@ -3432,12 +3432,12 @@ fn paste_primary_selection_impl(editor: &mut Editor, action: Paste) -> anyhow::R } } -fn paste_primary_selection_after(cx: &mut Context) { - let _ = paste_primary_selection_impl(&mut cx.editor, Paste::After); +fn paste_primary_clipboard_after(cx: &mut Context) { + let _ = paste_primary_clipboard_impl(&mut cx.editor, Paste::After); } -fn paste_primary_selection_before(cx: &mut Context) { - let _ = paste_primary_selection_impl(&mut cx.editor, Paste::Before); +fn paste_primary_clipboard_before(cx: &mut Context) { + let _ = paste_primary_clipboard_impl(&mut cx.editor, Paste::Before); } fn replace_with_yanked(cx: &mut Context) { @@ -3484,10 +3484,10 @@ fn replace_selections_with_clipboard(cx: &mut Context) { let _ = replace_selections_with_clipboard_impl(&mut cx.editor); } -fn replace_selections_with_primary_selection_impl(editor: &mut Editor) -> anyhow::Result<()> { +fn replace_selections_with_primary_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); - match editor.primary_selection_provider.get_contents() { + match editor.primary_clipboard_provider.get_contents() { Ok(contents) => { let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { @@ -3498,12 +3498,12 @@ fn replace_selections_with_primary_selection_impl(editor: &mut Editor) -> anyhow doc.append_changes_to_history(view.id); Ok(()) } - Err(e) => Err(e.context("Couldn't get system primary selection contents")), + Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), } } -fn replace_selections_with_primary_selection(cx: &mut Context) { - let _ = replace_selections_with_primary_selection_impl(&mut cx.editor); +fn replace_selections_with_primary_clipboard(cx: &mut Context) { + let _ = replace_selections_with_primary_clipboard_impl(&mut cx.editor); } fn paste_after(cx: &mut Context) { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index bfa1972308c0..4f9f3129acbd 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -877,7 +877,7 @@ impl Component for EditorView { jobs: cx.jobs, }; - commands::Command::yank_main_selection_to_primary_selection.execute(&mut cxt); + commands::Command::yank_main_selection_to_primary_clipboard.execute(&mut cxt); EventResult::Consumed(None) } @@ -901,7 +901,7 @@ impl Component for EditorView { jobs: cx.jobs, }; - commands::Command::replace_selections_with_primary_selection.execute(&mut cxt); + commands::Command::replace_selections_with_primary_clipboard.execute(&mut cxt); return EventResult::Consumed(None); } @@ -927,7 +927,7 @@ impl Component for EditorView { jobs: cx.jobs, }; - commands::Command::paste_primary_selection_before.execute(&mut cxt); + commands::Command::paste_primary_clipboard_before.execute(&mut cxt); return EventResult::Consumed(None); } diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 26a10249b138..d79c87f5f759 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -85,7 +85,7 @@ pub fn get_clipboard_provider() -> Box { } } -pub fn get_primary_selection_provider() -> Box { +pub fn get_primary_clipboard_provider() -> Box { // TODO: support for user-defined provider, probably when we have plugin support by setting a // variable? diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 72a33a544c55..7d38813b2ba1 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,5 @@ use crate::{ - clipboard::{get_clipboard_provider, get_primary_selection_provider, ClipboardProvider}, + clipboard::{get_clipboard_provider, get_primary_clipboard_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::Tree, @@ -28,7 +28,7 @@ pub struct Editor { pub theme: Theme, pub language_servers: helix_lsp::Registry, pub clipboard_provider: Box, - pub primary_selection_provider: Box, + pub primary_clipboard_provider: Box, pub syn_loader: Arc, pub theme_loader: Arc, @@ -66,7 +66,7 @@ impl Editor { theme_loader: themes, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), - primary_selection_provider: get_primary_selection_provider(), + primary_clipboard_provider: get_primary_clipboard_provider(), status_msg: None, } } From 1e8162e6eaa8b270f452450563a90c19bb5dbe45 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 17:37:51 +0300 Subject: [PATCH 13/28] helix-term: make middle click paste optional Signed-off-by: Dmitry Sharshakov --- helix-term/src/application.rs | 2 +- helix-term/src/config.rs | 15 ++++++++++----- helix-term/src/ui/editor.rs | 14 ++++++++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a4d727f61634..88411315c171 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -82,7 +82,7 @@ impl Application { let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone()); - let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); + let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys), config.terminal.middle_click_paste)); compositor.push(editor_view); if !args.files.is_empty() { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 38cd3bfbdce6..80949dfb7d52 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -19,16 +19,21 @@ pub struct LspConfig { pub display_messages: bool, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct TerminalConfig { + #[serde(default = "mouse_default")] pub mouse: bool, + #[serde(default = "middle_click_paste_default")] + pub middle_click_paste: bool, +} + +fn mouse_default() -> bool { + true } -impl Default for TerminalConfig { - fn default() -> Self { - Self { mouse: true } - } +fn middle_click_paste_default() -> bool { + true } #[test] diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4f9f3129acbd..63ef73d772af 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -29,6 +29,7 @@ use tui::buffer::Buffer as Surface; pub struct EditorView { keymaps: Keymaps, + middle_click_paste: bool, on_next_key: Option>, last_insert: (commands::Command, Vec), completion: Option, @@ -40,14 +41,15 @@ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter impl Default for EditorView { fn default() -> Self { - Self::new(Keymaps::default()) + Self::new(Keymaps::default(), true) } } impl EditorView { - pub fn new(keymaps: Keymaps) -> Self { + pub fn new(keymaps: Keymaps, middle_click_paste: bool) -> Self { Self { keymaps, + middle_click_paste, on_next_key: None, last_insert: (commands::Command::normal_mode, Vec::new()), completion: None, @@ -861,6 +863,10 @@ impl Component for EditorView { kind: MouseEventKind::Up(MouseButton::Left), .. }) => { + if !self.middle_click_paste { + return EventResult::Ignored; + } + let (view, doc) = current!(cx.editor); let range = doc.selection(view.id).primary(); @@ -889,6 +895,10 @@ impl Component for EditorView { modifiers, .. }) => { + if !self.middle_click_paste { + return EventResult::Ignored; + } + let editor = &mut cx.editor; if modifiers == crossterm::event::KeyModifiers::ALT { From db0d5d5524d0d4628ae8263ddb8f48e9dae50095 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 4 Aug 2021 17:41:00 +0300 Subject: [PATCH 14/28] Format Signed-off-by: Dmitry Sharshakov --- helix-term/src/application.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 88411315c171..0bc193f36edb 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -82,7 +82,10 @@ impl Application { let mut editor = Editor::new(size, theme_loader.clone(), syn_loader.clone()); - let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys), config.terminal.middle_click_paste)); + let editor_view = Box::new(ui::EditorView::new( + std::mem::take(&mut config.keys), + config.terminal.middle_click_paste, + )); compositor.push(editor_view); if !args.files.is_empty() { From b273396ef299920807462771d55b1b1054fb9f61 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 14:11:17 +0300 Subject: [PATCH 15/28] Update helix-term/src/ui/editor.rs --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 63ef73d772af..74aa663b5326 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -904,7 +904,7 @@ impl Component for EditorView { if modifiers == crossterm::event::KeyModifiers::ALT { let mut cxt = commands::Context { selected_register: helix_view::RegisterSelection::default(), - editor: &mut cx.editor, + editor: editor, count: None, callback: None, on_next_key_callback: None, From ffef3eadb6f88154b28e723ddde5ca4d27aad3b1 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 14:24:54 +0300 Subject: [PATCH 16/28] fix formatting Signed-off-by: Dmitry Sharshakov --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 74aa663b5326..fd78e653fad5 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -904,7 +904,7 @@ impl Component for EditorView { if modifiers == crossterm::event::KeyModifiers::ALT { let mut cxt = commands::Context { selected_register: helix_view::RegisterSelection::default(), - editor: editor, + editor, count: None, callback: None, on_next_key_callback: None, From b76b8e7c95b9fc321fdd0262f66e0176715bbb36 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 18:24:08 +0300 Subject: [PATCH 17/28] config: correct defaults if terminal prop is not set Signed-off-by: Dmitry Sharshakov --- helix-term/src/config.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 80949dfb7d52..68742b4e3385 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -19,7 +19,7 @@ pub struct LspConfig { pub display_messages: bool, } -#[derive(Debug, Default, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct TerminalConfig { #[serde(default = "mouse_default")] @@ -28,6 +28,15 @@ pub struct TerminalConfig { pub middle_click_paste: bool, } +impl Default for TerminalConfig { + fn default() -> Self { + Self { + mouse: true, + middle_click_paste: true, + } + } +} + fn mouse_default() -> bool { true } From 2316230017ddd7212e5eee70ba4ce6d4429c36b0 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 18:29:03 +0300 Subject: [PATCH 18/28] refactor: merge clipboard and primary selection implementations Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 181 +++++++++++++----------------------- helix-view/src/clipboard.rs | 181 +++++++++++++++++++++++++----------- helix-view/src/editor.rs | 4 +- 3 files changed, 195 insertions(+), 171 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 84bf9683462a..b9d305be9c1d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -12,6 +12,7 @@ use helix_core::{ }; use helix_view::{ + clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, @@ -1631,7 +1632,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - yank_main_selection_to_clipboard_impl(&mut cx.editor) + yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard) } fn yank_joined_to_clipboard( @@ -1644,7 +1645,7 @@ mod cmd { .first() .copied() .unwrap_or_else(|| doc.line_ending.as_str()); - yank_joined_to_clipboard_impl(&mut cx.editor, separator) + yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Clipboard) } fn yank_main_selection_to_primary_clipboard( @@ -1652,7 +1653,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - yank_main_selection_to_primary_clipboard_impl(&mut cx.editor) + yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection) } fn yank_joined_to_primary_clipboard( @@ -1665,14 +1666,15 @@ mod cmd { .first() .copied() .unwrap_or_else(|| doc.line_ending.as_str()); - yank_joined_to_primary_clipboard_impl(&mut cx.editor, separator) + yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection) } + fn paste_clipboard_after( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_clipboard_impl(&mut cx.editor, Paste::After) + paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard) } fn paste_clipboard_before( @@ -1680,7 +1682,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_clipboard_impl(&mut cx.editor, Paste::After) + paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard) } fn replace_selections_with_clipboard( @@ -1690,7 +1692,11 @@ mod cmd { ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - match cx.editor.clipboard_provider.get_contents() { + match cx + .editor + .clipboard_provider + .get_contents(ClipboardType::Clipboard) + { Ok(contents) => { let selection = doc.selection(view.id); let transaction = @@ -1711,7 +1717,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_primary_clipboard_impl(&mut cx.editor, Paste::After) + paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection) } fn paste_primary_clipboard_before( @@ -1719,7 +1725,7 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - paste_primary_clipboard_impl(&mut cx.editor, Paste::After) + paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection) } fn replace_selections_with_primary_clipboard( @@ -1729,7 +1735,11 @@ mod cmd { ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - match cx.editor.primary_clipboard_provider.get_contents() { + match cx + .editor + .clipboard_provider + .get_contents(ClipboardType::Selection) + { Ok(contents) => { let selection = doc.selection(view.id); let transaction = @@ -1750,11 +1760,8 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor.set_status(format!( - "Clipboard: {}; Primary selection: {}", - cx.editor.clipboard_provider.name(), - cx.editor.primary_clipboard_provider.name() - )); + cx.editor + .set_status(cx.editor.clipboard_provider.name().to_string()); Ok(()) } @@ -3233,7 +3240,11 @@ fn yank(cx: &mut Context) { cx.editor.set_status(msg) } -fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> { +fn yank_joined_to_clipboard_impl( + editor: &mut Editor, + separator: &str, + clipboard_type: ClipboardType, +) -> anyhow::Result<()> { let (view, doc) = current!(editor); let text = doc.text().slice(..); @@ -3252,7 +3263,7 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow editor .clipboard_provider - .set_contents(joined) + .set_contents(joined, clipboard_type) .context("Couldn't set system clipboard content")?; editor.set_status(msg); @@ -3262,16 +3273,26 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow fn yank_joined_to_clipboard(cx: &mut Context) { let line_ending = current!(cx.editor).1.line_ending; - let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str()); + let _ = yank_joined_to_clipboard_impl( + &mut cx.editor, + line_ending.as_str(), + ClipboardType::Clipboard, + ); } -fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { +fn yank_main_selection_to_clipboard_impl( + editor: &mut Editor, + clipboard_type: ClipboardType, +) -> anyhow::Result<()> { let (view, doc) = current!(editor); let text = doc.text().slice(..); let value = doc.selection(view.id).primary().fragment(text); - if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) { + if let Err(e) = editor + .clipboard_provider + .set_contents(value.into_owned(), clipboard_type) + { bail!("Couldn't set system clipboard content: {:?}", e); } @@ -3280,63 +3301,20 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result< } fn yank_main_selection_to_clipboard(cx: &mut Context) { - let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor); -} - -fn yank_joined_to_primary_clipboard_impl( - editor: &mut Editor, - separator: &str, -) -> anyhow::Result<()> { - let (view, doc) = current!(editor); - let text = doc.text().slice(..); - - let values: Vec = doc - .selection(view.id) - .fragments(text) - .map(Cow::into_owned) - .collect(); - - let msg = format!( - "joined and yanked {} selection(s) to system primary clipboard", - values.len(), - ); - - let joined = values.join(separator); - - editor - .primary_clipboard_provider - .set_contents(joined) - .context("Couldn't set system primary clipboard content")?; - - editor.set_status(msg); - - Ok(()) + let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard); } fn yank_joined_to_primary_clipboard(cx: &mut Context) { let line_ending = current!(cx.editor).1.line_ending; - let _ = yank_joined_to_primary_clipboard_impl(&mut cx.editor, line_ending.as_str()); -} - -fn yank_main_selection_to_primary_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { - let (view, doc) = current!(editor); - let text = doc.text().slice(..); - - let value = doc.selection(view.id).primary().fragment(text); - - if let Err(e) = editor - .primary_clipboard_provider - .set_contents(value.into_owned()) - { - bail!("Couldn't set system primary clipboard content: {:?}", e); - } - - editor.set_status("yanked main selection to system primary clipboard".to_owned()); - Ok(()) + let _ = yank_joined_to_clipboard_impl( + &mut cx.editor, + line_ending.as_str(), + ClipboardType::Selection, + ); } fn yank_main_selection_to_primary_clipboard(cx: &mut Context) { - let _ = yank_main_selection_to_primary_clipboard_impl(&mut cx.editor); + let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection); } #[derive(Copy, Clone)] @@ -3388,12 +3366,16 @@ fn paste_impl( Some(transaction) } -fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { +fn paste_clipboard_impl( + editor: &mut Editor, + action: Paste, + clipboard_type: ClipboardType, +) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor .clipboard_provider - .get_contents() + .get_contents(clipboard_type) .map(|contents| paste_impl(&[contents], doc, view, action)) { Ok(Some(transaction)) => { @@ -3407,37 +3389,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<() } fn paste_clipboard_after(cx: &mut Context) { - let _ = paste_clipboard_impl(&mut cx.editor, Paste::After); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard); } fn paste_clipboard_before(cx: &mut Context) { - let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before); -} - -fn paste_primary_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { - let (view, doc) = current!(editor); - - match editor - .primary_clipboard_provider - .get_contents() - .map(|contents| paste_impl(&[contents], doc, view, action)) - { - Ok(Some(transaction)) => { - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view.id); - Ok(()) - } - Ok(None) => Ok(()), - Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), - } + let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Clipboard); } fn paste_primary_clipboard_after(cx: &mut Context) { - let _ = paste_primary_clipboard_impl(&mut cx.editor, Paste::After); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection); } fn paste_primary_clipboard_before(cx: &mut Context) { - let _ = paste_primary_clipboard_impl(&mut cx.editor, Paste::Before); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Selection); } fn replace_with_yanked(cx: &mut Context) { @@ -3462,10 +3426,13 @@ fn replace_with_yanked(cx: &mut Context) { } } -fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { +fn replace_selections_with_clipboard_impl( + editor: &mut Editor, + clipboard_type: ClipboardType, +) -> anyhow::Result<()> { let (view, doc) = current!(editor); - match editor.clipboard_provider.get_contents() { + match editor.clipboard_provider.get_contents(clipboard_type) { Ok(contents) => { let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { @@ -3481,29 +3448,11 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result } fn replace_selections_with_clipboard(cx: &mut Context) { - let _ = replace_selections_with_clipboard_impl(&mut cx.editor); -} - -fn replace_selections_with_primary_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { - let (view, doc) = current!(editor); - - match editor.primary_clipboard_provider.get_contents() { - Ok(contents) => { - let selection = doc.selection(view.id); - let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { - (range.from(), range.to(), Some(contents.as_str().into())) - }); - - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view.id); - Ok(()) - } - Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), - } + let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard); } fn replace_selections_with_primary_clipboard(cx: &mut Context) { - let _ = replace_selections_with_primary_clipboard_impl(&mut cx.editor); + let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Selection); } fn paste_after(cx: &mut Context) { diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index d79c87f5f759..6f5da76579a4 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -3,10 +3,15 @@ use anyhow::Result; use std::borrow::Cow; +pub enum ClipboardType { + Clipboard, + Selection, +} + pub trait ClipboardProvider: std::fmt::Debug { fn name(&self) -> Cow; - fn get_contents(&self) -> Result; - fn set_contents(&mut self, contents: String) -> Result<()>; + fn get_contents(&self, clipboard_type: ClipboardType) -> Result; + fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()>; } macro_rules! command_provider { @@ -20,6 +25,33 @@ macro_rules! command_provider { prg: $set_prg, args: &[ $( $set_arg ),* ], }, + get_primary_cmd: None, + set_primary_cmd: None, + selection_buf: String::new(), + }) + }}; +} + +macro_rules! command_provider_with_primary { + (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; ) => {{ + Box::new(provider::CommandProvider { + get_cmd: provider::CommandConfig { + prg: $get_prg, + args: &[ $( $get_arg ),* ], + }, + set_cmd: provider::CommandConfig { + prg: $set_prg, + args: &[ $( $set_arg ),* ], + }, + get_primary_cmd: Some(provider::CommandConfig { + prg: $pr_get_prg, + args: &[ $( $pr_get_arg ),* ], + }), + set_primary_cmd: Some(provider::CommandConfig { + prg: $pr_set_prg, + args: &[ $( $pr_set_arg ),* ], + }), + selection_buf: String::new(), }) }}; } @@ -34,21 +66,27 @@ pub fn get_clipboard_provider() -> Box { copy => "pbcopy"; } } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { - command_provider! { + command_provider_with_primary! { paste => "wl-paste", "--no-newline"; copy => "wl-copy", "--type", "text/plain"; + primary_paste => "wl-paste", "-p", "--no-newline"; + primary_copy => "wl-copy", "-p", "--type", "text/plain"; } } else if env_var_is_set("DISPLAY") && exists("xclip") { - command_provider! { + command_provider_with_primary! { paste => "xclip", "-o", "-selection", "clipboard"; copy => "xclip", "-i", "-selection", "clipboard"; + primary_paste => "xclip", "-o"; + primary_copy => "xclip", "-i"; } } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"]) { // FIXME: check performance of is_exit_success - command_provider! { + command_provider_with_primary! { paste => "xsel", "-o", "-b"; copy => "xsel", "--nodetach", "-i", "-b"; + primary_paste => "xsel", "-o"; + primary_copy => "xsel", "-i"; } } else if exists("lemonade") { command_provider! { @@ -78,35 +116,10 @@ pub fn get_clipboard_provider() -> Box { } } else { #[cfg(target_os = "windows")] - return Box::new(provider::WindowsProvider); + return Box::new(provider::WindowsProvider::new()); #[cfg(not(target_os = "windows"))] - return Box::new(provider::NopProvider { buf: String::new() }); - } -} - -pub fn get_primary_clipboard_provider() -> Box { - // TODO: support for user-defined provider, probably when we have plugin support by setting a - // variable? - - if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o"]) { - // FIXME: check performance of is_exit_success - command_provider! { - paste => "xsel", "-o"; - copy => "xsel", "-i"; - } - } else if env_var_is_set("DISPLAY") && exists("xclip") { - command_provider! { - paste => "xclip", "-o"; - copy => "xclip", "-i"; - } - } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { - command_provider! { - paste => "wl-paste", "-p", "--no-newline"; - copy => "wl-copy", "-p", "--type", "text/plain"; - } - } else { - Box::new(provider::NopProvider { buf: String::new() }) + return Box::new(provider::NopProvider::new()); } } @@ -128,13 +141,23 @@ fn is_exit_success(program: &str, args: &[&str]) -> bool { } mod provider { - use super::ClipboardProvider; + use super::{ClipboardProvider, ClipboardType}; use anyhow::{bail, Context as _, Result}; use std::borrow::Cow; #[derive(Debug)] pub struct NopProvider { - pub buf: String, + buf: String, + primary_buf: String, + } + + impl NopProvider { + pub fn new() -> Self { + Self { + buf: String::new(), + primary_buf: String::new(), + } + } } impl ClipboardProvider for NopProvider { @@ -142,19 +165,38 @@ mod provider { Cow::Borrowed("none") } - fn get_contents(&self) -> Result { - Ok(self.buf.clone()) + fn get_contents(&self, clipboard_type: ClipboardType) -> Result { + let value = match clipboard_type { + ClipboardType::Clipboard => self.buf.clone(), + ClipboardType::Selection => self.primary_buf.clone(), + }; + + Ok(value) } - fn set_contents(&mut self, content: String) -> Result<()> { - self.buf = content; + fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> { + match clipboard_type { + ClipboardType::Clipboard => self.buf = content, + ClipboardType::Selection => self.primary_buf = content, + } Ok(()) } } #[cfg(target_os = "windows")] #[derive(Debug)] - pub struct WindowsProvider; + pub struct WindowsProvider { + selection_buf: String, + } + + #[cfg(target_os = "windows")] + impl WindowsProvider { + pub fn new() -> Self { + Self { + selection_buf: String::new(), + } + } + } #[cfg(target_os = "windows")] impl ClipboardProvider for WindowsProvider { @@ -162,13 +204,23 @@ mod provider { Cow::Borrowed("clipboard-win") } - fn get_contents(&self) -> Result { - let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?; + fn get_contents(&self, clipboard_type: ClipboardType) -> Result { + let contents = match clipboard_type { + ClipboardType::Clipboard => { + clipboard_win::get_clipboard(clipboard_win::formats::Unicode)? + } + ClipboardType::Selection => self.selection_buf.clone(), + }; Ok(contents) } - fn set_contents(&mut self, contents: String) -> Result<()> { - clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?; + fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> { + match clipboard_type { + ClipboardType::Clipboard => { + clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)? + } + ClipboardType::Selection => self.selection_buf = contents, + } Ok(()) } } @@ -220,27 +272,52 @@ mod provider { pub struct CommandProvider { pub get_cmd: CommandConfig, pub set_cmd: CommandConfig, + pub get_primary_cmd: Option, + pub set_primary_cmd: Option, + pub selection_buf: String, } impl ClipboardProvider for CommandProvider { fn name(&self) -> Cow { - if self.get_cmd.prg != self.set_cmd.prg { + if let Some(get_primary) = &self.get_primary_cmd { + Cow::Owned(format!("{}/{}", self.get_cmd.prg, get_primary.prg)) + } else if self.get_cmd.prg != self.set_cmd.prg { Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg)) } else { Cow::Borrowed(self.get_cmd.prg) } } - fn get_contents(&self) -> Result { - let output = self - .get_cmd - .execute(None, true)? - .context("output is missing")?; - Ok(output) + fn get_contents(&self, clipboard_type: ClipboardType) -> Result { + match clipboard_type { + ClipboardType::Clipboard => { + return Ok(self + .get_cmd + .execute(None, true)? + .context("output is missing")?) + } + ClipboardType::Selection => { + if let Some(cmd) = &self.get_primary_cmd { + return Ok(cmd.execute(None, true)?.context("output is missing")?); + } + + return Ok(self.selection_buf.clone()); + } + }; } - fn set_contents(&mut self, value: String) -> Result<()> { - self.set_cmd.execute(Some(&value), false).map(|_| ()) + fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> { + return match clipboard_type { + ClipboardType::Clipboard => self.set_cmd.execute(Some(&value), false).map(|_| ()), + ClipboardType::Selection => { + if let Some(cmd) = &self.set_primary_cmd { + return cmd.execute(Some(&value), false).map(|_| ()); + } + + self.selection_buf = value; + return Ok(()); + } + }; } } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7d38813b2ba1..7e8548e73180 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,5 @@ use crate::{ - clipboard::{get_clipboard_provider, get_primary_clipboard_provider, ClipboardProvider}, + clipboard::{get_clipboard_provider, ClipboardProvider}, graphics::{CursorKind, Rect}, theme::{self, Theme}, tree::Tree, @@ -28,7 +28,6 @@ pub struct Editor { pub theme: Theme, pub language_servers: helix_lsp::Registry, pub clipboard_provider: Box, - pub primary_clipboard_provider: Box, pub syn_loader: Arc, pub theme_loader: Arc, @@ -66,7 +65,6 @@ impl Editor { theme_loader: themes, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), - primary_clipboard_provider: get_primary_clipboard_provider(), status_msg: None, } } From 7e4a85b474a9a684307ef28e6c99cee5c382564e Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 18:41:48 +0300 Subject: [PATCH 19/28] Tidy up code Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 6f5da76579a4..c7bee19adcda 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -290,24 +290,22 @@ mod provider { fn get_contents(&self, clipboard_type: ClipboardType) -> Result { match clipboard_type { - ClipboardType::Clipboard => { - return Ok(self - .get_cmd - .execute(None, true)? - .context("output is missing")?) - } + ClipboardType::Clipboard => Ok(self + .get_cmd + .execute(None, true)? + .context("output is missing")?), ClipboardType::Selection => { if let Some(cmd) = &self.get_primary_cmd { - return Ok(cmd.execute(None, true)?.context("output is missing")?); + return cmd.execute(None, true)?.context("output is missing"); } - return Ok(self.selection_buf.clone()); + Ok(self.selection_buf.clone()) } - }; + } } fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> { - return match clipboard_type { + match clipboard_type { ClipboardType::Clipboard => self.set_cmd.execute(Some(&value), false).map(|_| ()), ClipboardType::Selection => { if let Some(cmd) = &self.set_primary_cmd { @@ -315,9 +313,9 @@ mod provider { } self.selection_buf = value; - return Ok(()); + Ok(()) } - }; + } } } } From ada6b9db1a27567419bc54b1eadcc49cfd8881a1 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Thu, 5 Aug 2021 18:49:45 +0300 Subject: [PATCH 20/28] view: remove names for different clipboard/selection providers Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index c7bee19adcda..e1d5b9f0aefa 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -279,9 +279,7 @@ mod provider { impl ClipboardProvider for CommandProvider { fn name(&self) -> Cow { - if let Some(get_primary) = &self.get_primary_cmd { - Cow::Owned(format!("{}/{}", self.get_cmd.prg, get_primary.prg)) - } else if self.get_cmd.prg != self.set_cmd.prg { + if self.get_cmd.prg != self.set_cmd.prg { Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg)) } else { Cow::Borrowed(self.get_cmd.prg) From a530f881f9b94d4627b45a86c9734f0714833159 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sat, 7 Aug 2021 15:45:30 +0300 Subject: [PATCH 21/28] Update helix-view/src/clipboard.rs Co-authored-by: Gokul Soumya --- helix-view/src/clipboard.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index e1d5b9f0aefa..c74a861f8cc1 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -303,17 +303,19 @@ mod provider { } fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> { - match clipboard_type { - ClipboardType::Clipboard => self.set_cmd.execute(Some(&value), false).map(|_| ()), + let cmd = match clipboard_type { + ClipboardType::Clipboard => &self.set_cmd, ClipboardType::Selection => { if let Some(cmd) = &self.set_primary_cmd { - return cmd.execute(Some(&value), false).map(|_| ()); + cmd + } else { + self.selection_buf = value; + return Ok(()); } - - self.selection_buf = value; - Ok(()) } - } + }; + cmd.execute(Some(&value), false).map(|_| ()) } + } } From b363b0baeee7891b26000f5c2fb6295b6c109071 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sat, 7 Aug 2021 15:52:53 +0300 Subject: [PATCH 22/28] helix-view: tidy macros Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index c74a861f8cc1..28f82bf4413e 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -30,10 +30,12 @@ macro_rules! command_provider { selection_buf: String::new(), }) }}; -} -macro_rules! command_provider_with_primary { - (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; ) => {{ + (paste => $get_prg:literal $( , $get_arg:literal )* ; + copy => $set_prg:literal $( , $set_arg:literal )* ; + primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; + primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; + ) => {{ Box::new(provider::CommandProvider { get_cmd: provider::CommandConfig { prg: $get_prg, @@ -66,14 +68,14 @@ pub fn get_clipboard_provider() -> Box { copy => "pbcopy"; } } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") { - command_provider_with_primary! { + command_provider! { paste => "wl-paste", "--no-newline"; copy => "wl-copy", "--type", "text/plain"; primary_paste => "wl-paste", "-p", "--no-newline"; primary_copy => "wl-copy", "-p", "--type", "text/plain"; } } else if env_var_is_set("DISPLAY") && exists("xclip") { - command_provider_with_primary! { + command_provider! { paste => "xclip", "-o", "-selection", "clipboard"; copy => "xclip", "-i", "-selection", "clipboard"; primary_paste => "xclip", "-o"; @@ -82,7 +84,7 @@ pub fn get_clipboard_provider() -> Box { } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"]) { // FIXME: check performance of is_exit_success - command_provider_with_primary! { + command_provider! { paste => "xsel", "-o", "-b"; copy => "xsel", "--nodetach", "-i", "-b"; primary_paste => "xsel", "-o"; @@ -316,6 +318,5 @@ mod provider { }; cmd.execute(Some(&value), false).map(|_| ()) } - } } From 7e82895dec1953c289d6984f0b6329dc10058d03 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sat, 7 Aug 2021 16:22:28 +0300 Subject: [PATCH 23/28] helix-term: refactor paste-replace commands Signed-off-by: Dmitry Sharshakov --- helix-term/src/commands.rs | 56 ++++++++++++++------------------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b370a8cd6f1f..23eba4e05e56 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1757,33 +1757,6 @@ mod cmd { paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard) } - fn replace_selections_with_clipboard( - cx: &mut compositor::Context, - _args: &[&str], - _event: PromptEvent, - ) -> anyhow::Result<()> { - let (view, doc) = current!(cx.editor); - - match cx - .editor - .clipboard_provider - .get_contents(ClipboardType::Clipboard) - { - Ok(contents) => { - let selection = doc.selection(view.id); - let transaction = - Transaction::change_by_selection(doc.text(), selection, |range| { - (range.from(), range.to(), Some(contents.as_str().into())) - }); - - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view.id); - Ok(()) - } - Err(e) => Err(e.context("Couldn't get system clipboard contents")), - } - } - fn paste_primary_clipboard_after( cx: &mut compositor::Context, _args: &[&str], @@ -1800,18 +1773,13 @@ mod cmd { paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection) } - fn replace_selections_with_primary_clipboard( + fn replace_selections_with_clipboard_impl( cx: &mut compositor::Context, - _args: &[&str], - _event: PromptEvent, + clipboard_type: ClipboardType, ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - match cx - .editor - .clipboard_provider - .get_contents(ClipboardType::Selection) - { + match cx.editor.clipboard_provider.get_contents(clipboard_type) { Ok(contents) => { let selection = doc.selection(view.id); let transaction = @@ -1823,10 +1791,26 @@ mod cmd { doc.append_changes_to_history(view.id); Ok(()) } - Err(e) => Err(e.context("Couldn't get system primary clipboard contents")), + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } + fn replace_selections_with_clipboard( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard) + } + + fn replace_selections_with_primary_clipboard( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + replace_selections_with_clipboard_impl(cx, ClipboardType::Selection) + } + fn show_clipboard_provider( cx: &mut compositor::Context, _args: &[&str], From 92948353eb1e0982ad8223fff5ccee671994fae3 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 8 Aug 2021 13:42:24 +0300 Subject: [PATCH 24/28] helix-term: use new config for middle-click-paste Signed-off-by: Dmitry Sharshakov --- helix-term/src/application.rs | 5 +---- helix-term/src/ui/editor.rs | 13 +++++-------- helix-view/src/editor.rs | 5 ++++- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index bc569c5307e1..9cd9ee7e4fd9 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -87,10 +87,7 @@ impl Application { config.editor.clone(), ); - let editor_view = Box::new(ui::EditorView::new( - std::mem::take(&mut config.keys), - config.terminal.middle_click_paste, - )); + let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); compositor.push(editor_view); if !args.files.is_empty() { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 0919b5b4716a..7d8086e46ee2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -29,7 +29,6 @@ use tui::buffer::Buffer as Surface; pub struct EditorView { keymaps: Keymaps, - middle_click_paste: bool, on_next_key: Option>, last_insert: (commands::Command, Vec), completion: Option, @@ -41,15 +40,14 @@ pub const GUTTER_OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter impl Default for EditorView { fn default() -> Self { - Self::new(Keymaps::default(), true) + Self::new(Keymaps::default()) } } impl EditorView { - pub fn new(keymaps: Keymaps, middle_click_paste: bool) -> Self { + pub fn new(keymaps: Keymaps) -> Self { Self { keymaps, - middle_click_paste, on_next_key: None, last_insert: (commands::Command::normal_mode, Vec::new()), completion: None, @@ -863,7 +861,7 @@ impl Component for EditorView { kind: MouseEventKind::Up(MouseButton::Left), .. }) => { - if !self.middle_click_paste { + if !cx.editor.config.middle_click_paste { return EventResult::Ignored; } @@ -895,12 +893,11 @@ impl Component for EditorView { modifiers, .. }) => { - if !self.middle_click_paste { + let editor = &mut cx.editor; + if !editor.config.middle_click_paste { return EventResult::Ignored; } - let editor = &mut cx.editor; - if modifiers == crossterm::event::KeyModifiers::ALT { let mut cxt = commands::Context { selected_register: helix_view::RegisterSelection::default(), diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index e5ba0d512fdc..9102bf7480b5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -21,12 +21,14 @@ use helix_core::Position; use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "kebab-case", default)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, /// Mouse support. Defaults to true. pub mouse: bool, + /// Middle click paste support. Defaults to true + pub middle_click_paste: bool, } impl Default for Config { @@ -34,6 +36,7 @@ impl Default for Config { Self { scrolloff: 5, mouse: true, + middle_click_paste: true, } } } From 13c8a0c5e4e01aaa251dd37d5bede3c3e0f10388 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 8 Aug 2021 13:54:30 +0300 Subject: [PATCH 25/28] clipboard: remove memory fallback for command and windows providers Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 28f82bf4413e..f4d3f6eb4b16 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -27,7 +27,6 @@ macro_rules! command_provider { }, get_primary_cmd: None, set_primary_cmd: None, - selection_buf: String::new(), }) }}; @@ -53,7 +52,6 @@ macro_rules! command_provider { prg: $pr_set_prg, args: &[ $( $pr_set_arg ),* ], }), - selection_buf: String::new(), }) }}; } @@ -207,23 +205,29 @@ mod provider { } fn get_contents(&self, clipboard_type: ClipboardType) -> Result { - let contents = match clipboard_type { + match clipboard_type { ClipboardType::Clipboard => { - clipboard_win::get_clipboard(clipboard_win::formats::Unicode)? + clipboard_win::get_clipboard(clipboard_win::formats::Unicode) } - ClipboardType::Selection => self.selection_buf.clone(), + ClipboardType::Selection => Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Primary clipboard is not supported on Windows", + ) + .into()), }; - Ok(contents) } fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> { match clipboard_type { ClipboardType::Clipboard => { - clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)? + clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents) } - ClipboardType::Selection => self.selection_buf = contents, + ClipboardType::Selection => Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Primary clipboard is not supported on Windows", + ) + .into()), } - Ok(()) } } @@ -276,7 +280,6 @@ mod provider { pub set_cmd: CommandConfig, pub get_primary_cmd: Option, pub set_primary_cmd: Option, - pub selection_buf: String, } impl ClipboardProvider for CommandProvider { @@ -299,7 +302,11 @@ mod provider { return cmd.execute(None, true)?.context("output is missing"); } - Ok(self.selection_buf.clone()) + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "No command is set for primary clipboard", + ) + .into()) } } } @@ -311,8 +318,11 @@ mod provider { if let Some(cmd) = &self.set_primary_cmd { cmd } else { - self.selection_buf = value; - return Ok(()); + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "No command is set for primary clipboard", + ) + .into()); } } }; From cbd58f1a2d83a717d27da79dcd149988e55c4803 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 8 Aug 2021 14:15:38 +0300 Subject: [PATCH 26/28] clipboard-win: fix build Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index f4d3f6eb4b16..f6838e36de78 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -214,7 +214,7 @@ mod provider { "Primary clipboard is not supported on Windows", ) .into()), - }; + } } fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> { @@ -222,12 +222,15 @@ mod provider { ClipboardType::Clipboard => { clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents) } - ClipboardType::Selection => Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Primary clipboard is not supported on Windows", - ) - .into()), - } + ClipboardType::Selection => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Primary clipboard is not supported on Windows", + ) + .into()) + } + }; + Ok(()) } } From 79a79c5b7a6ce4fa420ee1c9e2697a58f9a91446 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 8 Aug 2021 14:27:03 +0300 Subject: [PATCH 27/28] clipboard: return empty string when primary clipboard is missing Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index f6838e36de78..1a158e7c101d 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -209,11 +209,7 @@ mod provider { ClipboardType::Clipboard => { clipboard_win::get_clipboard(clipboard_win::formats::Unicode) } - ClipboardType::Selection => Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Primary clipboard is not supported on Windows", - ) - .into()), + ClipboardType::Selection => String::new(), } } @@ -222,13 +218,7 @@ mod provider { ClipboardType::Clipboard => { clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents) } - ClipboardType::Selection => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Primary clipboard is not supported on Windows", - ) - .into()) - } + ClipboardType::Selection => {} }; Ok(()) } @@ -305,11 +295,7 @@ mod provider { return cmd.execute(None, true)?.context("output is missing"); } - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "No command is set for primary clipboard", - ) - .into()) + Ok(String::new()) } } } @@ -321,11 +307,7 @@ mod provider { if let Some(cmd) = &self.set_primary_cmd { cmd } else { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "No command is set for primary clipboard", - ) - .into()); + return Ok(()); } } }; From 5ba62411d8b27d13beefaa9aeeae3cb20c60e3ac Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sun, 8 Aug 2021 14:33:21 +0300 Subject: [PATCH 28/28] clipboard: fix errors in Windows build Signed-off-by: Dmitry Sharshakov --- helix-view/src/clipboard.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 1a158e7c101d..3778c8d4522e 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -207,16 +207,17 @@ mod provider { fn get_contents(&self, clipboard_type: ClipboardType) -> Result { match clipboard_type { ClipboardType::Clipboard => { - clipboard_win::get_clipboard(clipboard_win::formats::Unicode) + let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?; + Ok(contents) } - ClipboardType::Selection => String::new(), + ClipboardType::Selection => Ok(String::new()), } } fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> { match clipboard_type { ClipboardType::Clipboard => { - clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents) + clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents); } ClipboardType::Selection => {} };