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}