diff --git a/Cargo.lock b/Cargo.lock index 85907443ea35f8..9b97c99b0c3566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2598,8 +2598,8 @@ dependencies = [ "exec", "fork", "ipc-channel", + "only_instance", "parking_lot", - "paths", "plist", "release_channel", "serde", @@ -8752,6 +8752,17 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "only_instance" +version = "0.1.0" +dependencies = [ + "log", + "paths", + "release_channel", + "sysinfo", + "windows 0.58.0", +] + [[package]] name = "oo7" version = "0.4.0" @@ -16736,6 +16747,7 @@ dependencies = [ "nix", "node_runtime", "notifications", + "only_instance", "outline", "outline_panel", "parking_lot", @@ -16765,7 +16777,6 @@ dependencies = [ "snippet_provider", "snippets_ui", "supermaven", - "sysinfo", "tab_switcher", "task", "tasks_ui", diff --git a/Cargo.toml b/Cargo.toml index 448f653a519ef1..502985002fac11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ members = [ "crates/node_runtime", "crates/notifications", "crates/ollama", + "crates/only_instance", "crates/open_ai", "crates/outline", "crates/outline_panel", @@ -291,6 +292,7 @@ multi_buffer = { path = "crates/multi_buffer" } node_runtime = { path = "crates/node_runtime" } notifications = { path = "crates/notifications" } ollama = { path = "crates/ollama" } +only_instance = { path = "crates/only_instance" } open_ai = { path = "crates/open_ai" } outline = { path = "crates/outline" } outline_panel = { path = "crates/outline_panel" } @@ -366,7 +368,7 @@ zeta = { path = "crates/zeta" } # aho-corasick = "1.1" -alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e"} +alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" } any_vec = "0.14" anyhow = "1.0.86" arrayvec = { version = "0.7.4", features = ["serde"] } @@ -535,7 +537,7 @@ tree-sitter-cpp = "0.23" tree-sitter-css = "0.23" tree-sitter-elixir = "0.3" tree-sitter-embedded-template = "0.23.0" -tree-sitter-gitcommit = {git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9"} +tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" } tree-sitter-go = "0.23" tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" } tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" } @@ -630,7 +632,7 @@ features = [ # TODO livekit https://github.com/RustAudio/cpal/pull/891 [patch.crates-io] cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" } -real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls"} +real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" } [profile.dev] split-debuginfo = "unpacked" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 94e79bd389c674..91b0788b8e25df 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -25,15 +25,15 @@ anyhow.workspace = true clap.workspace = true collections.workspace = true ipc-channel = "0.19" +only_instance.workspace = true parking_lot.workspace = true -paths.workspace = true release_channel.workspace = true serde.workspace = true util.workspace = true tempfile.workspace = true [target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] -exec.workspace = true +exec.workspace = true fork.workspace = true [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a8568360254346..67d3e482d03859 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -355,12 +355,11 @@ mod linux { } fn launch(&self, ipc_url: String) -> anyhow::Result<()> { - let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL)); - let sock = UnixDatagram::unbound()?; - if sock.connect(&sock_path).is_err() { - self.boot_background(ipc_url)?; - } else { + if let Ok(sock) = only_instance::other_instance_running() { + // There is already an instance running sock.send(ipc_url.as_bytes())?; + } else { + self.boot_background(ipc_url)?; } Ok(()) } @@ -535,7 +534,14 @@ mod windows { unimplemented!() } fn launch(&self, _ipc_url: String) -> anyhow::Result<()> { - unimplemented!() + if only_instance::ensure_only_instance() { + // start a new instance + // send ipc_url to the new instance + } else { + // There is already an instance running + // send ipc_url to the existing instance + } + Ok(()) } fn run_foreground(&self, _ipc_url: String) -> io::Result { unimplemented!() diff --git a/crates/only_instance/Cargo.toml b/crates/only_instance/Cargo.toml new file mode 100644 index 00000000000000..3a9aa6f9df7dfd --- /dev/null +++ b/crates/only_instance/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "only_instance" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/only_instance.rs" + +[dependencies] +release_channel.workspace = true + +[target.'cfg(target_os = "macos")'.dependencies] +sysinfo.workspace = true +log.workspace = true + +[target.'cfg(target_os = "linux")'.dependencies] +paths.workspace = true + +[target.'cfg(target_os = "windows")'.dependencies] +windows.workspace = true diff --git a/crates/only_instance/src/linux.rs b/crates/only_instance/src/linux.rs new file mode 100644 index 00000000000000..73525e781be030 --- /dev/null +++ b/crates/only_instance/src/linux.rs @@ -0,0 +1,21 @@ +use release_channel::RELEASE_CHANNEL_NAME; + +use std::io::Result; +use std::os::unix::net::UnixDatagram; + +pub fn ensure_only_instance() -> Result { + let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME)); + // remove the socket if the process listening on it has died + if let Err(e) = UnixDatagram::unbound()?.connect(&sock_path) { + if e.kind() == std::io::ErrorKind::ConnectionRefused { + std::fs::remove_file(&sock_path)?; + } + } + UnixDatagram::bind(&sock_path) +} + +pub fn other_instance_running() -> Result { + let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME)); + let sock = UnixDatagram::unbound()?; + sock.connect(&sock_path).and(Ok(sock)) +} diff --git a/crates/zed/src/zed/mac_only_instance.rs b/crates/only_instance/src/macos.rs similarity index 100% rename from crates/zed/src/zed/mac_only_instance.rs rename to crates/only_instance/src/macos.rs diff --git a/crates/only_instance/src/only_instance.rs b/crates/only_instance/src/only_instance.rs new file mode 100644 index 00000000000000..048c5ef8ec6174 --- /dev/null +++ b/crates/only_instance/src/only_instance.rs @@ -0,0 +1,13 @@ +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub mod linux; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "windows")] +pub mod windows; + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub use linux::*; +#[cfg(target_os = "macos")] +pub use macos::*; +#[cfg(target_os = "windows")] +pub use windows::*; diff --git a/crates/zed/src/zed/windows_only_instance.rs b/crates/only_instance/src/windows.rs similarity index 95% rename from crates/zed/src/zed/windows_only_instance.rs rename to crates/only_instance/src/windows.rs index 2645650bfae70e..2c1a05a2897e15 100644 --- a/crates/zed/src/zed/windows_only_instance.rs +++ b/crates/only_instance/src/windows.rs @@ -7,16 +7,7 @@ use windows::{ }, }; -fn retrieve_app_instance_event_identifier() -> &'static str { - match *release_channel::RELEASE_CHANNEL { - ReleaseChannel::Dev => "Local\\Zed-Editor-Dev-Instance-Event", - ReleaseChannel::Nightly => "Local\\Zed-Editor-Nightly-Instance-Event", - ReleaseChannel::Preview => "Local\\Zed-Editor-Preview-Instance-Event", - ReleaseChannel::Stable => "Local\\Zed-Editor-Stable-Instance-Event", - } -} - -pub fn check_single_instance() -> bool { +pub fn ensure_only_instance() -> bool { unsafe { CreateEventW( None, @@ -29,3 +20,12 @@ pub fn check_single_instance() -> bool { let last_err = unsafe { GetLastError() }; last_err != ERROR_ALREADY_EXISTS } + +fn retrieve_app_instance_event_identifier() -> &'static str { + match *release_channel::RELEASE_CHANNEL { + ReleaseChannel::Dev => "Local\\Zed-Editor-Dev-Instance-Event", + ReleaseChannel::Nightly => "Local\\Zed-Editor-Nightly-Instance-Event", + ReleaseChannel::Preview => "Local\\Zed-Editor-Preview-Instance-Event", + ReleaseChannel::Stable => "Local\\Zed-Editor-Stable-Instance-Event", + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 529c5f8b6bad55..18afea2c7dbfb9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -81,6 +81,7 @@ mimalloc = { version = "0.1", optional = true } nix = { workspace = true, features = ["pthread", "signal"] } node_runtime.workspace = true notifications.workspace = true +only_instance.workspace = true outline.workspace = true outline_panel.workspace = true parking_lot.workspace = true @@ -110,7 +111,6 @@ smol.workspace = true snippet_provider.workspace = true snippets_ui.workspace = true supermaven.workspace = true -sysinfo.workspace = true tab_switcher.workspace = true task.workspace = true tasks_ui.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6ec65acb40188a..46c792effe76d7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -202,26 +202,41 @@ fn main() { let (open_listener, mut open_rx) = OpenListener::new(); - let failed_single_instance_check = - if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev { - false - } else { - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - { - crate::zed::listen_for_cli_connections(open_listener.clone()).is_err() + let failed_single_instance_check = if *db::ZED_STATELESS + || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev + { + false + } else { + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + { + if let Ok(sock) = only_instance::ensure_only_instance() { + // We are the only instance + std::thread::spawn({ + let opener = open_listener.clone(); + move || { + let mut buf = [0u8; 1024]; + while let Ok(len) = sock.recv(&mut buf) { + opener + .open_urls(vec![String::from_utf8_lossy(&buf[..len]).to_string()]); + } + } + }); + false + } else { + true } + } - #[cfg(target_os = "windows")] - { - !crate::zed::windows_only_instance::check_single_instance() - } + #[cfg(target_os = "windows")] + { + !only_instance::ensure_only_instance() + } - #[cfg(target_os = "macos")] - { - use zed::mac_only_instance::*; - ensure_only_instance() != IsOnlyInstance::Yes - } - }; + #[cfg(target_os = "macos")] + { + only_instance::ensure_only_instance() != only_instance::IsOnlyInstance::Yes + } + }; if failed_single_instance_check { println!("zed is already running"); return; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index adb1d2b03f490d..6dad57f35705d3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2,13 +2,9 @@ mod app_menus; pub mod inline_completion_registry; #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) mod linux_prompts; -#[cfg(target_os = "macos")] -pub(crate) mod mac_only_instance; mod migrate; mod open_listener; mod quick_action_bar; -#[cfg(target_os = "windows")] -pub(crate) mod windows_only_instance; use anyhow::Context as _; pub use app_menus::*; diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 4fa22fed796f89..1c04160a7402dd 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -143,28 +143,6 @@ impl OpenListener { } } -#[cfg(any(target_os = "linux", target_os = "freebsd"))] -pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> { - use release_channel::RELEASE_CHANNEL_NAME; - use std::os::unix::net::UnixDatagram; - - let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL_NAME)); - // remove the socket if the process listening on it has died - if let Err(e) = UnixDatagram::unbound()?.connect(&sock_path) { - if e.kind() == std::io::ErrorKind::ConnectionRefused { - std::fs::remove_file(&sock_path)?; - } - } - let listener = UnixDatagram::bind(&sock_path)?; - 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)> {