|
| 1 | +#[cfg(unix)] |
| 2 | +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; |
1 | 3 | use std::{ |
2 | 4 | fs::File, |
3 | | - io::{self, Read, Write}, |
| 5 | + io::{self, IsTerminal, Read, Write}, |
4 | 6 | path::PathBuf, |
5 | 7 | }; |
6 | 8 |
|
@@ -70,6 +72,12 @@ pub struct Args { |
70 | 72 | " |
71 | 73 | )] |
72 | 74 | default_filter: Option<String>, |
| 75 | + |
| 76 | + #[arg( |
| 77 | + long = "write-to-stdout", |
| 78 | + help = "Write the current JSON result to stdout when exiting" |
| 79 | + )] |
| 80 | + write_to_stdout: bool, |
73 | 81 | } |
74 | 82 |
|
75 | 83 | /// Parses the input based on the provided arguments. |
@@ -141,6 +149,80 @@ fn determine_config_file(config_path: Option<PathBuf>) -> anyhow::Result<PathBuf |
141 | 149 | Ok(default_path) |
142 | 150 | } |
143 | 151 |
|
| 152 | +struct StdoutRedirect { |
| 153 | + #[cfg(unix)] |
| 154 | + saved_stdout: Option<OwnedFd>, |
| 155 | +} |
| 156 | + |
| 157 | +impl StdoutRedirect { |
| 158 | + fn for_tui(write_to_stdout: bool) -> anyhow::Result<Self> { |
| 159 | + if !write_to_stdout || io::stdout().is_terminal() { |
| 160 | + return Ok(Self { |
| 161 | + #[cfg(unix)] |
| 162 | + saved_stdout: None, |
| 163 | + }); |
| 164 | + } |
| 165 | + |
| 166 | + #[cfg(unix)] |
| 167 | + { |
| 168 | + let tty = File::options() |
| 169 | + .read(true) |
| 170 | + .write(true) |
| 171 | + .open("/dev/tty") |
| 172 | + .map_err(|e| anyhow!("Failed to open /dev/tty for TUI rendering: {e}"))?; |
| 173 | + |
| 174 | + let saved_fd = unsafe { libc::dup(libc::STDOUT_FILENO) }; |
| 175 | + if saved_fd < 0 { |
| 176 | + return Err(anyhow!( |
| 177 | + "Failed to duplicate stdout: {}", |
| 178 | + io::Error::last_os_error() |
| 179 | + )); |
| 180 | + } |
| 181 | + |
| 182 | + let redirected = unsafe { libc::dup2(tty.as_raw_fd(), libc::STDOUT_FILENO) }; |
| 183 | + if redirected < 0 { |
| 184 | + let _ = unsafe { libc::close(saved_fd) }; |
| 185 | + return Err(anyhow!( |
| 186 | + "Failed to redirect stdout to /dev/tty: {}", |
| 187 | + io::Error::last_os_error() |
| 188 | + )); |
| 189 | + } |
| 190 | + |
| 191 | + Ok(Self { |
| 192 | + saved_stdout: Some(unsafe { OwnedFd::from_raw_fd(saved_fd) }), |
| 193 | + }) |
| 194 | + } |
| 195 | + |
| 196 | + #[cfg(not(unix))] |
| 197 | + { |
| 198 | + Err(anyhow!( |
| 199 | + "`--write-to-stdout` with piped stdout is not supported on this platform" |
| 200 | + )) |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + fn restore(&mut self) -> anyhow::Result<()> { |
| 205 | + #[cfg(unix)] |
| 206 | + if let Some(saved_stdout) = self.saved_stdout.take() { |
| 207 | + let restored = unsafe { libc::dup2(saved_stdout.as_raw_fd(), libc::STDOUT_FILENO) }; |
| 208 | + if restored < 0 { |
| 209 | + return Err(anyhow!( |
| 210 | + "Failed to restore stdout: {}", |
| 211 | + io::Error::last_os_error() |
| 212 | + )); |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + Ok(()) |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +impl Drop for StdoutRedirect { |
| 221 | + fn drop(&mut self) { |
| 222 | + let _ = self.restore(); |
| 223 | + } |
| 224 | +} |
| 225 | + |
144 | 226 | #[tokio::main] |
145 | 227 | async fn main() -> anyhow::Result<()> { |
146 | 228 | let args = Args::parse(); |
@@ -194,17 +276,31 @@ async fn main() -> anyhow::Result<()> { |
194 | 276 | config.keybinds.on_editor.clone(), |
195 | 277 | ); |
196 | 278 |
|
| 279 | + let mut stdout_redirect = StdoutRedirect::for_tui(args.write_to_stdout)?; |
| 280 | + |
197 | 281 | // TODO: put all logics here. |
198 | | - prompt::run( |
| 282 | + let maybe_output = prompt::run( |
199 | 283 | item, |
200 | 284 | config.reactivity_control, |
201 | 285 | provider, |
202 | 286 | editor, |
203 | 287 | loading_suggestions_task, |
204 | 288 | config.no_hint, |
205 | 289 | config.keybinds, |
| 290 | + args.write_to_stdout, |
206 | 291 | ) |
207 | | - .await?; |
| 292 | + .await; |
| 293 | + |
| 294 | + stdout_redirect.restore()?; |
| 295 | + let maybe_output = maybe_output?; |
| 296 | + |
| 297 | + if let Some(output) = maybe_output { |
| 298 | + let mut stdout = io::stdout(); |
| 299 | + stdout.write_all(output.as_bytes())?; |
| 300 | + if !output.ends_with('\n') { |
| 301 | + stdout.write_all(b"\n")?; |
| 302 | + } |
| 303 | + } |
208 | 304 |
|
209 | 305 | Ok(()) |
210 | 306 | } |
0 commit comments