Skip to content

Commit

Permalink
Support primary clipboard (#548)
Browse files Browse the repository at this point in the history
* clipboard-none: add in-memory fallback buffer

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* view: add Wayland primary clipboard

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* Format

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: copy to primary selection after mouse move stops

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: don't update primary selection if it is a single character

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: discard result of setting primary selection

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: add commands for interaction with primary clipboard

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* editor: implement primary selection copy/paste using commands

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard: support xsel for primary selection

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard: support xclip for primary selection

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: multiple cursor support for middle click paste

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* rename primary selection to primary clipboard in scope of PR

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: make middle click paste optional

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* Format

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* Update helix-term/src/ui/editor.rs

* fix formatting

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* config: correct defaults if terminal prop is not set

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* refactor: merge clipboard and primary selection implementations

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* Tidy up code

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* view: remove names for different clipboard/selection providers

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* Update helix-view/src/clipboard.rs

Co-authored-by: Gokul Soumya <gokulps15@gmail.com>

* helix-view: tidy macros

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: refactor paste-replace commands

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* helix-term: use new config for middle-click-paste

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard: remove memory fallback for command and windows providers

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard-win: fix build

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard: return empty string when primary clipboard is missing

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

* clipboard: fix errors in Windows build

Signed-off-by: Dmitry Sharshakov <d3dx12.xx@gmail.com>

Co-authored-by: Gokul Soumya <gokulps15@gmail.com>
  • Loading branch information
dsseng and sudormrfbin authored Aug 12, 2021
1 parent d03982e commit 7d51805
Show file tree
Hide file tree
Showing 4 changed files with 341 additions and 45 deletions.
182 changes: 160 additions & 22 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _};
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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(
Expand All @@ -1718,33 +1723,69 @@ 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(
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(
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 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 =
Expand All @@ -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(())
}

Expand Down Expand Up @@ -1967,6 +2024,20 @@ mod cmd {
fun: yank_joined_to_clipboard,
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,
Expand All @@ -1988,6 +2059,27 @@ mod cmd {
fun: replace_selections_with_clipboard,
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,
Expand Down Expand Up @@ -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(..);

Expand All @@ -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);
Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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)) => {
Expand All @@ -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) {
Expand All @@ -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| {
Expand All @@ -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) {
Expand Down
55 changes: 55 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
Loading

0 comments on commit 7d51805

Please sign in to comment.