Merge branch 'main' into ime-panel

This commit is contained in:
Mikayla
2024-08-28 19:40:07 -07:00
9 changed files with 192 additions and 102 deletions

1
Cargo.lock generated
View File

@@ -14212,6 +14212,7 @@ dependencies = [
"uuid",
"vim",
"welcome",
"windows 0.58.0",
"winresource",
"workspace",
"zed_actions",

View File

@@ -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
}

View File

@@ -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());

View File

@@ -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>,

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View 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
}