aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/commands.rs182
-rw-r--r--helix-term/src/ui/editor.rs55
-rw-r--r--helix-view/src/clipboard.rs146
-rw-r--r--helix-view/src/editor.rs3
4 files changed, 341 insertions, 45 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 0acb57e2..2b1b859b 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -12,8 +12,8 @@ use helix_core::{
};
use helix_view::{
- document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode, view::View, Document,
- DocumentId, Editor, ViewId,
+ clipboard::ClipboardType, document::Mode, editor::Action, input::KeyEvent, keyboard::KeyCode,
+ view::View, Document, DocumentId, Editor, ViewId,
};
use anyhow::{anyhow, bail, Context as _};
@@ -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_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_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_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",
@@ -1705,7 +1710,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(
@@ -1718,7 +1723,28 @@ 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(
+ cx: &mut compositor::Context,
+ _args: &[&str],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ yank_main_selection_to_clipboard_impl(&mut cx.editor, ClipboardType::Selection)
+ }
+
+ fn yank_joined_to_primary_clipboard(
+ 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_clipboard_impl(&mut cx.editor, separator, ClipboardType::Selection)
}
fn paste_clipboard_after(
@@ -1726,7 +1752,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 paste_clipboard_before(
@@ -1734,17 +1760,32 @@ 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(
+ fn paste_primary_clipboard_after(
cx: &mut compositor::Context,
_args: &[&str],
_event: PromptEvent,
) -> anyhow::Result<()> {
+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
+ }
+
+ fn paste_primary_clipboard_before(
+ cx: &mut compositor::Context,
+ _args: &[&str],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection)
+ }
+
+ fn replace_selections_with_clipboard_impl(
+ cx: &mut compositor::Context,
+ clipboard_type: ClipboardType,
+ ) -> anyhow::Result<()> {
let (view, doc) = current!(cx.editor);
- match cx.editor.clipboard_provider.get_contents() {
+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
Ok(contents) => {
let selection = doc.selection(view.id);
let transaction =
@@ -1760,13 +1801,29 @@ mod cmd {
}
}
+ 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],
_event: PromptEvent,
) -> anyhow::Result<()> {
cx.editor
- .set_status(cx.editor.clipboard_provider.name().into());
+ .set_status(cx.editor.clipboard_provider.name().to_string());
Ok(())
}
@@ -1968,6 +2025,20 @@ mod cmd {
completer: None,
},
TypableCommand {
+ name: "primary-clipboard-yank",
+ alias: None,
+ doc: "Yank main selection into system primary clipboard.",
+ fun: yank_main_selection_to_primary_clipboard,
+ completer: None,
+ },
+ TypableCommand {
+ name: "primary-clipboard-yank-join",
+ alias: None,
+ 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 {
name: "clipboard-paste-after",
alias: None,
doc: "Paste system clipboard after selections.",
@@ -1989,6 +2060,27 @@ mod cmd {
completer: None,
},
TypableCommand {
+ name: "primary-clipboard-paste-after",
+ alias: None,
+ doc: "Paste primary clipboard after selections.",
+ fun: paste_primary_clipboard_after,
+ completer: None,
+ },
+ TypableCommand {
+ name: "primary-clipboard-paste-before",
+ alias: None,
+ doc: "Paste primary clipboard before selections.",
+ fun: paste_primary_clipboard_before,
+ completer: None,
+ },
+ TypableCommand {
+ name: "primary-clipboard-paste-replace",
+ alias: None,
+ doc: "Replace selections with content of system primary clipboard.",
+ fun: replace_selections_with_primary_clipboard,
+ completer: None,
+ },
+ TypableCommand {
name: "show-clipboard-provider",
alias: None,
doc: "Show clipboard provider name in status bar.",
@@ -3209,7 +3301,11 @@ fn yank(cx: &mut Context) {
exit_select_mode(cx);
}
-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(..);
@@ -3228,7 +3324,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);
@@ -3238,17 +3334,27 @@ 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,
+ );
exit_select_mode(cx);
}
-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);
}
@@ -3257,7 +3363,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);
+ 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_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_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
exit_select_mode(cx);
}
@@ -3310,12 +3429,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)) => {
@@ -3329,11 +3452,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);
+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Clipboard);
+}
+
+fn paste_primary_clipboard_after(cx: &mut Context) {
+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::After, ClipboardType::Selection);
+}
+
+fn paste_primary_clipboard_before(cx: &mut Context) {
+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before, ClipboardType::Selection);
}
fn replace_with_yanked(cx: &mut Context) {
@@ -3358,10 +3489,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| {
@@ -3377,7 +3511,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);
+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Clipboard);
+}
+
+fn replace_selections_with_primary_clipboard(cx: &mut Context) {
+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor, ClipboardType::Selection);
}
fn paste_after(cx: &mut Context) {
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index aa21a389..96a4afe8 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -785,6 +785,61 @@ impl EditorView {
EventResult::Consumed(None)
}
+
+ MouseEvent {
+ kind: MouseEventKind::Up(MouseButton::Left),
+ ..
+ } => {
+ if !cxt.editor.config.middle_click_paste {
+ return EventResult::Ignored;
+ }
+
+ let (view, doc) = current!(cxt.editor);
+ let range = doc.selection(view.id).primary();
+
+ if range.to() - range.from() <= 1 {
+ return EventResult::Ignored;
+ }
+
+ commands::Command::yank_main_selection_to_primary_clipboard.execute(cxt);
+
+ EventResult::Consumed(None)
+ }
+
+ MouseEvent {
+ kind: MouseEventKind::Up(MouseButton::Middle),
+ row,
+ column,
+ modifiers,
+ ..
+ } => {
+ let editor = &mut cxt.editor;
+ if !editor.config.middle_click_paste {
+ return EventResult::Ignored;
+ }
+
+ if modifiers == crossterm::event::KeyModifiers::ALT {
+ commands::Command::replace_selections_with_primary_clipboard.execute(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))
+ });
+
+ 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;
+ commands::Command::paste_primary_clipboard_before.execute(cxt);
+ return EventResult::Consumed(None);
+ }
+
+ EventResult::Ignored
+ }
+
_ => EventResult::Ignored,
}
}
diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs
index 401c0459..3778c8d4 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<str>;
- fn get_contents(&self) -> Result<String>;
- fn set_contents(&self, contents: String) -> Result<()>;
+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String>;
+ 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,
+ })
+ }};
+
+ (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 ),* ],
+ }),
})
}};
}
@@ -37,11 +69,15 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
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! {
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"])
{
@@ -49,6 +85,8 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
command_provider! {
paste => "xsel", "-o", "-b";
copy => "xsel", "--nodetach", "-i", "-b";
+ primary_paste => "xsel", "-o";
+ primary_copy => "xsel", "-i";
}
} else if exists("lemonade") {
command_provider! {
@@ -78,10 +116,10 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
}
} 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);
+ return Box::new(provider::NopProvider::new());
}
}
@@ -103,30 +141,62 @@ 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 struct NopProvider {
+ buf: String,
+ primary_buf: String,
+ }
+
+ impl NopProvider {
+ pub fn new() -> Self {
+ Self {
+ buf: String::new(),
+ primary_buf: String::new(),
+ }
+ }
+ }
impl ClipboardProvider for NopProvider {
fn name(&self) -> Cow<str> {
Cow::Borrowed("none")
}
- fn get_contents(&self) -> Result<String> {
- Ok(String::new())
+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
+ let value = match clipboard_type {
+ ClipboardType::Clipboard => self.buf.clone(),
+ ClipboardType::Selection => self.primary_buf.clone(),
+ };
+
+ Ok(value)
}
- fn set_contents(&self, _: String) -> Result<()> {
+ 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 {
@@ -134,13 +204,23 @@ mod provider {
Cow::Borrowed("clipboard-win")
}
- fn get_contents(&self) -> Result<String> {
- let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
- Ok(contents)
+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
+ match clipboard_type {
+ ClipboardType::Clipboard => {
+ let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
+ Ok(contents)
+ }
+ ClipboardType::Selection => Ok(String::new()),
+ }
}
- fn set_contents(&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 => {}
+ };
Ok(())
}
}
@@ -192,6 +272,8 @@ mod provider {
pub struct CommandProvider {
pub get_cmd: CommandConfig,
pub set_cmd: CommandConfig,
+ pub get_primary_cmd: Option<CommandConfig>,
+ pub set_primary_cmd: Option<CommandConfig>,
}
impl ClipboardProvider for CommandProvider {
@@ -203,16 +285,34 @@ mod provider {
}
}
- fn get_contents(&self) -> Result<String> {
- let output = self
- .get_cmd
- .execute(None, true)?
- .context("output is missing")?;
- Ok(output)
+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
+ match clipboard_type {
+ ClipboardType::Clipboard => Ok(self
+ .get_cmd
+ .execute(None, true)?
+ .context("output is missing")?),
+ ClipboardType::Selection => {
+ if let Some(cmd) = &self.get_primary_cmd {
+ return cmd.execute(None, true)?.context("output is missing");
+ }
+
+ Ok(String::new())
+ }
+ }
}
- fn set_contents(&self, value: String) -> Result<()> {
- self.set_cmd.execute(Some(&value), false).map(|_| ())
+ fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> {
+ let cmd = match clipboard_type {
+ ClipboardType::Clipboard => &self.set_cmd,
+ ClipboardType::Selection => {
+ if let Some(cmd) = &self.set_primary_cmd {
+ cmd
+ } else {
+ return Ok(());
+ }
+ }
+ };
+ cmd.execute(Some(&value), false).map(|_| ())
}
}
}
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index ec3cedd6..9b7f8429 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -29,6 +29,8 @@ pub struct Config {
pub scroll_lines: isize,
/// Mouse support. Defaults to true.
pub mouse: bool,
+ /// Middle click paste support. Defaults to true
+ pub middle_click_paste: bool,
}
impl Default for Config {
@@ -37,6 +39,7 @@ impl Default for Config {
scrolloff: 5,
scroll_lines: 3,
mouse: true,
+ middle_click_paste: true,
}
}
}