diff --git a/Cargo.lock b/Cargo.lock index 1bf917f984..60e9a5b035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2119,7 +2119,11 @@ dependencies = [ "clap 4.4.4", "core-foundation", "core-services", + "exec", + "fork", "ipc-channel", + "libc", + "once_cell", "plist", "release_channel", "serde", @@ -3575,6 +3579,17 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + [[package]] name = "errno" version = "0.3.8" @@ -3585,6 +3600,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "etagere" version = "0.2.8" @@ -3663,6 +3688,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886b70328cba8871bfc025858e1de4be16b1d5088f2ba50b57816f4210672615" +dependencies = [ + "errno 0.2.8", + "libc", +] + [[package]] name = "extension" version = "0.1.0" @@ -4063,6 +4098,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "fork" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e74d3423998a57e9d906e49252fb79eb4a04d5cdfe188fb1b7ff9fc076a8ed" +dependencies = [ + "libc", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -4788,7 +4832,6 @@ version = "0.1.0" dependencies = [ "anyhow", "client", - "ctrlc", "fs", "futures 0.3.28", "gpui", @@ -4800,6 +4843,7 @@ dependencies = [ "rpc", "settings", "shellexpand", + "signal-hook", "util", ] @@ -8400,7 +8444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", - "errno", + "errno 0.3.8", "io-lifetimes 1.0.11", "libc", "linux-raw-sys 0.3.8", @@ -8414,7 +8458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ "bitflags 2.4.2", - "errno", + "errno 0.3.8", "itoa", "libc", "linux-raw-sys 0.4.12", @@ -8428,7 +8472,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ - "errno", + "errno 0.3.8", "libc", "rustix 0.38.32", ] @@ -12832,7 +12876,6 @@ dependencies = [ "clap 4.4.4", "cli", "client", - "clock", "collab_ui", "collections", "command_palette", @@ -12863,6 +12906,7 @@ dependencies = [ "language_selector", "language_tools", "languages", + "libc", "log", "markdown_preview", "menu", diff --git a/Cargo.toml b/Cargo.toml index 1f1bfe5a5d..5785e5b7ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -266,12 +266,14 @@ chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.4", features = ["derive"] } clickhouse = { version = "0.11.6" } ctor = "0.2.6" -ctrlc = "3.4.4" +signal-hook = "0.3.17" core-foundation = { version = "0.9.3" } core-foundation-sys = "0.8.6" derive_more = "0.99.17" emojis = "0.6.1" env_logger = "0.9" +exec = "0.3.1" +fork = "0.1.23" futures = "0.3" futures-batch = "0.6.1" futures-lite = "1.13" @@ -290,10 +292,12 @@ isahc = { version = "1.7.2", default-features = false, features = [ ] } itertools = "0.11.0" lazy_static = "1.4.0" +libc = "0.2" linkify = "0.10.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } nanoid = "0.4" nix = "0.28" +once_cell = "1.19.0" ordered-float = "2.1.1" palette = { version = "0.7.5", default-features = false, features = ["std"] } parking_lot = "0.12.1" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 5b982e9bfb..199b6bb62d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,11 +19,17 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true clap.workspace = true +libc.workspace = true ipc-channel = "0.18" +once_cell.workspace = true release_channel.workspace = true serde.workspace = true util.workspace = true +[target.'cfg(target_os = "linux")'.dependencies] +exec.workspace = true +fork.workspace = true + [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true core-services = "0.2" diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 03201a1ddf..8b5faaf382 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -13,6 +13,7 @@ pub enum CliRequest { paths: Vec, wait: bool, open_new_workspace: Option, + dev_server_token: Option, }, } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 12440819d0..7935b451b7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,17 +1,21 @@ #![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))] -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use clap::Parser; -use cli::{CliRequest, CliResponse}; -use serde::Deserialize; +use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake}; use std::{ - env, - ffi::OsStr, - fs, + env, fs, path::{Path, PathBuf}, }; use util::paths::PathLikeWithPosition; +struct Detect; + +trait InstalledApp { + fn zed_version_string(&self) -> String; + fn launch(&self, ipc_url: String) -> anyhow::Result<()>; +} + #[derive(Parser, Debug)] #[command(name = "zed", disable_version_flag = true)] struct Args { @@ -33,9 +37,9 @@ struct Args { /// Print Zed's version and the app path. #[arg(short, long)] version: bool, - /// Custom Zed.app path - #[arg(short, long)] - bundle_path: Option, + /// Custom path to Zed.app or the zed binary + #[arg(long)] + zed: Option, /// Run zed in dev-server mode #[arg(long)] dev_server_token: Option, @@ -49,12 +53,6 @@ fn parse_path_with_position( }) } -#[derive(Debug, Deserialize)] -struct InfoPlist { - #[serde(rename = "CFBundleShortVersionString")] - bundle_short_version_string: String, -} - fn main() -> Result<()> { // Intercept version designators #[cfg(target_os = "macos")] @@ -68,14 +66,10 @@ fn main() -> Result<()> { } let args = Args::parse(); - let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?; - - if let Some(dev_server_token) = args.dev_server_token { - return bundle.spawn(vec!["--dev-server-token".into(), dev_server_token]); - } + let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?; if args.version { - println!("{}", bundle.zed_version_string()); + println!("{}", app.zed_version_string()); return Ok(()); } @@ -101,7 +95,14 @@ fn main() -> Result<()> { paths.push(canonicalized.to_string(|path| path.display().to_string())) } - let (tx, rx) = bundle.launch()?; + let (server, server_name) = + IpcOneShotServer::::new().context("Handshake before Zed spawn")?; + let url = format!("zed-cli://{server_name}"); + + app.launch(url)?; + let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; + let (tx, rx) = (handshake.requests, handshake.responses); + let open_new_workspace = if args.new { Some(true) } else if args.add { @@ -114,6 +115,7 @@ fn main() -> Result<()> { paths, wait: args.wait, open_new_workspace, + dev_server_token: args.dev_server_token, })?; while let Ok(response) = rx.recv() { @@ -128,60 +130,125 @@ fn main() -> Result<()> { Ok(()) } -enum Bundle { - App { - app_bundle: PathBuf, - plist: InfoPlist, - }, - LocalPath { - executable: PathBuf, - plist: InfoPlist, - }, -} - -fn locate_bundle() -> Result { - let cli_path = std::env::current_exe()?.canonicalize()?; - let mut app_path = cli_path.clone(); - while app_path.extension() != Some(OsStr::new("app")) { - if !app_path.pop() { - return Err(anyhow!("cannot find app bundle containing {:?}", cli_path)); - } - } - Ok(app_path) -} - #[cfg(target_os = "linux")] mod linux { - use std::path::Path; + use std::{ + env, + ffi::OsString, + io, + os::{ + linux::net::SocketAddrExt, + unix::net::{SocketAddr, UnixDatagram}, + }, + path::{Path, PathBuf}, + process, thread, + time::Duration, + }; - use cli::{CliRequest, CliResponse}; - use ipc_channel::ipc::{IpcReceiver, IpcSender}; + use anyhow::anyhow; + use cli::FORCE_CLI_MODE_ENV_VAR_NAME; + use fork::Fork; + use once_cell::sync::Lazy; - use crate::{Bundle, InfoPlist}; + use crate::{Detect, InstalledApp}; - impl Bundle { - pub fn detect(_args_bundle_path: Option<&Path>) -> anyhow::Result { - unimplemented!() + static RELEASE_CHANNEL: Lazy = + Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string()); + + struct App(PathBuf); + + impl Detect { + pub fn detect(path: Option<&Path>) -> anyhow::Result { + let path = if let Some(path) = path { + path.to_path_buf().canonicalize() + } else { + let cli = env::current_exe()?; + let dir = cli + .parent() + .ok_or_else(|| anyhow!("no parent path for cli"))?; + + match dir.join("zed").canonicalize() { + Ok(path) => Ok(path), + // development builds have Zed capitalized + Err(e) => match dir.join("Zed").canonicalize() { + Ok(path) => Ok(path), + Err(_) => Err(e), + }, + } + }?; + + Ok(App(path)) + } + } + + impl InstalledApp for App { + fn zed_version_string(&self) -> String { + format!( + "Zed {}{} – {}", + if *RELEASE_CHANNEL == "stable" { + "".to_string() + } else { + format!(" {} ", *RELEASE_CHANNEL) + }, + option_env!("RELEASE_VERSION").unwrap_or_default(), + self.0.display(), + ) } - pub fn plist(&self) -> &InfoPlist { - unimplemented!() + fn launch(&self, ipc_url: String) -> anyhow::Result<()> { + let uid: u32 = unsafe { libc::getuid() }; + let sock_addr = + SocketAddr::from_abstract_name(format!("zed-{}-{}", *RELEASE_CHANNEL, uid))?; + + let sock = UnixDatagram::unbound()?; + if sock.connect_addr(&sock_addr).is_err() { + self.boot_background(ipc_url)?; + } else { + sock.send(ipc_url.as_bytes())?; + } + Ok(()) + } + } + + impl App { + fn boot_background(&self, ipc_url: String) -> anyhow::Result<()> { + let path = &self.0; + + match fork::fork() { + Ok(Fork::Parent(_)) => Ok(()), + Ok(Fork::Child) => { + std::env::set_var(FORCE_CLI_MODE_ENV_VAR_NAME, ""); + if let Err(_) = fork::setsid() { + eprintln!("failed to setsid: {}", std::io::Error::last_os_error()); + process::exit(1); + } + if std::env::var("ZED_KEEP_FD").is_err() { + if let Err(_) = fork::close_fd() { + eprintln!("failed to close_fd: {}", std::io::Error::last_os_error()); + } + } + let error = + exec::execvp(path.clone(), &[path.as_os_str(), &OsString::from(ipc_url)]); + // if exec succeeded, we never get here. + eprintln!("failed to exec {:?}: {}", path, error); + process::exit(1) + } + Err(_) => Err(anyhow!(io::Error::last_os_error())), + } } - pub fn path(&self) -> &Path { - unimplemented!() - } - - pub fn launch(&self) -> anyhow::Result<(IpcSender, IpcReceiver)> { - unimplemented!() - } - - pub fn spawn(&self, _args: Vec) -> anyhow::Result<()> { - unimplemented!() - } - - pub fn zed_version_string(&self) -> String { - unimplemented!() + fn wait_for_socket( + &self, + sock_addr: &SocketAddr, + sock: &mut UnixDatagram, + ) -> Result<(), std::io::Error> { + for _ in 0..100 { + thread::sleep(Duration::from_millis(10)); + if sock.connect_addr(&sock_addr).is_ok() { + return Ok(()); + } + } + sock.connect_addr(&sock_addr) } } } @@ -189,59 +256,79 @@ mod linux { // todo("windows") #[cfg(target_os = "windows")] mod windows { + use crate::{Detect, InstalledApp}; use std::path::Path; - use cli::{CliRequest, CliResponse}; - use ipc_channel::ipc::{IpcReceiver, IpcSender}; - - use crate::{Bundle, InfoPlist}; - - impl Bundle { - pub fn detect(_args_bundle_path: Option<&Path>) -> anyhow::Result { + struct App; + impl InstalledApp for App { + fn zed_version_string(&self) -> String { unimplemented!() } - - pub fn plist(&self) -> &InfoPlist { + fn launch(&self, _ipc_url: String) -> anyhow::Result<()> { unimplemented!() } + } - pub fn path(&self) -> &Path { - unimplemented!() - } - - pub fn launch(&self) -> anyhow::Result<(IpcSender, IpcReceiver)> { - unimplemented!() - } - - pub fn spawn(&self, _args: Vec) -> anyhow::Result<()> { - unimplemented!() - } - - pub fn zed_version_string(&self) -> String { - unimplemented!() + impl Detect { + pub fn detect(_path: Option<&Path>) -> anyhow::Result { + Ok(App) } } } #[cfg(target_os = "macos")] mod mac_os { - use anyhow::{Context, Result}; + use anyhow::{anyhow, Context, Result}; use core_foundation::{ array::{CFArray, CFIndex}, string::kCFStringEncodingUTF8, url::{CFURLCreateWithBytes, CFURL}, }; use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType}; - use std::{fs, path::Path, process::Command, ptr}; + use serde::Deserialize; + use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, + process::Command, + ptr, + }; - use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME}; - use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender}; + use cli::FORCE_CLI_MODE_ENV_VAR_NAME; - use crate::{locate_bundle, Bundle, InfoPlist}; + use crate::{Detect, InstalledApp}; - impl Bundle { - pub fn detect(args_bundle_path: Option<&Path>) -> anyhow::Result { - let bundle_path = if let Some(bundle_path) = args_bundle_path { + #[derive(Debug, Deserialize)] + struct InfoPlist { + #[serde(rename = "CFBundleShortVersionString")] + bundle_short_version_string: String, + } + + enum Bundle { + App { + app_bundle: PathBuf, + plist: InfoPlist, + }, + LocalPath { + executable: PathBuf, + plist: InfoPlist, + }, + } + + fn locate_bundle() -> Result { + let cli_path = std::env::current_exe()?.canonicalize()?; + let mut app_path = cli_path.clone(); + while app_path.extension() != Some(OsStr::new("app")) { + if !app_path.pop() { + return Err(anyhow!("cannot find app bundle containing {:?}", cli_path)); + } + } + Ok(app_path) + } + + impl Detect { + pub fn detect(path: Option<&Path>) -> anyhow::Result { + let bundle_path = if let Some(bundle_path) = path { bundle_path .canonicalize() .with_context(|| format!("Args bundle path {bundle_path:?} canonicalization"))? @@ -256,7 +343,7 @@ mod mac_os { plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| { format!("Reading *.app bundle plist file at {plist_path:?}") })?; - Ok(Self::App { + Ok(Bundle::App { app_bundle: bundle_path, plist, }) @@ -271,42 +358,27 @@ mod mac_os { plist::from_file::<_, InfoPlist>(&plist_path).with_context(|| { format!("Reading dev bundle plist file at {plist_path:?}") })?; - Ok(Self::LocalPath { + Ok(Bundle::LocalPath { executable: bundle_path, plist, }) } } } + } - fn plist(&self) -> &InfoPlist { - match self { - Self::App { plist, .. } => plist, - Self::LocalPath { plist, .. } => plist, - } + impl InstalledApp for Bundle { + fn zed_version_string(&self) -> String { + let is_dev = matches!(self, Self::LocalPath { .. }); + format!( + "Zed {}{} – {}", + self.plist().bundle_short_version_string, + if is_dev { " (dev)" } else { "" }, + self.path().display(), + ) } - fn path(&self) -> &Path { - match self { - Self::App { app_bundle, .. } => app_bundle, - Self::LocalPath { executable, .. } => executable, - } - } - - pub fn spawn(&self, args: Vec) -> Result<()> { - let path = match self { - Self::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"), - Self::LocalPath { executable, .. } => executable.clone(), - }; - Command::new(path).args(args).status()?; - Ok(()) - } - - pub fn launch(&self) -> anyhow::Result<(IpcSender, IpcReceiver)> { - let (server, server_name) = - IpcOneShotServer::::new().context("Handshake before Zed spawn")?; - let url = format!("zed-cli://{server_name}"); - + fn launch(&self, url: String) -> anyhow::Result<()> { match self { Self::App { app_bundle, .. } => { let app_path = app_bundle; @@ -368,18 +440,23 @@ mod mac_os { } } - let (_, handshake) = server.accept().context("Handshake after Zed spawn")?; - Ok((handshake.requests, handshake.responses)) + Ok(()) + } + } + + impl Bundle { + fn plist(&self) -> &InfoPlist { + match self { + Self::App { plist, .. } => plist, + Self::LocalPath { plist, .. } => plist, + } } - pub fn zed_version_string(&self) -> String { - let is_dev = matches!(self, Self::LocalPath { .. }); - format!( - "Zed {}{} – {}", - self.plist().bundle_short_version_string, - if is_dev { " (dev)" } else { "" }, - self.path().display(), - ) + fn path(&self) -> &Path { + match self { + Self::App { app_bundle, .. } => app_bundle, + Self::LocalPath { executable, .. } => executable, + } } } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index abb54e1ec9..23468d423e 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -27,7 +27,7 @@ futures.workspace = true gpui.workspace = true lazy_static.workspace = true log.workspace = true -once_cell = "1.19.0" +once_cell.workspace = true parking_lot.workspace = true postage.workspace = true rand.workspace = true diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 650495ef8e..18d010b828 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -428,8 +428,10 @@ impl TestServer { node_runtime: app_state.node_runtime.clone(), }, cx, - ); - }); + ) + }) + .await + .unwrap(); TestClient { app_state, diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index f6bd040c53..5d95688042 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -164,7 +164,7 @@ pub struct ExtensionIndexLanguageEntry { actions!(zed, [ReloadExtensions]); pub fn init( - fs: Arc, + fs: Arc, client: Arc, node_runtime: Arc, language_registry: Arc, diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 39c888c4d1..caa74c7082 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -29,7 +29,7 @@ git.workspace = true git2.workspace = true serde.workspace = true serde_json.workspace = true -libc = "0.2" +libc.workspace = true time.workspace = true gpui = { workspace = true, optional = true } diff --git a/crates/headless/Cargo.toml b/crates/headless/Cargo.toml index 28a213f79e..71a7df72c9 100644 --- a/crates/headless/Cargo.toml +++ b/crates/headless/Cargo.toml @@ -15,7 +15,7 @@ doctest = false [dependencies] anyhow.workspace = true client.workspace = true -ctrlc.workspace = true +signal-hook.workspace = true gpui.workspace = true log.workspace = true rpc.workspace = true diff --git a/crates/headless/src/headless.rs b/crates/headless/src/headless.rs index aeaa042017..14e84df53a 100644 --- a/crates/headless/src/headless.rs +++ b/crates/headless/src/headless.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use client::DevServerProjectId; use client::{user::UserStore, Client, ClientSettings}; use fs::Fs; @@ -36,7 +36,7 @@ struct GlobalDevServer(Model); impl Global for GlobalDevServer {} -pub fn init(client: Arc, app_state: AppState, cx: &mut AppContext) { +pub fn init(client: Arc, app_state: AppState, cx: &mut AppContext) -> Task> { let dev_server = cx.new_model(|cx| DevServer::new(client.clone(), app_state, cx)); cx.set_global(GlobalDevServer(dev_server.clone())); @@ -49,42 +49,36 @@ pub fn init(client: Arc, app_state: AppState, cx: &mut AppContext) { }); }); - // Set up a handler when the dev server is shut down by the user pressing Ctrl-C - let (tx, rx) = futures::channel::oneshot::channel(); - set_ctrlc_handler(move || tx.send(()).log_err().unwrap()).log_err(); - - cx.spawn(|cx| async move { - rx.await.log_err(); - log::info!("Received interrupt signal"); - cx.update(|cx| cx.quit()).log_err(); - }) - .detach(); + #[cfg(not(target_os = "windows"))] + { + use signal_hook::consts::{SIGINT, SIGTERM}; + use signal_hook::iterator::Signals; + // Set up a handler when the dev server is shut down + // with ctrl-c or kill + let (tx, rx) = futures::channel::oneshot::channel(); + let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); + std::thread::spawn({ + move || { + if let Some(sig) = signals.forever().next() { + tx.send(sig).log_err(); + } + } + }); + cx.spawn(|cx| async move { + if let Ok(sig) = rx.await { + log::info!("received signal {sig:?}"); + cx.update(|cx| cx.quit()).log_err(); + } + }) + .detach(); + } let server_url = ClientSettings::get_global(&cx).server_url.clone(); cx.spawn(|cx| async move { - match client.authenticate_and_connect(false, &cx).await { - Ok(_) => { - log::info!("Connected to {}", server_url); - } - Err(e) => { - log::error!("Error connecting to '{}': {}", server_url, e); - cx.update(|cx| cx.quit()).log_err(); - } - } - }) - .detach(); -} - -fn set_ctrlc_handler(f: F) -> Result<(), ctrlc::Error> -where - F: FnOnce() + 'static + Send, -{ - let f = std::sync::Mutex::new(Some(f)); - ctrlc::set_handler(move || { - if let Ok(mut guard) = f.lock() { - let f = guard.take().expect("f can only be taken once"); - f(); - } + client + .authenticate_and_connect(false, &cx) + .await + .map_err(|e| anyhow!("Error connecting to '{}': {}", server_url, e)) }) } @@ -186,7 +180,7 @@ impl DevServer { let path_exists = fs.is_dir(path).await; if !path_exists { - return Err(anyhow::anyhow!(ErrorCode::DevServerProjectPathDoesNotExist))?; + return Err(anyhow!(ErrorCode::DevServerProjectPathDoesNotExist))?; } Ok(proto::Ack {}) diff --git a/crates/release_channel/Cargo.toml b/crates/release_channel/Cargo.toml index acea552d63..cf664ceff1 100644 --- a/crates/release_channel/Cargo.toml +++ b/crates/release_channel/Cargo.toml @@ -10,4 +10,4 @@ workspace = true [dependencies] gpui.workspace = true -once_cell = "1.19.0" +once_cell.workspace = true diff --git a/crates/release_channel/src/lib.rs b/crates/release_channel/src/lib.rs index 9c90fa9437..eeec61331f 100644 --- a/crates/release_channel/src/lib.rs +++ b/crates/release_channel/src/lib.rs @@ -7,7 +7,8 @@ use std::{env, str::FromStr}; use gpui::{AppContext, Global, SemanticVersion}; use once_cell::sync::Lazy; -static RELEASE_CHANNEL_NAME: Lazy = if cfg!(debug_assertions) { +/// stable | dev | nightly | preview +pub static RELEASE_CHANNEL_NAME: Lazy = if cfg!(debug_assertions) { Lazy::new(|| { env::var("ZED_RELEASE_CHANNEL") .unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string()) diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 1b4f1fdb13..ef62607302 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -20,7 +20,7 @@ collections.workspace = true dirs = "4.0.0" futures.workspace = true gpui.workspace = true -libc = "0.2" +libc.workspace = true task.workspace = true schemars.workspace = true serde.workspace = true diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 738c23423f..87d3affb90 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -30,7 +30,6 @@ chrono.workspace = true clap.workspace = true cli.workspace = true client.workspace = true -clock.workspace = true collab_ui.workspace = true collections.workspace = true command_palette.workspace = true @@ -60,11 +59,12 @@ language.workspace = true language_selector.workspace = true language_tools.workspace = true languages.workspace = true +libc.workspace = true log.workspace = true markdown_preview.workspace = true menu.workspace = true mimalloc = { version = "0.1", optional = true } -nix = {workspace = true, features = ["pthread"] } +nix = {workspace = true, features = ["pthread", "signal"] } node_runtime.workspace = true notifications.workspace = true outline.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 3bcc66dd93..d3e8e73afe 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -17,7 +17,7 @@ use env_logger::Builder; use fs::RealFs; use futures::{future, StreamExt}; use git::GitHostingProviderRegistry; -use gpui::{App, AppContext, AsyncAppContext, Context, Task, VisualContext}; +use gpui::{App, AppContext, AsyncAppContext, Context, Global, Task, VisualContext}; use image_viewer; use language::LanguageRegistry; use log::LevelFilter; @@ -26,9 +26,7 @@ use assets::Assets; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use release_channel::AppCommitSha; -use settings::{ - default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, -}; +use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore}; use simplelog::ConfigBuilder; use smol::process::Command; use std::{ @@ -36,11 +34,11 @@ use std::{ fs::OpenOptions, io::{IsTerminal, Write}, path::Path, + process, sync::Arc, }; use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings}; use util::{ - http::HttpClientWithUrl, maybe, parse_env_output, paths::{self}, ResultExt, TryFutureExt, @@ -49,9 +47,8 @@ use uuid::Uuid; use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; use workspace::{AppState, WorkspaceSettings, WorkspaceStore}; use zed::{ - app_menus, build_window_options, ensure_only_instance, handle_cli_connection, - handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, IsOnlyInstance, - OpenListener, OpenRequest, + app_menus, build_window_options, handle_cli_connection, handle_keymap_file_changes, + initialize_workspace, open_paths_with_positions, OpenListener, OpenRequest, }; use crate::zed::inline_completion_registry; @@ -76,95 +73,169 @@ fn fail_to_launch(e: anyhow::Error) { }) } -fn init_headless(dev_server_token: DevServerToken) { - if let Err(e) = init_paths() { - log::error!("Failed to launch: {}", e); - return; - } - init_logger(); +enum AppMode { + Headless(DevServerToken), + Ui, +} +impl Global for AppMode {} - let app = App::new(); - - let session_id = Uuid::new_v4().to_string(); - let (installation_id, _) = app - .background_executor() - .block(installation_id()) - .ok() - .unzip(); - - reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone()); - - app.run(|cx| { - release_channel::init(env!("CARGO_PKG_VERSION"), cx); - if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") { - AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx); +fn init_headless( + dev_server_token: DevServerToken, + app_state: Arc, + cx: &mut AppContext, +) -> Task> { + match cx.try_global::() { + Some(AppMode::Headless(token)) if token == &dev_server_token => return Task::ready(Ok(())), + Some(_) => { + return Task::ready(Err(anyhow!( + "zed is already running. Use `kill {}` to stop it", + process::id() + ))) } - - let mut store = SettingsStore::default(); - store - .set_default_settings(default_settings().as_ref(), cx) - .unwrap(); - cx.set_global(store); - - client::init_settings(cx); - - let clock = Arc::new(clock::RealSystemClock); - let http = Arc::new(HttpClientWithUrl::new( - &client::ClientSettings::get_global(cx).server_url, - )); - - let client = client::Client::new(clock, http.clone(), cx); - let client = client.clone(); - client.set_dev_server_token(dev_server_token); - - project::Project::init(&client, cx); - client::init(&client, cx); - - let git_hosting_provider_registry = GitHostingProviderRegistry::default_global(cx); - let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") { - cx.path_for_auxiliary_executable("git") - .context("could not find git binary path") - .log_err() - } else { - None - }; - let fs = Arc::new(RealFs::new(git_hosting_provider_registry, git_binary_path)); - - git_hosting_providers::init(cx); - - let mut languages = - LanguageRegistry::new(Task::ready(()), cx.background_executor().clone()); - languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); - let languages = Arc::new(languages); - let node_runtime = RealNodeRuntime::new(http.clone()); - - language::init(cx); - languages::init(languages.clone(), node_runtime.clone(), cx); - let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); - - let user_settings_file_rx = watch_config_file( - &cx.background_executor(), - fs.clone(), - paths::SETTINGS.clone(), - ); - handle_settings_file_changes(user_settings_file_rx, cx); - - reliability::init(client.http_client(), installation_id, cx); - - headless::init( - client.clone(), - headless::AppState { - languages: languages.clone(), - user_store: user_store.clone(), - fs: fs.clone(), - node_runtime: node_runtime.clone(), - }, - cx, - ); - }) + None => { + cx.set_global(AppMode::Headless(dev_server_token.clone())); + } + }; + let client = app_state.client.clone(); + client.set_dev_server_token(dev_server_token); + headless::init( + client.clone(), + headless::AppState { + languages: app_state.languages.clone(), + user_store: app_state.user_store.clone(), + fs: app_state.fs.clone(), + node_runtime: app_state.node_runtime.clone(), + }, + cx, + ) } -fn init_ui(args: Args) { +fn init_ui(app_state: Arc, cx: &mut AppContext) -> Result<()> { + match cx.try_global::() { + Some(AppMode::Headless(_)) => { + return Err(anyhow!( + "zed is already running in headless mode. Use `kill {}` to stop it", + process::id() + )) + } + Some(AppMode::Ui) => return Ok(()), + None => { + cx.set_global(AppMode::Ui); + } + }; + + SystemAppearance::init(cx); + load_embedded_fonts(cx); + + theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); + app_state.languages.set_theme(cx.theme().clone()); + command_palette::init(cx); + editor::init(cx); + image_viewer::init(cx); + diagnostics::init(cx); + + audio::init(Assets, cx); + workspace::init(app_state.clone(), cx); + recent_projects::init(cx); + + go_to_line::init(cx); + file_finder::init(cx); + tab_switcher::init(cx); + outline::init(cx); + project_symbols::init(cx); + project_panel::init(Assets, cx); + tasks_ui::init(cx); + channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx); + search::init(cx); + vim::init(cx); + terminal_view::init(cx); + + journal::init(app_state.clone(), cx); + language_selector::init(cx); + theme_selector::init(cx); + language_tools::init(cx); + call::init(app_state.client.clone(), app_state.user_store.clone(), cx); + notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); + collab_ui::init(&app_state, cx); + feedback::init(cx); + markdown_preview::init(cx); + welcome::init(cx); + extensions_ui::init(cx); + + // Initialize each completion provider. Settings are used for toggling between them. + let copilot_language_server_id = app_state.languages.next_language_server_id(); + copilot::init( + copilot_language_server_id, + app_state.client.http_client(), + app_state.node_runtime.clone(), + cx, + ); + supermaven::init(app_state.client.clone(), cx); + + inline_completion_registry::init(app_state.client.telemetry().clone(), cx); + + assistant::init(app_state.client.clone(), cx); + assistant2::init(app_state.client.clone(), cx); + + cx.observe_global::({ + let languages = app_state.languages.clone(); + let http = app_state.client.http_client(); + let client = app_state.client.clone(); + + move |cx| { + for &mut window in cx.windows().iter_mut() { + let background_appearance = cx.theme().window_background_appearance(); + window + .update(cx, |_, cx| { + cx.set_background_appearance(background_appearance) + }) + .ok(); + } + languages.set_theme(cx.theme().clone()); + let new_host = &client::ClientSettings::get_global(cx).server_url; + if &http.base_url() != new_host { + http.set_base_url(new_host); + if client.status().borrow().is_connected() { + client.reconnect(&cx.to_async()); + } + } + } + }) + .detach(); + let telemetry = app_state.client.telemetry(); + telemetry.report_setting_event("theme", cx.theme().name.to_string()); + telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string()); + telemetry.flush_events(); + + extension::init( + app_state.fs.clone(), + app_state.client.clone(), + app_state.node_runtime.clone(), + app_state.languages.clone(), + ThemeRegistry::global(cx), + cx, + ); + + dev_server_projects::init(app_state.client.clone(), cx); + + let fs = app_state.fs.clone(); + load_user_themes_in_background(fs.clone(), cx); + watch_themes(fs.clone(), cx); + watch_languages(fs.clone(), app_state.languages.clone(), cx); + watch_file_types(fs.clone(), cx); + + cx.set_menus(app_menus()); + initialize_workspace(app_state.clone(), cx); + + cx.activate(true); + + cx.spawn(|cx| async move { authenticate(app_state.client.clone(), &cx).await }) + .detach_and_log_err(cx); + + Ok(()) +} + +fn main() { menu::init(); zed_actions::init(); @@ -175,10 +246,6 @@ fn init_ui(args: Args) { init_logger(); - if ensure_only_instance() != IsOnlyInstance::Yes { - return; - } - log::info!("========== starting zed =========="); let app = App::new().with_assets(Assets); @@ -190,6 +257,26 @@ fn init_ui(args: Args) { let session_id = Uuid::new_v4().to_string(); reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone()); + let (listener, mut open_rx) = OpenListener::new(); + let listener = Arc::new(listener); + let open_listener = listener.clone(); + + #[cfg(target_os = "linux")] + { + if crate::zed::listen_for_cli_connections(listener.clone()).is_err() { + println!("zed is already running"); + return; + } + } + #[cfg(not(target_os = "linux"))] + { + use zed::only_instance::*; + if ensure_only_instance() != IsOnlyInstance::Yes { + println!("zed is already running"); + return; + } + } + let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new()); let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") { app.path_for_auxiliary_executable("git") @@ -223,9 +310,6 @@ fn init_ui(args: Args) { }) }; - let (listener, mut open_rx) = OpenListener::new(); - let listener = Arc::new(listener); - let open_listener = listener.clone(); app.on_open_urls(move |urls| open_listener.open_urls(urls)); app.on_reopen(move |cx| { if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade()) @@ -247,11 +331,8 @@ fn init_ui(args: Args) { GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx); git_hosting_providers::init(cx); - SystemAppearance::init(cx); OpenListener::set_global(listener.clone(), cx); - load_embedded_fonts(cx); - settings::init(cx); handle_settings_file_changes(user_settings_file_rx, cx); handle_keymap_file_changes(user_keymap_file_rx, cx); @@ -260,7 +341,6 @@ fn init_ui(args: Args) { let client = Client::production(cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone()); - let copilot_language_server_id = languages.next_language_server_id(); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); let node_runtime = RealNodeRuntime::new(client.http_client()); @@ -273,76 +353,11 @@ fn init_ui(args: Args) { Client::set_global(client.clone(), cx); zed::init(cx); - theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); project::Project::init(&client, cx); client::init(&client, cx); - command_palette::init(cx); language::init(cx); - editor::init(cx); - image_viewer::init(cx); - diagnostics::init(cx); - - // Initialize each completion provider. Settings are used for toggling between them. - copilot::init( - copilot_language_server_id, - client.http_client(), - node_runtime.clone(), - cx, - ); - supermaven::init(client.clone(), cx); - - assistant::init(client.clone(), cx); - assistant2::init(client.clone(), cx); - - inline_completion_registry::init(client.telemetry().clone(), cx); - - extension::init( - fs.clone(), - client.clone(), - node_runtime.clone(), - languages.clone(), - ThemeRegistry::global(cx), - cx, - ); - dev_server_projects::init(client.clone(), cx); - - load_user_themes_in_background(fs.clone(), cx); - watch_themes(fs.clone(), cx); - watch_languages(fs.clone(), languages.clone(), cx); - watch_file_types(fs.clone(), cx); - - languages.set_theme(cx.theme().clone()); - - cx.observe_global::({ - let languages = languages.clone(); - let http = client.http_client(); - let client = client.clone(); - - move |cx| { - for &mut window in cx.windows().iter_mut() { - let background_appearance = cx.theme().window_background_appearance(); - window - .update(cx, |_, cx| { - cx.set_background_appearance(background_appearance) - }) - .ok(); - } - languages.set_theme(cx.theme().clone()); - let new_host = &client::ClientSettings::get_global(cx).server_url; - if &http.base_url() != new_host { - http.set_base_url(new_host); - if client.status().borrow().is_connected() { - client.reconnect(&cx.to_async()); - } - } - } - }) - .detach(); - let telemetry = client.telemetry(); telemetry.start(installation_id.clone(), session_id, cx); - telemetry.report_setting_event("theme", cx.theme().name.to_string()); - telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string()); telemetry.report_app_event( match existing_installation_id_found { Some(false) => "first open", @@ -350,7 +365,6 @@ fn init_ui(args: Args) { } .to_string(), ); - telemetry.flush_events(); let app_state = Arc::new(AppState { languages: languages.clone(), client: client.clone(), @@ -362,44 +376,11 @@ fn init_ui(args: Args) { }); AppState::set_global(Arc::downgrade(&app_state), cx); - audio::init(Assets, cx); auto_update::init(client.http_client(), cx); - workspace::init(app_state.clone(), cx); - recent_projects::init(cx); - - go_to_line::init(cx); - file_finder::init(cx); - tab_switcher::init(cx); - outline::init(cx); - project_symbols::init(cx); - project_panel::init(Assets, cx); - tasks_ui::init(cx); - channel::init(&client, user_store.clone(), cx); - search::init(cx); - vim::init(cx); - terminal_view::init(cx); - - journal::init(app_state.clone(), cx); - language_selector::init(cx); - theme_selector::init(cx); - language_tools::init(cx); - call::init(app_state.client.clone(), app_state.user_store.clone(), cx); - notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); - collab_ui::init(&app_state, cx); - feedback::init(cx); - markdown_preview::init(cx); - welcome::init(cx); - extensions_ui::init(cx); - - cx.set_menus(app_menus()); - initialize_workspace(app_state.clone(), cx); - reliability::init(client.http_client(), installation_id, cx); - cx.activate(true); - - let mut triggered_authentication = false; + let args = Args::parse(); let urls: Vec<_> = args .paths_or_urls .iter() @@ -417,14 +398,30 @@ fn init_ui(args: Args) { .and_then(|urls| OpenRequest::parse(urls, cx).log_err()) { Some(request) => { - triggered_authentication = handle_open_request(request, app_state.clone(), cx) + handle_open_request(request, app_state.clone(), cx); + } + None => { + if let Some(dev_server_token) = args.dev_server_token { + let task = + init_headless(DevServerToken(dev_server_token), app_state.clone(), cx); + cx.spawn(|cx| async move { + if let Err(e) = task.await { + log::error!("{}", e); + cx.update(|cx| cx.quit()).log_err(); + } else { + log::info!("connected!"); + } + }) + .detach(); + } else { + init_ui(app_state.clone(), cx).unwrap(); + cx.spawn({ + let app_state = app_state.clone(); + |cx| async move { restore_or_create_workspace(app_state, cx).await } + }) + .detach(); + } } - None => cx - .spawn({ - let app_state = app_state.clone(); - |cx| async move { restore_or_create_workspace(app_state, cx).await } - }) - .detach(), } let app_state = app_state.clone(); @@ -439,34 +436,20 @@ fn init_ui(args: Args) { } }) .detach(); - - if !triggered_authentication { - cx.spawn(|cx| async move { authenticate(client, &cx).await }) - .detach_and_log_err(cx); - } }); } -fn main() { - let mut args = Args::parse(); - if let Some(dev_server_token) = args.dev_server_token.take() { - let dev_server_token = DevServerToken(dev_server_token); - init_headless(dev_server_token) - } else { - init_ui(args) - } -} - -fn handle_open_request( - request: OpenRequest, - app_state: Arc, - cx: &mut AppContext, -) -> bool { +fn handle_open_request(request: OpenRequest, app_state: Arc, cx: &mut AppContext) { if let Some(connection) = request.cli_connection { let app_state = app_state.clone(); cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx)) .detach(); - return false; + return; + } + + if let Err(e) = init_ui(app_state.clone(), cx) { + log::error!("{}", e); + return; } let mut task = None; @@ -531,12 +514,8 @@ fn handle_open_request( anyhow::Ok(()) }) .detach_and_log_err(cx); - true - } else { - if let Some(task) = task { - task.detach_and_log_err(cx) - } - false + } else if let Some(task) = task { + task.detach_and_log_err(cx) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 63bf427b5a..fe3845bd22 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,6 +1,7 @@ mod app_menus; pub mod inline_completion_registry; -mod only_instance; +#[cfg(not(target_os = "linux"))] +pub(crate) mod only_instance; mod open_listener; pub use app_menus::*; @@ -12,7 +13,6 @@ use gpui::{ actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, }; -pub use only_instance::*; pub use open_listener::*; use anyhow::Context as _; diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index a820f0cc4b..9e8e9afe1d 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -13,13 +13,15 @@ use language::{Bias, Point}; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; -use std::thread; use std::time::Duration; +use std::{process, thread}; use util::paths::PathLikeWithPosition; use util::ResultExt; use workspace::item::ItemHandle; use workspace::{AppState, Workspace}; +use crate::{init_headless, init_ui}; + #[derive(Default, Debug)] pub struct OpenRequest { pub cli_connection: Option<(mpsc::Receiver, IpcSender)>, @@ -116,6 +118,24 @@ impl OpenListener { } } +#[cfg(target_os = "linux")] +pub fn listen_for_cli_connections(opener: Arc) -> Result<()> { + use release_channel::RELEASE_CHANNEL_NAME; + use std::os::{linux::net::SocketAddrExt, unix::net::SocketAddr, unix::net::UnixDatagram}; + + let uid: u32 = unsafe { libc::getuid() }; + let sock_addr = + SocketAddr::from_abstract_name(format!("zed-{}-{}", *RELEASE_CHANNEL_NAME, uid))?; + let listener = UnixDatagram::bind_addr(&sock_addr)?; + thread::spawn(move || { + let mut buf = [0u8; 1024]; + while let Ok(len) = listener.recv(&mut buf) { + opener.open_urls(vec![String::from_utf8_lossy(&buf[..len]).to_string()]); + } + }); + Ok(()) +} + fn connect_to_cli( server_name: &str, ) -> Result<(mpsc::Receiver, IpcSender)> { @@ -211,7 +231,50 @@ pub async fn handle_cli_connection( paths, wait, open_new_workspace, + dev_server_token, } => { + if let Some(dev_server_token) = dev_server_token { + match cx + .update(|cx| { + init_headless(client::DevServerToken(dev_server_token), app_state, cx) + }) + .unwrap() + .await + { + Ok(_) => { + responses + .send(CliResponse::Stdout { + message: format!("zed (pid {}) connected!", process::id()), + }) + .log_err(); + responses.send(CliResponse::Exit { status: 0 }).log_err(); + } + Err(error) => { + responses + .send(CliResponse::Stderr { + message: format!("{}", error), + }) + .log_err(); + responses.send(CliResponse::Exit { status: 1 }).log_err(); + cx.update(|cx| cx.quit()).log_err(); + } + } + return; + } + + if let Err(e) = cx + .update(|cx| init_ui(app_state.clone(), cx)) + .and_then(|r| r) + { + responses + .send(CliResponse::Stderr { + message: format!("{}", e), + }) + .log_err(); + responses.send(CliResponse::Exit { status: 1 }).log_err(); + return; + } + let paths = if paths.is_empty() { if open_new_workspace == Some(true) { vec![] diff --git a/script/bundle-linux b/script/bundle-linux index 9cf869dc01..d4fbdc61d8 100755 --- a/script/bundle-linux +++ b/script/bundle-linux @@ -38,11 +38,12 @@ host_line=$(echo "$version_info" | grep host) target_triple=${host_line#*: } # Build binary in release mode -cargo build --release --target "${target_triple}" --package zed +cargo build --release --target "${target_triple}" --package zed --package cli # Strip the binary of all debug symbols # Later, we probably want to do something like this: https://github.com/GabrielMajeri/separate-symbols strip "target/${target_triple}/release/Zed" +strip "target/${target_triple}/release/cli" suffix="" if [ "$channel" != "stable" ]; then @@ -57,6 +58,7 @@ zed_dir="${temp_dir}/zed$suffix.app" # Binary mkdir -p "${zed_dir}/bin" cp "target/${target_triple}/release/Zed" "${zed_dir}/bin/zed" +cp "target/${target_triple}/release/cli" "${zed_dir}/bin/cli" # Icons mkdir -p "${zed_dir}/share/icons/hicolor/512x512/apps" diff --git a/script/install.sh b/script/install.sh index f41ff1bdec..b30d067492 100755 --- a/script/install.sh +++ b/script/install.sh @@ -80,7 +80,7 @@ linux() { mkdir -p "$HOME/.local/bin" "$HOME/.local/share/applications" # Link the binary - ln -sf ~/.local/zed$suffix.app/bin/zed "$HOME/.local/bin/zed" + ln -sf ~/.local/zed$suffix.app/bin/cli "$HOME/.local/bin/zed" # Copy .desktop file desktop_file_path="$HOME/.local/share/applications/${appid}.desktop"