@@ -3,6 +3,7 @@
3use anyhow::Result;
3use anyhow::Result;
4use std::borrow::Cow;
4use std::borrow::Cow;
6+#[derive(Clone, Copy, Debug)]
6pub enum ClipboardType {
7pub enum ClipboardType {
@@ -72,45 +73,48 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
73#[cfg(target_os = "macos")]
74#[cfg(target_os = "macos")]
74pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
75pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
75− use provider::command::exists;
76+ use crate::env::binary_exists;
77− if exists("pbcopy") && exists("pbpaste") {
78+ if binary_exists("pbcopy") && binary_exists("pbpaste") {
78 command_provider! {
79 command_provider! {
79 paste => "pbpaste";
80 paste => "pbpaste";
80 copy => "pbcopy";
81 copy => "pbcopy";
83− Box::new(provider::NopProvider::new())
84+ Box::new(provider::FallbackProvider::new())
87#[cfg(target_os = "wasm32")]
88#[cfg(target_os = "wasm32")]
88pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
89pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
90− Box::new(provider::NopProvider::new())
91+ Box::new(provider::FallbackProvider::new())
93#[cfg(not(any(windows, target_os = "wasm32", target_os = "macos")))]
94#[cfg(not(any(windows, target_os = "wasm32", target_os = "macos")))]
94pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
95pub 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;
96 // TODO: support for user-defined provider, probably when we have plugin support by setting a
98 // TODO: support for user-defined provider, probably when we have plugin support by setting a
97 // variable?
99 // variable?
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") {
100 command_provider! {
102 command_provider! {
101 paste => "wl-paste", "--no-newline";
103 paste => "wl-paste", "--no-newline";
102 copy => "wl-copy", "--type", "text/plain";
104 copy => "wl-copy", "--type", "text/plain";
103 primary_paste => "wl-paste", "-p", "--no-newline";
105 primary_paste => "wl-paste", "-p", "--no-newline";
104 primary_copy => "wl-copy", "-p", "--type", "text/plain";
106 primary_copy => "wl-copy", "-p", "--type", "text/plain";
106− } else if env_var_is_set("DISPLAY") && exists("xclip") {
108+ } else if env_var_is_set("DISPLAY") && binary_exists("xclip") {
107 command_provider! {
109 command_provider! {
108 paste => "xclip", "-o", "-selection", "clipboard";
110 paste => "xclip", "-o", "-selection", "clipboard";
109 copy => "xclip", "-i", "-selection", "clipboard";
111 copy => "xclip", "-i", "-selection", "clipboard";
110 primary_paste => "xclip", "-o";
112 primary_paste => "xclip", "-o";
111 primary_copy => "xclip", "-i";
113 primary_copy => "xclip", "-i";
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"])
115 // FIXME: check performance of is_exit_success
119 // FIXME: check performance of is_exit_success
116 command_provider! {
120 command_provider! {
@@ -119,43 +123,78 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
119 primary_paste => "xsel", "-o";
123 primary_paste => "xsel", "-o";
120 primary_copy => "xsel", "-i";
124 primary_copy => "xsel", "-i";
122− } else if exists("win32yank.exe") {
126+ } else if binary_exists("win32yank.exe") {
123 command_provider! {
127 command_provider! {
124 paste => "win32yank.exe", "-o", "--lf";
128 paste => "win32yank.exe", "-o", "--lf";
125 copy => "win32yank.exe", "-i", "--crlf";
129 copy => "win32yank.exe", "-i", "--crlf";
127− } else if exists("termux-clipboard-set") && exists("termux-clipboard-get") {
131+ } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get") {
128 command_provider! {
132 command_provider! {
129 paste => "termux-clipboard-get";
133 paste => "termux-clipboard-get";
130 copy => "termux-clipboard-set";
134 copy => "termux-clipboard-set";
132− } else if env_var_is_set("TMUX") && exists("tmux") {
136+ } else if env_var_is_set("TMUX") && binary_exists("tmux") {
133 command_provider! {
137 command_provider! {
134 paste => "tmux", "save-buffer", "-";
138 paste => "tmux", "save-buffer", "-";
135 copy => "tmux", "load-buffer", "-";
139 copy => "tmux", "load-buffer", "-";
138− Box::new(provider::NopProvider::new())
142+ Box::new(provider::FallbackProvider::new())
146+#[cfg(not(target_os = "windows"))]
142pub mod provider {
147pub mod provider {
143 use super::{ClipboardProvider, ClipboardType};
148 use super::{ClipboardProvider, ClipboardType};
144 use anyhow::Result;
149 use anyhow::Result;
145 use std::borrow::Cow;
150 use std::borrow::Cow;
147− #[cfg(not(target_os = "windows"))]
152+ #[cfg(feature = "term")]
154+ use {super::ClipboardType, crate::base64, crossterm};
157+ pub struct SetClipboardCommand {
158+ encoded_content: String,
159+ clipboard_type: ClipboardType,
162+ impl SetClipboardCommand {
163+ pub fn new(content: &str, clipboard_type: ClipboardType) -> Self {
165+ encoded_content: base64::encode(content.as_bytes()),
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",
177+ // Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/
178+ write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content)
148 #[derive(Debug)]
183 #[derive(Debug)]
149− pub struct NopProvider {
184+ pub struct FallbackProvider {
150 buf: String,
185 buf: String,
151 primary_buf: String,
186 primary_buf: String,
154− #[cfg(not(target_os = "windows"))]
189+ impl FallbackProvider {
156 pub fn new() -> Self {
190 pub fn new() -> Self {
191+ #[cfg(feature = "term")]
193+ "No native clipboard provider found. Yanking by OSC 52 and pasting will be internal to Helix"
195+ #[cfg(not(feature = "term"))]
157 log::warn!(
196 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"
161 buf: String::new(),
200 buf: String::new(),
@@ -164,20 +203,27 @@ pub mod provider {
167− #[cfg(not(target_os = "windows"))]
168− impl Default for NopProvider {
206+ impl Default for FallbackProvider {
169 fn default() -> Self {
207 fn default() -> Self {
170 Self::new()
208 Self::new()
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")
218+ #[cfg(not(feature = "term"))]
176 fn name(&self) -> Cow<str> {
219 fn name(&self) -> Cow<str> {
177 Cow::Borrowed("none")
220 Cow::Borrowed("none")
180 fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
223 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
181 let value = match clipboard_type {
227 let value = match clipboard_type {
182 ClipboardType::Clipboard => self.buf.clone(),
228 ClipboardType::Clipboard => self.buf.clone(),
183 ClipboardType::Selection => self.primary_buf.clone(),
229 ClipboardType::Selection => self.primary_buf.clone(),
@@ -187,6 +233,12 @@ pub mod provider {
189 fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
235 fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
236+ #[cfg(feature = "term")]
237+ crossterm::execute!(
239+ osc52::SetClipboardCommand::new(&content, clipboard_type)
241+ // Set our internal variables to use in get_content regardless of using OSC 52
190 match clipboard_type {
242 match clipboard_type {
191 ClipboardType::Clipboard => self.buf = content,
243 ClipboardType::Clipboard => self.buf = content,
192 ClipboardType::Selection => self.primary_buf = content,
244 ClipboardType::Selection => self.primary_buf = content,
@@ -195,52 +247,11 @@ pub mod provider {
198− #[cfg(target_os = "windows")]
199− #[derive(Default, Debug)]
200− pub struct WindowsProvider;
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")
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)?;
215− ClipboardType::Selection => Ok(String::new()),
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)?;
224− ClipboardType::Selection => {}
230 #[cfg(not(target_arch = "wasm32"))]
250 #[cfg(not(target_arch = "wasm32"))]
231 pub mod command {
251 pub mod command {
232 use super::*;
252 use super::*;
233 use anyhow::{bail, Context as _, Result};
253 use anyhow::{bail, Context as _, Result};
235− pub fn exists(executable_name: &str) -> bool {
236− which::which(executable_name).is_ok()
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()
244 #[cfg(not(any(windows, target_os = "macos")))]
255 #[cfg(not(any(windows, target_os = "macos")))]
245 pub fn is_exit_success(program: &str, args: &[&str]) -> bool {
256 pub fn is_exit_success(program: &str, args: &[&str]) -> bool {
246 std::process::Command::new(program)
257 std::process::Command::new(program)
@@ -343,3 +354,40 @@ pub mod provider {
358+#[cfg(target_os = "windows")]
360+ use super::{ClipboardProvider, ClipboardType};
362+ use std::borrow::Cow;
364+ #[derive(Default, Debug)]
365+ pub struct WindowsProvider;
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")
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)?;
379+ ClipboardType::Selection => Ok(String::new()),
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)?;
388+ ClipboardType::Selection => {}