1use crate::{editor::Action, input::Event, util::normalize_line_endings};
4use std::{
5 env,
6 ffi::OsStr,
7 fmt,
8 io::{self, Read, Write},
9 path::Path,
10 process::{Command, Stdio},
11 sync::mpsc::Sender,
12 thread::spawn,
13};
14
15pub trait System: fmt::Debug {
17 fn set_clipboard(&mut self, s: &str) -> io::Result<()>;
19
20 fn read_clipboard(&self) -> io::Result<String>;
22
23 fn run_command_blocking<I, S>(
25 &self,
26 cmd: &str,
27 args: I,
28 cwd: &Path,
29 bufid: usize,
30 ) -> io::Result<String>
31 where
32 I: IntoIterator<Item = S>,
33 S: AsRef<OsStr>,
34 {
35 run_command_blocking(cmd, args, cwd, bufid)
36 }
37
38 fn run_command<I, S>(&self, cmd: &str, args: I, cwd: &Path, bufid: usize, tx: Sender<Event>)
41 where
42 I: IntoIterator<Item = S>,
43 S: AsRef<OsStr>,
44 {
45 run_command(cmd, args, cwd, bufid, tx)
46 }
47
48 fn pipe_through_command<I, S>(
50 &self,
51 cmd: &str,
52 args: I,
53 input: &str,
54 cwd: &Path,
55 bufid: usize,
56 ) -> io::Result<String>
57 where
58 I: IntoIterator<Item = S>,
59 S: AsRef<OsStr>,
60 {
61 pipe_through_command(cmd, args, input, cwd, bufid)
62 }
63}
64
65#[derive(Debug, Clone, Copy)]
67pub struct DefaultSystem;
68
69#[cfg(target_os = "linux")]
70impl System for DefaultSystem {
71 fn set_clipboard(&mut self, s: &str) -> io::Result<()> {
72 let mut child = Command::new("xclip")
73 .args(["-selection", "clipboard", "-i"])
74 .stdin(Stdio::piped())
75 .spawn()?;
76
77 child.stdin.take().unwrap().write_all(s.as_bytes())
78 }
79
80 fn read_clipboard(&self) -> io::Result<String> {
81 let output = Command::new("xclip")
82 .args(["-selection", "clipboard", "-o"])
83 .output()?;
84
85 Ok(String::from_utf8(output.stdout).unwrap_or_default())
86 }
87}
88
89#[cfg(target_os = "macos")]
90impl System for DefaultSystem {
91 fn set_clipboard(&mut self, s: &str) -> io::Result<()> {
92 let mut child = Command::new("pbcopy").stdin(Stdio::piped()).spawn()?;
93
94 child.stdin.take().unwrap().write_all(s.as_bytes())
95 }
96
97 fn read_clipboard(&self) -> io::Result<String> {
98 let output = Command::new("pbpaste").output()?;
99
100 Ok(String::from_utf8(output.stdout).unwrap_or_default())
101 }
102}
103
104fn prepare_command<I, S>(cmd: &str, args: I, cwd: &Path, bufid: usize) -> Command
105where
106 I: IntoIterator<Item = S>,
107 S: AsRef<OsStr>,
108{
109 let path = env::var("PATH").unwrap();
110 let home = env::var("HOME").unwrap();
111 let mut command = Command::new(cmd);
112 command
113 .env("PATH", format!("{home}/.ad/bin:{path}"))
114 .env("bufid", bufid.to_string())
115 .current_dir(cwd)
116 .args(args);
117
118 command
119}
120
121fn run_command_blocking<I, S>(cmd: &str, args: I, cwd: &Path, bufid: usize) -> io::Result<String>
122where
123 I: IntoIterator<Item = S>,
124 S: AsRef<OsStr>,
125{
126 let output = prepare_command(cmd, args, cwd, bufid).output()?;
127 let mut stdout = String::from_utf8(output.stdout).unwrap_or_default();
128 let stderr = String::from_utf8(output.stderr).unwrap_or_default();
129 stdout.push_str(&stderr);
130
131 Ok(normalize_line_endings(stdout))
132}
133
134fn run_command<I, S>(cmd: &str, args: I, cwd: &Path, bufid: usize, tx: Sender<Event>)
135where
136 I: IntoIterator<Item = S>,
137 S: AsRef<OsStr>,
138{
139 let mut command = prepare_command(cmd, args, cwd, bufid);
140
141 spawn(move || {
142 let output = match command.output() {
143 Ok(output) => output,
144 Err(err) => {
145 _ = tx.send(Event::Action(Action::SetStatusMessage {
146 message: err.to_string(),
147 }));
148 return;
149 }
150 };
151
152 let mut content = String::from_utf8(output.stdout).unwrap_or_default();
153 let stderr = String::from_utf8(output.stderr).unwrap_or_default();
154 content.push_str(&stderr);
155 if content.is_empty() {
156 return;
157 }
158 _ = tx.send(Event::Action(Action::AppendToOutputBuffer {
159 bufid,
160 content: normalize_line_endings(content),
161 }));
162 });
163}
164
165pub fn pipe_through_command<I, S>(
167 cmd: &str,
168 args: I,
169 input: &str,
170 cwd: &Path,
171 bufid: usize,
172) -> io::Result<String>
173where
174 I: IntoIterator<Item = S>,
175 S: AsRef<OsStr>,
176{
177 let mut child = prepare_command(cmd, args, cwd, bufid)
178 .stdin(Stdio::piped())
179 .stdout(Stdio::piped())
180 .stderr(Stdio::piped())
181 .spawn()?;
182
183 let mut buf = String::new();
184 child.stdin.take().unwrap().write_all(input.as_bytes())?;
185 child.stdout.take().unwrap().read_to_string(&mut buf)?;
186 child.stderr.take().unwrap().read_to_string(&mut buf)?;
187 _ = child.wait();
188
189 Ok(normalize_line_endings(buf))
190}