cli: Support --foreground for debugging (#11819)

Conrad Irwin created

Release Notes:

- Added `--foreground` to the cli to allow running zed on the current
PTY.

Change summary

crates/cli/src/main.rs         | 78 ++++++++++++++++++++++++++---------
docs/src/remote-development.md | 20 ++++++---
2 files changed, 70 insertions(+), 28 deletions(-)

Detailed changes

crates/cli/src/main.rs 🔗

@@ -4,8 +4,10 @@ use anyhow::{Context, Result};
 use clap::Parser;
 use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
 use std::{
-    env, fs,
+    env, fs, io,
     path::{Path, PathBuf},
+    process::ExitStatus,
+    thread::{self, JoinHandle},
 };
 use util::paths::PathLikeWithPosition;
 
@@ -14,6 +16,7 @@ struct Detect;
 trait InstalledApp {
     fn zed_version_string(&self) -> String;
     fn launch(&self, ipc_url: String) -> anyhow::Result<()>;
+    fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus>;
 }
 
 #[derive(Parser, Debug)]
@@ -37,6 +40,9 @@ struct Args {
     /// Print Zed's version and the app path.
     #[arg(short, long)]
     version: bool,
+    /// Run zed in the foreground (useful for debugging)
+    #[arg(long)]
+    foreground: bool,
     /// Custom path to Zed.app or the zed binary
     #[arg(long)]
     zed: Option<PathBuf>,
@@ -99,10 +105,6 @@ fn main() -> Result<()> {
         IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
     let url = format!("zed-cli://{server_name}");
 
-    app.launch(url)?;
-    let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
-    let (tx, rx) = (handshake.requests, handshake.responses);
-
     let open_new_workspace = if args.new {
         Some(true)
     } else if args.add {
@@ -111,20 +113,33 @@ fn main() -> Result<()> {
         None
     };
 
-    tx.send(CliRequest::Open {
-        paths,
-        wait: args.wait,
-        open_new_workspace,
-        dev_server_token: args.dev_server_token,
-    })?;
-
-    while let Ok(response) = rx.recv() {
-        match response {
-            CliResponse::Ping => {}
-            CliResponse::Stdout { message } => println!("{message}"),
-            CliResponse::Stderr { message } => eprintln!("{message}"),
-            CliResponse::Exit { status } => std::process::exit(status),
+    let sender: JoinHandle<anyhow::Result<()>> = thread::spawn(move || {
+        let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
+        let (tx, rx) = (handshake.requests, handshake.responses);
+        tx.send(CliRequest::Open {
+            paths,
+            wait: args.wait,
+            open_new_workspace,
+            dev_server_token: args.dev_server_token,
+        })?;
+
+        while let Ok(response) = rx.recv() {
+            match response {
+                CliResponse::Ping => {}
+                CliResponse::Stdout { message } => println!("{message}"),
+                CliResponse::Stderr { message } => eprintln!("{message}"),
+                CliResponse::Exit { status } => std::process::exit(status),
+            }
         }
+
+        Ok(())
+    });
+
+    if args.foreground {
+        app.run_foreground(url)?;
+    } else {
+        app.launch(url)?;
+        sender.join().unwrap()?;
     }
 
     Ok(())
@@ -141,7 +156,8 @@ mod linux {
             unix::net::{SocketAddr, UnixDatagram},
         },
         path::{Path, PathBuf},
-        process, thread,
+        process::{self, ExitStatus},
+        thread,
         time::Duration,
     };
 
@@ -208,6 +224,12 @@ mod linux {
             }
             Ok(())
         }
+
+        fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
+            std::process::Command::new(self.0.clone())
+                .arg(ipc_url)
+                .status()
+        }
     }
 
     impl App {
@@ -257,7 +279,9 @@ mod linux {
 #[cfg(target_os = "windows")]
 mod windows {
     use crate::{Detect, InstalledApp};
+    use std::io;
     use std::path::Path;
+    use std::process::ExitStatus;
 
     struct App;
     impl InstalledApp for App {
@@ -267,6 +291,9 @@ mod windows {
         fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
             unimplemented!()
         }
+        fn run_foreground(&self, _ipc_url: String) -> io::Result<ExitStatus> {
+            unimplemented!()
+        }
     }
 
     impl Detect {
@@ -288,9 +315,9 @@ mod mac_os {
     use serde::Deserialize;
     use std::{
         ffi::OsStr,
-        fs,
+        fs, io,
         path::{Path, PathBuf},
-        process::Command,
+        process::{Command, ExitStatus},
         ptr,
     };
 
@@ -442,6 +469,15 @@ mod mac_os {
 
             Ok(())
         }
+
+        fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
+            let path = match self {
+                Bundle::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"),
+                Bundle::LocalPath { executable, .. } => executable.clone(),
+            };
+
+            std::process::Command::new(path).arg(ipc_url).status()
+        }
     }
 
     impl Bundle {

docs/src/remote-development.md 🔗

@@ -12,7 +12,7 @@ Currently the two instances connect via Zed's servers, but we intend to build pe
 
 ## Setup
 
-> **Note**: You must be in the alpha program to see this UI. The instructions will likely change as the feature gets closer to launch.
+> NOTE: You must be in the alpha program to see this UI. The instructions will likely change as the feature gets closer to launch.
 
 1. Open the projects dialog with `cmd-option-o` and then click "Connect…".
 2. Click "Add Server"
@@ -21,20 +21,26 @@ Currently the two instances connect via Zed's servers, but we intend to build pe
    ```
    curl https://zed.dev/install.sh | bash
    ```
-5. On the remote machine, paste the instructions from step 3.
+5. On the remote machine, paste the instructions from step 3. You should see `connected!`.
+   > NOTE: If this command runs but doesn't output anything, try running `zed --foreground --dev-server-token YY.XXX`. It is possible that the zed background process is crashing on startup.
+6. On your laptop you can now open folders on the remote machine.
+   > NOTE: Zed does not currently handle opening very large directories (e.g. `/` or `~` that may have >100,000 files) very well. We are working on improving this, but suggest in the meantime opening only specific projects, or subfolders of very large mono-repos.
 
-   > **Note**: Currently you must keep this process open. We are working on making it background itself.
-   > In the meantime, you can run `nohup zed --dev-server-token YY.XXX >~/.zed-log 2>&1 &`
+## Supported platforms
 
-6. On your laptop you can now open folders on the remote machine.
-   > **Note**: Zed does not currently handle opening very large directories (e.g. `/` or `~` that may have >100,000 files) very well. We are working on improving this, but suggest in the meantime opening only specific projects, or subfolders of very large mono-repos.
+The remote machine must be able to run Zed. The following platforms should work, though note that we have not exhaustively tested every linux distribution:
+
+* macOS Catalina or later (Intel or Apple Silicon))
+* Linux (x86_64 only). You must have `glibc` installed at version 2.29 (released in 2019) or greater and available globally.
+* Windows is not yet supported.
 
 ## Known Limitations
 
 - The Terminal does not work remotely.
 - You cannot spawn Tasks remotely.
 - Extensions aren't yet supported in headless Zed.
+- You can not run `zed` in headless mode and in GUI mode at the same time on the same machine.
 
 ## Feedback
 
-- Please join the [#remoting-feedback](https://discord.com/channels/869392257814519848/1235290452270387241) Discord channel.
+- Please join the #remoting-feedback in the [Zed Discord](https://discord.gg/qSDQ8VWc7k).