Add option to pipe from stdin on cli (#16084)

Vitor Ramos created

Closes #5044

Release Notes:

- Linux: Added CLI pipe support.

Change summary

Cargo.lock             |  1 +
crates/cli/Cargo.toml  |  1 +
crates/cli/src/main.rs | 33 ++++++++++++++++++++++++++++++++-
3 files changed, 34 insertions(+), 1 deletion(-)

Detailed changes

Cargo.lock 🔗

@@ -2266,6 +2266,7 @@ dependencies = [
  "plist",
  "release_channel",
  "serde",
+ "tempfile",
  "util",
 ]
 

crates/cli/Cargo.toml 🔗

@@ -26,6 +26,7 @@ paths.workspace = true
 release_channel.workspace = true
 serde.workspace = true
 util.workspace = true
+tempfile.workspace = true
 
 [target.'cfg(target_os = "linux")'.dependencies]
 exec.workspace =  true

crates/cli/src/main.rs 🔗

@@ -11,6 +11,7 @@ use std::{
     sync::Arc,
     thread::{self, JoinHandle},
 };
+use tempfile::NamedTempFile;
 use util::paths::PathWithPosition;
 
 struct Detect;
@@ -22,7 +23,11 @@ trait InstalledApp {
 }
 
 #[derive(Parser, Debug)]
-#[command(name = "zed", disable_version_flag = true)]
+#[command(
+    name = "zed",
+    disable_version_flag = true,
+    after_help = "To read from stdin, append '-' (e.g. 'ps axf | zed -')"
+)]
 struct Args {
     /// Wait for all of the given paths to be opened/closed before exiting.
     #[arg(short, long)]
@@ -120,6 +125,7 @@ fn main() -> Result<()> {
     let exit_status = Arc::new(Mutex::new(None));
     let mut paths = vec![];
     let mut urls = vec![];
+    let mut stdin_tmp_file: Option<fs::File> = None;
     for path in args.paths_with_position.iter() {
         if path.starts_with("zed://")
             || path.starts_with("http://")
@@ -128,6 +134,11 @@ fn main() -> Result<()> {
             || path.starts_with("ssh://")
         {
             urls.push(path.to_string());
+        } else if path == "-" && args.paths_with_position.len() == 1 {
+            let file = NamedTempFile::new()?;
+            paths.push(file.path().to_string_lossy().to_string());
+            let (file, _) = file.keep()?;
+            stdin_tmp_file = Some(file);
         } else {
             paths.push(parse_path_with_position(path)?)
         }
@@ -162,11 +173,31 @@ fn main() -> Result<()> {
         }
     });
 
+    let pipe_handle: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
+        if let Some(mut tmp_file) = stdin_tmp_file {
+            let mut stdin = std::io::stdin().lock();
+            if io::IsTerminal::is_terminal(&stdin) {
+                return Ok(());
+            }
+            let mut buffer = [0; 8 * 1024];
+            loop {
+                let bytes_read = io::Read::read(&mut stdin, &mut buffer)?;
+                if bytes_read == 0 {
+                    break;
+                }
+                io::Write::write(&mut tmp_file, &buffer[..bytes_read])?;
+            }
+            io::Write::flush(&mut tmp_file)?;
+        }
+        Ok(())
+    });
+
     if args.foreground {
         app.run_foreground(url)?;
     } else {
         app.launch(url)?;
         sender.join().unwrap()?;
+        pipe_handle.join().unwrap()?;
     }
 
     if let Some(exit_status) = exit_status.lock().take() {