输入法是fcitx5, 在pi coding agent使用的时候输入框出现如图固定的偏移。
检索了网络,没找到方法,问了ai,也没解决。
不知道问题出在哪里,一头雾水。
解决问题后重述:
详细请看:pi-agent输入框bug - #10,来自 lulala
解决方法:终端输入pi --tui-mode fullscreen,或,在pi中 /settings → TUI mode → fullscreen
输入法是fcitx5, 在pi coding agent使用的时候输入框出现如图固定的偏移。
检索了网络,没找到方法,问了ai,也没解决。
不知道问题出在哪里,一头雾水。
解决问题后重述:
详细请看:pi-agent输入框bug - #10,来自 lulala
解决方法:终端输入pi --tui-mode fullscreen,或,在pi中 /settings → TUI mode → fullscreen
可以试试其他终端看看,例如 ghostty 之类的是否能复现,排除一下是否是 konsole 的问题
那你可以试试运行 vim,是否也有这个问题?如果是的话,感觉应该可以通过配置 Fcitx5 来解决,要不然应该早有人报 bug 了,不过我不用 KDE 和 Fcitx5 就不能帮你测了
好的,谢谢帮忙排查问题。
vim 输入中文无问题。
建议你附上一份 fcitx5-diagnose 命令的输出
好的,谢谢指点,现在感觉有点方向了,我再试着让ai排除下bug。
嗯嗯,可以看看是否和 Pi 的硬件光标有关系 PI_HARDWARE_CURSOR=1 pi
神医啊,找到问题了,修好了,多谢指点。
待我明天我总结一下,把原因和解决方式贴出来。
本文由AI排查bug并提出方案,经过本人检阅修改,不保证信息完全准确,但保证解决方法完全可用。
首先终端输入PI_HARDWARE_CURSOR=1 pi 切换光标可见性,再输入文字,看光标在哪里。
终端输入pi --tui-mode fullscreen,如果fullscreen 正常 / regular 异常 ⇒ 问题在 regular 渲染路径;与插件、与光标可见性无关。
简单来说:
一句话:程序先摆好东西、再挪光标;终端却在"摆好东西"那一刻就把光标位置报给了输入法,之后不再更新。
顺带解释为什么全屏模式没事:全屏那套代码是先把光标挪好、再发"这一行画完了",所以照片拍到的是正确位置。
临时使用使用全屏模式 pi --tui-mode fullscreen
或用 /settings → TUI mode → fullscreen,永久修改
把「计算光标字节」与「写出」拆开,让调用点能把字节 append 到 ?2026l 之前。
// 新增:只计算字节 + 维护 hardwareCursorRow,不直接 write
cursorSequences(cursorPos: { row: number; col: number } | null, totalLines: number) {
const hidden = "\x1B[?25l"; // 无可定位行时维持旧行为:隐藏
if (!cursorPos || totalLines <= 0) return { moves: "", visibility: hidden };
const targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));
const targetCol = Math.max(0, cursorPos.col);
const rowDelta = targetRow - this.hardwareCursorRow;
let moves = "";
if (rowDelta > 0) moves += `\x1B[${rowDelta}B`;
else if (rowDelta < 0) moves += `\x1B[${-rowDelta}A`;
moves += `\x1B[${targetCol + 1}G`;
this.hardwareCursorRow = targetRow;
return { moves, visibility: this.getShowHardwareCursor() ? "\x1B[?25h" : hidden };
}
// 保留:仅用于「没有同步块可并入」的分支(firstChanged === -1)
positionHardwareCursor(cursorPos: any, totalLines: number) {
const { moves, visibility } = this.cursorSequences(cursorPos, totalLines);
if (moves) this.terminal.write(moves);
this.terminal.write(visibility);
}
调用点改法(增量路径示例,fullRender 与删行路径同理):
- output.append("\x1B[?2026l");
- output.flush();
this.cursorRow = Math.max(0, newLines.length - 1);
this.hardwareCursorRow = finalCursorRow;
this.maxLinesRendered = Math.max(this.maxLinesRendered, newLines.length);
this.previousViewportTop = Math.max(prevViewportTop, finalCursorRow - height + 1);
- this.positionHardwareCursor(cursorPos, newLines.length);
+ // 光标定位必须留在同步块内:终端会在 ?2026l 处 flush 并把锚点交给 IME
+ const cur = this.cursorSequences(cursorPos, newLines.length);
+ output.append(cur.moves);
+ output.append(cur.visibility);
+ output.append("\x1B[?2026l");
+ output.flush();
注意事项:
hardwareCursorRow = finalCursorRow(帧写完后光标实际所在行)再算 rowDelta,否则下一帧的相对位移会错。?2026l 后定位」的调用点;firstChanged === -1 分支不写帧,保持 positionHardwareCursor 即可。?25h/?25l 一起进同步块是安全的(TuiAltScreen 已如此),还能顺带修掉上游在 Windows Terminal 上报告的「中间帧闪烁」。cursorPos 为空外与旧实现等价,只改变字节顺序;CURSOR_MARKER、cursorPos 计算、编辑器渲染都不动。| 编号 | 标题 | 状态 | 相关性 |
|---|---|---|---|
| IME candidate window stuck at right edge in WezTerm (TUI) | closed(维护者判为 docs 不一致,指派 badlogic) | 症状完全相同。其 follow-up 评论即本 bug 的根因:regular 渲染器在定位/显示光标之前就结束了 ?2026h/?2026l;把定位移入同步块可修好(并在 Windows Terminal 上表现为闪烁)。WezTerm 那半边的根因不同(终端要求硬件光标可见) |
|
| 同上(旧仓库镜像) | closed | 同 issue 的另一份记录 | |
| IME candidate window misplaced when slash-command autocomplete is active | — | 同族:斜杠补全激活时 marker 未发出 → 锚点错位 | |
| fix(tui): keep hardware cursor marker during slash-command autocomplete | PR | 上一条的修复:去掉 !autocompleteState 守卫,让 CURSOR_MARKER 始终发出 |
|
| sync real cursor to fake cursor position in editor/input | PR | 硬件光标定位的早期方案(提到 #491:DSR 查询纠正假光标) | |
| feat(tui): added hardware cursor positioning | PR | 最终采用的 CURSOR_MARKER + Focusable 方案(本 bug 所在的机制) |
|
| Add hardwareCursor setting | PR | showHardwareCursor / PI_HARDWARE_CURSOR / /settings 开关的来源 |
| 编号 | 标题 | 说明 |
|---|---|---|
| Konsole: input method window are not shown under cursor | RESOLVED FIXED;Konsole 的 IME 定位历史 | |
| [konsole] Fcitx5 may work unexpectedly in Konsole | fcitx5 + Konsole 候选框/预编辑异常 | |
| Input Method Occasionally Breaks in X11 Applications in Wayland Session | Wayland 会话下输入法偶发失效 | |
源码要点Konsole master:
src/Vt102Emulation.cpp:?2026h/l → programRequestedSynchronizedUpdate;CSI pn G(CHA) → Screen::setCursorX(p)src/Emulation.cpp:receiveData() 开头 bufferedUpdate();synchronizedUpdateChanged() → showBulk();showBulk() 停定时器并 Q_EMIT outputChanged()src/terminalDisplay/TerminalDisplay.cpp:outputChanged → QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);inputMethodQuery(ImCursorRectangle) = 终端光标格(与光标可见性无关)src/Screen.cpp:setCursorX()(1-based,正确);getImage() 把 cursor 标记烧进图像缓存src/ScreenWindow.cpp:cursorPosition();notifyOutputChanged()| 编号 | 标题 |
|---|---|
fcitx5 never sees the caret pixel position from any Wayland app(cursorRect=(0,0,0,0)) |
|
| 候选框不跟随文本 / 过低的输入框无法显示 | |
| Candidate window position mismatch(在 A 处输入,候选框出现在 B) | |
| 竖排候选列表时候选框位置不对 |
| 编号 | 标题 | 说明 |
|---|---|---|
| terminal: Fix IME position when cursor is hidden | 同构根因:cursor shape == Hidden 时跳过光标位置计算 |
|
| fix: keep IME anchor aligned when terminal cursor is hidden | 同类:TUI 在渲染周期里隐藏光标导致锚点漂移 | |
| Chinese IME candidate window position incorrect inside Zellij | 同类现象(TUI + 隐藏光标 + IME) |
归纳成一句可复用的规则:任何「会被终端当作逻辑光标消费的状态」(IME 锚点、光标位置上报、光标绘制)都必须在同步输出块内部写入。
pi 每帧(tui-main-screen.ts):
write#1: ESC[?2026h …rows… ESC[?2026l ← 写完满宽 padding 后光标在右边缘
write#2: ESC[2A ESC[5G ESC[?25h ← positionHardwareCursor(),在 ?2026l 之后
终端: ?2026l 边界 → flush + outputChanged → inputMethod()->update(ImCursorRectangle)
→ 锚点 = 右边缘;此后不再更新(write#2 不触发任何重画/通知)
对应到 tui-main-screen.ts 的三处「先 ?2026l、后定位」的调用点:
fullRender()(全量重画)firstChanged >= newLines.length 的删行路径tui-alt-screen.ts 恰好相反:它把 ESC[r;cH + ?25h/?25l 追加进 buffer 之后才 buffer += END_SYNCHRONIZED_OUTPUT —— 这就是 fullscreen 正常的全部原因。
为什么 regular 是两次
write():BoundedTerminalWriter.flush()写出帧(含?2026l),随后positionHardwareCursor()才单独terminal.write()。
两次 write 相隔微秒级,Konsole 通常在一次read()里读走;若被拆成两次 read,Konsole 的 10ms 合并定时器会「兜住」并纠正 —— 所以这个 bug 具有时序敏感性(也解释了为什么不同终端/机器表现不一致)。§6 的复现程序把这个变量显式固定下来。
inputMethodQuery(ImCursorRectangle) 虽然实时取 cursorPosition(),但只在 outputChanged 时才被查询,而最后一次 outputChanged 发生在 ?2026l(此时 _cuX 还在右边缘)。Screen::getImage() 会把 rendition.f.cursor 烧进图像缓存,TerminalDisplay 重画用的是这份 _image;updateImage() 只由 outputChanged/resize 触发,而 Screen::setCursorX() 不发信号。⇒ 光标块也停在 ?2026l 时的位置,直到下一次输出。?2026h 会短暂把两者纠正到「上一帧的光标处」,但该帧自己的 ?2026l 又把它钉回右边缘 ⇒ 稳态 = 右边缘。