main.rs

 1use anyhow::{anyhow, Result};
 2use clap::Parser;
 3use cli::{CliRequest, CliResponse, IpcHandshake};
 4use core_foundation::{
 5    array::{CFArray, CFIndex},
 6    string::kCFStringEncodingUTF8,
 7    url::{CFURLCreateWithBytes, CFURL},
 8};
 9use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
10use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
11use std::{fs, path::PathBuf, ptr};
12
13#[derive(Parser)]
14#[clap(name = "zed")]
15struct Args {
16    /// Wait for all of the given paths to be closed before exiting.
17    #[clap(short, long)]
18    wait: bool,
19    /// A sequence of space-separated paths that you want to open.
20    #[clap()]
21    paths: Vec<PathBuf>,
22}
23
24fn main() -> Result<()> {
25    let args = Args::parse();
26
27    let app_path = locate_app()?;
28    let (tx, rx) = launch_app(app_path)?;
29
30    tx.send(CliRequest::Open {
31        paths: args
32            .paths
33            .into_iter()
34            .map(|path| fs::canonicalize(path).map_err(|error| anyhow!(error)))
35            .collect::<Result<Vec<PathBuf>>>()?,
36        wait: false,
37    })?;
38
39    while let Ok(response) = rx.recv() {
40        match response {
41            CliResponse::Stdout { message } => println!("{message}"),
42            CliResponse::Stderr { message } => eprintln!("{message}"),
43            CliResponse::Exit { status } => std::process::exit(status),
44        }
45    }
46
47    Ok(())
48}
49
50fn locate_app() -> Result<PathBuf> {
51    Ok("/Users/nathan/src/zed/target/debug/bundle/osx/Zed.app".into())
52    // Ok("/Applications/Zed.app".into())
53}
54
55fn launch_app(app_path: PathBuf) -> Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
56    let (server, server_name) = IpcOneShotServer::<IpcHandshake>::new()?;
57
58    let status = unsafe {
59        let app_url =
60            CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?;
61
62        let url = format!("zed-cli://{server_name}");
63        let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes(
64            ptr::null(),
65            url.as_ptr(),
66            url.len() as CFIndex,
67            kCFStringEncodingUTF8,
68            ptr::null(),
69        ));
70
71        let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]);
72
73        LSOpenFromURLSpec(
74            &LSLaunchURLSpec {
75                appURL: app_url.as_concrete_TypeRef(),
76                itemURLs: urls_to_open.as_concrete_TypeRef(),
77                passThruParams: ptr::null(),
78                launchFlags: kLSLaunchDefaults,
79                asyncRefCon: ptr::null_mut(),
80            },
81            ptr::null_mut(),
82        )
83    };
84
85    if status == 0 {
86        let (_, handshake) = server.accept()?;
87        Ok((handshake.requests, handshake.responses))
88    } else {
89        Err(anyhow!("cannot start {:?}", app_path))
90    }
91}