Skip to content

TUI 在 iTerm2 上静默吞掉中文输入法提交(ime.py 的阈值修复覆盖不到这种失效) #16

Description

@limin112

概述

在 iTerm2 上,用中文输入法向 TUI 输入框提交文字,什么都不会出现 —— 没有字符,
没有乱码,也没有报错。同一个会话里敲英文一切正常。设置
TEXTUAL_DISABLE_KITTY_KEY=1 之后完全恢复。

这跟 apodex/tui/ime.py 已经处理的那个问题不是同一回事。那个模块修的是
「超长提交被还原成可见乱码」(^[32;;26377:24456:...u)。而这里是一个字节都没有
产出,所以现有的修复根本没有机会介入。

环境

终端 iTerm2 3.6.11(build 3.6.11)
系统 macOS,Darwin 25.2.0(Apple Silicon)
textual 8.2.8
模式 --mode react,全屏 TUI
TERM / COLORTERM xterm-256color / truecolor
输入法 macOS 自带拼音

复现步骤

  1. 在 iTerm2 里启动 TUI:uv run frontier-agent --mode react
  2. 焦点放到输入框,切到中文输入法,打拼音,从候选窗口按空格/回车提交
  3. 输入框里什么都没有。同一个输入框里敲英文正常
  4. 加上 TEXTUAL_DISABLE_KITTY_KEY=1 重启 —— 中文输入恢复正常

提交前我先验证过的事

widen_escape_sequence_limit() 确实被调用了apodex/tui/app.py:449),而且在
textual 8.2.8 上确实生效 —— _MAX_SEQUENCE_SEARCH_THRESHOLD 仍然存在,默认值
仍然是 32。把 ime_commit_sequence() 直接喂给 XTermParser,行为跟 ime.py 里写的
分析完全一致:

from textual._xterm_parser import XTermParser
from apodex.tui.ime import ime_commit_sequence, widen_escape_sequence_limit
提交内容 序列长度 阈值 32(上游默认) 阈值 512(修复后)
你好世界(4 字) 29 4 个正确的 Key 事件 4 个正确的 Key 事件
有很大差别(5 字) 36 36 个乱码 Key 事件^[32…) 5 个正确的 Key 事件
我需要中文打字啊(8 字) 54 54 个乱码 Key 事件 8 个正确的 Key 事件

也就是说,解析器层面的这个修复是可靠的。既然「加宽阈值能正确解析这些序列」是可证明的,
而我们在 iTerm2 上观察到的是零个事件、而不是错误的事件,那么结论只能是:
提交的文本压根没有以 Kitty 关联文本序列的形式抵达解析器 —— 丢失发生在
_xterm_parser 的上游。

我还没有抓到 iTerm2 在 CSI > 25 u 模式下实际发出的原始字节,所以无法区分是
iTerm2 没有实现 KITTY_REPORT_ASSOCIATED_TEXT、还是声称支持但提交时不发文本、
还是别的原因。需要的话我可以补一份字节 trace。

为什么值得修

iTerm2 是 macOS 上占比很高的终端,而这个失效是静默的:没有报错,没有乱码,
也没有任何迹象暗示背后是一次键盘协议协商。一个中文用户最自然的结论是「这个 TUI
不支持中文」。README 和 --help 里也没有任何地方提到 TEXTUAL_DISABLE_KITTY_KEY

建议的修法,按成本从低到高

  1. 先写进文档。 README 的故障排查里加一行 TEXTUAL_DISABLE_KITTY_KEY=1
    就能省掉整个排查过程。
  2. 检测并降级。 环境变量里能拿到 TERM_PROGRAM=iTerm.app;在这类终端上可以
    干脆不发 Kitty 请求(或者先用 CSI ? u 查询、检查关联文本那一位),
    等 iTerm2 支持了再启用。
  3. 让现有的静默失效发出声音 —— 见下。

附带问题:widen_escape_sequence_limit() 在一种有歧义的情况下会静默失效

current = getattr(_xterm_parser, "_MAX_SEQUENCE_SEARCH_THRESHOLD", None)
if not isinstance(current, int):
    logger.debug("textual no longer exposes the escape-sequence limit")
    return 0

文档字符串的理由是:常量不存在意味着「要么上游已经修了,要么挪了地方,两种情况下
静默跳过都是对的」。但这两种情况的后果是相反的 —— 如果常量只是被改了名字
截断行为依然存在,这个兜底就会悄无声息地停止工作,中文输入随之回归损坏,
而且在任何用户看得到的日志级别上都不会有提示。debug 不够,改成 warning
成本为零,却能让这类回归被发现。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions