windows: implement `IME` caret movement and editing while composing (#9659)

张小白 created

https://github.com/zed-industries/zed/assets/14981363/598440f7-0364-4053-9f44-710291f8aa92



Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/window.rs | 31 ++++++++++++++++++++---
1 file changed, 27 insertions(+), 4 deletions(-)

Detailed changes

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

@@ -51,6 +51,7 @@ pub(crate) struct WindowsWindowInner {
     pub(crate) handle: AnyWindowHandle,
     hide_title_bar: bool,
     display: RefCell<Rc<WindowsDisplay>>,
+    last_ime_input: RefCell<Option<String>>,
 }
 
 impl WindowsWindowInner {
@@ -110,6 +111,7 @@ impl WindowsWindowInner {
         let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
         let callbacks = RefCell::new(Callbacks::default());
         let display = RefCell::new(display);
+        let last_ime_input = RefCell::new(None);
         Self {
             hwnd,
             origin,
@@ -122,6 +124,7 @@ impl WindowsWindowInner {
             handle,
             hide_title_bar,
             display,
+            last_ime_input,
         }
     }
 
@@ -733,6 +736,15 @@ impl WindowsWindowInner {
         }
     }
 
+    fn retrieve_composition_cursor_position(&self) -> usize {
+        unsafe {
+            let ctx = ImmGetContext(self.hwnd);
+            let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
+            ImmReleaseContext(self.hwnd, ctx);
+            ret as usize
+        }
+    }
+
     fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
         if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
             let Some((string, string_len)) = self.parse_ime_compostion_string() else {
@@ -747,11 +759,21 @@ impl WindowsWindowInner {
                 Some(0..string_len),
             );
             self.input_handler.set(Some(input_handler));
-            None
-        } else {
-            // currently, we don't care other stuff
-            None
+            *self.last_ime_input.borrow_mut() = Some(string);
         }
+        if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
+            let Some(ref comp_string) = *self.last_ime_input.borrow() else {
+                return None;
+            };
+            let caret_pos = self.retrieve_composition_cursor_position();
+            let Some(mut input_handler) = self.input_handler.take() else {
+                return None;
+            };
+            input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
+            self.input_handler.set(Some(input_handler));
+        }
+        // currently, we don't care other stuff
+        None
     }
 
     fn parse_ime_char(&self, wparam: WPARAM) -> Option<String> {
@@ -771,6 +793,7 @@ impl WindowsWindowInner {
         };
         input_handler.replace_text_in_range(None, &ime_char);
         self.input_handler.set(Some(input_handler));
+        *self.last_ime_input.borrow_mut() = None;
         self.invalidate_client_area();
         Some(0)
     }