on master help sign in
Refactor clipboard to make it easier to feature gate std::process
8611c5bBlaž Hrastnik
helix-view/src/clipboard.rs+106−98
@@ -16,12 +16,12 @@ pub trait ClipboardProvider: std::fmt::Debug {
16
16
17macro_rules! command_provider {
17macro_rules! command_provider {
18 (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
18 (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
19− Box::new(provider::CommandProvider {
19+ Box::new(provider::command::Provider {
20− get_cmd: provider::CommandConfig {
20+ get_cmd: provider::command::Config {
21 prg: $get_prg,
21 prg: $get_prg,
22 args: &[ $( $get_arg ),* ],
22 args: &[ $( $get_arg ),* ],
23 },
23 },
24− set_cmd: provider::CommandConfig {
24+ set_cmd: provider::command::Config {
25 prg: $set_prg,
25 prg: $set_prg,
26 args: &[ $( $set_arg ),* ],
26 args: &[ $( $set_arg ),* ],
27 },
27 },
@@ -35,20 +35,20 @@ macro_rules! command_provider {
35 primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
35 primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
36 primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
36 primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
37 ) => {{
37 ) => {{
38− Box::new(provider::CommandProvider {
38+ Box::new(provider::command::Provider {
39− get_cmd: provider::CommandConfig {
39+ get_cmd: provider::command::Config {
40 prg: $get_prg,
40 prg: $get_prg,
41 args: &[ $( $get_arg ),* ],
41 args: &[ $( $get_arg ),* ],
42 },
42 },
43− set_cmd: provider::CommandConfig {
43+ set_cmd: provider::command::Config {
44 prg: $set_prg,
44 prg: $set_prg,
45 args: &[ $( $set_arg ),* ],
45 args: &[ $( $set_arg ),* ],
46 },
46 },
47− get_primary_cmd: Some(provider::CommandConfig {
47+ get_primary_cmd: Some(provider::command::Config {
48 prg: $pr_get_prg,
48 prg: $pr_get_prg,
49 args: &[ $( $pr_get_arg ),* ],
49 args: &[ $( $pr_get_arg ),* ],
50 }),
50 }),
51− set_primary_cmd: Some(provider::CommandConfig {
51+ set_primary_cmd: Some(provider::command::Config {
52 prg: $pr_set_prg,
52 prg: $pr_set_prg,
53 args: &[ $( $pr_set_arg ),* ],
53 args: &[ $( $pr_set_arg ),* ],
54 }),
54 }),
@@ -63,6 +63,8 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
63
63
64#[cfg(target_os = "macos")]
64#[cfg(target_os = "macos")]
65pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
65pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
66+ use command::exists;
67+
66 if exists("pbcopy") && exists("pbpaste") {
68 if exists("pbcopy") && exists("pbpaste") {
67 command_provider! {
69 command_provider! {
68 paste => "pbpaste";
70 paste => "pbpaste";
@@ -75,6 +77,7 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
75
77
76#[cfg(not(any(windows, target_os = "macos")))]
78#[cfg(not(any(windows, target_os = "macos")))]
77pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
79pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
80+ use provider::command::{env_var_is_set, exists, is_exit_success};
78 // TODO: support for user-defined provider, probably when we have plugin support by setting a
81 // TODO: support for user-defined provider, probably when we have plugin support by setting a
79 // variable?
82 // variable?
80
83
@@ -116,26 +119,9 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
116 }
119 }
117}
120}
118
121
119−fn exists(executable_name: &str) -> bool {
120− which::which(executable_name).is_ok()
121−}
122−
123−fn env_var_is_set(env_var_name: &str) -> bool {
124− std::env::var_os(env_var_name).is_some()
125−}
126−
127−fn is_exit_success(program: &str, args: &[&str]) -> bool {
128− std::process::Command::new(program)
129− .args(args)
130− .output()
131− .ok()
132− .and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized
133− .is_some()
134−}
135−
136mod provider {
122mod provider {
137 use super::{ClipboardProvider, ClipboardType};
123 use super::{ClipboardProvider, ClipboardType};
138− use anyhow::{bail, Context as _, Result};
124+ use anyhow::Result;
139 use std::borrow::Cow;
125 use std::borrow::Cow;
140
126
141 #[cfg(not(target_os = "windows"))]
127 #[cfg(not(target_os = "windows"))]
@@ -210,94 +196,116 @@ mod provider {
210 }
196 }
211 }
197 }
212
198
213− #[derive(Debug)]
199+ pub mod command {
214− pub struct CommandConfig {
200+ use super::*;
215− pub prg: &'static str,
201+ use anyhow::{bail, Context as _, Result};
216− pub args: &'static [&'static str],
217− }
218
202
219− impl CommandConfig {
203+ pub fn exists(executable_name: &str) -> bool {
220− fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> {
204+ which::which(executable_name).is_ok()
221− use std::io::Write;
222− use std::process::{Command, Stdio};
223−
224− let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null);
225− let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null);
226−
227− let mut child = Command::new(self.prg)
228− .args(self.args)
229− .stdin(stdin)
230− .stdout(stdout)
231− .stderr(Stdio::null())
232− .spawn()?;
233−
234− if let Some(input) = input {
235− let mut stdin = child.stdin.take().context("stdin is missing")?;
236− stdin
237− .write_all(input.as_bytes())
238− .context("couldn't write in stdin")?;
239− }
205+ }
240
206
241− // TODO: add timer?
207+ pub fn env_var_is_set(env_var_name: &str) -> bool {
242− let output = child.wait_with_output()?;
208+ std::env::var_os(env_var_name).is_some()
209+ }
243
210
211+ pub fn is_exit_success(program: &str, args: &[&str]) -> bool {
212+ std::process::Command::new(program)
213+ .args(args)
244− if !output.status.success() {
214+ .output()
245− bail!("clipboard provider {} failed", self.prg);
215+ .ok()
216+ .and_then(|out| out.status.success().then(|| ())) // TODO: use then_some when stabilized
217+ .is_some()
246− }
218+ }
247
219
248− if pipe_output {
220+ #[derive(Debug)]
249− Ok(Some(String::from_utf8(output.stdout)?))
221+ pub struct Config {
250− } else {
222+ pub prg: &'static str,
251− Ok(None)
223+ pub args: &'static [&'static str],
252− }
253 }
224 }
254− }
255
225
256− #[derive(Debug)]
226+ impl Config {
257− pub struct CommandProvider {
227+ fn execute(&self, input: Option<&str>, pipe_output: bool) -> Result<Option<String>> {
258− pub get_cmd: CommandConfig,
228+ use std::io::Write;
259− pub set_cmd: CommandConfig,
229+ use std::process::{Command, Stdio};
260− pub get_primary_cmd: Option<CommandConfig>,
230+
261− pub set_primary_cmd: Option<CommandConfig>,
231+ let stdin = input.map(|_| Stdio::piped()).unwrap_or_else(Stdio::null);
232+ let stdout = pipe_output.then(Stdio::piped).unwrap_or_else(Stdio::null);
233+
234+ let mut child = Command::new(self.prg)
235+ .args(self.args)
236+ .stdin(stdin)
237+ .stdout(stdout)
238+ .stderr(Stdio::null())
239+ .spawn()?;
240+
241+ if let Some(input) = input {
242+ let mut stdin = child.stdin.take().context("stdin is missing")?;
243+ stdin
244+ .write_all(input.as_bytes())
245+ .context("couldn't write in stdin")?;
262− }
246+ }
263
247
264− impl ClipboardProvider for CommandProvider {
248+ // TODO: add timer?
265− fn name(&self) -> Cow<str> {
249+ let output = child.wait_with_output()?;
266− if self.get_cmd.prg != self.set_cmd.prg {
250+
267− Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg))
251+ if !output.status.success() {
252+ bail!("clipboard provider {} failed", self.prg);
253+ }
254+
255+ if pipe_output {
256+ Ok(Some(String::from_utf8(output.stdout)?))
268− } else {
257+ } else {
269− Cow::Borrowed(self.get_cmd.prg)
258+ Ok(None)
259+ }
270 }
260 }
271 }
261 }
272
262
273− fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
263+ #[derive(Debug)]
274− match clipboard_type {
264+ pub struct Provider {
275− ClipboardType::Clipboard => Ok(self
276− .get_cmd
265+ pub get_cmd: Config,
277− .execute(None, true)?
266+ pub set_cmd: Config,
278− .context("output is missing")?),
279− ClipboardType::Selection => {
280− if let Some(cmd) = &self.get_primary_cmd {
267+ pub get_primary_cmd: Option<Config>,
281− return cmd.execute(None, true)?.context("output is missing");
268+ pub set_primary_cmd: Option<Config>,
282− }
269+ }
283
270
284− Ok(String::new())
271+ impl ClipboardProvider for Provider {
272+ fn name(&self) -> Cow<str> {
273+ if self.get_cmd.prg != self.set_cmd.prg {
274+ Cow::Owned(format!("{}+{}", self.get_cmd.prg, self.set_cmd.prg))
275+ } else {
276+ Cow::Borrowed(self.get_cmd.prg)
285 }
277 }
286 }
278 }
287− }
288
279
289− fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> {
280+ fn get_contents(&self, clipboard_type: ClipboardType) -> Result<String> {
290− let cmd = match clipboard_type {
281+ match clipboard_type {
291− ClipboardType::Clipboard => &self.set_cmd,
282+ ClipboardType::Clipboard => Ok(self
283+ .get_cmd
284+ .execute(None, true)?
285+ .context("output is missing")?),
292− ClipboardType::Selection => {
286+ ClipboardType::Selection => {
293− if let Some(cmd) = &self.set_primary_cmd {
287+ if let Some(cmd) = &self.get_primary_cmd {
294− cmd
288+ return cmd.execute(None, true)?.context("output is missing");
295− } else {
289+ }
296− return Ok(());
290+
291+ Ok(String::new())
297 }
292 }
298 }
293 }
299− };
294+ }
295+
296+ fn set_contents(&mut self, value: String, clipboard_type: ClipboardType) -> Result<()> {
297+ let cmd = match clipboard_type {
298+ ClipboardType::Clipboard => &self.set_cmd,
299+ ClipboardType::Selection => {
300+ if let Some(cmd) = &self.set_primary_cmd {
301+ cmd
302+ } else {
303+ return Ok(());
304+ }
305+ }
306+ };
300− cmd.execute(Some(&value), false).map(|_| ())
307+ cmd.execute(Some(&value), false).map(|_| ())
308+ }
301 }
309 }
302 }
310 }
303}
311}