fix(input): anchor the IME candidate window at the fake caret, not the parked cursor (#275)

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 <noreply@anthropic.com>
This commit is contained in:
thomas
2026-07-31 23:48:01 +08:00
committed by l0ng-ai
co-authored by Claude Fable 5
parent 9fd84bf555
commit b8dc38fb98
2 changed files with 62 additions and 29 deletions
Generated
+25 -25
View File
@@ -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"
+37 -4
View File
@@ -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));