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 9 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
210 changes: 208 additions & 2 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
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_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",
dsseng marked this conversation as resolved.
Show resolved Hide resolved
indent, "Indent selection",
unindent, "Unindent selection",
format_selections, "Format selection",
Expand Down Expand Up @@ -1642,6 +1647,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],
Expand Down Expand Up @@ -1681,13 +1706,55 @@ 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],
_event: PromptEvent,
) -> anyhow::Result<()> {
cx.editor
.set_status(cx.editor.clipboard_provider.name().into());
cx.editor.set_status(format!(
"Clipboard: {}; Primary selection: {}",
cx.editor.clipboard_provider.name(),
cx.editor.primary_selection_provider.name()
));
Ok(())
}

Expand Down Expand Up @@ -1888,6 +1955,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,
Expand All @@ -1909,6 +1990,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,
Expand Down Expand Up @@ -3181,6 +3283,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<String> = 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,
Expand Down Expand Up @@ -3256,6 +3414,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);
Expand Down Expand Up @@ -3300,6 +3484,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);
Expand Down
63 changes: 63 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,69 @@ 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);
let range = doc.selection(view.id).primary();

if range.to() - range.from() <= 1 {
return EventResult::Ignored;
}

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;
dsseng marked this conversation as resolved.
Show resolved Hide resolved

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,
}
}
Expand Down
Loading