这个包是什么What this package ispi-tui 是一个独立的终端 UI 库:组件只需实现 render(width): string[],TUI 把组件树渲染成行,再只把变化的部分写进终端。它自带两种屏幕(保留滚动历史的 regular 与备用屏 fullscreen)、Kitty 键盘协议、覆盖层、多行编辑器和自动补全。pi-tui is a standalone terminal UI library: a component only implements render(width): string[]; the TUI renders the tree to lines and writes only what changed. It ships two screens (regular, which keeps scrollback, and fullscreen on the alternate screen), the Kitty keyboard protocol, overlays, a multi-line editor and autocomplete.
没有 pi 内部依赖,pi-coding-agent 的 interactive 模式建立在它之上。共 19,409 行 TypeScript、18 个内置组件;package.json 没有 exports 字段,入口是 dist/index.js。No internal pi dependencies; pi-coding-agent's interactive mode is built on it. 19,409 lines of TypeScript and 18 built-in components; package.json has no exports map, the entry is dist/index.js.
模块结构Module structure#
一句话结论Takeaway四块:终端与输入(terminal.ts、stdin-buffer.ts、keys.ts)、渲染核心(tui.ts 与两种屏幕)、编辑(editor.ts 与自动补全)、文本与图形(宽度计算、Markdown、LaTeX、终端图片)。components/ 下是可直接使用的组件。Four blocks: terminal & input (terminal.ts, stdin-buffer.ts, keys.ts), the render core (tui.ts and the two screens), editing (editor.ts and autocomplete), and text & graphics (width maths, Markdown, LaTeX, terminal images). components/ holds ready-to-use components.
要点Key points
- 最大的文件是
components/editor.ts:多行编辑、撤销、kill ring、历史、粘贴折叠与补全菜单都在这里。The largest file iscomponents/editor.ts: multi-line editing, undo, the kill ring, history, paste folding and the completion menu all live there. utils.ts的visibleWidth()/truncateToWidth()处理 ANSI 序列与全角字符,是所有组件遵守宽度约定的基础。utils.ts'svisibleWidth()/truncateToWidth()handle ANSI sequences and wide characters; every component relies on them to respect the width contract.native-platform.ts加载可选的原生模块(剪贴板、修饰键状态);没有时功能降级而不报错。native-platform.tsloads an optional native module (clipboard, modifier state); without it features degrade instead of failing.
工作原理How it works
- 创建
ProcessTerminal,再用它创建TuiMainScreen或TuiAltScreen。Create aProcessTerminal, then aTuiMainScreenorTuiAltScreenon top of it. addChild()组件、setFocus()输入组件,start()。addChild()components,setFocus()the input component,start().- 状态变化后调用
requestRender();输入由 TUI 交给获得焦点的组件。CallrequestRender()after state changes; the TUI routes input to the focused component.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/index.tsL182 | export … | 包入口package entry |
| packages/tui/src/tui.tsL495 | TuiBase | 两种屏幕的共同基类base of both screens |
| packages/tui/src/terminal.tsL150 | ProcessTerminal | 真实终端the real terminal |
| packages/tui/src/utils.tsL250 | visibleWidth() | 可见宽度visible width |
| packages/tui/src/components/editor.tsL296 | Editor | 最大的组件the largest component |
公共 APIPublic API#
一句话结论Takeawaypackage.json 没有 exports 字段,只有一个入口 dist/index.js。src/index.ts 从 36 个模块重新导出 90 个值和 85 个类型:组件、TUI 与两种屏幕、终端、按键与键位、编辑器接口、宽度工具和图片。package.json has no exports map — one entry, dist/index.js. src/index.ts re-exports 90 values and 85 types from 36 modules: components, the TUI and both screens, the terminal, keys and bindings, the editor interface, width helpers and images.
常用导出Key exports
| 模块Module | 常用导出Key exports | 用途Use |
|---|---|---|
| packages/tui/src/tui.ts | Container CURSOR_MARKER | 组件模型component model |
| packages/tui/src/tui-main-screen.ts | TuiMainScreen | regular 屏幕regular screen |
| packages/tui/src/tui-alt-screen.ts | TuiAltScreen | fullscreen 屏幕fullscreen screen |
| packages/tui/src/terminal.ts | ProcessTerminal | 真实终端the real terminal |
| packages/tui/src/components/editor.ts | Editor | 多行编辑器multi-line editor |
| packages/tui/src/autocomplete.ts | CombinedAutocompleteProvider | / · @ · 路径补全/ · @ · path completion |
| packages/tui/src/keys.ts | matchesKey parseKey | 按键识别key recognition |
| packages/tui/src/keybindings.ts | KeybindingsManager getKeybindings | 键位bindings |
| packages/tui/src/utils.ts | visibleWidth truncateToWidth | 宽度工具width helpers |
index.ts 的全部导出(按来源模块)Every index.ts export, by source module
展开全部 36 个来源模块Show all 36 source modules90 values85 types
| 来源模块Source module | 值导出Value exports | 类型导出Type exports |
|---|---|---|
marked | Marked | Token Tokens |
| packages/tui/src/autocomplete.ts | CombinedAutocompleteProvider | AutocompleteItem AutocompleteProvider AutocompleteSuggestions SlashCommand |
| packages/tui/src/colors.ts | backgroundAnsi colorToHex colorToOkhsl colorToOklch colorToRgb foregroundAnsi indexedColor mixColors okhslColor oklchColor parseColor rgbColor styleText styleTextWithAnsi | Color ColorMixSpace IndexedColor OkhslChannels OklchChannels OklchColorValue RgbColorValue TerminalColorMode TextAttributes TextStyle |
| packages/tui/src/components/box.ts | Box | — |
| packages/tui/src/components/cancellable-loader.ts | CancellableLoader | — |
| packages/tui/src/components/editor.ts | Editor | EditorOptions EditorTheme |
| packages/tui/src/components/h-stack.ts | HStack | — |
| packages/tui/src/components/image.ts | Image setImageTranscoder | ImageOptions ImageTheme ImageTranscoder |
| packages/tui/src/components/input.ts | Input | — |
| packages/tui/src/components/loader.ts | Loader | LoaderIndicatorOptions |
| packages/tui/src/components/markdown.ts | Markdown | DefaultTextStyle MarkdownOptions MarkdownTheme |
| packages/tui/src/components/mouse-region.ts | MouseRegion | MouseRegionHandler |
| packages/tui/src/components/scroll-view.ts | ScrollView | ScrollViewOptions ScrollViewScrollbar ScrollViewScrollToOptions |
| packages/tui/src/components/select-list.ts | SelectList | SelectItem SelectListLayoutOptions SelectListTheme SelectListTruncatePrimaryContext |
| packages/tui/src/components/settings-list.ts | SettingsList | SettingItem SettingsListTheme |
| packages/tui/src/components/spacer.ts | Spacer | — |
| packages/tui/src/components/text.ts | Text | — |
| packages/tui/src/components/truncated-text.ts | TruncatedText | — |
| packages/tui/src/components/v-stack.ts | VStack | StackChild StackEntry StackEntryOptions StackOptions |
| packages/tui/src/editor-component.ts | — | EditorComponent |
| packages/tui/src/fuzzy.ts | fuzzyFilter fuzzyMatch | FuzzyMatch |
| packages/tui/src/keybindings.ts | getKeybindings KeybindingsManager setKeybindings TUI_KEYBINDINGS | Keybinding KeybindingConflict KeybindingDefinition KeybindingDefinitions Keybindings KeybindingsConfig |
| packages/tui/src/keys.ts | decodeKittyPrintable isKeyRelease isKeyRepeat isKittyProtocolActive Key matchesKey parseKey setKittyProtocolActive | KeyEventType KeyId |
| packages/tui/src/latex.ts | renderLatex | RenderLatexOptions |
| packages/tui/src/native-platform.ts | getNativeClipboard | NativeClipboard |
| packages/tui/src/oklab.ts | oklabToOkhslLightness | — |
| packages/tui/src/program-status.ts | formatProgramStatus | ProgramStatus |
| packages/tui/src/stdin-buffer.ts | StdinBuffer | StdinBufferEventMap StdinBufferOptions |
| packages/tui/src/terminal.ts | isAppleTerminalSession ProcessTerminal | Terminal |
| packages/tui/src/terminal-colors.ts | parseTerminalColorSchemeReport | RgbColor TerminalColorScheme TerminalColors |
| packages/tui/src/terminal-image.ts | allocateImageId calculateImageRows deleteAllKittyImages deleteKittyImage detectCapabilities encodeITerm2 encodeKitty getCapabilities getCellDimensions getGifDimensions getImageDimensions getJpegDimensions getPngDimensions getTerminalColorMode +8 | CellDimensions ImageDimensions ImageProtocol ImageRenderOptions TerminalCapabilities |
| packages/tui/src/tui.ts | Container CURSOR_MARKER compositeTuiLine isFocusable isViewportTUI | Component Focusable OverlayAnchor OverlayBounds OverlayHandle OverlayMargin OverlayOptions OverlayUnfocusOptions SizeValue TUI +9 |
| packages/tui/src/tui-alt-screen.ts | TuiAltScreen | TuiAltScreenOptions |
| packages/tui/src/tui-main-screen.ts | TuiMainScreen | TuiMainScreenRenderState |
| packages/tui/src/utils.ts | getOsc8LinkAtColumn sliceByColumn stripTerminalSequences truncateToWidth visibleWidth wrapTextWithAnsi | — |
| packages/tui/src/wheel-scroll.ts | — | WheelScrollLines |
由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.
组件模型The component model#
一句话结论TakeawayComponent 只有一个必需的渲染方法:render(width) 返回字符串数组,每个元素是一行。没有虚拟 DOM、没有布局树——Container 把子组件的行依次拼起来,TUI 再对这些行做差分。Component has one required render method: render(width) returns an array of strings, one per line. No virtual DOM, no layout tree — Container concatenates its children's lines, and the TUI diffs those lines.
要点Key points
- 每一行的可见宽度不能超过
width;超过时 TUI 抛错并写pi-tui-crash.log,而不是让终端自动换行弄乱画面。No line may be wider thanwidth; if one is, the TUI throws and writespi-tui-crash.loginstead of letting the terminal wrap and corrupt the screen. - 获得焦点的组件(
Focusable.focused = true)在光标处输出零宽的CURSOR_MARKER;TUI 找到它、删掉它,再把硬件光标移过去——输入法候选框因此出现在正确位置。The focused component (Focusable.focused = true) emits a zero-widthCURSOR_MARKERat the cursor; the TUI finds it, strips it and moves the hardware cursor there — so IME candidate windows appear in the right place. - 每行末尾都追加
SEGMENT_RESET(重置样式并关闭超链接),一行的样式不会漏到下一行。SEGMENT_RESET(reset style, close hyperlinks) is appended to every line, so styles never leak into the next line. wantsKeyRelease默认 false:Kitty 协议的按键释放事件会被过滤,组件需要时再显式打开。wantsKeyReleasedefaults to false: Kitty key-release events are filtered unless a component opts in.
工作原理How it works
- 实现
render(width):用visibleWidth()/truncateToWidth()保证每行不超宽。Implementrender(width): usevisibleWidth()/truncateToWidth()so no line is too wide. - 需要输入就实现
handleInput(data),用matchesKey(data, "ctrl+c")等判断按键。For input, implementhandleInput(data)and test keys withmatchesKey(data, "ctrl+c")and friends. - 缓存渲染结果时实现
invalidate(),主题变化时 TUI 会调用它。If you cache rendered output, implementinvalidate(); the TUI calls it when the theme changes.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui.tsL117 | Component | 组件接口component interface |
| packages/tui/src/tui.tsL180 | Focusable | 焦点focus |
| packages/tui/src/tui.tsL196 | CURSOR_MARKER | 光标标记cursor marker |
| packages/tui/src/tui.tsL347 | Container | 子组件拼接child concatenation |
| packages/tui/src/tui.tsL453 | TUI | TUI 接口TUI interface |
TUI 渲染TUI rendering#
一句话结论Takeawaypi-tui 的组件只需实现 render(width) → string[];渲染器批量合并请求,每帧最多 16 ms 一次,只重写变化的行。A pi-tui component only implements render(width) → string[]; the renderer batches requests, caps frames at one per 16 ms, and rewrites only changed lines.
本节沿用第一版的图(原位于总览页),已按 v1.1.0 重新核对。This section keeps its first-edition diagram (formerly on the overview page), re-checked against v1.1.0.
要点Key points
Container组合子组件;可选的handleInput/handleMouse处理输入。Containercomposes children; optionalhandleInput/handleMousehandle input.- 两种屏幕:
TuiMainScreen(主屏,保留 scrollback)与TuiAltScreen(备用屏全屏)。Two screens:TuiMainScreen(main buffer, keeps scrollback) andTuiAltScreen(alternate-screen fullscreen). - 内置组件:Editor、Input、Markdown、SelectList、Image、Loader、ScrollView、Stack 等。Built-in components: Editor, Input, Markdown, SelectList, Image, Loader, ScrollView, Stack and more.
工作原理How it works
ProcessTerminal用StdinBuffer把批量到达的输入拆成完整的按键序列,交给获得焦点的组件。ProcessTerminalusesStdinBufferto split batched input into complete key sequences for the focused component.requestRender()在process.nextTick中合并请求,scheduleRender()保证两帧间隔 ≥MIN_RENDER_INTERVAL_MS(16 ms);force会立即渲染。requestRender()coalesces onprocess.nextTick;scheduleRender()keeps frames ≥MIN_RENDER_INTERVAL_MS(16 ms) apart;forcerenders immediately.TuiMainScreen.doRender()合成 overlay 后与previousLines比较,在同步输出(CSI ?2026h)中只写出变化的行。TuiMainScreen.doRender()composites overlays, diffs againstpreviousLines, and writes only changed lines inside synchronized output (CSI ?2026h).
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui.tsL117 | Component | 最小组件接口minimal component interface |
| packages/tui/src/tui.tsL990 | requestRender() | 批量与节流batching and throttling |
| packages/tui/src/tui-main-screen.tsL124 | TuiMainScreen | 差分渲染differential rendering |
| packages/tui/src/terminal.tsL150 | ProcessTerminal | 终端 I/Oterminal I/O |
| packages/coding-agent/src/modes/interactive/tui-renderer.tsL22 | createInteractiveTui() | 选择屏幕类型picks the screen type |
差分渲染Differential rendering#
一句话结论TakeawayrequestRender() 不立即渲染:同一 tick 内的请求被合并,两帧之间至少间隔 16ms;按键则走 requestImmediateRender() 立即出帧。regular 屏幕按行比较新旧两帧,只重写第一处到最后一处变化之间的行,并且整个写入包在同步输出序列里。requestRender() does not render right away: requests in one tick are merged and frames are at least 16 ms apart; key presses use requestImmediateRender() for an immediate frame. The regular screen compares old and new frames line by line, rewrites only the lines from the first to the last change, and wraps the whole write in synchronized output.
要点Key points
- 宽度变化、高度变化(Termux 除外)、内容变短(开启
clearOnShrink时)以及变化发生在视口上方时,会清屏重绘——终端滚动区里已经滚出去的行无法原地修改。A width change, a height change (except on Termux), shrinking content (withclearOnShrink) or a change above the viewport triggers a full redraw — lines already scrolled into the terminal's history cannot be edited in place. - 只删了行时不重写任何内容,只清掉尾部多出来的行。If lines were only deleted, nothing is rewritten; the extra tail lines are just cleared.
- Kitty 图片占多行:变化区间会扩大到覆盖整张图,避免半张图残留。Kitty images span several rows: the changed range widens to cover the whole image so no half-image remains.
TUI.fullRedraws记录全量重绘次数,可用来检查组件是否引起了不必要的重绘。TUI.fullRedrawscounts full redraws, handy for spotting components that cause needless redraws.
工作原理How it works
- 准备一帧:
render(width)→compositeOverlays()→extractCursorPosition()→applyLineResets()。Prepare a frame:render(width)→compositeOverlays()→extractCursorPosition()→applyLineResets(). - 依次判断是否需要全量重绘(见图中 7 个判断)。Check, in order, whether a full redraw is needed (the seven checks in the figure).
- 否则移动光标到
firstChanged,逐行写到lastChanged,每行先\x1b[2K清除。Otherwise move tofirstChangedand write each line throughlastChanged, clearing each with\x1b[2Kfirst. - 最后把硬件光标放到
CURSOR_MARKER的位置。Finally place the hardware cursor whereCURSOR_MARKERwas.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui.tsL473 | requestRender() | 合并与节流coalesce and throttle |
| packages/tui/src/tui.tsL507 | MIN_RENDER_INTERVAL_MS | 16ms16 ms |
| packages/tui/src/tui.tsL1001 | requestImmediateRender() | 输入后立即渲染render right after input |
| packages/tui/src/tui-main-screen.tsL124 | TuiMainScreen | regular 屏幕the regular screen |
| packages/tui/src/tui-main-screen.tsL247 | doRender() | 差分逻辑the diff logic |
两种屏幕:regular 与 fullscreenTwo screens: regular and fullscreen#
一句话结论Takeawayregular(TuiMainScreen)把内容写进终端自己的滚动区,退出后历史仍在;fullscreen(TuiAltScreen)进入备用屏,自己管理滚动、鼠标、选择与搜索,每帧按屏幕行比较。pi-coding-agent 默认用 fullscreen,可在设置里改为 regular。Regular (TuiMainScreen) writes into the terminal's own scrollback, so history survives exit; fullscreen (TuiAltScreen) switches to the alternate screen and manages scrolling, mouse, selection and search itself, diffing by screen row every frame. pi-coding-agent defaults to fullscreen; settings can switch it to regular.
要点Key points
- 备用屏一帧依次经过:布局帧 → 搜索高亮 → 回到底部提示 → 覆盖层 → 选区 → 短暂提示 → 光标与行尾重置,最后只写变化的行。An alternate-screen frame goes through: layout frame → search highlights → jump-to-end pill → overlays → selection → flashes → cursor and line resets, then only changed rows are written.
- 备用屏开启鼠标报告(
?1000/?1002/?1003/?1004/?1006),滚轮带加速,选择后可通过 OSC 52 复制。The alternate screen enables mouse reporting (?1000/?1002/?1003/?1004/?1006), accelerated wheel scrolling, and copy via OSC 52 after a selection. - 布局用
HStack/VStack/ScrollView,支持 basis / grow / shrink;regular 屏幕不需要这些。Layout usesHStack/VStack/ScrollViewwith basis / grow / shrink; the regular screen does without.
工作原理How it works
TuiAltScreen.start()写\x1b[?1049h进入备用屏并开启鼠标。TuiAltScreen.start()writes\x1b[?1049hto enter the alternate screen and enables the mouse.doRender()调renderLayoutFrame(root, width, height)得到布局与行。doRender()callsrenderLayoutFrame(root, width, height)for the layout and lines.- 与
previousScreen逐行比较;尺寸变化时整屏重绘。Compare row by row withpreviousScreen; redraw the whole screen when the size changes.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui-alt-screen.tsL202 | TuiAltScreen | fullscreen 屏幕the fullscreen screen |
| packages/tui/src/tui-alt-screen.tsL1678 | doRender() | 备用屏一帧one alt-screen frame |
| packages/tui/src/layout.tsL379 | renderLayoutFrame() | 布局帧layout frame |
| packages/tui/src/wheel-scroll.tsL11 | MAX_AUTO_LINES | 滚轮加速上限wheel acceleration cap |
| packages/coding-agent/src/core/settings-manager.tsL1371 | getTuiMode() | 默认 fullscreendefaults to fullscreen |
输入与按键Input and keys#
一句话结论Takeawaystdin 的原始字节先由 StdinBuffer 拼成完整的转义序列,再经过 handleTerminalInput() 的 8 个步骤——终端回复、输入监听器、调试键、覆盖层焦点修复、按键释放过滤——最后才交给获得焦点的组件。Raw stdin bytes are first assembled into whole escape sequences by StdinBuffer, then pass the eight steps of handleTerminalInput() — terminal replies, input listeners, the debug key, overlay focus repair, key-release filtering — before reaching the focused component.
要点Key points
- 单独的 ESC 要等 10ms 才能确定是 Escape 键而不是序列开头;SSH 下默认 100ms,可用
PI_TUI_ESC_TIMEOUT调整。A lone ESC needs 10 ms to be sure it is the Escape key rather than the start of a sequence; 100 ms over SSH, adjustable withPI_TUI_ESC_TIMEOUT. - 启动时请求 Kitty 键盘协议(flags 7);终端不回复时改用 modifyOtherKeys,这样 shift+enter 等组合键仍可区分。Start-up requests the Kitty keyboard protocol (flags 7); if the terminal stays silent it falls back to modifyOtherKeys so combos like shift+enter remain distinguishable.
- 按键用语义 id 表示(
tui.input.submit、tui.editor.undo…,共 47 个),默认键位可被用户覆盖。Keys are referred to by semantic ids (tui.input.submit,tui.editor.undo…, 47 in total), and users can override the default bindings.
工作原理How it works
ProcessTerminal.start()打开 raw 模式与括号粘贴,并发送协议查询。ProcessTerminal.start()enables raw mode and bracketed paste and sends the protocol queries.StdinBuffer输出完整序列;括号粘贴内容作为一整段交付。StdinBufferemits whole sequences; bracketed-paste content is delivered as one chunk.handleTerminalInput(data)走完 8 步,调用focused.handleInput(data),再立即渲染。handleTerminalInput(data)runs the eight steps, callsfocused.handleInput(data), then renders immediately.- 组件里用
getKeybindings().matches(data, id)或matchesKey(data, "ctrl+c")判断。Inside components, test withgetKeybindings().matches(data, id)ormatchesKey(data, "ctrl+c").
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/stdin-buffer.tsL24 | StdinBuffer | 序列重组sequence reassembly |
| packages/tui/src/terminal.tsL136 | resolveEscapeTimeoutMs() | ESC 超时ESC timeout |
| packages/tui/src/tui.tsL1044 | handleTerminalInput() | 8 步分发eight-step dispatch |
| packages/tui/src/keys.tsL820 | matchesKey() | 按键匹配key matching |
| packages/tui/src/keybindings.tsL71 | TUI_KEYBINDINGS | 默认键位default bindings |
| packages/tui/src/keybindings.tsL231 | KeybindingsManager | 可覆盖的绑定overridable bindings |
编辑器与自动补全The editor and autocomplete#
一句话结论TakeawayEditor 是一个完整的多行编辑器:状态是 { lines, cursorLine, cursorCol },配有快照式撤销、Emacs 风格的 kill ring、最多 100 条历史和大段粘贴折叠。handleInput() 按固定顺序尝试 13 类按键;自动补全由可替换的 AutocompleteProvider 提供。Editor is a full multi-line editor: its state is { lines, cursorLine, cursorCol }, with snapshot undo, an Emacs-style kill ring, up to 100 history entries and folding of large pastes. handleInput() tries 13 kinds of keys in a fixed order; completions come from a swappable AutocompleteProvider.
要点Key points
- 超过 10 行或 1000 字符的粘贴显示为
[paste #1 +42 lines]这样的标记,提交时再换回原文——编辑器本身保持轻量。Pastes over 10 lines or 1000 characters show as markers like[paste #1 +42 lines]and expand back on submit — the editor itself stays light. - 内置的
CombinedAutocompleteProvider处理三种情况:行首/的斜杠命令(模糊匹配)、@文件附件(用 fd 搜索,20ms 防抖)、Tab 路径补全。The built-inCombinedAutocompleteProvidercovers three cases:/slash commands at line start (fuzzy matched),@file attachments (searched with fd, 20 ms debounce), and Tab path completion. - 光标前一个字符是
\时,Enter 会删掉它并插入换行而不是提交——给不支持 shift+enter 的终端用。When the character before the cursor is\, Enter deletes it and inserts a newline instead of submitting — a workaround for terminals without shift+enter.
工作原理How it works
handleInput(data):跳转模式 → 括号粘贴 → ctrl+c 交给上层 → 撤销 → 补全菜单 → … → 提交 → 插入字符。handleInput(data): jump mode → bracketed paste → ctrl+c to the parent → undo → completion menu → … → submit → insert text.- 输入触发字符后调用
provider.getSuggestions(lines, line, col, { signal }),结果显示在SelectList中。After a trigger character,provider.getSuggestions(lines, line, col, { signal })runs and results show in aSelectList. - 确认后
applyCompletion()返回新的文本与光标位置。On confirm,applyCompletion()returns the new text and cursor position.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/components/editor.tsL296 | Editor | 编辑器the editor |
| packages/tui/src/components/editor.tsL695 | handleInput() | 按键分支顺序key-branch order |
| packages/tui/src/components/editor.tsL1302 | paste markers | 粘贴折叠阈值paste-fold threshold |
| packages/tui/src/autocomplete.tsL271 | AutocompleteProvider | 补全接口completion interface |
| packages/tui/src/autocomplete.tsL303 | CombinedAutocompleteProvider | / · @ · 路径/ · @ · paths |
| packages/tui/src/kill-ring.tsL8 | KillRing | kill / yankkill / yank |
覆盖层Overlays#
一句话结论TakeawayshowOverlay(component, options) 把一个组件画在其他内容之上并返回 OverlayHandle。位置由 9 个锚点、偏移、百分比 row/col 和边距决定;每帧在 compositeOverlays() 中按列拼进底层行。覆盖层组成一个栈,关闭时焦点回到打开前的组件。showOverlay(component, options) draws a component on top of everything and returns an OverlayHandle. Position comes from nine anchors, offsets, percentage row/col and margins; every frame compositeOverlays() splices it into the base lines by column. Overlays form a stack, and closing one returns focus to whatever had it before.
要点Key points
- 默认居中,宽度为
min(80, 可用宽度);width/maxHeight/row/col都接受数字或"50%"这样的百分比。Default: centred, widthmin(80, available);width/maxHeight/row/colaccept a number or a percentage such as"50%". visible(w, h)每帧重新求值:终端变小时覆盖层可以自动隐藏,焦点也随之转移。visible(w, h)is re-evaluated every frame: an overlay can hide itself on small terminals, and focus moves accordingly.nonCapturing: true的覆盖层只显示、不抢焦点,适合状态提示。AnonCapturing: trueoverlay shows without taking focus — good for status pop-ups.
工作原理How it works
showOverlay()记下当前焦点为preFocus,把覆盖层压栈并聚焦(nonCapturing 除外)。showOverlay()records the current focus aspreFocus, pushes the overlay and focuses it (unless nonCapturing).- 每帧:筛选可见的 → 解析宽度 →
render(width)并截到 maxHeight → 用实际高度解析 row/col →compositeLineAt()。Each frame: filter visible → resolve width →render(width)cut to maxHeight → resolve row/col with the real height →compositeLineAt(). hide()出栈,焦点交给下一个可见覆盖层或preFocus。hide()pops it; focus goes to the next visible overlay orpreFocus.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/tui.tsL243 | OverlayOptions | 位置与尺寸position and size |
| packages/tui/src/tui.tsL298 | OverlayHandle | 控制句柄control handle |
| packages/tui/src/tui.tsL1204 | resolveOverlayLayout() | 布局计算layout maths |
| packages/tui/src/tui.tsL1339 | compositeOverlays() | 按列拼接column splicing |
| packages/tui/src/tui.tsL203 | OverlayAnchor | 9 个锚点nine anchors |
终端能力Terminal capabilities#
一句话结论TakeawayProcessTerminal 在启动时协商能力(raw 模式、括号粘贴、Kitty 键盘协议或 modifyOtherKeys),运行中用 OSC 序列设置标题、进度和程序状态、查询前景/背景色。图片协议、真彩色和超链接按环境变量识别终端后决定,并可用 PI_* 环境变量覆盖。ProcessTerminal negotiates capabilities at start-up (raw mode, bracketed paste, Kitty keyboard protocol or modifyOtherKeys) and at run time uses OSC sequences for the title, progress, program status and fg/bg colour queries. Image protocol, truecolor and hyperlinks are decided by recognising the terminal from env vars, with PI_* overrides.
要点Key points
- 图片:kitty、ghostty、wezterm、warp 用 Kitty 图形协议,iTerm2 用自己的协议;tmux、screen 与其他终端不显示图片。Images: kitty, ghostty, wezterm and warp use the Kitty graphics protocol, iTerm2 its own; tmux, screen and other terminals show no images.
- 超链接(OSC 8)只在确认支持的终端里开启,否则 URL 会被吞掉;tmux 下会实际探测是否转发。Hyperlinks (OSC 8) are enabled only where known to work, since otherwise the URL disappears; under tmux forwarding is actually probed.
- DA 查询(
\x1b[c)用作“查询结束”的哨兵:在它之前没收到 Kitty 回复,就说明终端不支持。The DA query (\x1b[c) is the end-of-queries sentinel: no Kitty reply before it means no support.
工作原理How it works
start(onInput, onResize):raw 模式 →?2004h→ Kitty 查询 → DA。start(onInput, onResize): raw mode →?2004h→ Kitty query → DA.detectCapabilities()根据TERM_PROGRAM、KITTY_WINDOW_ID、TMUX等变量给出 images / trueColor / hyperlinks。detectCapabilities()derives images / trueColor / hyperlinks fromTERM_PROGRAM,KITTY_WINDOW_ID,TMUXand similar variables.stop()关闭各模式并恢复原来的 raw 状态。stop()turns the modes off and restores the previous raw state.
关键文件Key files · v1.1.0
| 文件File | 符号Symbol | 作用Role |
|---|---|---|
| packages/tui/src/terminal.tsL78 | Terminal | 终端接口terminal interface |
| packages/tui/src/terminal.tsL20 | KITTY_KEYBOARD_PROTOCOL_QUERY | Kitty 查询Kitty query |
| packages/tui/src/terminal-image.tsL146 | detectCapabilities() | 能力识别capability detection |
| packages/tui/src/terminal-colors.tsL1 | terminal colours | 颜色查询colour queries |
| packages/tui/src/program-status.tsL8 | ProgramStatus | OSC 7501OSC 7501 |
扩展点Extension points#
Component实现 render(width) 即可成为组件;可选 handleInput / handleMouse。Implement render(width) to be a component; handleInput / handleMouse are optional.AutocompleteProvider替换或组合补全来源。Replace or combine completion sources.KeybindingsManager覆盖默认键位,或增加自己的按键 id。Override default bindings or add your own key ids.showOverlay(component, options)对话框、选择器、提示浮层。Dialogs, pickers, pop-ups.addInputListener()在组件之前拦截或改写输入。Intercept or rewrite input before components see it.Terminal实现接口即可在非真实终端上运行(测试、虚拟终端)。Implement the interface to run on something other than a real terminal (tests, virtual terminals).PI_IMAGE_PROTOCOL · PI_TRUE_COLOR · PI_HYPERLINKS手动覆盖能力识别结果。Manually override capability detection.测试Tests#
一句话结论Takeaway测试集中在按键解析、编辑器行为、差分渲染与覆盖层布局;大部分用虚拟终端断言实际输出的转义序列和屏幕内容。Tests concentrate on key parsing, editor behaviour, differential rendering and overlay layout; most assert the actual escape sequences and screen contents on a virtual terminal.
另有 23 个测试文件未列出(共 43 个,954 个用例)。23 more test files not listed (43 files, 954 cases in total).
用例数 = 源码中 it( / test( 的出现次数(静态统计,不运行测试)。Cases = occurrences of it( / test( in the source (static count; tests were not run).