windows: Fix IME candidate window not appearing at cursor position (#46079)

Lukas Wirth created

Release Notes:

- Fixed some IME popups and the emoji picker not appearing at the cursor
position on windows

Change summary

crates/gpui/src/platform/windows/events.rs | 40 ++++++++++++-----------
crates/gpui/src/platform/windows/window.rs | 11 +++++-
crates/language/src/buffer.rs              |  6 +++
3 files changed, 35 insertions(+), 22 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/events.rs 🔗

@@ -586,30 +586,32 @@ impl WindowsWindowInner {
     }
 
     fn handle_ime_position(&self, handle: HWND) -> Option<isize> {
+        if let Some(caret_position) = self.retrieve_caret_position() {
+            self.update_ime_position(handle, caret_position);
+        }
+        Some(0)
+    }
+
+    pub(crate) fn update_ime_position(&self, handle: HWND, caret_position: POINT) {
         unsafe {
             let ctx = ImmGetContext(handle);
+            if ctx.is_invalid() {
+                return;
+            }
 
-            let Some(caret_position) = self.retrieve_caret_position() else {
-                return Some(0);
+            let config = COMPOSITIONFORM {
+                dwStyle: CFS_POINT,
+                ptCurrentPos: caret_position,
+                ..Default::default()
             };
-            {
-                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();
-            }
+            ImmSetCompositionWindow(ctx, &config).ok().log_err();
+            let config = CANDIDATEFORM {
+                dwStyle: CFS_CANDIDATEPOS,
+                ptCurrentPos: caret_position,
+                ..Default::default()
+            };
+            ImmSetCandidateWindow(ctx, &config).ok().log_err();
             ImmReleaseContext(handle, ctx).ok().log_err();
-            Some(0)
         }
     }
 

crates/gpui/src/platform/windows/window.rs 🔗

@@ -920,8 +920,15 @@ impl PlatformWindow for WindowsWindow {
         self.state.renderer.borrow().gpu_specs().log_err()
     }
 
-    fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
-        // There is no such thing on Windows.
+    fn update_ime_position(&self, bounds: Bounds<Pixels>) {
+        let scale_factor = self.state.scale_factor.get();
+        let caret_position = POINT {
+            x: (bounds.origin.x.0 * scale_factor) as i32,
+            y: (bounds.origin.y.0 * scale_factor) as i32
+                + ((bounds.size.height.0 * scale_factor) as i32 / 2),
+        };
+
+        self.0.update_ime_position(self.0.hwnd, caret_position);
     }
 }
 

crates/language/src/buffer.rs 🔗

@@ -1124,7 +1124,11 @@ impl Buffer {
             syntax_map,
             reparse: None,
             non_text_state_update_count: 0,
-            sync_parse_timeout: Some(Duration::from_millis(1)),
+            sync_parse_timeout: if cfg!(any(test, feature = "test-support")) {
+                Some(Duration::from_millis(10))
+            } else {
+                Some(Duration::from_millis(1))
+            },
             parse_status: watch::channel(ParseStatus::Idle),
             autoindent_requests: Default::default(),
             wait_for_autoindent_txs: Default::default(),