diff --git a/Cargo.lock b/Cargo.lock index f2e3d128f2..e7b6bff119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14212,6 +14212,7 @@ dependencies = [ "uuid", "vim", "welcome", + "windows 0.58.0", "winresource", "workspace", "zed_actions", diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 6843224928..62fd9a4270 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -386,22 +386,18 @@ fn handle_char_msg( keystroke, is_held: lparam.0 & (0x1 << 30) > 0, }; - let dispatch_event_result = func(PlatformInput::KeyDown(event)); - let mut lock = state_ptr.state.borrow_mut(); - lock.callbacks.input = Some(func); + state_ptr.state.borrow_mut().callbacks.input = Some(func); + if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { return Some(0); } let Some(ime_char) = ime_key else { return Some(1); }; - let Some(mut input_handler) = lock.input_handler.take() else { - return Some(1); - }; - drop(lock); - input_handler.replace_text_in_range(None, &ime_char); - state_ptr.state.borrow_mut().input_handler = Some(input_handler); + with_input_handler(&state_ptr, |input_handler| { + input_handler.replace_text_in_range(None, &ime_char); + }); Some(0) } @@ -581,33 +577,42 @@ fn handle_mouse_horizontal_wheel_msg( } } +fn retrieve_caret_position(state_ptr: &Rc) -> Option { + with_input_handler_and_scale_factor(state_ptr, |input_handler, scale_factor| { + let caret_range = input_handler.selected_text_range(false)?; + let caret_position = input_handler.bounds_for_range(caret_range.range)?; + Some(POINT { + // logical to physical + x: (caret_position.origin.x.0 * scale_factor) as i32, + y: (caret_position.origin.y.0 * scale_factor) as i32 + + ((caret_position.size.height.0 * scale_factor) as i32 / 2), + }) + }) +} + fn handle_ime_position(handle: HWND, state_ptr: Rc) -> Option { unsafe { - let mut lock = state_ptr.state.borrow_mut(); let ctx = ImmGetContext(handle); - let Some(mut input_handler) = lock.input_handler.take() else { - return Some(1); - }; - let scale_factor = lock.scale_factor; - drop(lock); - let Some(selection) = input_handler.selected_text_range(false) else { - state_ptr.state.borrow_mut().input_handler = Some(input_handler); + let Some(caret_position) = retrieve_caret_position(&state_ptr) else { return Some(0); }; - let caret_position = input_handler.bounds_for_range(selection.range).unwrap(); - state_ptr.state.borrow_mut().input_handler = Some(input_handler); - let config = CANDIDATEFORM { - dwStyle: CFS_CANDIDATEPOS, - // logical to physical - ptCurrentPos: POINT { - x: (caret_position.origin.x.0 * scale_factor) as i32, - y: (caret_position.origin.y.0 * scale_factor) as i32 - + ((caret_position.size.height.0 * scale_factor) as i32 / 2), - }, - ..Default::default() - }; - ImmSetCandidateWindow(ctx, &config as _).ok().log_err(); + { + let config = COMPOSITIONFORM { + dwStyle: CFS_POINT, + ptCurrentPos: caret_position, + ..Default::default() + }; + ImmSetCompositionWindow(ctx, &config as _).ok().log_err(); + } + { + let config = CANDIDATEFORM { + dwStyle: CFS_CANDIDATEPOS, + ptCurrentPos: caret_position, + ..Default::default() + }; + ImmSetCandidateWindow(ctx, &config as _).ok().log_err(); + } ImmReleaseContext(handle, ctx).ok().log_err(); Some(0) } @@ -617,35 +622,46 @@ fn handle_ime_composition( handle: HWND, lparam: LPARAM, state_ptr: Rc, +) -> Option { + let ctx = unsafe { ImmGetContext(handle) }; + let result = handle_ime_composition_inner(ctx, lparam, state_ptr); + unsafe { ImmReleaseContext(handle, ctx).ok().log_err() }; + result +} + +fn handle_ime_composition_inner( + ctx: HIMC, + lparam: LPARAM, + state_ptr: Rc, ) -> Option { let mut ime_input = None; if lparam.0 as u32 & GCS_COMPSTR.0 > 0 { - let (comp_string, string_len) = parse_ime_compostion_string(handle)?; - let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?; - input_handler.replace_and_mark_text_in_range( - None, - &comp_string, - Some(string_len..string_len), - ); - state_ptr.state.borrow_mut().input_handler = Some(input_handler); + let (comp_string, string_len) = parse_ime_compostion_string(ctx)?; + with_input_handler(&state_ptr, |input_handler| { + input_handler.replace_and_mark_text_in_range( + None, + &comp_string, + Some(string_len..string_len), + ); + })?; ime_input = Some(comp_string); } if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 { let comp_string = &ime_input?; - let caret_pos = retrieve_composition_cursor_position(handle); - let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?; - input_handler.replace_and_mark_text_in_range(None, comp_string, Some(caret_pos..caret_pos)); - state_ptr.state.borrow_mut().input_handler = Some(input_handler); + let caret_pos = retrieve_composition_cursor_position(ctx); + with_input_handler(&state_ptr, |input_handler| { + input_handler.replace_and_mark_text_in_range( + None, + comp_string, + Some(caret_pos..caret_pos), + ); + })?; } if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 { - let comp_result = parse_ime_compostion_result(handle)?; - let mut lock = state_ptr.state.borrow_mut(); - let Some(mut input_handler) = lock.input_handler.take() else { - return Some(1); - }; - drop(lock); - input_handler.replace_text_in_range(None, &comp_result); - state_ptr.state.borrow_mut().input_handler = Some(input_handler); + let comp_result = parse_ime_compostion_result(ctx)?; + with_input_handler(&state_ptr, |input_handler| { + input_handler.replace_text_in_range(None, &comp_result); + })?; return Some(0); } // currently, we don't care other stuff @@ -1218,11 +1234,10 @@ fn parse_char_msg_keystroke(wparam: WPARAM) -> Option { } } -fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> { +fn parse_ime_compostion_string(ctx: HIMC) -> Option<(String, usize)> { unsafe { - let ctx = ImmGetContext(handle); let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0); - let result = if string_len >= 0 { + if string_len >= 0 { let mut buffer = vec![0u8; string_len as usize + 2]; ImmGetCompositionStringW( ctx, @@ -1238,26 +1253,19 @@ fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> { Some((string, string_len as usize / 2)) } else { None - }; - ImmReleaseContext(handle, ctx).ok().log_err(); - result + } } } -fn retrieve_composition_cursor_position(handle: HWND) -> usize { - unsafe { - let ctx = ImmGetContext(handle); - let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0); - ImmReleaseContext(handle, ctx).ok().log_err(); - ret as usize - } +#[inline] +fn retrieve_composition_cursor_position(ctx: HIMC) -> usize { + unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize } } -fn parse_ime_compostion_result(handle: HWND) -> Option { +fn parse_ime_compostion_result(ctx: HIMC) -> Option { unsafe { - let ctx = ImmGetContext(handle); let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0); - let result = if string_len >= 0 { + if string_len >= 0 { let mut buffer = vec![0u8; string_len as usize + 2]; ImmGetCompositionStringW( ctx, @@ -1273,9 +1281,7 @@ fn parse_ime_compostion_result(handle: HWND) -> Option { Some(string) } else { None - }; - ImmReleaseContext(handle, ctx).ok().log_err(); - result + } } } @@ -1323,3 +1329,29 @@ pub(crate) fn current_modifiers() -> Modifiers { function: false, } } + +fn with_input_handler(state_ptr: &Rc, f: F) -> Option +where + F: FnOnce(&mut PlatformInputHandler) -> R, +{ + let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?; + let result = f(&mut input_handler); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + Some(result) +} + +fn with_input_handler_and_scale_factor( + state_ptr: &Rc, + f: F, +) -> Option +where + F: FnOnce(&mut PlatformInputHandler, f32) -> Option, +{ + let mut lock = state_ptr.state.borrow_mut(); + let mut input_handler = lock.input_handler.take()?; + let scale_factor = lock.scale_factor; + drop(lock); + let result = f(&mut input_handler, scale_factor); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + result +} diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index e4486efe8e..39ccc8afa1 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -23,10 +23,16 @@ const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server.ps1"; #[cfg(not(target_os = "windows"))] const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server"; +#[cfg(not(target_os = "windows"))] fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } +#[cfg(target_os = "windows")] +fn server_binary_arguments(server_path: &Path) -> Vec { + vec!["-File".into(), server_path.into(), "--stdio".into()] +} + pub struct TailwindLspAdapter { node: Arc, } @@ -113,20 +119,7 @@ impl LspAdapter for TailwindLspAdapter { #[cfg(target_os = "windows")] { - let mut env_path = vec![self - .node - .binary_path() - .await? - .parent() - .expect("invalid node binary path") - .to_path_buf()]; - - if let Some(existing_path) = std::env::var_os("PATH") { - let mut paths = std::env::split_paths(&existing_path).collect::>(); - env_path.append(&mut paths); - } - - let env_path = std::env::join_paths(env_path)?; + let env_path = self.node.node_environment_path().await?; let mut env = HashMap::default(); env.insert("PATH".to_string(), env_path.to_string_lossy().to_string()); diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index cc4bacc54d..8ea392d1a7 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -10,6 +10,7 @@ use semver::Version; use serde::Deserialize; use smol::io::BufReader; use smol::{fs, lock::Mutex, process::Command}; +use std::ffi::OsString; use std::io; use std::process::{Output, Stdio}; use std::{ @@ -55,6 +56,7 @@ pub struct NpmInfoDistTags { #[async_trait::async_trait] pub trait NodeRuntime: Send + Sync { async fn binary_path(&self) -> Result; + async fn node_environment_path(&self) -> Result; async fn run_npm_subcommand( &self, @@ -216,6 +218,22 @@ impl NodeRuntime for RealNodeRuntime { Ok(installation_path.join(NODE_PATH)) } + async fn node_environment_path(&self) -> Result { + let installation_path = self.install_if_needed().await?; + let node_binary = installation_path.join(NODE_PATH); + let mut env_path = vec![node_binary + .parent() + .expect("invalid node binary path") + .to_path_buf()]; + + if let Some(existing_path) = std::env::var_os("PATH") { + let mut paths = std::env::split_paths(&existing_path).collect::>(); + env_path.append(&mut paths); + } + + Ok(std::env::join_paths(env_path).context("failed to create PATH env variable")?) + } + async fn run_npm_subcommand( &self, directory: Option<&Path>, @@ -224,21 +242,9 @@ impl NodeRuntime for RealNodeRuntime { ) -> Result { let attempt = || async move { let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join(NODE_PATH); let npm_file = installation_path.join(NPM_PATH); - let mut env_path = vec![node_binary - .parent() - .expect("invalid node binary path") - .to_path_buf()]; - - if let Some(existing_path) = std::env::var_os("PATH") { - let mut paths = std::env::split_paths(&existing_path).collect::>(); - env_path.append(&mut paths); - } - - let env_path = - std::env::join_paths(env_path).context("failed to create PATH env variable")?; + let env_path = self.node_environment_path().await?; if smol::fs::metadata(&node_binary).await.is_err() { return Err(anyhow!("missing node binary file")); @@ -423,6 +429,10 @@ impl NodeRuntime for FakeNodeRuntime { unreachable!() } + async fn node_environment_path(&self) -> anyhow::Result { + unreachable!() + } + async fn run_npm_subcommand( &self, _: Option<&Path>, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c4647593e9..0fbdbe1ca7 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -67,7 +67,7 @@ log.workspace = true markdown_preview.workspace = true menu.workspace = true mimalloc = { version = "0.1", optional = true } -nix = {workspace = true, features = ["pthread", "signal"] } +nix = { workspace = true, features = ["pthread", "signal"] } node_runtime.workspace = true notifications.workspace = true outline.workspace = true @@ -99,7 +99,7 @@ tab_switcher.workspace = true supermaven.workspace = true task.workspace = true tasks_ui.workspace = true -time.workspace = true +time.workspace = true telemetry_events.workspace = true terminal_view.workspace = true theme.workspace = true @@ -114,6 +114,9 @@ welcome.workspace = true workspace.workspace = true zed_actions.workspace = true +[target.'cfg(target_os = "windows")'.dependencies] +windows.workspace = true + [target.'cfg(target_os = "windows")'.build-dependencies] winresource = "0.1" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e23ed69584..a92f278939 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -360,9 +360,19 @@ fn main() { } } } - #[cfg(not(target_os = "linux"))] + + #[cfg(target_os = "windows")] { - use zed::only_instance::*; + use zed::windows_only_instance::*; + if !check_single_instance() { + println!("zed is already running"); + return; + } + } + + #[cfg(target_os = "macos")] + { + use zed::mac_only_instance::*; if ensure_only_instance() != IsOnlyInstance::Yes { println!("zed is already running"); return; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1b9b8ee042..b321a8c04c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2,9 +2,11 @@ mod app_menus; pub mod inline_completion_registry; #[cfg(target_os = "linux")] pub(crate) mod linux_prompts; -#[cfg(not(target_os = "linux"))] -pub(crate) mod only_instance; +#[cfg(target_os = "macos")] +pub(crate) mod mac_only_instance; mod open_listener; +#[cfg(target_os = "windows")] +pub(crate) mod windows_only_instance; pub use app_menus::*; use assistant::PromptBuilder; diff --git a/crates/zed/src/zed/only_instance.rs b/crates/zed/src/zed/mac_only_instance.rs similarity index 100% rename from crates/zed/src/zed/only_instance.rs rename to crates/zed/src/zed/mac_only_instance.rs diff --git a/crates/zed/src/zed/windows_only_instance.rs b/crates/zed/src/zed/windows_only_instance.rs new file mode 100644 index 0000000000..e8d32e7ed0 --- /dev/null +++ b/crates/zed/src/zed/windows_only_instance.rs @@ -0,0 +1,39 @@ +use release_channel::ReleaseChannel; +use windows::{ + core::HSTRING, + Win32::{ + Foundation::{GetLastError, ERROR_ALREADY_EXISTS}, + System::Threading::CreateEventW, + }, +}; + +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 { + if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev { + return true; + } + + check_single_instance_event() +} + +fn check_single_instance_event() -> bool { + unsafe { + CreateEventW( + None, + false, + false, + &HSTRING::from(retrieve_app_instance_event_identifier()), + ) + .expect("Unable to create instance sync event") + }; + let last_err = unsafe { GetLastError() }; + last_err != ERROR_ALREADY_EXISTS +}