Skip to content

Commit

Permalink
Add system & primary clipboards as special registers
Browse files Browse the repository at this point in the history
These special registers join and copy the values to the clipboards
with '*' corresponding to the system clipboard and '+' to the primary
as they are in Vim. This also uses the trick from PR6889 to save the
values in the register and re-use them without joining into one
value when pasting a value which was yanked and not changed.

These registers are not implemented in Kakoune but Kakoune also does
not have built-in clipboard integration.

Co-authored-by: CcydtN <[email protected]>
Co-authored-by: Pascal Kuthe <[email protected]>
  • Loading branch information
3 people committed May 6, 2023
1 parent 005705e commit 25d0e29
Showing 1 changed file with 128 additions and 2 deletions.
130 changes: 128 additions & 2 deletions helix-view/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use std::{borrow::Cow, collections::HashMap};
use anyhow::Result;
use helix_core::hashmap;

use crate::{document::SCRATCH_BUFFER_NAME, Editor};
use crate::{clipboard::ClipboardType, document::SCRATCH_BUFFER_NAME, Editor};

pub const SPECIAL_REGISTERS: [char; 4] = ['_', '#', '.', '%'];
pub const SPECIAL_REGISTERS: [char; 6] = ['_', '#', '.', '%', '*', '+'];

pub trait Register: std::fmt::Debug {
fn name(&self) -> char;
Expand Down Expand Up @@ -91,6 +91,8 @@ impl Default for Registers {
'#' => Box::new(SelectionIndexRegister::default()),
'.' => Box::new(SelectionContentsRegister::default()),
'%' => Box::new(DocumentPathRegister::default()),
'*' => Box::new(SystemClipboardRegister::default()),
'+' => Box::new(PrimaryClipboardRegister::default()),
);

Self { inner }
Expand Down Expand Up @@ -227,3 +229,127 @@ impl Register for DocumentPathRegister {
vec![path.into()]
}
}

#[derive(Debug, Default)]
struct SystemClipboardRegister {
values: Vec<String>,
}

impl Register for SystemClipboardRegister {
fn name(&self) -> char {
'*'
}

fn preview(&self) -> &str {
"<system clipboard>"
}

fn read(&self, editor: &Editor) -> Vec<String> {
read_from_clipboard(&self.values, editor, ClipboardType::Clipboard)
}

fn write(&mut self, editor: &mut Editor, values: Vec<String>) -> Result<()> {
self.values = values;
save_to_clipboard(&self.values, editor, ClipboardType::Clipboard)
}

fn push(&mut self, editor: &mut Editor, value: String) -> Result<()> {
self.values.push(value);
save_to_clipboard(&self.values, editor, ClipboardType::Clipboard)
}
}

#[derive(Debug, Default)]
struct PrimaryClipboardRegister {
values: Vec<String>,
}

impl Register for PrimaryClipboardRegister {
fn name(&self) -> char {
'+'
}

fn preview(&self) -> &str {
"<primary clipboard>"
}

fn read(&self, editor: &Editor) -> Vec<String> {
read_from_clipboard(&self.values, editor, ClipboardType::Selection)
}

fn write(&mut self, editor: &mut Editor, values: Vec<String>) -> Result<()> {
self.values = values;
save_to_clipboard(&self.values, editor, ClipboardType::Selection)
}

fn push(&mut self, editor: &mut Editor, value: String) -> Result<()> {
self.values.push(value);
save_to_clipboard(&self.values, editor, ClipboardType::Selection)
}
}

fn save_to_clipboard(
values: &Vec<String>,
editor: &mut Editor,
clipboard_type: ClipboardType,
) -> Result<()> {
let line_ending = doc!(editor).line_ending;
let joined = values.join(line_ending.as_str());

editor
.clipboard_provider
.set_contents(joined, clipboard_type)
}

fn read_from_clipboard(
saved_values: &Vec<String>,
editor: &Editor,
clipboard_type: ClipboardType,
) -> Vec<String> {
match editor.clipboard_provider.get_contents(clipboard_type) {
Ok(contents) => {
// If we're pasting the same value that we just yanked, re-use
// the saved values. This allows pasting multiple selections
// even when yanked to a clipboard.
if contents_are_saved(saved_values, editor, &contents) {
saved_values.clone()
} else {
vec![contents]
}
}
Err(err) => {
log::error!(
"Failed to read {} clipboard: {}",
match clipboard_type {
ClipboardType::Clipboard => "system",
ClipboardType::Selection => "primary",
},
err
);

Vec::new()
}
}
}

fn contents_are_saved(saved_values: &Vec<String>, editor: &Editor, mut contents: &str) -> bool {
let line_ending = doc!(editor).line_ending.as_str();
let mut values = saved_values.iter();

match values.next() {
Some(first) if contents.starts_with(first) => {
contents = &contents[first.len()..];
}
_ => return false,
}

for value in values {
if contents.starts_with(line_ending) == contents[line_ending.len()..].starts_with(value) {
contents = &contents[line_ending.len() + value.len()..];
} else {
return false;
}
}

true
}

0 comments on commit 25d0e29

Please sign in to comment.