angular-switcher 0.1.0

Switch between Angular component files (.ts, .html, styles, .spec.ts) from the Zed editor with a customizable keybinding.
Documentation
use crate::error::SwitcherError;
use std::path::{Path, PathBuf};
use std::process::Command;

/// Launch the `zed` CLI on the given path. Never invokes a shell.
pub fn open_in_zed(path: &Path) -> Result<(), SwitcherError> {
    let binary = locate_zed_binary().ok_or_else(|| {
        SwitcherError::LaunchFailed(
            "could not find a `zed` binary on PATH or in standard locations".into(),
        )
    })?;

    // `path` is a real PathBuf — we pass it as-is to spawn(), never through a shell.
    let status = Command::new(&binary)
        .arg(path)
        .status()
        .map_err(|e| SwitcherError::LaunchFailed(format!("{}: {e}", binary.display())))?;

    if !status.success() {
        return Err(SwitcherError::LaunchFailed(format!(
            "`{}` exited with status {status}",
            binary.display()
        )));
    }
    Ok(())
}

fn locate_zed_binary() -> Option<PathBuf> {
    // 1. PATH lookup via `which`-equivalent — try plain "zed" first; spawn errors will tell us
    //    if it's not callable. To avoid the spawn-and-fail UX we do a manual PATH scan.
    let candidates = ["zed", "zed.exe"];
    if let Some(path_var) = std::env::var_os("PATH") {
        for dir in std::env::split_paths(&path_var) {
            for name in &candidates {
                let p = dir.join(name);
                if p.is_file() {
                    return Some(p);
                }
            }
        }
    }
    // 2. macOS app-bundle fallback.
    #[cfg(target_os = "macos")]
    {
        let bundled = PathBuf::from("/Applications/Zed.app/Contents/MacOS/cli");
        if bundled.is_file() {
            return Some(bundled);
        }
    }
    None
}