- add render frame viewport state - wrap render writes in synchronized output - reset terminal styles per rendered line
16 KiB
TODO
当前定位
TinyTUI 现在已经具备最小可运行的 C# TUI 框架骨架:终端输入输出、输入缓冲、按键解析、运行时、组件树、差分渲染、overlay、基础组件和示例都已经成型
后续 TODO 不再按单个控件的小功能罗列,而是对齐 tmp/tui 的完整框架能力,优先补齐会影响整个 TUI 可用性、稳定性、可扩展性和可验证性的部分
框架级缺口
1. 终端会话能力
目标:把当前 console 封装升级成完整的终端会话抽象,负责启动、停止、协议协商和状态恢复
- 管理 bracketed paste、光标显示隐藏、清屏、窗口标题、进度指示等终端模式
- 启动时保存原始终端状态,停止时稳定恢复,避免 raw mode、paste mode、键盘协议泄漏到父 shell
- 支持 Kitty keyboard protocol 探测和启用,失败时 fallback 到 modifyOtherKeys
- Windows 下处理 Virtual Terminal Input,保证 Shift+Tab、组合方向键等输入不会丢失修饰键
- 停止前 drain stdin,避免慢 SSH 或终端延迟导致的按键释放事件泄漏
- 提供可替换的 Terminal 接口,为真实终端和测试终端使用同一套运行时
参考:tmp/tui/src/terminal.ts
本次推进:
- 新增
ITerminalSession,把输入、输出、尺寸、启动停止和终端控制序列收敛为一个可替换的会话抽象 - 新增
ConsoleTerminalSession,统一管理 bracketed paste、光标显示隐藏、窗口标题恢复、OSC 9;4 进度指示、Windows Virtual Terminal Input、退出前输入 drain 和停止时模式恢复 TuiRuntime改为依赖终端会话,保留旧ITerminalInput构造函数用于兼容现有调用方- Example 改为用同一个 terminal session 同时提供 renderer 输出和 runtime 生命周期
为什么先做:
tmp/tui/src/terminal.ts的能力不是单个输出方法,而是完整生命周期对象;先把 C# 侧入口统一起来,后续 Kitty protocol、modifyOtherKeys、虚拟终端测试和渲染管线才能接在同一个抽象上- 当前实现把 Runtime 从“直接控制 input 生命周期”推进到“只协调组件和会话”,终端状态恢复责任更清晰
当前更好的点:
- C# 侧接口直接继承
ITerminalOutput,Renderer 和 Runtime 可以共享同一个 terminal session,避免真实终端和测试终端各自维护两套输出入口 - 保留旧构造函数降低迁移成本,现有使用
ITerminalInput的代码不会立刻断裂
后续仍需补齐:
- Kitty keyboard protocol 目前只预留
KittyProtocolActive和停止恢复序列,还没有实现查询、响应解析、启用和 fallback 到 modifyOtherKeys - Windows VT input 已用 P/Invoke 处理 console mode,但还需要结合真实终端验证 Shift+Tab、Ctrl+方向键、Alt 组合键在不同宿主中的表现
DrainInput目前是基于Console.KeyAvailable的同步排空,后续要和 stdin buffer / keyboard protocol release 事件联动,避免吞掉应由应用处理的 late input- 标题恢复当前只在 Windows 通过
Console.Title尝试保存;Unix 终端通常无法可靠读取原始标题,后续可以增加可选 title stack 或由调用方提供恢复标题 ITerminalSession还缺键盘协议协商事件和测试 fake terminal,实现虚拟终端回归测试前需要继续抽象可观测输出和模拟输入
2. 输入协议和 Keybinding 系统
目标:把按键解析从“组件里判断具体 key name”升级为“协议解析 + 动作映射”的框架能力
- 完整处理 Kitty keyboard protocol 的 press、repeat、release 和 alternate key 信息
- 支持 key release 过滤,并允许组件声明是否接收 release 事件
- 建立统一 KeyId 表达和
MatchesKey能力,避免组件分散处理 escape sequence - 建立全局 keybinding registry,按动作名绑定默认快捷键,并允许外部覆盖
- 提供 keybinding 冲突检测,避免多个动作抢同一个快捷键
- 输入 listener 支持全局拦截和 consume,用于 debug、退出、快捷命令等框架级输入
参考:tmp/tui/src/keys.ts、tmp/tui/src/keybindings.ts
本次推进:
- 新增
KeyId,把组件使用的按键名称收敛为可比较、可匹配的标准按键标识,并兼容pageUp/pageDown与当前page-up/page-down命名差异 - 新增
KeybindingDefinition、KeybindingConflict、KeybindingRegistry和TuiKeybindings,建立动作名到默认快捷键的注册表、用户覆盖入口和冲突检测 Input、SelectList、Editor改为通过 keybinding 动作处理快捷键,不再在组件内直接散落判断具体 key name- 组件构造函数支持注入
KeybindingRegistry,外部可以先在组件级覆盖默认快捷键,后续再接到运行时级全局配置
为什么先做:
tmp/tui/src/keybindings.ts的关键价值是把“按键是什么”和“动作是什么”拆开;先补 registry 后,自动补全、选择列表、编辑器和全局快捷命令可以复用同一个动作系统- C# 侧目前解析器已经能产出标准 key name,本次先不扩大到 Kitty 完整协议,避免同时重写输入解析和组件行为造成不可控回归
- 保持
IInputComponent.HandleInput(TuiInputEvent)不变,只在组件内部使用 action matching,兼容现有 Runtime 和 Example
当前更好的点:
- C# 侧
KeybindingRegistry.SetUserBinding会校验动作名,调用方写错动作会立即抛错,而不是静默忽略 - 默认动作常量集中在
TuiKeybindings,组件调用处能直接看出“行为意图”,比继续比较ctrl+...字符串更容易维护 - 单行输入、选择列表和多行编辑器已经区分
tui.input.*、tui.select.*、tui.editor.*,后续做用户配置时不会因为复用 Enter 语义互相污染
后续仍需补齐:
KeyId目前仍基于已解析的 key name 匹配,还没有像tmp/tui/src/keys.ts那样直接识别 raw escape sequence、Kitty CSI-u、modifyOtherKeys 和 alternate key- 还没有解析 Kitty keyboard protocol 的 press、repeat、release 事件类型,也没有为组件提供是否接收 release 事件的声明能力
KeybindingRegistry目前只支持组件级注入,尚未接入 Runtime 统一持有,也没有全局 input listener、拦截、consume 和 debug 快捷键入口- 冲突检测当前对用户覆盖生效,后续可以增加“默认绑定冲突是否允许按上下文共存”的策略,比如 editor/select 都使用 Enter 但不会同时处理
DefaultInputParser还需要继续补parseKey级能力:空格、Alt+字母、Ctrl+符号、Shift+Tab、Alt+方向键、Windows Ctrl+Backspace 等特殊兼容路径
3. 渲染管线和视口模型
目标:把当前按行差分刷新升级成能长期运行、低闪烁、能处理滚动区域和复杂内容的渲染管线
- 使用 synchronized output 包裹每次渲染,降低闪烁和中间态显示
- 维护 viewport top、工作区高度、硬件光标位置和上一帧尺寸,避免内容增长或缩短时滚动错位
- 区分首次渲染、宽度变化、视口外变化、普通差分更新、内容缩短清理等策略
- 每行输出前统一追加 SGR reset 和 OSC 8 reset,避免 ANSI 样式或超链接泄漏到后续行
- 在渲染层校验每行可见宽度不超过终端宽度,错误时提供可定位的调试信息
- 增加可选写出日志和重绘调试日志,方便定位真实终端中的闪烁、残影和错位问题
参考:tmp/tui/src/tui.ts
本次推进:
- 新增
RenderFrameBuilder、RenderedFrame、RenderViewportState和RenderPipelineOptions,把组件输出统一转换成逻辑帧、可见视口帧和可调试的渲染状态 DifferentialRenderer改为维护上一帧尺寸、viewport top、working height 和内容高水位,能区分首次渲染、宽度变化、高度变化、viewport 变化、内容缩短和普通差分更新- 每次实际写终端时使用 synchronized output 包裹,并且每行统一追加 SGR reset 和 OSC 8 reset,降低闪烁和样式/超链接泄漏风险
- 渲染层先校验可见宽度,默认继续按当前文本模型截断作为兼容防线,也支持通过
ThrowOnWidthOverflow让组件宽度溢出直接暴露 - 增加
TINYTUI_DEBUG_REDRAW和TINYTUI_DEBUG_RENDER对应的重绘/差分日志,默认写到临时目录下的tinytui FullScreenRenderer也复用同一套帧构建逻辑,避免全量渲染和差分渲染在截断、cursor marker、行尾 reset 上行为不一致
为什么先做:
tmp/tui/src/tui.ts的渲染稳定性核心不是单个 escape sequence,而是“渲染前归一化 + viewport 状态 + full redraw 策略 + 差分写入”组合;先把 C# 侧帧模型抽出来,后续 overlay、文本工具和图像能力都能接入同一条管线- 当前组件仍可能输出超过终端宽度的内容,本次保留截断兼容,同时提供溢出日志和可选异常,避免一次性把所有组件改成严格模式造成使用方回归
- 先采用 bottom viewport 模型,只渲染终端可见的最后
Rows行,能避免内容高度超过终端时继续用绝对行号写到不可见区域
当前更好的点:
- C# 侧把渲染帧构建抽成内部类型,
DifferentialRenderer和FullScreenRenderer共享同一套规则,后续做测试 fake output 时更容易只验证帧输出和状态 RenderPipelineOptions是显式对象,不完全依赖环境变量,测试和调用方可以按实例控制 synchronized output、行 reset、缩短清理和溢出策略ViewportState暴露最近一次渲染状态,Example 或后续测试可以直接检查 viewport top、working height 和 logical line count
后续仍需补齐:
- 当前 viewport 变化直接触发全量重绘,还没有像
tmp/tui/src/tui.ts那样在部分追加行场景里精确滚动并更新 hardware cursor row - 宽度校验仍依赖现有
TerminalTextMeasurer,还没有 grapheme cluster、Kitty image line、OSC/APC 完整识别和按列严格切片能力 - 内容缩短清理现在对所有 shrink 默认 full redraw,后续可以增加 overlay-aware 策略,避免 overlay 需要 padding 时误清理工作区
- 调试日志目前记录帧和变化行,但还没有写出最终 ANSI buffer,也没有像参考实现那样在崩溃前恢复终端会话状态
- 硬件光标位置目前仍以绝对
MoveCursorTo到可见行处理,尚未维护hardwareCursorRow与相对移动模型,IME 定位在复杂滚动场景下还需要真实终端验证
4. Overlay 合成正确性
目标:让 overlay 成为菜单、弹窗、自动补全、设置面板等上层交互的稳定基础
- 合成时保留 overlay 右侧的底层内容,而不是简单截断整行
- 处理 ANSI、OSC 8 超链接、APC、Kitty image sequence 等不可见序列
- 使用按列切片和宽字符边界保护,避免中文、emoji、regional indicator 在 overlay 边界被切坏
- overlay 前后插入样式 reset,避免底层样式污染 overlay 或 overlay 样式污染底层
- 完善多层 overlay 的焦点恢复策略,处理 overlay 被临时隐藏、释放焦点、被其他组件短暂抢焦点后的恢复
- 支持 overlay 显示区域与底部视口对齐,避免在内容超过终端高度时覆盖位置偏移
参考:tmp/tui/src/tui.ts、tmp/tui/test/tui-overlay-style-leak.test.ts、tmp/tui/test/overlay-short-content.test.ts
5. 文本模型和 ANSI 感知工具
目标:把文本宽度、截断、切片、换行作为框架底座,而不是散落在组件里的局部逻辑
- 用 grapheme cluster 计算宽度,覆盖 ZWJ emoji、肤色、variation selector、regional indicator
- 提供 ANSI / OSC 感知的
VisibleWidth、TruncateToWidth、SliceByColumn、WrapTextWithAnsi - 支持 tab 宽度配置
- 截断和换行时保持 ANSI 样式闭合,并在换行后恢复必要样式
- 抽象 terminal output normalization,统一清理和补齐行尾 reset
- 所有组件渲染统一依赖这套文本工具,减少宽度计算不一致导致的渲染 bug
参考:tmp/tui/src/utils.ts
6. 组件基础设施
目标:补齐组件体系的横向能力,而不是继续只补单个组件的小功能
- 给组件接口增加
Invalidate语义,用于主题变化、缓存失效和强制重绘 - 建立 Focusable 模型,由运行时统一设置焦点状态,组件只负责在渲染中输出 cursor marker
- 支持硬件光标定位配置,兼容 IME 候选窗定位
- 引入主题接口和默认主题,让组件样式可配置且跨组件一致
- 增加通用组件:
Spacer、TruncatedText、SettingsList、CancellableLoader - 组件渲染缓存统一由宽度、内容和主题状态驱动,避免每帧重复解析 Markdown 或重算复杂布局
参考:tmp/tui/src/components/*
7. 自动补全和命令交互基础
目标:把自动补全从 Editor 局部功能升级为可复用的交互能力
- 定义 autocomplete provider 接口,支持同步和异步候选
- 支持 slash command 候选、文件路径候选、特殊前缀候选
- 使用 overlay 呈现补全列表,并复用 SelectList 的选择、过滤和滚动能力
- 将补全确认、取消、预览、应用文本变更抽象成独立流程
- 让 keybinding 系统负责 Tab、Enter、Escape 等动作触发,避免补全逻辑和具体按键强绑定
参考:tmp/tui/src/autocomplete.ts、tmp/tui/src/components/editor.ts
8. 图像和特殊终端内容
目标:为 Kitty / iTerm2 图像和特殊终端序列预留完整框架入口
- 检测终端图像能力,选择 Kitty graphics protocol、iTerm2 inline image 或文本 fallback
- 解析图片尺寸并按终端 cell 尺寸计算显示区域
- 渲染差分时追踪已显示的 Kitty image id,在内容变化或清屏时删除旧图像
- 合成和截断逻辑识别 image line,避免把图像序列当普通文本切坏
- 在终端 resize 或 cell size 变化后刷新图像布局
参考:tmp/tui/src/terminal-image.ts、tmp/tui/src/components/image.ts
9. 测试基础设施
目标:引入可以验证真实终端行为的测试层,而不是只靠 Example 手动看效果
- 建立虚拟终端或 fake terminal output,捕获 ANSI 输出并模拟窗口尺寸
- 覆盖输入缓冲、按键协议、keybinding、文本宽度、ANSI 换行和截断
- 覆盖差分渲染、内容缩短清理、resize full redraw、viewport 覆盖和硬件光标定位
- 覆盖 overlay 样式泄漏、短内容覆盖长内容、宽字符边界和多层焦点恢复
- 覆盖 Markdown、SelectList、Editor、Autocomplete 等组件的框架行为
- 示例仍用于人工验收,但不能替代回归测试
参考:tmp/tui/test/virtual-terminal.ts、tmp/tui/test/*.test.ts
10. 工程化和发布完整性
目标:让 TinyTUI 从本地骨架变成可维护、可发布、可集成的类库
- 完善 README,覆盖快速开始、核心 API、组件接口、overlay、keybinding、文本工具和自定义组件约束
- 补 XML documentation 和包元数据,为 NuGet 发布做准备
- 增加 CI:restore、build、format、test、pack
- 增加 benchmark 或压力示例,用于观察大文本、频繁刷新、overlay 合成和 Markdown 缓存性能
- 拆分 Example 为多个真实场景:基础输入、overlay 菜单、聊天界面、Markdown 浏览、设置面板、图像 fallback
- 明确公开 API 和内部实现边界,避免后续重构时破坏使用者代码
建议执行顺序
- 先补终端会话能力、输入协议和 keybinding,因为它们会影响所有交互组件
- 再升级文本模型、渲染管线和 overlay 合成正确性,因为它们决定整体显示稳定性
- 然后补组件基础设施、自动补全和主题,让上层组件可以复用统一能力
- 接着引入虚拟终端测试和关键回归用例,把已修过的问题固化下来
- 最后处理图像、benchmark、README、CI、NuGet 等发布和扩展能力
暂缓
- 不继续列举 Editor、SelectList、Markdown 的零散小功能
- 不优先追求所有组件与
tmp/tui逐项一致 - 图像能力可以等终端、渲染、文本和测试基础稳定后再做
- NuGet 发布可以等公开 API 基本稳定后再做