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

Add system clipboard yank and paste commands #310

Merged
merged 3 commits into from
Jun 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions book/src/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ This layer is a kludge of mappings I had under leader key in neovim.
| `s` | Open symbol picker (current document) |
| `w` | Enter [window mode](#window-mode) |
| `space` | Keep primary selection TODO: it's here because space mode replaced it |
| `p` | paste system clipboard after selections |
| `P` | paste system clipboard before selections |
| `y` | join and yank selections to clipboard |
| `Y` | yank main selection to clipboard |
| `R` | replace selections by clipboard contents |

# Picker

Expand Down
187 changes: 186 additions & 1 deletion helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,14 @@ impl Command {
undo,
redo,
yank,
yank_joined_to_clipboard,
yank_main_selection_to_clipboard,
replace_with_yanked,
replace_selections_with_clipboard,
paste_after,
paste_before,
paste_clipboard_after,
paste_clipboard_before,
indent,
unindent,
format_selections,
Expand Down Expand Up @@ -1280,6 +1285,46 @@ mod cmd {
editor.set_theme_from_name(theme);
}

fn yank_main_selection_to_clipboard(editor: &mut Editor, _: &[&str], _: PromptEvent) {
yank_main_selection_to_clipboard_impl(editor);
}

fn yank_joined_to_clipboard(editor: &mut Editor, args: &[&str], _: PromptEvent) {
let separator = args.first().copied().unwrap_or("\n");
yank_joined_to_clipboard_impl(editor, separator);
}

fn paste_clipboard_after(editor: &mut Editor, _: &[&str], _: PromptEvent) {
paste_clipboard_impl(editor, Paste::After);
}

fn paste_clipboard_before(editor: &mut Editor, _: &[&str], _: PromptEvent) {
paste_clipboard_impl(editor, Paste::After);
}

fn replace_selections_with_clipboard(editor: &mut Editor, _: &[&str], _: PromptEvent) {
let (view, doc) = current!(editor);

match editor.clipboard_provider.get_contents() {
Ok(contents) => {
let transaction =
Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
let max_to = doc.text().len_chars().saturating_sub(1);
let to = std::cmp::min(max_to, range.to() + 1);
(range.from(), to, Some(contents.as_str().into()))
});

doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
}
}

fn show_clipboard_provider(editor: &mut Editor, _: &[&str], _: PromptEvent) {
editor.set_status(editor.clipboard_provider.name().into());
}

pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
Expand Down Expand Up @@ -1400,7 +1445,48 @@ mod cmd {
fun: theme,
completer: Some(completers::theme),
},

TypableCommand {
name: "clipboard-yank",
alias: None,
doc: "Yank main selection into system clipboard.",
fun: yank_main_selection_to_clipboard,
completer: None,
},
TypableCommand {
name: "clipboard-yank-join",
alias: None,
doc: "Yank joined selections into system 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_clipboard,
completer: None,
},
TypableCommand {
name: "clipboard-paste-after",
alias: None,
doc: "Paste system clipboard after selections.",
fun: paste_clipboard_after,
completer: None,
},
TypableCommand {
name: "clipboard-paste-before",
alias: None,
doc: "Paste system clipboard before selections.",
fun: paste_clipboard_before,
completer: None,
},
TypableCommand {
name: "clipboard-paste-replace",
alias: None,
doc: "Replace selections with content of system clipboard.",
fun: replace_selections_with_clipboard,
completer: None,
},
TypableCommand {
name: "show-clipboard-provider",
alias: None,
doc: "Show clipboard provider name in status bar.",
fun: show_clipboard_provider,
completer: None,
},
];

pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
Expand Down Expand Up @@ -2394,6 +2480,52 @@ fn yank(cx: &mut Context) {
cx.editor.set_status(msg)
}

fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
let (view, doc) = current!(editor);

let values: Vec<String> = doc
.selection(view.id)
.fragments(doc.text().slice(..))
.map(Cow::into_owned)
.collect();

let msg = format!(
"joined and yanked {} selection(s) to system clipboard",
values.len(),
);

let joined = values.join(separator);

if let Err(e) = editor.clipboard_provider.set_contents(joined) {
log::error!("Couldn't set system clipboard content: {:?}", e);
}

editor.set_status(msg);
}

fn yank_joined_to_clipboard(cx: &mut Context) {
yank_joined_to_clipboard_impl(&mut cx.editor, "\n");
}

fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
let (view, doc) = current!(editor);

let value = doc
.selection(view.id)
.primary()
.fragment(doc.text().slice(..));

if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
log::error!("Couldn't set system clipboard content: {:?}", e);
}

editor.set_status("yanked main selection to system clipboard".to_owned());
}

fn yank_main_selection_to_clipboard(cx: &mut Context) {
yank_main_selection_to_clipboard_impl(&mut cx.editor);
}

#[derive(Copy, Clone)]
enum Paste {
Before,
Expand Down Expand Up @@ -2437,6 +2569,31 @@ fn paste_impl(
Some(transaction)
}

fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
let (view, doc) = current!(editor);

match editor
.clipboard_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(None) => {}
Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
}
}

fn paste_clipboard_after(cx: &mut Context) {
paste_clipboard_impl(&mut cx.editor, Paste::After);
}

fn paste_clipboard_before(cx: &mut Context) {
paste_clipboard_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 All @@ -2457,6 +2614,29 @@ fn replace_with_yanked(cx: &mut Context) {
}
}

fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
let (view, doc) = current!(editor);

match editor.clipboard_provider.get_contents() {
Ok(contents) => {
let transaction =
Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
let max_to = doc.text().len_chars().saturating_sub(1);
let to = std::cmp::min(max_to, range.to() + 1);
Comment on lines +2624 to +2625
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is now frequent enough that maybe we should add a helper function doc.clipped_range or something, it would help with #224. Can be done over there I guess

Copy link
Member Author

@CBenoit CBenoit Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it helps in #224, it's probably more useful if done there indeed. I'll make sure to use the helper if it gets merged before my PR.

(range.from(), to, Some(contents.as_str().into()))
});

doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
}
}

fn replace_selections_with_clipboard(cx: &mut Context) {
replace_selections_with_clipboard_impl(&mut cx.editor);
}

// alt-p => paste every yanked selection after selected text
// alt-P => paste every yanked selection before selected text
// R => replace selected text with yanked text
Expand Down Expand Up @@ -2977,6 +3157,11 @@ fn space_mode(cx: &mut Context) {
'b' => buffer_picker(cx),
's' => symbol_picker(cx),
'w' => window_mode(cx),
'y' => yank_joined_to_clipboard(cx),
CBenoit marked this conversation as resolved.
Show resolved Hide resolved
'Y' => yank_main_selection_to_clipboard(cx),
'p' => paste_clipboard_after(cx),
'P' => paste_clipboard_before(cx),
'R' => replace_selections_with_clipboard(cx),
// ' ' => toggle_alternate_buffer(cx),
// TODO: temporary since space mode took its old key
' ' => keep_primary_selection(cx),
Expand Down
3 changes: 3 additions & 0 deletions helix-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ slotmap = "1"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
log = "~0.4"

which = "4.1"

Loading