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.rs709
1 files changed, 316 insertions, 393 deletions
diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs
index 2641d98f..4f83fb4d 100644
--- a/helix-view/src/clipboard.rs
+++ b/helix-view/src/clipboard.rs
@@ -1,225 +1,336 @@
// 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::info!(
+ "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(windows)]
+pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
+ Box::new(provider::WindowsProvider::default())
+}
-#[cfg(not(target_arch = "wasm32"))]
-pub use external::ClipboardProvider;
-#[cfg(target_arch = "wasm32")]
-pub use noop::ClipboardProvider;
+#[cfg(target_os = "macos")]
+pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
+ use crate::env::binary_exists;
-// Clipboard not supported for wasm
-#[cfg(target_arch = "wasm32")]
-mod noop {
- use super::*;
+ 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_os = "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_os = "wasm32", target_os = "macos")))]
+pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
+ use crate::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())
+ }
+}
+
+#[cfg(not(target_os = "windows"))]
+pub mod provider {
+ use super::{ClipboardProvider, ClipboardType};
+ use anyhow::Result;
+ use std::borrow::Cow;
- pub fn name(&self) -> Cow<str> {
- "none".into()
+ #[cfg(feature = "term")]
+ mod osc52 {
+ use {super::ClipboardType, crate::base64, crossterm};
+
+ #[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 _, Result};
#[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(|| ())) // TODO: use then_some when stabilized
+ .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 child = Command::new(self.prg)
+ .args(self.args)
+ .stdin(stdin)
+ .stdout(stdout)
+ .stderr(Stdio::null())
+ .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 +338,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::info!("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(())
}
}
}