Detailed changes
@@ -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",
@@ -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"
@@ -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"
@@ -13,6 +13,7 @@ pub enum CliRequest {
paths: Vec<String>,
wait: bool,
open_new_workspace: Option<bool>,
+ dev_server_token: Option<String>,
},
}
@@ -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<PathBuf>,
+ /// Custom path to Zed.app or the zed binary
+ #[arg(long)]
+ zed: Option<PathBuf>,
/// Run zed in dev-server mode
#[arg(long)]
dev_server_token: Option<String>,
@@ -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::<IpcHandshake>::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<PathBuf> {
- 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<Self> {
- unimplemented!()
- }
+ static RELEASE_CHANNEL: Lazy<String> =
+ Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
- pub fn plist(&self) -> &InfoPlist {
- unimplemented!()
+ struct App(PathBuf);
+
+ impl Detect {
+ pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
+ 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))
}
+ }
- pub fn path(&self) -> &Path {
- unimplemented!()
+ 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 launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
- 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(())
}
+ }
- pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
- unimplemented!()
+ 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 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<Self> {
- unimplemented!()
- }
-
- pub fn plist(&self) -> &InfoPlist {
- unimplemented!()
- }
-
- pub fn path(&self) -> &Path {
- unimplemented!()
- }
-
- pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
+ struct App;
+ impl InstalledApp for App {
+ fn zed_version_string(&self) -> String {
unimplemented!()
}
-
- pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
+ fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
unimplemented!()
}
+ }
- pub fn zed_version_string(&self) -> String {
- unimplemented!()
+ impl Detect {
+ pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
+ 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<Self> {
- 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<PathBuf> {
+ 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<impl InstalledApp> {
+ 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,
- }
- }
-
- fn path(&self) -> &Path {
- match self {
- Self::App { app_bundle, .. } => app_bundle,
- Self::LocalPath { executable, .. } => executable,
- }
- }
-
- pub fn spawn(&self, args: Vec<String>) -> 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(())
+ 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(),
+ )
}
- pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
- let (server, server_name) =
- IpcOneShotServer::<IpcHandshake>::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(())
}
+ }
- 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(),
- )
+ impl Bundle {
+ fn plist(&self) -> &InfoPlist {
+ match self {
+ Self::App { plist, .. } => plist,
+ Self::LocalPath { plist, .. } => plist,
+ }
+ }
+
+ fn path(&self) -> &Path {
+ match self {
+ Self::App { app_bundle, .. } => app_bundle,
+ Self::LocalPath { executable, .. } => executable,
+ }
}
}
@@ -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
@@ -428,8 +428,10 @@ impl TestServer {
node_runtime: app_state.node_runtime.clone(),
},
cx,
- );
- });
+ )
+ })
+ .await
+ .unwrap();
TestClient {
app_state,
@@ -164,7 +164,7 @@ pub struct ExtensionIndexLanguageEntry {
actions!(zed, [ReloadExtensions]);
pub fn init(
- fs: Arc<fs::RealFs>,
+ fs: Arc<dyn Fs>,
client: Arc<Client>,
node_runtime: Arc<dyn NodeRuntime>,
language_registry: Arc<LanguageRegistry>,
@@ -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 }
@@ -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
@@ -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<DevServer>);
impl Global for GlobalDevServer {}
-pub fn init(client: Arc<Client>, app_state: AppState, cx: &mut AppContext) {
+pub fn init(client: Arc<Client>, app_state: AppState, cx: &mut AppContext) -> Task<Result<()>> {
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<Client>, 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();
-
- 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);
+ #[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();
+ }
}
- Err(e) => {
- log::error!("Error connecting to '{}': {}", server_url, e);
+ });
+ cx.spawn(|cx| async move {
+ if let Ok(sig) = rx.await {
+ log::info!("received signal {sig:?}");
cx.update(|cx| cx.quit()).log_err();
}
- }
- })
- .detach();
-}
+ })
+ .detach();
+ }
-fn set_ctrlc_handler<F>(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();
- }
+ let server_url = ClientSettings::get_global(&cx).server_url.clone();
+ cx.spawn(|cx| async move {
+ 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 {})
@@ -10,4 +10,4 @@ workspace = true
[dependencies]
gpui.workspace = true
-once_cell = "1.19.0"
+once_cell.workspace = true
@@ -7,7 +7,8 @@ use std::{env, str::FromStr};
use gpui::{AppContext, Global, SemanticVersion};
use once_cell::sync::Lazy;
-static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
+/// stable | dev | nightly | preview
+pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
Lazy::new(|| {
env::var("ZED_RELEASE_CHANNEL")
.unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
@@ -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
@@ -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
@@ -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();
-
- 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());
+enum AppMode {
+ Headless(DevServerToken),
+ Ui,
+}
+impl Global for AppMode {}
- 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<AppState>,
+ cx: &mut AppContext,
+) -> Task<Result<()>> {
+ match cx.try_global::<AppMode>() {
+ 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()
+ )))
}
+ 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,
+ )
+}
- 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);
+fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
+ match cx.try_global::<AppMode>() {
+ 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);
+ }
+ };
- 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));
+ 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::<SettingsStore>({
+ 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,
+ );
- git_hosting_providers::init(cx);
+ dev_server_projects::init(app_state.client.clone(), 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());
+ 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);
- language::init(cx);
- languages::init(languages.clone(), node_runtime.clone(), cx);
- let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
+ cx.set_menus(app_menus());
+ initialize_workspace(app_state.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);
+ cx.activate(true);
- reliability::init(client.http_client(), installation_id, cx);
+ cx.spawn(|cx| async move { authenticate(app_state.client.clone(), &cx).await })
+ .detach_and_log_err(cx);
- headless::init(
- client.clone(),
- headless::AppState {
- languages: languages.clone(),
- user_store: user_store.clone(),
- fs: fs.clone(),
- node_runtime: node_runtime.clone(),
- },
- cx,
- );
- })
+ Ok(())
}
-fn init_ui(args: Args) {
+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::<SettingsStore>({
- 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<AppState>,
- cx: &mut AppContext,
-) -> bool {
+fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, 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)
}
}
@@ -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 _;
@@ -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<CliRequest>, IpcSender<CliResponse>)>,
@@ -116,6 +118,24 @@ impl OpenListener {
}
}
+#[cfg(target_os = "linux")]
+pub fn listen_for_cli_connections(opener: Arc<OpenListener>) -> 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<CliRequest>, IpcSender<CliResponse>)> {
@@ -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![]
@@ -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"
@@ -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"