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(std::env::current_exe()?
52        .parent()
53        .unwrap()
54        .join("bundle/osx/Zed.app"))
55}
56
57fn launch_app(app_path: PathBuf) -> Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
58    let (server, server_name) = IpcOneShotServer::<IpcHandshake>::new()?;
59
60    let status = unsafe {
61        let app_url =
62            CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?;
63
64        let url = format!("zed-cli://{server_name}");
65        let url_to_open = CFURL::wrap_under_create_rule(CFURLCreateWithBytes(
66            ptr::null(),
67            url.as_ptr(),
68            url.len() as CFIndex,
69            kCFStringEncodingUTF8,
70            ptr::null(),
71        ));
72
73        let urls_to_open = CFArray::from_copyable(&[url_to_open.as_concrete_TypeRef()]);
74
75        LSOpenFromURLSpec(
76            &LSLaunchURLSpec {
77                appURL: app_url.as_concrete_TypeRef(),
78                itemURLs: urls_to_open.as_concrete_TypeRef(),
79                passThruParams: ptr::null(),
80                launchFlags: kLSLaunchDefaults,
81                asyncRefCon: ptr::null_mut(),
82            },
83            ptr::null_mut(),
84        )
85    };
86
87    if status == 0 {
88        let (_, handshake) = server.accept()?;
89        Ok((handshake.requests, handshake.responses))
90    } else {
91        Err(anyhow!("cannot start {:?}", app_path))
92    }
93}