1// Implementation reference: https://github.com/neovim/neovim/blob/f2906a4669a2eef6d7bf86a29648793d63c98949/runtime/autoload/provider/clipboard.vim#L68-L152
6pub enum ClipboardType {
11pub trait ClipboardProvider: std::fmt::Debug {
12 fn name(&self) -> Cow<str>;
13 fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String>;
14 fn set_contents(&mut self, contents: String, clipboard_type: ClipboardType) -> Result<()>;
17macro_rules! command_provider {
18 (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
19 Box::new(provider::CommandProvider {
20 get_cmd: provider::CommandConfig {
22 args: &[ $( $get_arg ),* ],
24 set_cmd: provider::CommandConfig {
26 args: &[ $( $set_arg ),* ],
28 get_primary_cmd: None,
29 set_primary_cmd: None,
33 (paste => $get_prg:literal $( , $get_arg:literal )* ;
34 copy => $set_prg:literal $( , $set_arg:literal )* ;
35 primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
36 primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
38 Box::new(provider::CommandProvider {
39 get_cmd: provider::CommandConfig {
41 args: &[ $( $get_arg ),* ],
43 set_cmd: provider::CommandConfig {
45 args: &[ $( $set_arg ),* ],
47 get_primary_cmd: Some(provider::CommandConfig {
49 args: &[ $( $pr_get_arg ),* ],
51 set_primary_cmd: Some(provider::CommandConfig {
53 args: &[ $( $pr_set_arg ),* ],
59pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
60 // TODO: support for user-defined provider, probably when we have plugin support by setting a
63 if exists("pbcopy") && exists("pbpaste") {
68 } else if env_var_is_set("WAYLAND_DISPLAY") && exists("wl-copy") && exists("wl-paste") {
70 paste => "wl-paste", "--no-newline";
71 copy => "wl-copy", "--type", "text/plain";
72 primary_paste => "wl-paste", "-p", "--no-newline";
73 primary_copy => "wl-copy", "-p", "--type", "text/plain";
75 } else if env_var_is_set("DISPLAY") && exists("xclip") {
77 paste => "xclip", "-o", "-selection", "clipboard";
78 copy => "xclip", "-i", "-selection", "clipboard";
79 primary_paste => "xclip", "-o";
80 primary_copy => "xclip", "-i";
82 } else if env_var_is_set("DISPLAY") && exists("xsel") && is_exit_success("xsel", &["-o", "-b"])
84 // FIXME: check performance of is_exit_success
86 paste => "xsel", "-o", "-b";
87 copy => "xsel", "--nodetach", "-i", "-b";
88 primary_paste => "xsel", "-o";
89 primary_copy => "xsel", "-i";
91 } else if exists("lemonade") {
93 paste => "lemonade", "paste";
94 copy => "lemonade", "copy";
96 } else if exists("doitclient") {
98 paste => "doitclient", "wclip", "-r";
99 copy => "doitclient", "wclip";
101 } else if exists("win32yank.exe") {
102 // FIXME: does it work within WSL?
104 paste => "win32yank.exe", "-o", "--lf";
105 copy => "win32yank.exe", "-i", "--crlf";
107 } else if exists("termux-clipboard-set") && exists("termux-clipboard-get") {
109 paste => "termux-clipboard-get";
110 copy => "termux-clipboard-set";
112 } else if env_var_is_set("TMUX") && exists("tmux") {
114 paste => "tmux", "save-buffer", "-";
115 copy => "tmux", "load-buffer", "-";
118 #[cfg(target_os = "windows")]
119 return Box::new(provider::WindowsProvider::new());
121 #[cfg(not(target_os = "windows"))]
122 return Box::new(provider::NopProvider::new());
126fn exists(executable_name: &str) -> bool {
127 which::which(executable_name).is_ok()
130fn env_var_is_set(env_var_name: &str) -> bool {
131 std::env::var_os(env_var_name).is_some()
134fn is_exit_success(program: &str, args: &[&str]) -> bool {
135 std::process::Command::new(program)
139 .and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized
144 use super::{ClipboardProvider, ClipboardType};
145 use anyhow::{bail, Context as _, Result};
146 use std::borrow::Cow;
149 pub struct NopProvider {
156 // Only dead_code on Windows.
157 pub fn new() -> Self {
160 primary_buf: String::new(),
165 impl ClipboardProvider for NopProvider {
166 fn name(&self) -> Cow<str> {
167 Cow::Borrowed("none")
170 fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
171 let value = match clipboard_type {
172 ClipboardType::Clipboard => self.buf.clone(),
173 ClipboardType::Selection => self.primary_buf.clone(),
179 fn set_contents(&mut self, content: String, clipboard_type: ClipboardType) -> Result<()> {
180 match clipboard_type {
181 ClipboardType::Clipboard => self.buf = content,
182 ClipboardType::Selection => self.primary_buf = content,
188 #[cfg(target_os = "windows")]
190 pub struct WindowsProvider {
191 selection_buf: String,
194 #[cfg(target_os = "windows")]
195 impl WindowsProvider {
196 pub fn new() -> Self {
198 selection_buf: String::new(),
203 #[cfg(target_os = "windows")]
204 impl ClipboardProvider for WindowsProvider {
205 fn name(&self) -> Cow<str> {
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 => {}
231 pub struct CommandConfig {
232 pub prg: &'static str,
233 pub args: &'static [&'static str],
237 fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> {
239 use std::process::{Command, Stdio};
241 let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null);
242 let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null);
244 let mut child = Command::new(self.prg)
248 .stderr(Stdio::null())
251 if let Some(input) = input {
252 let mut stdin = child.stdin.take().context("stdin is missing")?;
254 .write_all(input.as_bytes())
255 .context("couldn't write in stdin")?;
259 let output = child.wait_with_output()?;
261 if !output.status.success() {
262 bail!("clipboard provider {} failed", self.prg);
266 Ok(Some(String::from_utf8(output.stdout)?))
274 pub struct CommandProvider {
275 pub get_cmd: CommandConfig,
276 pub set_cmd: CommandConfig,
277 pub get_primary_cmd: Option<CommandConfig>,
278 pub set_primary_cmd: Option<CommandConfig>,
281 impl ClipboardProvider for CommandProvider {
282 fn name(&self) -> Cow<str> {
283 if self.get_cmd.prg != self.set_cmd.prg {
284 Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg))
286 Cow::Borrowed(self.get_cmd.prg)
290 fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
291 match clipboard_type {
292 ClipboardType::Clipboard => Ok(self
294 .execute(None, true)?
295 .context("output is missing")?),
296 ClipboardType::Selection => {
297 if let Some(cmd) = &self.get_primary_cmd {
298 return cmd.execute(None, true)?.context("output is missing");
306 fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> {
307 let cmd = match clipboard_type {
308 ClipboardType::Clipboard => &self.set_cmd,
309 ClipboardType::Selection => {
310 if let Some(cmd) = &self.set_primary_cmd {
317 cmd.execute(Some(&value), false).map(|_| ())
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list