windows: Fix IME window position on Win10 (#15471)

张小白 created

On Windows, different input methods use different APIs to set their
window positions:
- The Japanese input method on Windows 11 uses `ImmSetCandidateWindow`.
- The Chinese input method on Windows 10 uses `ImmSetCompositionWindow`.
- The Chinese input method on Windows 11 can use either.

Therefore, this PR calls both functions to cover the various scenarios.

Additionally, introduced a helper function `with_input_handler` to
improve code readability.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/events.rs | 171 ++++++++++++++---------
1 file changed, 101 insertions(+), 70 deletions(-)

Detailed changes

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,41 @@ 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()?;
+        let caret_position = input_handler.bounds_for_range(caret_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(caret_range) = input_handler.selected_text_range() 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(caret_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 +621,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 +1233,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 +1252,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 +1280,7 @@ fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
             Some(string)
         } else {
             None
-        };
-        ImmReleaseContext(handle, ctx).ok().log_err();
-        result
+        }
     }
 }
 
@@ -1323,3 +1328,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
+}