Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support primary clipboard #548

Merged
merged 31 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
75fed68
clipboard-none: add in-memory fallback buffer
dsseng Aug 4, 2021
2c20d35
view: add Wayland primary clipboard
dsseng Aug 4, 2021
5b88151
Format
dsseng Aug 4, 2021
8ccbe36
helix-term: copy to primary selection after mouse move stops
dsseng Aug 4, 2021
c938a7b
helix-term: don't update primary selection if it is a single character
dsseng Aug 4, 2021
684b8dc
helix-term: discard result of setting primary selection
dsseng Aug 4, 2021
71eb635
helix-term: add commands for interaction with primary clipboard
dsseng Aug 4, 2021
fb06c20
editor: implement primary selection copy/paste using commands
dsseng Aug 4, 2021
74681bd
clipboard: support xsel for primary selection
dsseng Aug 4, 2021
f898fad
clipboard: support xclip for primary selection
dsseng Aug 4, 2021
19f6c94
helix-term: multiple cursor support for middle click paste
dsseng Aug 4, 2021
05ae650
rename primary selection to primary clipboard in scope of PR
dsseng Aug 4, 2021
1e8162e
helix-term: make middle click paste optional
dsseng Aug 4, 2021
db0d5d5
Format
dsseng Aug 4, 2021
b273396
Update helix-term/src/ui/editor.rs
dsseng Aug 5, 2021
ffef3ea
fix formatting
dsseng Aug 5, 2021
b76b8e7
config: correct defaults if terminal prop is not set
dsseng Aug 5, 2021
2316230
refactor: merge clipboard and primary selection implementations
dsseng Aug 5, 2021
d16eab4
Merge branch 'master' into primary-clipboard
dsseng Aug 5, 2021
7e4a85b
Tidy up code
dsseng Aug 5, 2021
ada6b9d
view: remove names for different clipboard/selection providers
dsseng Aug 5, 2021
a530f88
Update helix-view/src/clipboard.rs
dsseng Aug 7, 2021
b363b0b
helix-view: tidy macros
dsseng Aug 7, 2021
7e82895
helix-term: refactor paste-replace commands
dsseng Aug 7, 2021
bded2fd
Merge branch 'master' into primary-clipboard
dsseng Aug 8, 2021
9294835
helix-term: use new config for middle-click-paste
dsseng Aug 8, 2021
13c8a0c
clipboard: remove memory fallback for command and windows providers
dsseng Aug 8, 2021
cbd58f1
clipboard-win: fix build
dsseng Aug 8, 2021
79a79c5
clipboard: return empty string when primary clipboard is missing
dsseng Aug 8, 2021
5ba6241
clipboard: fix errors in Windows build
dsseng Aug 8, 2021
7fbc25a
Merge branch 'master' into primary-clipboard
dsseng Aug 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -256,12 +256,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 @@ -1702,7 +1707,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 @@ -1715,33 +1720,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 @@ -1757,13 +1798,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 @@ -1964,6 +2021,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 @@ -1985,6 +2056,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 @@ -3206,7 +3298,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 @@ -3225,7 +3321,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 @@ -3235,17 +3331,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 @@ -3254,7 +3360,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 @@ -3307,12 +3426,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 @@ -3326,11 +3449,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 @@ -3355,10 +3486,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 @@ -3374,7 +3508,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