From b8dc38fb9836692bb7b61d1becc651ea025f8d54 Mon Sep 17 00:00:00 2001 From: thomas Date: Fri, 31 Jul 2026 18:50:02 +0800 Subject: [PATCH] fix(input): anchor the IME candidate window at the fake caret, not the parked cursor (#275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cursor-hiding TUIs (Kimi CLI, Ink apps) draw their caret as a reverse-video cell and leave the real cursor wherever the frame's last write ended — for Kimi that is the input box's right border, and the IME candidate list was stranded there. When the cursor is hidden and its row holds exactly one caret-sized inverse run, snap the IME anchor (and the marked-text preview) to that run; rendering is untouched. The gpui side (bumped here) now also answers IMR_QUERYCHARPOSITION — the query the Windows 11 Microsoft Pinyin IME uses instead of CANDIDATEFORM — and re-anchors the candidate window on every WM_IME_COMPOSITION. Co-Authored-By: Claude Fable 5 --- Cargo.lock | 50 ++++++++++++++++++++--------------------- src/terminal/element.rs | 41 +++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfd9967c..d9879cac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,7 +1459,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "gpui_util", "indexmap", @@ -2023,7 +2023,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "proc-macro2", "quote", @@ -3182,7 +3182,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "accesskit", "anyhow", @@ -3373,7 +3373,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "accesskit", "accesskit_unix", @@ -3424,7 +3424,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "accesskit", "accesskit_macos", @@ -3471,7 +3471,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3482,7 +3482,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "console_error_panic_hook", "gpui", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "schemars", "serde", @@ -3505,7 +3505,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "log", @@ -3514,7 +3514,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "console_error_panic_hook", @@ -3538,7 +3538,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "bytemuck", @@ -3567,7 +3567,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "accesskit", "accesskit_windows", @@ -3886,7 +3886,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "async-compression", @@ -3911,7 +3911,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "rustls", "rustls-platform-verifier", @@ -5039,7 +5039,7 @@ checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "bindgen", @@ -6143,7 +6143,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "collections", "serde", @@ -7116,7 +7116,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "derive_refineable", ] @@ -7159,7 +7159,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "bytes", @@ -7693,7 +7693,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "async-task", "backtrace", @@ -8506,7 +8506,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "heapless", "log", @@ -9971,7 +9971,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "async-fs", @@ -10010,7 +10010,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "perf", "quote", @@ -11825,7 +11825,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "anyhow", "chrono", @@ -11870,7 +11870,7 @@ dependencies = [ [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" dependencies = [ "tracing", "tracing-subscriber", @@ -11881,7 +11881,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/l0ng-ai/zed?branch=tty7#3aac3ef3a1411d539c32610ed29e399ef1d3b0ae" +source = "git+https://github.com/l0ng-ai/zed?branch=tty7#faeb8732b2ae508022740505a27222d2f61ece5f" [[package]] name = "zune-core" diff --git a/src/terminal/element.rs b/src/terminal/element.rs index 3fea8fed..5e1360b8 100644 --- a/src/terminal/element.rs +++ b/src/terminal/element.rs @@ -716,6 +716,9 @@ fn paint_glyphs( struct GridCursor { row: usize, col: usize, + // Where the IME candidate window should anchor: the fake caret drawn by + // cursor-hiding TUIs when one is identifiable, else `col`. + ime_col: usize, hidden: bool, style: crate::core::config::CursorStyle, } @@ -859,12 +862,36 @@ impl TerminalElement { history_size = term.grid().history_size(); let selection = content.selection; + let cur = content.cursor; + let cursor_row = cur.point.line.0 + display_offset; + let cursor_hidden = matches!(cur.shape, CursorShape::Hidden); + // TUIs that hide the hardware cursor (Kimi CLI, Ink apps) draw + // their own caret as a reverse-video cell and park the real + // cursor wherever the frame's last write ended, which strands + // the IME candidate window there (#275). A lone short inverse + // run on the cursor's row is that fake caret; collect runs so + // the IME anchor can snap to it. + let mut inverse_runs: Vec<(usize, usize)> = Vec::new(); + for cell in content.display_iter { let row = cell.point.line.0 + display_offset; let col = cell.point.column.0; if row < 0 || row as usize >= rows || col >= cols { continue; } + if cursor_hidden + && row == cursor_row + && cell.cell.flags.contains(Flags::INVERSE) + && !cell + .cell + .flags + .intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) + { + match inverse_runs.last_mut() { + Some((start, len)) if *start + *len == col => *len += 1, + _ => inverse_runs.push((col, 1)), + } + } let rc = snapshot_cell(cell.cell, cell.point, &palette, colors, selection.as_ref()); any_selected |= rc.selected; buf[row as usize * cols + col] = rc; @@ -891,14 +918,20 @@ impl TerminalElement { sliver = Some(row_buf); } - let cur = content.cursor; - let row = cur.point.line.0 + display_offset; + let row = cursor_row; let col = cur.point.column.0; if row >= 0 && (row as usize) < rows && col < cols { + // Snap the IME anchor to the fake caret when there is + // exactly one caret-sized inverse run on the row. + let ime_col = match inverse_runs.as_slice() { + [(start, len)] if cursor_hidden && *len <= 2 => *start, + _ => col, + }; cursor = Some(GridCursor { row: row as usize, col, - hidden: matches!(cur.shape, CursorShape::Hidden), + ime_col, + hidden: cursor_hidden, style: cursor_style_from_shape(cur.shape), }); } @@ -1238,7 +1271,7 @@ impl Element for TerminalElement { let cursor = snap.cursor; let sliver = snap.sliver.as_ref(); - let cursor_cell = cursor.map(|c| (c.row, c.col)); + let cursor_cell = cursor.map(|c| (c.row, c.ime_col)); let render_cursor = cursor .filter(|c| !c.hidden) .map(|c| (c.row, c.col, c.style));