Skip to content

Commit

Permalink
Use OSC 52 as a fallback for setting the system clipboard
Browse files Browse the repository at this point in the history
  • Loading branch information
groves committed Jul 31, 2022
1 parent 7e06681 commit 8a5c873
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 42 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion helix-view/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ homepage = "https://helix-editor.com"

[features]
default = []
term = ["crossterm"]
term = ["crossterm", "base64"]

[dependencies]
bitflags = "1.3"
Expand All @@ -20,6 +20,7 @@ helix-core = { version = "0.6", path = "../helix-core" }
helix-lsp = { version = "0.6", path = "../helix-lsp" }
helix-dap = { version = "0.6", path = "../helix-dap" }
crossterm = { version = "0.24", optional = true }
base64 = { version = "0.13", optional = true }

# Conversion traits
once_cell = "1.13"
Expand Down
123 changes: 82 additions & 41 deletions helix-view/src/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
copy => "pbcopy";
}
} else {
Box::new(provider::NopProvider::new())
Box::new(provider::FallbackProvider::new())
}
}

#[cfg(target_os = "wasm32")]
pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
// TODO:
Box::new(provider::NopProvider::new())
Box::new(provider::FallbackProvider::new())
}

#[cfg(not(any(windows, target_os = "wasm32", target_os = "macos")))]
Expand Down Expand Up @@ -135,27 +135,31 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
copy => "tmux", "load-buffer", "-";
}
} else {
Box::new(provider::NopProvider::new())
Box::new(provider::FallbackProvider::new())
}
}

#[cfg(not(target_os = "windows"))]
mod provider {
use super::{ClipboardProvider, ClipboardType};
use anyhow::Result;
use std::borrow::Cow;

#[cfg(not(target_os = "windows"))]
#[derive(Debug)]
pub struct NopProvider {
pub struct FallbackProvider {
buf: String,
primary_buf: String,
}

#[cfg(not(target_os = "windows"))]
impl NopProvider {
impl FallbackProvider {
pub fn new() -> Self {
#[cfg(feature = "term")]
log::info!(
"No native clipboard provider found. Yanking by OSC 52 and pasting will be internal to Helix"
);
#[cfg(not(feature = "term"))]
log::warn!(
"No clipboard provider found! Yanking and pasting will be internal to Helix"
"No native clipboard provider found! Yanking and pasting will be internal to Helix"
);
Self {
buf: String::new(),
Expand All @@ -164,13 +168,24 @@ mod provider {
}
}

#[cfg(not(target_os = "windows"))]
impl ClipboardProvider for NopProvider {
#[cfg(feature = "term")]
use {base64, crossterm, std::io::stdout};

impl ClipboardProvider for FallbackProvider {
#[cfg(feature = "term")]
fn name(&self) -> Cow<str> {
Cow::Borrowed("termcode")
}

#[cfg(not(feature = "term"))]
fn name(&self) -> Cow<str> {
Cow::Borrowed("none")
}

fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
// This is the same noop if term is enabled or not.
// We don't use the get side of OSC 52 as it isn't often enabled, it's a security hole,
// and it would require this to be async to listen for the response
let value = match clipboard_type {
ClipboardType::Clipboard => self.buf.clone(),
ClipboardType::Selection => self.primary_buf.clone(),
Expand All @@ -179,43 +194,32 @@ mod provider {
Ok(value)
}

#[cfg(feature = "term")]
fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
match clipboard_type {
ClipboardType::Clipboard => self.buf = content,
ClipboardType::Selection => self.primary_buf = content,
}
Ok(())
}
}

#[cfg(target_os = "windows")]
#[derive(Default, Debug)]
pub struct WindowsProvider;

#[cfg(target_os = "windows")]
impl ClipboardProvider for WindowsProvider {
fn name(&self) -> Cow<str> {
log::info!("Using clipboard-win to interact with the system clipboard");
Cow::Borrowed("clipboard-win")
}

fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
match clipboard_type {
let encoded = base64::encode(&content);
let kind = match clipboard_type {
ClipboardType::Clipboard => {
let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
Ok(contents)
// Still set our internal variables to use in get_content
self.buf = content;
"c"
}
ClipboardType::Selection => Ok(String::new()),
}
ClipboardType::Selection => {
self.primary_buf = content;
"p"
}
};
// Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/
let cmd = crossterm::style::Print(format!("\x1b]52;{};{}\x1b\\", kind, encoded));
crossterm::execute!(stdout(), cmd)?;
Ok(())
}

fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> {
#[cfg(not(feature = "term"))]
fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
match clipboard_type {
ClipboardType::Clipboard => {
clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?;
}
ClipboardType::Selection => {}
};
ClipboardType::Clipboard => self.buf = content,
ClipboardType::Selection => self.primary_buf = content,
}
Ok(())
}
}
Expand Down Expand Up @@ -337,3 +341,40 @@ mod provider {
}
}
}

#[cfg(target_os = "windows")]
mod provider {
use super::{ClipboardProvider, ClipboardType};
use anyhow::Result;
use std::borrow::Cow;

#[derive(Default, Debug)]
pub struct WindowsProvider;

impl ClipboardProvider for WindowsProvider {
fn name(&self) -> Cow<str> {
log::info!("Using clipboard-win to interact with the system clipboard");
Cow::Borrowed("clipboard-win")
}

fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
match clipboard_type {
ClipboardType::Clipboard => {
let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
Ok(contents)
}
ClipboardType::Selection => Ok(String::new()),
}
}

fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> {
match clipboard_type {
ClipboardType::Clipboard => {
clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?;
}
ClipboardType::Selection => {}
};
Ok(())
}
}
}

0 comments on commit 8a5c873

Please sign in to comment.