1use anyhow::{anyhow, Result};
2use clap::Parser;
3use core_foundation::{
4 array::{CFArray, CFIndex},
5 string::kCFStringEncodingUTF8,
6 url::{CFURLCreateWithBytes, CFURL},
7};
8use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
9use ipc_channel::ipc::IpcOneShotServer;
10use serde::{Deserialize, Serialize};
11use std::{path::PathBuf, process, 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
24#[derive(Serialize, Deserialize)]
25struct OpenResult {
26 exit_status: i32,
27 stdout_message: Option<String>,
28 stderr_message: Option<String>,
29}
30
31fn main() -> Result<()> {
32 let args = Args::parse();
33
34 let (server, server_name) = IpcOneShotServer::<OpenResult>::new()?;
35 let app_path = locate_app()?;
36 launch_app(app_path, args.paths, server_name)?;
37
38 let (_, result) = server.accept()?;
39 if let Some(message) = result.stdout_message {
40 println!("{}", message);
41 }
42 if let Some(message) = result.stderr_message {
43 eprintln!("{}", message);
44 }
45
46 process::exit(result.exit_status)
47}
48
49fn locate_app() -> Result<PathBuf> {
50 Ok("/Applications/Zed.app".into())
51}
52
53fn launch_app(app_path: PathBuf, paths_to_open: Vec<PathBuf>, server_name: String) -> Result<()> {
54 let status = unsafe {
55 let app_url =
56 CFURL::from_path(&app_path, true).ok_or_else(|| anyhow!("invalid app path"))?;
57 let mut urls_to_open = paths_to_open
58 .into_iter()
59 .map(|path| {
60 CFURL::from_path(&path, true).ok_or_else(|| anyhow!("{:?} is invalid", path))
61 })
62 .collect::<Result<Vec<_>>>()?;
63
64 let server_url = format!("zed_cli_response://{server_name}");
65 urls_to_open.push(CFURL::wrap_under_create_rule(CFURLCreateWithBytes(
66 ptr::null(),
67 server_url.as_ptr(),
68 server_url.len() as CFIndex,
69 kCFStringEncodingUTF8,
70 ptr::null(),
71 )));
72
73 let urls_to_open = CFArray::from_copyable(
74 &urls_to_open
75 .iter()
76 .map(|url| url.as_concrete_TypeRef())
77 .collect::<Vec<_>>(),
78 );
79 LSOpenFromURLSpec(
80 &LSLaunchURLSpec {
81 appURL: app_url.as_concrete_TypeRef(),
82 itemURLs: urls_to_open.as_concrete_TypeRef(),
83 passThruParams: ptr::null(),
84 launchFlags: kLSLaunchDefaults,
85 asyncRefCon: ptr::null_mut(),
86 },
87 ptr::null_mut(),
88 )
89 };
90 if status == 0 {
91 Ok(())
92 } else {
93 Err(anyhow!("cannot start {:?}", app_path))
94 }
95}