Skip to main content

angular_switcher/
opener.rs

1use crate::error::SwitcherError;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5/// Launch the `zed` CLI on the given path. Never invokes a shell.
6pub fn open_in_zed(path: &Path) -> Result<(), SwitcherError> {
7    let binary = locate_zed_binary().ok_or_else(|| {
8        SwitcherError::LaunchFailed(
9            "could not find a `zed` binary on PATH or in standard locations".into(),
10        )
11    })?;
12
13    // `path` is a real PathBuf — we pass it as-is to spawn(), never through a shell.
14    let status = Command::new(&binary)
15        .arg(path)
16        .status()
17        .map_err(|e| SwitcherError::LaunchFailed(format!("{}: {e}", binary.display())))?;
18
19    if !status.success() {
20        return Err(SwitcherError::LaunchFailed(format!(
21            "`{}` exited with status {status}",
22            binary.display()
23        )));
24    }
25    Ok(())
26}
27
28fn locate_zed_binary() -> Option<PathBuf> {
29    // 1. PATH lookup via `which`-equivalent — try plain "zed" first; spawn errors will tell us
30    //    if it's not callable. To avoid the spawn-and-fail UX we do a manual PATH scan.
31    let candidates = ["zed", "zed.exe"];
32    if let Some(path_var) = std::env::var_os("PATH") {
33        for dir in std::env::split_paths(&path_var) {
34            for name in &candidates {
35                let p = dir.join(name);
36                if p.is_file() {
37                    return Some(p);
38                }
39            }
40        }
41    }
42    // 2. macOS app-bundle fallback.
43    #[cfg(target_os = "macos")]
44    {
45        let bundled = PathBuf::from("/Applications/Zed.app/Contents/MacOS/cli");
46        if bundled.is_file() {
47            return Some(bundled);
48        }
49    }
50    None
51}