on master help sign in
Support primary clipboard (#548)
7d51805Dmitry Sharshakov
helix-term/src/commands.rs+160−22
@@ -12,8 +12,8 @@ use helix_core::{
12};
12};
13
13
14use helix_view::{
14use helix_view::{
15− document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode, view::View, Document,
15+ clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode,
16− DocumentId, Editor, ViewId,
16+ view::View, Document, DocumentId, Editor, ViewId,
17};
17};
18
18
19use anyhow::{anyhow, bail, Context as _};
19use anyhow::{anyhow, bail, Context as _};
@@ -258,12 +258,17 @@ impl Command {
258 yank, "Yank selection",
258 yank, "Yank selection",
259 yank_joined_to_clipboard, "Join and yank selections to clipboard",
259 yank_joined_to_clipboard, "Join and yank selections to clipboard",
260 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
260 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
261+ yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
262+ yank_main_selection_to_primary_clipboard, "Yank main selection to primary clipboard",
261 replace_with_yanked, "Replace with yanked text",
263 replace_with_yanked, "Replace with yanked text",
262 replace_selections_with_clipboard, "Replace selections by clipboard content",
264 replace_selections_with_clipboard, "Replace selections by clipboard content",
265+ replace_selections_with_primary_clipboard, "Replace selections by primary clipboard content",
263 paste_after, "Paste after selection",
266 paste_after, "Paste after selection",
264 paste_before, "Paste before selection",
267 paste_before, "Paste before selection",
265 paste_clipboard_after, "Paste clipboard after selections",
268 paste_clipboard_after, "Paste clipboard after selections",
266 paste_clipboard_before, "Paste clipboard before selections",
269 paste_clipboard_before, "Paste clipboard before selections",
270+ paste_primary_clipboard_after, "Paste primary clipboard after selections",
271+ paste_primary_clipboard_before, "Paste primary clipboard before selections",
267 indent, "Indent selection",
272 indent, "Indent selection",
268 unindent, "Unindent selection",
273 unindent, "Unindent selection",
269 format_selections, "Format selection",
274 format_selections, "Format selection",
@@ -1705,7 +1710,7 @@ mod cmd {
1705 _args: &[&str],
1710 _args: &[&str],
1706 _event: PromptEvent,
1711 _event: PromptEvent,
1707 ) -> anyhow::Result<()> {
1712 ) -> anyhow::Result<()> {
1708− yank_main_selection_to_clipboard_impl(&mut cx.editor)
1713+ yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard)
1709 }
1714 }
1710
1715
1711 fn yank_joined_to_clipboard(
1716 fn yank_joined_to_clipboard(
@@ -1718,7 +1723,28 @@ mod cmd {
1718 .first()
1723 .first()
1719 .copied()
1724 .copied()
1720 .unwrap_or_else(|| doc.line_ending.as_str());
1725 .unwrap_or_else(|| doc.line_ending.as_str());
1721− yank_joined_to_clipboard_impl(&mut cx.editor, separator)
1726+ yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Clipboard)
1727+ }
1728+
1729+ fn yank_main_selection_to_primary_clipboard(
1730+ cx: &mut compositor::Context,
1731+ _args: &[&str],
1732+ _event: PromptEvent,
1733+ ) -> anyhow::Result<()> {
1734+ yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection)
1735+ }
1736+
1737+ fn yank_joined_to_primary_clipboard(
1738+ cx: &mut compositor::Context,
1739+ args: &[&str],
1740+ _event: PromptEvent,
1741+ ) -> anyhow::Result<()> {
1742+ let (_, doc) = current!(cx.editor);
1743+ let separator = args
1744+ .first()
1745+ .copied()
1746+ .unwrap_or_else(|| doc.line_ending.as_str());
1747+ yank_joined_to_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection)
1722 }
1748 }
1723
1749
1724 fn paste_clipboard_after(
1750 fn paste_clipboard_after(
@@ -1726,7 +1752,7 @@ mod cmd {
1726 _args: &[&str],
1752 _args: &[&str],
1727 _event: PromptEvent,
1753 _event: PromptEvent,
1728 ) -> anyhow::Result<()> {
1754 ) -> anyhow::Result<()> {
1729− paste_clipboard_impl(&mut cx.editor, Paste::After)
1755+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
1730 }
1756 }
1731
1757
1732 fn paste_clipboard_before(
1758 fn paste_clipboard_before(
@@ -1734,17 +1760,32 @@ mod cmd {
1734 _args: &[&str],
1760 _args: &[&str],
1735 _event: PromptEvent,
1761 _event: PromptEvent,
1736 ) -> anyhow::Result<()> {
1762 ) -> anyhow::Result<()> {
1737− paste_clipboard_impl(&mut cx.editor, Paste::After)
1763+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard)
1738 }
1764 }
1739
1765
1740− fn replace_selections_with_clipboard(
1766+ fn paste_primary_clipboard_after(
1741 cx: &mut compositor::Context,
1767 cx: &mut compositor::Context,
1742 _args: &[&str],
1768 _args: &[&str],
1743 _event: PromptEvent,
1769 _event: PromptEvent,
1770+ ) -> anyhow::Result<()> {
1771+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
1772+ }
1773+
1774+ fn paste_primary_clipboard_before(
1775+ cx: &mut compositor::Context,
1776+ _args: &[&str],
1777+ _event: PromptEvent,
1778+ ) -> anyhow::Result<()> {
1779+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
1780+ }
1781+
1782+ fn replace_selections_with_clipboard_impl(
1783+ cx: &mut compositor::Context,
1784+ clipboard_type: ClipboardType,
1744 ) -> anyhow::Result<()> {
1785 ) -> anyhow::Result<()> {
1745 let (view, doc) = current!(cx.editor);
1786 let (view, doc) = current!(cx.editor);
1746
1787
1747− match cx.editor.clipboard_provider.get_contents() {
1788+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
1748 Ok(contents) => {
1789 Ok(contents) => {
1749 let selection = doc.selection(view.id);
1790 let selection = doc.selection(view.id);
1750 let transaction =
1791 let transaction =
@@ -1760,13 +1801,29 @@ mod cmd {
1760 }
1801 }
1761 }
1802 }
1762
1803
1804+ fn replace_selections_with_clipboard(
1805+ cx: &mut compositor::Context,
1806+ _args: &[&str],
1807+ _event: PromptEvent,
1808+ ) -> anyhow::Result<()> {
1809+ replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1810+ }
1811+
1812+ fn replace_selections_with_primary_clipboard(
1813+ cx: &mut compositor::Context,
1814+ _args: &[&str],
1815+ _event: PromptEvent,
1816+ ) -> anyhow::Result<()> {
1817+ replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1818+ }
1819+
1763 fn show_clipboard_provider(
1820 fn show_clipboard_provider(
1764 cx: &mut compositor::Context,
1821 cx: &mut compositor::Context,
1765 _args: &[&str],
1822 _args: &[&str],
1766 _event: PromptEvent,
1823 _event: PromptEvent,
1767 ) -> anyhow::Result<()> {
1824 ) -> anyhow::Result<()> {
1768 cx.editor
1825 cx.editor
1769− .set_status(cx.editor.clipboard_provider.name().into());
1826+ .set_status(cx.editor.clipboard_provider.name().to_string());
1770 Ok(())
1827 Ok(())
1771 }
1828 }
1772
1829
@@ -1967,6 +2024,20 @@ mod cmd {
1967 fun: yank_joined_to_clipboard,
2024 fun: yank_joined_to_clipboard,
1968 completer: None,
2025 completer: None,
1969 },
2026 },
2027+ TypableCommand {
2028+ name: "primary-clipboard-yank",
2029+ alias: None,
2030+ doc: "Yank main selection into system primary clipboard.",
2031+ fun: yank_main_selection_to_primary_clipboard,
2032+ completer: None,
2033+ },
2034+ TypableCommand {
2035+ name: "primary-clipboard-yank-join",
2036+ alias: None,
2037+ 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.
2038+ fun: yank_joined_to_primary_clipboard,
2039+ completer: None,
2040+ },
1970 TypableCommand {
2041 TypableCommand {
1971 name: "clipboard-paste-after",
2042 name: "clipboard-paste-after",
1972 alias: None,
2043 alias: None,
@@ -1988,6 +2059,27 @@ mod cmd {
1988 fun: replace_selections_with_clipboard,
2059 fun: replace_selections_with_clipboard,
1989 completer: None,
2060 completer: None,
1990 },
2061 },
2062+ TypableCommand {
2063+ name: "primary-clipboard-paste-after",
2064+ alias: None,
2065+ doc: "Paste primary clipboard after selections.",
2066+ fun: paste_primary_clipboard_after,
2067+ completer: None,
2068+ },
2069+ TypableCommand {
2070+ name: "primary-clipboard-paste-before",
2071+ alias: None,
2072+ doc: "Paste primary clipboard before selections.",
2073+ fun: paste_primary_clipboard_before,
2074+ completer: None,
2075+ },
2076+ TypableCommand {
2077+ name: "primary-clipboard-paste-replace",
2078+ alias: None,
2079+ doc: "Replace selections with content of system primary clipboard.",
2080+ fun: replace_selections_with_primary_clipboard,
2081+ completer: None,
2082+ },
1991 TypableCommand {
2083 TypableCommand {
1992 name: "show-clipboard-provider",
2084 name: "show-clipboard-provider",
1993 alias: None,
2085 alias: None,
@@ -3209,7 +3301,11 @@ fn yank(cx: &mut Context) {
3209 exit_select_mode(cx);
3301 exit_select_mode(cx);
3210}
3302}
3211
3303
3212−fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> {
3304+fn yank_joined_to_clipboard_impl(
3305+ editor: &mut Editor,
3306+ separator: &str,
3307+ clipboard_type: ClipboardType,
3308+) -> anyhow::Result<()> {
3213 let (view, doc) = current!(editor);
3309 let (view, doc) = current!(editor);
3214 let text = doc.text().slice(..);
3310 let text = doc.text().slice(..);
3215
3311
@@ -3228,7 +3324,7 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow
3228
3324
3229 editor
3325 editor
3230 .clipboard_provider
3326 .clipboard_provider
3231− .set_contents(joined)
3327+ .set_contents(joined, clipboard_type)
3232 .context("Couldn't set system clipboard content")?;
3328 .context("Couldn't set system clipboard content")?;
3233
3329
3234 editor.set_status(msg);
3330 editor.set_status(msg);
@@ -3238,17 +3334,27 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow
3238
3334
3239fn yank_joined_to_clipboard(cx: &mut Context) {
3335fn yank_joined_to_clipboard(cx: &mut Context) {
3240 let line_ending = current!(cx.editor).1.line_ending;
3336 let line_ending = current!(cx.editor).1.line_ending;
3241− let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str());
3337+ let _ = yank_joined_to_clipboard_impl(
3338+ &mut cx.editor,
3339+ line_ending.as_str(),
3340+ ClipboardType::Clipboard,
3341+ );
3242 exit_select_mode(cx);
3342 exit_select_mode(cx);
3243}
3343}
3244
3344
3245−fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
3345+fn yank_main_selection_to_clipboard_impl(
3346+ editor: &mut Editor,
3347+ clipboard_type: ClipboardType,
3348+) -> anyhow::Result<()> {
3246 let (view, doc) = current!(editor);
3349 let (view, doc) = current!(editor);
3247 let text = doc.text().slice(..);
3350 let text = doc.text().slice(..);
3248
3351
3249 let value = doc.selection(view.id).primary().fragment(text);
3352 let value = doc.selection(view.id).primary().fragment(text);
3250
3353
3354+ if let Err(e) = editor
3355+ .clipboard_provider
3251− if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
3356+ .set_contents(value.into_owned(), clipboard_type)
3357+ {
3252 bail!("Couldn't set system clipboard content: {:?}", e);
3358 bail!("Couldn't set system clipboard content: {:?}", e);
3253 }
3359 }
3254
3360
@@ -3257,7 +3363,20 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<
3257}
3363}
3258
3364
3259fn yank_main_selection_to_clipboard(cx: &mut Context) {
3365fn yank_main_selection_to_clipboard(cx: &mut Context) {
3260− let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor);
3366+ let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
3367+}
3368+
3369+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3370+ let line_ending = current!(cx.editor).1.line_ending;
3371+ let _ = yank_joined_to_clipboard_impl(
3372+ &mut cx.editor,
3373+ line_ending.as_str(),
3374+ ClipboardType::Selection,
3375+ );
3376+}
3377+
3378+fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3379+ let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
3261 exit_select_mode(cx);
3380 exit_select_mode(cx);
3262}
3381}
3263
3382
@@ -3310,12 +3429,16 @@ fn paste_impl(
3310 Some(transaction)
3429 Some(transaction)
3311}
3430}
3312
3431
3313−fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> {
3432+fn paste_clipboard_impl(
3433+ editor: &mut Editor,
3434+ action: Paste,
3435+ clipboard_type: ClipboardType,
3436+) -> anyhow::Result<()> {
3314 let (view, doc) = current!(editor);
3437 let (view, doc) = current!(editor);
3315
3438
3316 match editor
3439 match editor
3317 .clipboard_provider
3440 .clipboard_provider
3318− .get_contents()
3441+ .get_contents(clipboard_type)
3319 .map(|contents| paste_impl(&[contents], doc, view, action))
3442 .map(|contents| paste_impl(&[contents], doc, view, action))
3320 {
3443 {
3321 Ok(Some(transaction)) => {
3444 Ok(Some(transaction)) => {
@@ -3329,11 +3452,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()
3329}
3452}
3330
3453
3331fn paste_clipboard_after(cx: &mut Context) {
3454fn paste_clipboard_after(cx: &mut Context) {
3332− let _ = paste_clipboard_impl(&mut cx.editor, Paste::After);
3455+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Clipboard);
3333}
3456}
3334
3457
3335fn paste_clipboard_before(cx: &mut Context) {
3458fn paste_clipboard_before(cx: &mut Context) {
3336− let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before);
3459+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Clipboard);
3460+}
3461+
3462+fn paste_primary_clipboard_after(cx: &mut Context) {
3463+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection);
3464+}
3465+
3466+fn paste_primary_clipboard_before(cx: &mut Context) {
3467+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Selection);
3337}
3468}
3338
3469
3339fn replace_with_yanked(cx: &mut Context) {
3470fn replace_with_yanked(cx: &mut Context) {
@@ -3358,10 +3489,13 @@ fn replace_with_yanked(cx: &mut Context) {
3358 }
3489 }
3359}
3490}
3360
3491
3361−fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
3492+fn replace_selections_with_clipboard_impl(
3493+ editor: &mut Editor,
3494+ clipboard_type: ClipboardType,
3495+) -> anyhow::Result<()> {
3362 let (view, doc) = current!(editor);
3496 let (view, doc) = current!(editor);
3363
3497
3364− match editor.clipboard_provider.get_contents() {
3498+ match editor.clipboard_provider.get_contents(clipboard_type) {
3365 Ok(contents) => {
3499 Ok(contents) => {
3366 let selection = doc.selection(view.id);
3500 let selection = doc.selection(view.id);
3367 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3501 let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
@@ -3377,7 +3511,11 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result
3377}
3511}
3378
3512
3379fn replace_selections_with_clipboard(cx: &mut Context) {
3513fn replace_selections_with_clipboard(cx: &mut Context) {
3380− let _ = replace_selections_with_clipboard_impl(&mut cx.editor);
3514+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
3515+}
3516+
3517+fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3518+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
3381}
3519}
3382
3520
3383fn paste_after(cx: &mut Context) {
3521fn paste_after(cx: &mut Context) {
helix-term/src/ui/editor.rs+55
@@ -785,6 +785,61 @@ impl EditorView {
785
785
786 EventResult::Consumed(None)
786 EventResult::Consumed(None)
787 }
787 }
788+
789+ MouseEvent {
790+ kind: MouseEventKind::Up(MouseButton::Left),
791+ ..
792+ } => {
793+ if !cxt.editor.config.middle_click_paste {
794+ return EventResult::Ignored;
795+ }
796+
797+ let (view, doc) = current!(cxt.editor);
798+ let range = doc.selection(view.id).primary();
799+
800+ if range.to() - range.from() <= 1 {
801+ return EventResult::Ignored;
802+ }
803+
804+ commands::Command::yank_main_selection_to_primary_clipboard.execute(cxt);
805+
806+ EventResult::Consumed(None)
807+ }
808+
809+ MouseEvent {
810+ kind: MouseEventKind::Up(MouseButton::Middle),
811+ row,
812+ column,
813+ modifiers,
814+ ..
815+ } => {
816+ let editor = &mut cxt.editor;
817+ if !editor.config.middle_click_paste {
818+ return EventResult::Ignored;
819+ }
820+
821+ if modifiers == crossterm::event::KeyModifiers::ALT {
822+ commands::Command::replace_selections_with_primary_clipboard.execute(cxt);
823+
824+ return EventResult::Consumed(None);
825+ }
826+
827+ let result = editor.tree.views().find_map(|(view, _focus)| {
828+ view.pos_at_screen_coords(&editor.documents[view.doc], row, column)
829+ .map(|pos| (pos, view.id))
830+ });
831+
832+ if let Some((pos, view_id)) = result {
833+ let doc = &mut editor.documents[editor.tree.get(view_id).doc];
834+ doc.set_selection(view_id, Selection::point(pos));
835+ editor.tree.focus = view_id;
836+ commands::Command::paste_primary_clipboard_before.execute(cxt);
837+ return EventResult::Consumed(None);
838+ }
839+
840+ EventResult::Ignored
841+ }
842+
788 _ => EventResult::Ignored,
843 _ => EventResult::Ignored,
789 }
844 }
790 }
845 }
helix-view/src/clipboard.rs+123−23
@@ -3,10 +3,15 @@
3use anyhow::Result;
3use anyhow::Result;
4use std::borrow::Cow;
4use std::borrow::Cow;
5
5
6+pub enum ClipboardType {
7+ Clipboard,
8+ Selection,
9+}
10+
6pub trait ClipboardProvider: std::fmt::Debug {
11pub trait ClipboardProvider: std::fmt::Debug {
7 fn name(&self) -> Cow<str>;
12 fn name(&self) -> Cow<str>;
8− fn get_contents(&self) -> Result<String>;
13+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String>;
9− fn set_contents(&self, contents: String) -> Result<()>;
14+ fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()>;
10}
15}
11
16
12macro_rules! command_provider {
17macro_rules! command_provider {
@@ -20,6 +25,33 @@ macro_rules! command_provider {
20 prg: $set_prg,
25 prg: $set_prg,
21 args: &[ $( $set_arg ),* ],
26 args: &[ $( $set_arg ),* ],
22 },
27 },
28+ get_primary_cmd: None,
29+ set_primary_cmd: None,
30+ })
31+ }};
32+
33+ (paste => $get_prg:literal $( , $get_arg:literal )* ;
34+ copy => $set_prg:literal $( , $set_arg:literal )* ;
35+ primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
36+ primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
37+ ) => {{
38+ Box::new(provider::CommandProvider {
39+ get_cmd: provider::CommandConfig {
40+ prg: $get_prg,
41+ args: &[ $( $get_arg ),* ],
42+ },
43+ set_cmd: provider::CommandConfig {
44+ prg: $set_prg,
45+ args: &[ $( $set_arg ),* ],
46+ },
47+ get_primary_cmd: Some(provider::CommandConfig {
48+ prg: $pr_get_prg,
49+ args: &[ $( $pr_get_arg ),* ],
50+ }),
51+ set_primary_cmd: Some(provider::CommandConfig {
52+ prg: $pr_set_prg,
53+ args: &[ $( $pr_set_arg ),* ],
54+ }),
23 })
55 })
24 }};
56 }};
25}
57}
@@ -37,11 +69,15 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
37 command_provider! {
69 command_provider! {
38 paste => "wl-paste", "--no-newline";
70 paste => "wl-paste", "--no-newline";
39 copy => "wl-copy", "--type", "text/plain";
71 copy => "wl-copy", "--type", "text/plain";
72+ primary_paste => "wl-paste", "-p", "--no-newline";
73+ primary_copy => "wl-copy", "-p", "--type", "text/plain";
40 }
74 }
41 } else if env_var_is_set("DISPLAY") && exists("xclip") {
75 } else if env_var_is_set("DISPLAY") && exists("xclip") {
42 command_provider! {
76 command_provider! {
43 paste => "xclip", "-o", "-selection", "clipboard";
77 paste => "xclip", "-o", "-selection", "clipboard";
44 copy => "xclip", "-i", "-selection", "clipboard";
78 copy => "xclip", "-i", "-selection", "clipboard";
79+ primary_paste => "xclip", "-o";
80+ primary_copy => "xclip", "-i";
45 }
81 }
46 } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
82 } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
47 {
83 {
@@ -49,6 +85,8 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
49 command_provider! {
85 command_provider! {
50 paste => "xsel", "-o", "-b";
86 paste => "xsel", "-o", "-b";
51 copy => "xsel", "--nodetach", "-i", "-b";
87 copy => "xsel", "--nodetach", "-i", "-b";
88+ primary_paste => "xsel", "-o";
89+ primary_copy => "xsel", "-i";
52 }
90 }
53 } else if exists("lemonade") {
91 } else if exists("lemonade") {
54 command_provider! {
92 command_provider! {
@@ -78,10 +116,10 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
78 }
116 }
79 } else {
117 } else {
80 #[cfg(target_os = "windows")]
118 #[cfg(target_os = "windows")]
81− return Box::new(provider::WindowsProvider);
119+ return Box::new(provider::WindowsProvider::new());
82
120
83 #[cfg(not(target_os = "windows"))]
121 #[cfg(not(target_os = "windows"))]
84− return Box::new(provider::NopProvider);
122+ return Box::new(provider::NopProvider::new());
85 }
123 }
86}
124}
87
125
@@ -103,30 +141,62 @@ fn is_exit_success(program: &str, args: &[&str]) -> bool {
103}
141}
104
142
105mod provider {
143mod provider {
106− use super::ClipboardProvider;
144+ use super::{ClipboardProvider, ClipboardType};
107 use anyhow::{bail, Context as _, Result};
145 use anyhow::{bail, Context as _, Result};
108 use std::borrow::Cow;
146 use std::borrow::Cow;
109
147
110 #[derive(Debug)]
148 #[derive(Debug)]
111− pub struct NopProvider;
149+ pub struct NopProvider {
150+ buf: String,
151+ primary_buf: String,
152+ }
153+
154+ impl NopProvider {
155+ pub fn new() -> Self {
156+ Self {
157+ buf: String::new(),
158+ primary_buf: String::new(),
159+ }
160+ }
161+ }
112
162
113 impl ClipboardProvider for NopProvider {
163 impl ClipboardProvider for NopProvider {
114 fn name(&self) -> Cow<str> {
164 fn name(&self) -> Cow<str> {
115 Cow::Borrowed("none")
165 Cow::Borrowed("none")
116 }
166 }
117
167
118− fn get_contents(&self) -> Result<String> {
168+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
119− Ok(String::new())
169+ let value = match clipboard_type {
170+ ClipboardType::Clipboard => self.buf.clone(),
171+ ClipboardType::Selection => self.primary_buf.clone(),
172+ };
173+
174+ Ok(value)
120 }
175 }
121
176
122− fn set_contents(&self, _: String) -> Result<()> {
177+ fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
178+ match clipboard_type {
179+ ClipboardType::Clipboard => self.buf = content,
180+ ClipboardType::Selection => self.primary_buf = content,
181+ }
123 Ok(())
182 Ok(())
124 }
183 }
125 }
184 }
126
185
127 #[cfg(target_os = "windows")]
186 #[cfg(target_os = "windows")]
128 #[derive(Debug)]
187 #[derive(Debug)]
129− pub struct WindowsProvider;
188+ pub struct WindowsProvider {
189+ selection_buf: String,
190+ }
191+
192+ #[cfg(target_os = "windows")]
193+ impl WindowsProvider {
194+ pub fn new() -> Self {
195+ Self {
196+ selection_buf: String::new(),
197+ }
198+ }
199+ }
130
200
131 #[cfg(target_os = "windows")]
201 #[cfg(target_os = "windows")]
132 impl ClipboardProvider for WindowsProvider {
202 impl ClipboardProvider for WindowsProvider {
@@ -134,13 +204,23 @@ mod provider {
134 Cow::Borrowed("clipboard-win")
204 Cow::Borrowed("clipboard-win")
135 }
205 }
136
206
137− fn get_contents(&self) -> Result<String> {
207+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
208+ match clipboard_type {
209+ ClipboardType::Clipboard => {
138− let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
210+ let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
139− Ok(contents)
211+ Ok(contents)
212+ }
213+ ClipboardType::Selection => Ok(String::new()),
214+ }
140 }
215 }
141
216
142− fn set_contents(&self, contents: String) -> Result<()> {
217+ fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> {
218+ match clipboard_type {
219+ ClipboardType::Clipboard => {
143− clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?;
220+ clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents);
221+ }
222+ ClipboardType::Selection => {}
223+ };
144 Ok(())
224 Ok(())
145 }
225 }
146 }
226 }
@@ -192,6 +272,8 @@ mod provider {
192 pub struct CommandProvider {
272 pub struct CommandProvider {
193 pub get_cmd: CommandConfig,
273 pub get_cmd: CommandConfig,
194 pub set_cmd: CommandConfig,
274 pub set_cmd: CommandConfig,
275+ pub get_primary_cmd: Option<CommandConfig>,
276+ pub set_primary_cmd: Option<CommandConfig>,
195 }
277 }
196
278
197 impl ClipboardProvider for CommandProvider {
279 impl ClipboardProvider for CommandProvider {
@@ -203,16 +285,34 @@ mod provider {
203 }
285 }
204 }
286 }
205
287
206− fn get_contents(&self) -> Result<String> {
288+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
207− let output = self
289+ match clipboard_type {
208− .get_cmd
290+ ClipboardType::Clipboard => Ok(self
209− .execute(None, true)?
291+ .get_cmd
292+ .execute(None, true)?
293+ .context("output is missing")?),
294+ ClipboardType::Selection => {
295+ if let Some(cmd) = &self.get_primary_cmd {
210− .context("output is missing")?;
296+ return cmd.execute(None, true)?.context("output is missing");
211− Ok(output)
297+ }
298+
299+ Ok(String::new())
300+ }
301+ }
212 }
302 }
213
303
214− fn set_contents(&self, value: String) -> Result<()> {
304+ fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> {
305+ let cmd = match clipboard_type {
306+ ClipboardType::Clipboard => &self.set_cmd,
307+ ClipboardType::Selection => {
308+ if let Some(cmd) = &self.set_primary_cmd {
309+ cmd
310+ } else {
311+ return Ok(());
312+ }
313+ }
314+ };
215− self.set_cmd.execute(Some(&value), false).map(|_| ())
315+ cmd.execute(Some(&value), false).map(|_| ())
216 }
316 }
217 }
317 }
218}
318}
helix-view/src/editor.rs+3
@@ -29,6 +29,8 @@ pub struct Config {
29 pub scroll_lines: isize,
29 pub scroll_lines: isize,
30 /// Mouse support. Defaults to true.
30 /// Mouse support. Defaults to true.
31 pub mouse: bool,
31 pub mouse: bool,
32+ /// Middle click paste support. Defaults to true
33+ pub middle_click_paste: bool,
32}
34}
33
35
34impl Default for Config {
36impl Default for Config {
@@ -37,6 +39,7 @@ impl Default for Config {
37 scrolloff: 5,
39 scrolloff: 5,
38 scroll_lines: 3,
40 scroll_lines: 3,
39 mouse: true,
41 mouse: true,
42+ middle_click_paste: true,
40 }
43 }
41 }
44 }
42}
45}