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}