@@ -201,6 +201,7 @@ impl Bundle {
self.zed_version_string()
);
}
+
Self::LocalPath { executable, .. } => {
let executable_parent = executable
.parent()
@@ -57,8 +57,9 @@ use staff_mode::StaffMode;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
use zed::{
- assets::Assets, build_window_options, handle_keymap_file_changes, initialize_workspace,
- languages, menus,
+ assets::Assets,
+ build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
+ only_instance::{ensure_only_instance, IsOnlyInstance},
};
fn main() {
@@ -66,6 +67,10 @@ fn main() {
init_paths();
init_logger();
+ if ensure_only_instance() != IsOnlyInstance::Yes {
+ return;
+ }
+
log::info!("========== starting zed ==========");
let mut app = gpui::App::new(Assets).unwrap();
@@ -0,0 +1,82 @@
+use std::{
+ io::{Read, Write},
+ net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream},
+ thread,
+ time::Duration,
+};
+
+const PORT: u16 = 43739;
+const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
+const ADDRESS: SocketAddr = SocketAddr::V4(SocketAddrV4::new(LOCALHOST, PORT));
+const INSTANCE_HANDSHAKE: &str = "Zed Editor Instance Running";
+const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
+const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
+const SEND_TIMEOUT: Duration = Duration::from_millis(20);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum IsOnlyInstance {
+ Yes,
+ No,
+}
+
+pub fn ensure_only_instance() -> IsOnlyInstance {
+ if check_got_handshake() {
+ return IsOnlyInstance::No;
+ }
+
+ let listener = match TcpListener::bind(ADDRESS) {
+ Ok(listener) => listener,
+
+ Err(err) => {
+ log::warn!("Error binding to single instance port: {err}");
+ if check_got_handshake() {
+ return IsOnlyInstance::No;
+ }
+
+ // Avoid failing to start when some other application by chance already has
+ // a claim on the port. This is sub-par as any other instance that gets launched
+ // will be unable to communicate with this instance and will duplicate
+ log::warn!("Backup handshake request failed, continuing without handshake");
+ return IsOnlyInstance::Yes;
+ }
+ };
+
+ thread::spawn(move || {
+ for stream in listener.incoming() {
+ let mut stream = match stream {
+ Ok(stream) => stream,
+ Err(_) => return,
+ };
+
+ _ = stream.set_nodelay(true);
+ _ = stream.set_read_timeout(Some(SEND_TIMEOUT));
+ _ = stream.write_all(INSTANCE_HANDSHAKE.as_bytes());
+ }
+ });
+
+ IsOnlyInstance::Yes
+}
+
+fn check_got_handshake() -> bool {
+ match TcpStream::connect_timeout(&ADDRESS, CONNECT_TIMEOUT) {
+ Ok(mut stream) => {
+ let mut buf = vec![0u8; INSTANCE_HANDSHAKE.len()];
+
+ stream.set_read_timeout(Some(RECEIVE_TIMEOUT)).unwrap();
+ if let Err(err) = stream.read_exact(&mut buf) {
+ log::warn!("Connected to single instance port but failed to read: {err}");
+ return false;
+ }
+
+ if buf == INSTANCE_HANDSHAKE.as_bytes() {
+ log::info!("Got instance handshake");
+ return true;
+ }
+
+ log::warn!("Got wrong instance handshake value");
+ false
+ }
+
+ Err(_) => false,
+ }
+}