Merge branch 'main' into ime-panel
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -14212,6 +14212,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"vim",
|
||||
"welcome",
|
||||
"windows 0.58.0",
|
||||
"winresource",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
|
||||
@@ -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<WindowsWindowStatePtr>) -> Option<POINT> {
|
||||
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<WindowsWindowStatePtr>) -> Option<isize> {
|
||||
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<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
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<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
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<Keystroke> {
|
||||
}
|
||||
}
|
||||
|
||||
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<String> {
|
||||
fn parse_ime_compostion_result(ctx: HIMC) -> Option<String> {
|
||||
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<String> {
|
||||
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<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Option<R>
|
||||
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<F, R>(
|
||||
state_ptr: &Rc<WindowsWindowStatePtr>,
|
||||
f: F,
|
||||
) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec!["-File".into(), server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct TailwindLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
@@ -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::<Vec<_>>();
|
||||
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());
|
||||
|
||||
|
||||
@@ -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<PathBuf>;
|
||||
async fn node_environment_path(&self) -> Result<OsString>;
|
||||
|
||||
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<OsString> {
|
||||
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::<Vec<_>>();
|
||||
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<Output> {
|
||||
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::<Vec<_>>();
|
||||
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<OsString> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn run_npm_subcommand(
|
||||
&self,
|
||||
_: Option<&Path>,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
39
crates/zed/src/zed/windows_only_instance.rs
Normal file
39
crates/zed/src/zed/windows_only_instance.rs
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user