Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/clipboard.rs')
| -rw-r--r-- | helix-view/src/clipboard.rs | 729 |
1 files changed, 336 insertions, 393 deletions
diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 2641d98f..379accc7 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -1,225 +1,356 @@ // Implementation reference: https://github.com/neovim/neovim/blob/f2906a4669a2eef6d7bf86a29648793d63c98949/runtime/autoload/provider/clipboard.vim#L68-L152 -use serde::{Deserialize, Serialize}; +use anyhow::Result; use std::borrow::Cow; -use thiserror::Error; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum ClipboardType { Clipboard, Selection, } -#[derive(Debug, Error)] -pub enum ClipboardError { - #[error(transparent)] - IoError(#[from] std::io::Error), - #[error("could not convert terminal output to UTF-8: {0}")] - FromUtf8Error(#[from] std::string::FromUtf8Error), - #[cfg(windows)] - #[error("Windows API error: {0}")] - WinAPI(#[from] clipboard_win::ErrorCode), - #[error("clipboard provider command failed")] - CommandFailed, - #[error("failed to write to clipboard provider's stdin")] - StdinWriteFailed, - #[error("clipboard provider did not return any contents")] - MissingStdout, - #[error("This clipboard provider does not support reading")] - ReadingNotSupported, +pub trait ClipboardProvider: std::fmt::Debug { + fn name(&self) -> Cow<str>; + fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String>; + fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()>; } -type Result<T> = std::result::Result<T, ClipboardError>; +#[cfg(not(windows))] +macro_rules! command_provider { + (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{ + log::debug!( + "Using {} to interact with the system clipboard", + if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() } + ); + Box::new(provider::command::Provider { + get_cmd: provider::command::Config { + prg: $get_prg, + args: &[ $( $get_arg ),* ], + }, + set_cmd: provider::command::Config { + prg: $set_prg, + args: &[ $( $set_arg ),* ], + }, + get_primary_cmd: None, + set_primary_cmd: None, + }) + }}; + + (paste => $get_prg:literal $( , $get_arg:literal )* ; + copy => $set_prg:literal $( , $set_arg:literal )* ; + primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; + primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; + ) => {{ + log::debug!( + "Using {} to interact with the system and selection (primary) clipboard", + if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() } + ); + Box::new(provider::command::Provider { + get_cmd: provider::command::Config { + prg: $get_prg, + args: &[ $( $get_arg ),* ], + }, + set_cmd: provider::command::Config { + prg: $set_prg, + args: &[ $( $set_arg ),* ], + }, + get_primary_cmd: Some(provider::command::Config { + prg: $pr_get_prg, + args: &[ $( $pr_get_arg ),* ], + }), + set_primary_cmd: Some(provider::command::Config { + prg: $pr_set_prg, + args: &[ $( $pr_set_arg ),* ], + }), + }) + }}; +} -#[cfg(not(target_arch = "wasm32"))] -pub use external::ClipboardProvider; -#[cfg(target_arch = "wasm32")] -pub use noop::ClipboardProvider; +#[cfg(windows)] +pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> { + Box::<provider::WindowsProvider>::default() +} -// Clipboard not supported for wasm -#[cfg(target_arch = "wasm32")] -mod noop { - use super::*; +#[cfg(target_os = "macos")] +pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> { + use helix_stdx::env::{binary_exists, env_var_is_set}; + + if env_var_is_set("TMUX") && binary_exists("tmux") { + command_provider! { + paste => "tmux", "save-buffer", "-"; + copy => "tmux", "load-buffer", "-w", "-"; + } + } else if binary_exists("pbcopy") && binary_exists("pbpaste") { + command_provider! { + paste => "pbpaste"; + copy => "pbcopy"; + } + } else { + Box::new(provider::FallbackProvider::new()) + } +} - #[derive(Debug, Clone)] - pub enum ClipboardProvider {} +#[cfg(target_arch = "wasm32")] +pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> { + // TODO: + Box::new(provider::FallbackProvider::new()) +} - impl ClipboardProvider { - pub fn detect() -> Self { - Self +#[cfg(not(any(windows, target_arch = "wasm32", target_os = "macos")))] +pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> { + use helix_stdx::env::{binary_exists, env_var_is_set}; + use provider::command::is_exit_success; + // TODO: support for user-defined provider, probably when we have plugin support by setting a + // variable? + + if env_var_is_set("WAYLAND_DISPLAY") && binary_exists("wl-copy") && binary_exists("wl-paste") { + command_provider! { + paste => "wl-paste", "--no-newline"; + copy => "wl-copy", "--type", "text/plain"; + primary_paste => "wl-paste", "-p", "--no-newline"; + primary_copy => "wl-copy", "-p", "--type", "text/plain"; + } + } else if env_var_is_set("DISPLAY") && binary_exists("xclip") { + command_provider! { + paste => "xclip", "-o", "-selection", "clipboard"; + copy => "xclip", "-i", "-selection", "clipboard"; + primary_paste => "xclip", "-o"; + primary_copy => "xclip", "-i"; + } + } else if env_var_is_set("DISPLAY") + && binary_exists("xsel") + && is_exit_success("xsel", &["-o", "-b"]) + { + // FIXME: check performance of is_exit_success + command_provider! { + paste => "xsel", "-o", "-b"; + copy => "xsel", "-i", "-b"; + primary_paste => "xsel", "-o"; + primary_copy => "xsel", "-i"; + } + } else if binary_exists("win32yank.exe") { + command_provider! { + paste => "win32yank.exe", "-o", "--lf"; + copy => "win32yank.exe", "-i", "--crlf"; } + } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get") { + command_provider! { + paste => "termux-clipboard-get"; + copy => "termux-clipboard-set"; + } + } else if env_var_is_set("TMUX") && binary_exists("tmux") { + command_provider! { + paste => "tmux", "save-buffer", "-"; + copy => "tmux", "load-buffer", "-w", "-"; + } + } else { + Box::new(provider::FallbackProvider::new()) + } +} - pub fn name(&self) -> Cow<str> { - "none".into() +#[cfg(not(target_os = "windows"))] +pub mod provider { + use super::{ClipboardProvider, ClipboardType}; + use anyhow::Result; + use std::borrow::Cow; + + #[cfg(feature = "term")] + mod osc52 { + use {super::ClipboardType, crate::base64}; + + #[derive(Debug)] + pub struct SetClipboardCommand { + encoded_content: String, + clipboard_type: ClipboardType, } - pub fn get_contents(&self, _clipboard_type: ClipboardType) -> Result<String> { - Err(ClipboardError::ReadingNotSupported) + impl SetClipboardCommand { + pub fn new(content: &str, clipboard_type: ClipboardType) -> Self { + Self { + encoded_content: base64::encode(content.as_bytes()), + clipboard_type, + } + } } - pub fn set_contents(&self, _content: &str, _clipboard_type: ClipboardType) -> Result<()> { - Ok(()) + impl crossterm::Command for SetClipboardCommand { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + let kind = match &self.clipboard_type { + ClipboardType::Clipboard => "c", + ClipboardType::Selection => "p", + }; + // Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/ + write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content) + } } } -} -#[cfg(not(target_arch = "wasm32"))] -mod external { - use super::*; + #[derive(Debug)] + pub struct FallbackProvider { + buf: String, + primary_buf: String, + } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - pub struct Command { - command: Cow<'static, str>, - #[serde(default)] - args: Cow<'static, [Cow<'static, str>]>, + impl FallbackProvider { + pub fn new() -> Self { + #[cfg(feature = "term")] + log::debug!( + "No native clipboard provider found. Yanking by OSC 52 and pasting will be internal to Helix" + ); + #[cfg(not(feature = "term"))] + log::warn!( + "No native clipboard provider found! Yanking and pasting will be internal to Helix" + ); + Self { + buf: String::new(), + primary_buf: String::new(), + } + } } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - #[serde(rename_all = "kebab-case")] - pub struct CommandProvider { - yank: Command, - paste: Command, - yank_primary: Option<Command>, - paste_primary: Option<Command>, + impl Default for FallbackProvider { + fn default() -> Self { + Self::new() + } } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - #[serde(rename_all = "kebab-case")] - #[allow(clippy::large_enum_variant)] - pub enum ClipboardProvider { - Pasteboard, - Wayland, - XClip, - XSel, - Win32Yank, - Tmux, - #[cfg(windows)] - Windows, - Termux, + impl ClipboardProvider for FallbackProvider { #[cfg(feature = "term")] - Termcode, - Custom(CommandProvider), - None, - } + fn name(&self) -> Cow<str> { + Cow::Borrowed("termcode") + } - impl Default for ClipboardProvider { - #[cfg(windows)] - fn default() -> Self { - use helix_stdx::env::binary_exists; + #[cfg(not(feature = "term"))] + fn name(&self) -> Cow<str> { + Cow::Borrowed("none") + } - if binary_exists("win32yank.exe") { - Self::Win32Yank - } else { - Self::Windows - } + 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(), + }; + + Ok(value) } - #[cfg(target_os = "macos")] - fn default() -> Self { - use helix_stdx::env::{binary_exists, env_var_is_set}; - - if env_var_is_set("TMUX") && binary_exists("tmux") { - Self::Tmux - } else if binary_exists("pbcopy") && binary_exists("pbpaste") { - Self::Pasteboard - } else { - #[cfg(feature = "term")] - return Self::Termcode; - #[cfg(not(feature = "term"))] - return Self::None; + fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> { + #[cfg(feature = "term")] + crossterm::execute!( + std::io::stdout(), + osc52::SetClipboardCommand::new(&content, clipboard_type) + )?; + // Set our internal variables to use in get_content regardless of using OSC 52 + match clipboard_type { + ClipboardType::Clipboard => self.buf = content, + ClipboardType::Selection => self.primary_buf = content, } + Ok(()) } + } + + #[cfg(not(target_arch = "wasm32"))] + pub mod command { + use super::*; + use anyhow::{bail, Context as _}; #[cfg(not(any(windows, target_os = "macos")))] - fn default() -> Self { - use helix_stdx::env::{binary_exists, env_var_is_set}; - - fn is_exit_success(program: &str, args: &[&str]) -> bool { - std::process::Command::new(program) - .args(args) - .output() - .ok() - .and_then(|out| out.status.success().then_some(())) - .is_some() - } + pub fn is_exit_success(program: &str, args: &[&str]) -> bool { + std::process::Command::new(program) + .args(args) + .output() + .ok() + .and_then(|out| out.status.success().then_some(())) + .is_some() + } - if env_var_is_set("WAYLAND_DISPLAY") - && binary_exists("wl-copy") - && binary_exists("wl-paste") - { - Self::Wayland - } else if env_var_is_set("DISPLAY") && binary_exists("xclip") { - Self::XClip - } else if env_var_is_set("DISPLAY") - && binary_exists("xsel") - // FIXME: check performance of is_exit_success - && is_exit_success("xsel", &["-o", "-b"]) - { - Self::XSel - } else if binary_exists("termux-clipboard-set") && binary_exists("termux-clipboard-get") - { - Self::Termux - } else if env_var_is_set("TMUX") && binary_exists("tmux") { - Self::Tmux - } else if binary_exists("win32yank.exe") { - Self::Win32Yank - } else if cfg!(feature = "term") { - Self::Termcode - } else { - Self::None - } + #[derive(Debug)] + pub struct Config { + pub prg: &'static str, + pub args: &'static [&'static str], } - } - impl ClipboardProvider { - pub fn name(&self) -> Cow<'_, str> { - fn builtin_name<'a>( - name: &'static str, - provider: &'static CommandProvider, - ) -> Cow<'a, str> { - if provider.yank.command != provider.paste.command { - Cow::Owned(format!( - "{} ({}+{})", - name, provider.yank.command, provider.paste.command - )) + impl Config { + fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> { + use std::io::Write; + use std::process::{Command, Stdio}; + + let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null); + let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null); + + let mut command: Command = Command::new(self.prg); + + let mut command_mut: &mut Command = command + .args(self.args) + .stdin(stdin) + .stdout(stdout) + .stderr(Stdio::null()); + + // Fix for https://github.com/helix-editor/helix/issues/5424 + if cfg!(unix) { + use std::os::unix::process::CommandExt; + + unsafe { + command_mut = command_mut.pre_exec(|| match libc::setsid() { + -1 => Err(std::io::Error::last_os_error()), + _ => Ok(()), + }); + } + } + + let mut child = command_mut.spawn()?; + + if let Some(input) = input { + let mut stdin = child.stdin.take().context("stdin is missing")?; + stdin + .write_all(input.as_bytes()) + .context("couldn't write in stdin")?; + } + + // TODO: add timer? + let output = child.wait_with_output()?; + + if !output.status.success() { + bail!("clipboard provider {} failed", self.prg); + } + + if pipe_output { + Ok(Some(String::from_utf8(output.stdout)?)) } else { - Cow::Owned(format!("{} ({})", name, provider.yank.command)) + Ok(None) } } + } - match self { - // These names should match the config option names from Serde - Self::Pasteboard => builtin_name("pasteboard", &PASTEBOARD), - Self::Wayland => builtin_name("wayland", &WL_CLIPBOARD), - Self::XClip => builtin_name("x-clip", &XCLIP), - Self::XSel => builtin_name("x-sel", &XSEL), - Self::Win32Yank => builtin_name("win32-yank", &WIN32), - Self::Tmux => builtin_name("tmux", &TMUX), - Self::Termux => builtin_name("termux", &TERMUX), - #[cfg(windows)] - Self::Windows => "windows".into(), - #[cfg(feature = "term")] - Self::Termcode => "termcode".into(), - Self::Custom(command_provider) => Cow::Owned(format!( - "custom ({}+{})", - command_provider.yank.command, command_provider.paste.command - )), - Self::None => "none".into(), - } + #[derive(Debug)] + pub struct Provider { + pub get_cmd: Config, + pub set_cmd: Config, + pub get_primary_cmd: Option<Config>, + pub set_primary_cmd: Option<Config>, } - pub fn get_contents(&self, clipboard_type: &ClipboardType) -> Result<String> { - fn yank_from_builtin( - provider: CommandProvider, - clipboard_type: &ClipboardType, - ) -> Result<String> { + impl ClipboardProvider for Provider { + fn name(&self) -> Cow<str> { + if self.get_cmd.prg != self.set_cmd.prg { + Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg)) + } else { + Cow::Borrowed(self.get_cmd.prg) + } + } + + fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> { match clipboard_type { - ClipboardType::Clipboard => execute_command(&provider.yank, None, true)? - .ok_or(ClipboardError::MissingStdout), + ClipboardType::Clipboard => Ok(self + .get_cmd + .execute(None, true)? + .context("output is missing")?), ClipboardType::Selection => { - if let Some(cmd) = provider.yank_primary.as_ref() { - return execute_command(cmd, None, true)? - .ok_or(ClipboardError::MissingStdout); + if let Some(cmd) = &self.get_primary_cmd { + return cmd.execute(None, true)?.context("output is missing"); } Ok(String::new()) @@ -227,244 +358,56 @@ mod external { } } - match self { - Self::Pasteboard => yank_from_builtin(PASTEBOARD, clipboard_type), - Self::Wayland => yank_from_builtin(WL_CLIPBOARD, clipboard_type), - Self::XClip => yank_from_builtin(XCLIP, clipboard_type), - Self::XSel => yank_from_builtin(XSEL, clipboard_type), - Self::Win32Yank => yank_from_builtin(WIN32, clipboard_type), - Self::Tmux => yank_from_builtin(TMUX, clipboard_type), - Self::Termux => yank_from_builtin(TERMUX, clipboard_type), - #[cfg(target_os = "windows")] - Self::Windows => match clipboard_type { - ClipboardType::Clipboard => { - let contents = - clipboard_win::get_clipboard(clipboard_win::formats::Unicode)?; - Ok(contents) - } - ClipboardType::Selection => Ok(String::new()), - }, - #[cfg(feature = "term")] - Self::Termcode => Err(ClipboardError::ReadingNotSupported), - Self::Custom(command_provider) => { - execute_command(&command_provider.yank, None, true)? - .ok_or(ClipboardError::MissingStdout) - } - Self::None => Err(ClipboardError::ReadingNotSupported), - } - } - - pub fn set_contents(&self, content: &str, clipboard_type: ClipboardType) -> Result<()> { - fn paste_to_builtin( - provider: CommandProvider, - content: &str, - clipboard_type: ClipboardType, - ) -> Result<()> { + fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> { let cmd = match clipboard_type { - ClipboardType::Clipboard => &provider.paste, + ClipboardType::Clipboard => &self.set_cmd, ClipboardType::Selection => { - if let Some(cmd) = provider.paste_primary.as_ref() { + if let Some(cmd) = &self.set_primary_cmd { cmd } else { return Ok(()); } } }; - - execute_command(cmd, Some(content), false).map(|_| ()) - } - - match self { - Self::Pasteboard => paste_to_builtin(PASTEBOARD, content, clipboard_type), - Self::Wayland => paste_to_builtin(WL_CLIPBOARD, content, clipboard_type), - Self::XClip => paste_to_builtin(XCLIP, content, clipboard_type), - Self::XSel => paste_to_builtin(XSEL, content, clipboard_type), - Self::Win32Yank => paste_to_builtin(WIN32, content, clipboard_type), - Self::Tmux => paste_to_builtin(TMUX, content, clipboard_type), - Self::Termux => paste_to_builtin(TERMUX, content, clipboard_type), - #[cfg(target_os = "windows")] - Self::Windows => match clipboard_type { - ClipboardType::Clipboard => { - clipboard_win::set_clipboard(clipboard_win::formats::Unicode, content)?; - Ok(()) - } - ClipboardType::Selection => Ok(()), - }, - #[cfg(feature = "term")] - Self::Termcode => { - use std::io::Write; - use termina::escape::osc::{self, Osc}; - let selection = match clipboard_type { - ClipboardType::Clipboard => osc::Selection::CLIPBOARD, - ClipboardType::Selection => osc::Selection::PRIMARY, - }; - // NOTE: it would be ideal to have the terminal execute this but it _should_ - // work to send this over stdout instead. - let mut stdout = std::io::stdout().lock(); - write!(stdout, "{}", Osc::SetSelection(selection, content))?; - stdout.flush()?; - Ok(()) - } - Self::Custom(command_provider) => match clipboard_type { - ClipboardType::Clipboard => { - execute_command(&command_provider.paste, Some(content), false).map(|_| ()) - } - ClipboardType::Selection => { - if let Some(cmd) = &command_provider.paste_primary { - execute_command(cmd, Some(content), false).map(|_| ()) - } else { - Ok(()) - } - } - }, - Self::None => Ok(()), + cmd.execute(Some(&value), false).map(|_| ()) } } } +} - macro_rules! command_provider { - ($name:ident, - yank => $yank_cmd:literal $( , $yank_arg:literal )* ; - paste => $paste_cmd:literal $( , $paste_arg:literal )* ; ) => { - const $name: CommandProvider = CommandProvider { - yank: Command { - command: Cow::Borrowed($yank_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($yank_arg) ),* ]) - }, - paste: Command { - command: Cow::Borrowed($paste_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($paste_arg) ),* ]) - }, - yank_primary: None, - paste_primary: None, - }; - }; - ($name:ident, - yank => $yank_cmd:literal $( , $yank_arg:literal )* ; - paste => $paste_cmd:literal $( , $paste_arg:literal )* ; - yank_primary => $yank_primary_cmd:literal $( , $yank_primary_arg:literal )* ; - paste_primary => $paste_primary_cmd:literal $( , $paste_primary_arg:literal )* ; ) => { - const $name: CommandProvider = CommandProvider { - yank: Command { - command: Cow::Borrowed($yank_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($yank_arg) ),* ]) - }, - paste: Command { - command: Cow::Borrowed($paste_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($paste_arg) ),* ]) - }, - yank_primary: Some(Command { - command: Cow::Borrowed($yank_primary_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($yank_primary_arg) ),* ]) - }), - paste_primary: Some(Command { - command: Cow::Borrowed($paste_primary_cmd), - args: Cow::Borrowed(&[ $( Cow::Borrowed($paste_primary_arg) ),* ]) - }), - }; - }; - } - - command_provider! { - TMUX, - yank => "tmux", "save-buffer", "-"; - paste => "tmux", "load-buffer", "-w", "-"; - } - command_provider! { - PASTEBOARD, - yank => "pbpaste"; - paste => "pbcopy"; - } - command_provider! { - WL_CLIPBOARD, - yank => "wl-paste", "--no-newline"; - paste => "wl-copy", "--type", "text/plain"; - yank_primary => "wl-paste", "-p", "--no-newline"; - paste_primary => "wl-copy", "-p", "--type", "text/plain"; - } - command_provider! { - XCLIP, - yank => "xclip", "-o", "-selection", "clipboard"; - paste => "xclip", "-i", "-selection", "clipboard"; - yank_primary => "xclip", "-o"; - paste_primary => "xclip", "-i"; - } - command_provider! { - XSEL, - yank => "xsel", "-o", "-b"; - paste => "xsel", "-i", "-b"; - yank_primary => "xsel", "-o"; - paste_primary => "xsel", "-i"; - } - command_provider! { - WIN32, - yank => "win32yank.exe", "-o", "--lf"; - paste => "win32yank.exe", "-i", "--crlf"; - } - command_provider! { - TERMUX, - yank => "termux-clipboard-get"; - paste => "termux-clipboard-set"; - } - - fn execute_command( - cmd: &Command, - input: Option<&str>, - pipe_output: bool, - ) -> Result<Option<String>> { - use std::io::Write; - use std::process::{Command, Stdio}; - - let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null); - let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null); - - let mut command: Command = Command::new(cmd.command.as_ref()); - - #[allow(unused_mut)] - let mut command_mut: &mut Command = command - .args(cmd.args.iter().map(AsRef::as_ref)) - .stdin(stdin) - .stdout(stdout) - .stderr(Stdio::null()); - - // Fix for https://github.com/helix-editor/helix/issues/5424 - #[cfg(unix)] - { - use std::os::unix::process::CommandExt; - - unsafe { - command_mut = command_mut.pre_exec(|| match libc::setsid() { - -1 => Err(std::io::Error::last_os_error()), - _ => Ok(()), - }); - } - } +#[cfg(target_os = "windows")] +mod provider { + use super::{ClipboardProvider, ClipboardType}; + use anyhow::Result; + use std::borrow::Cow; - let mut child = command_mut.spawn()?; + #[derive(Default, Debug)] + pub struct WindowsProvider; - if let Some(input) = input { - let mut stdin = child.stdin.take().ok_or(ClipboardError::StdinWriteFailed)?; - stdin - .write_all(input.as_bytes()) - .map_err(|_| ClipboardError::StdinWriteFailed)?; + impl ClipboardProvider for WindowsProvider { + fn name(&self) -> Cow<str> { + log::debug!("Using clipboard-win to interact with the system clipboard"); + Cow::Borrowed("clipboard-win") } - // TODO: add timer? - let output = child.wait_with_output()?; - - if !output.status.success() { - log::error!( - "clipboard provider {} failed with stderr: \"{}\"", - cmd.command, - String::from_utf8_lossy(&output.stderr) - ); - return Err(ClipboardError::CommandFailed); + 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()), + } } - if pipe_output { - Ok(Some(String::from_utf8(output.stdout)?)) - } else { - Ok(None) + 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(()) } } } |