@@ -3,6 +3,7 @@
33use anyhow::Result;
44use std::borrow::Cow;
55
6+#[derive(Clone, Copy, Debug)]
67pub enum ClipboardType {
78 Clipboard,
89 Selection,
@@ -72,45 +73,48 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
7273
7374#[cfg(target_os = "macos")]
7475pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
75− use provider::command::exists;
76+ use crate::env::binary_exists;
7677
77− if exists("pbcopy") && exists("pbpaste") {
78+ if binary_exists("pbcopy") && binary_exists("pbpaste") {
7879 command_provider! {
7980 paste => "pbpaste";
8081 copy => "pbcopy";
8182 }
8283 } else {
83− Box::new(provider::NopProvider::new())
84+ Box::new(provider::FallbackProvider::new())
8485 }
8586}
8687
8788#[cfg(target_os = "wasm32")]
8889pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
8990 // TODO:
90− Box::new(provider::NopProvider::new())
91+ Box::new(provider::FallbackProvider::new())
9192}
9293
9394#[cfg(not(any(windows, target_os = "wasm32", target_os = "macos")))]
9495pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
95− use provider::command::{env_var_is_set, exists, is_exit_success};
96+ use crate::env::{binary_exists, env_var_is_set};
97+ use provider::command::is_exit_success;
9698 // TODO: support for user-defined provider, probably when we have plugin support by setting a
9799 // variable?
98100
99− if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") {
101+ if env_var_is_set("WAYLAND_DISPLAY") && binary_exists("wl-copy") && binary_exists("wl-paste") {
100102 command_provider! {
101103 paste => "wl-paste", "--no-newline";
102104 copy => "wl-copy", "--type", "text/plain";
103105 primary_paste => "wl-paste", "-p", "--no-newline";
104106 primary_copy => "wl-copy", "-p", "--type", "text/plain";
105107 }
106− } else if env_var_is_set("DISPLAY") && exists("xclip") {
108+ } else if env_var_is_set("DISPLAY") && binary_exists("xclip") {
107109 command_provider! {
108110 paste => "xclip", "-o", "-selection", "clipboard";
109111 copy => "xclip", "-i", "-selection", "clipboard";
110112 primary_paste => "xclip", "-o";
111113 primary_copy => "xclip", "-i";
112114 }
113− } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
115+ } else if env_var_is_set("DISPLAY")
116+ && binary_exists("xsel")
117+ && is_exit_success("xsel", &["-o", "-b"])
114118 {
115119 // FIXME: check performance of is_exit_success
116120 command_provider! {
@@ -119,43 +123,78 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
119123 primary_paste => "xsel", "-o";
120124 primary_copy => "xsel", "-i";
121125 }
122− } else if exists("win32yank.exe") {
126+ } else if binary_exists("win32yank.exe") {
123127 command_provider! {
124128 paste => "win32yank.exe", "-o", "--lf";
125129 copy => "win32yank.exe", "-i", "--crlf";
126130 }
127− } else if exists("termux-clipboard-set") && exists("termux-clipboard-get") {
131+ } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get") {
128132 command_provider! {
129133 paste => "termux-clipboard-get";
130134 copy => "termux-clipboard-set";
131135 }
132− } else if env_var_is_set("TMUX") && exists("tmux") {
136+ } else if env_var_is_set("TMUX") && binary_exists("tmux") {
133137 command_provider! {
134138 paste => "tmux", "save-buffer", "-";
135139 copy => "tmux", "load-buffer", "-";
136140 }
137141 } else {
138− Box::new(provider::NopProvider::new())
142+ Box::new(provider::FallbackProvider::new())
139143 }
140144}
141145
146+#[cfg(not(target_os = "windows"))]
142147pub mod provider {
143148 use super::{ClipboardProvider, ClipboardType};
144149 use anyhow::Result;
145150 use std::borrow::Cow;
146151
147− #[cfg(not(target_os = "windows"))]
152+ #[cfg(feature = "term")]
153+ mod osc52 {
154+ use {super::ClipboardType, crate::base64, crossterm};
155+
156+ #[derive(Debug)]
157+ pub struct SetClipboardCommand {
158+ encoded_content: String,
159+ clipboard_type: ClipboardType,
160+ }
161+
162+ impl SetClipboardCommand {
163+ pub fn new(content: &str, clipboard_type: ClipboardType) -> Self {
164+ Self {
165+ encoded_content: base64::encode(content.as_bytes()),
166+ clipboard_type,
167+ }
168+ }
169+ }
170+
171+ impl crossterm::Command for SetClipboardCommand {
172+ fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
173+ let kind = match &self.clipboard_type {
174+ ClipboardType::Clipboard => "c",
175+ ClipboardType::Selection => "p",
176+ };
177+ // Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/
178+ write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content)
179+ }
180+ }
181+ }
182+
148183 #[derive(Debug)]
149− pub struct NopProvider {
184+ pub struct FallbackProvider {
150185 buf: String,
151186 primary_buf: String,
152187 }
153188
154− #[cfg(not(target_os = "windows"))]
155− impl NopProvider {
189+ impl FallbackProvider {
156190 pub fn new() -> Self {
191+ #[cfg(feature = "term")]
192+ log::debug!(
193+ "No native clipboard provider found. Yanking by OSC 52 and pasting will be internal to Helix"
194+ );
195+ #[cfg(not(feature = "term"))]
157196 log::warn!(
158− "No clipboard provider found! Yanking and pasting will be internal to Helix"
197+ "No native clipboard provider found! Yanking and pasting will be internal to Helix"
159198 );
160199 Self {
161200 buf: String::new(),
@@ -164,20 +203,27 @@ pub mod provider {
164203 }
165204 }
166205
167− #[cfg(not(target_os = "windows"))]
168− impl Default for NopProvider {
206+ impl Default for FallbackProvider {
169207 fn default() -> Self {
170208 Self::new()
171209 }
172210 }
173211
174− #[cfg(not(target_os = "windows"))]
175− impl ClipboardProvider for NopProvider {
212+ impl ClipboardProvider for FallbackProvider {
213+ #[cfg(feature = "term")]
214+ fn name(&self) -> Cow<str> {
215+ Cow::Borrowed("termcode")
216+ }
217+
218+ #[cfg(not(feature = "term"))]
176219 fn name(&self) -> Cow<str> {
177220 Cow::Borrowed("none")
178221 }
179222
180223 fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
224+ // This is the same noop if term is enabled or not.
225+ // We don't use the get side of OSC 52 as it isn't often enabled, it's a security hole,
226+ // and it would require this to be async to listen for the response
181227 let value = match clipboard_type {
182228 ClipboardType::Clipboard => self.buf.clone(),
183229 ClipboardType::Selection => self.primary_buf.clone(),
@@ -187,6 +233,12 @@ pub mod provider {
187233 }
188234
189235 fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
236+ #[cfg(feature = "term")]
237+ crossterm::execute!(
238+ std::io::stdout(),
239+ osc52::SetClipboardCommand::new(&content, clipboard_type)
240+ )?;
241+ // Set our internal variables to use in get_content regardless of using OSC 52
190242 match clipboard_type {
191243 ClipboardType::Clipboard => self.buf = content,
192244 ClipboardType::Selection => self.primary_buf = content,
@@ -195,52 +247,11 @@ pub mod provider {
195247 }
196248 }
197249
198− #[cfg(target_os = "windows")]
199− #[derive(Default, Debug)]
200− pub struct WindowsProvider;
201−
202− #[cfg(target_os = "windows")]
203− impl ClipboardProvider for WindowsProvider {
204− fn name(&self) -> Cow<str> {
205− log::info!("Using clipboard-win to interact with the system clipboard");
206− Cow::Borrowed("clipboard-win")
207− }
208−
209− fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
210− match clipboard_type {
211− ClipboardType::Clipboard => {
212− let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
213− Ok(contents)
214− }
215− ClipboardType::Selection => Ok(String::new()),
216− }
217− }
218−
219− fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> {
220− match clipboard_type {
221− ClipboardType::Clipboard => {
222− clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?;
223− }
224− ClipboardType::Selection => {}
225− };
226− Ok(())
227− }
228− }
229−
230250 #[cfg(not(target_arch = "wasm32"))]
231251 pub mod command {
232252 use super::*;
233253 use anyhow::{bail, Context as _, Result};
234254
235− pub fn exists(executable_name: &str) -> bool {
236− which::which(executable_name).is_ok()
237− }
238−
239− #[cfg(not(windows))]
240− pub fn env_var_is_set(env_var_name: &str) -> bool {
241− std::env::var_os(env_var_name).is_some()
242− }
243−
244255 #[cfg(not(any(windows, target_os = "macos")))]
245256 pub fn is_exit_success(program: &str, args: &[&str]) -> bool {
246257 std::process::Command::new(program)
@@ -343,3 +354,40 @@ pub mod provider {
343354 }
344355 }
345356}
357+
358+#[cfg(target_os = "windows")]
359+mod provider {
360+ use super::{ClipboardProvider, ClipboardType};
361+ use anyhow::Result;
362+ use std::borrow::Cow;
363+
364+ #[derive(Default, Debug)]
365+ pub struct WindowsProvider;
366+
367+ impl ClipboardProvider for WindowsProvider {
368+ fn name(&self) -> Cow<str> {
369+ log::info!("Using clipboard-win to interact with the system clipboard");
370+ Cow::Borrowed("clipboard-win")
371+ }
372+
373+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
374+ match clipboard_type {
375+ ClipboardType::Clipboard => {
376+ let contents = clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?;
377+ Ok(contents)
378+ }
379+ ClipboardType::Selection => Ok(String::new()),
380+ }
381+ }
382+
383+ fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()> {
384+ match clipboard_type {
385+ ClipboardType::Clipboard => {
386+ clipboard_win::set_clipboard(clipboard_win::formats::Unicode, contents)?;
387+ }
388+ ClipboardType::Selection => {}
389+ };
390+ Ok(())
391+ }
392+ }
393+}