图解 Pi AgentPi Agent, Illustratedv1.1.0
包Packages· tui
演示Slides
pi-tui已完成Donepackages/tui · v1.1.0

tui@earendil-works/pi-tui

差分渲染的终端 UI 库。Terminal User Interface library with differential rendering for efficient text-based applications.

1.1.0
版本version
46
源文件src files
19,409
源码行src LOC
43
测试文件test files
954
测试用例test cases
1
导出路径export paths
依赖Depends on无内部依赖no internal deps被依赖Used bycoding-agent

这个包是什么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.

01pi-tui

模块结构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.

四组模块与 components/(标签为行数)Four module groups and components/ (tags = lines)
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
pi-tui modules (19,409 lines of TypeScript)终端与输入Terminal & input渲染核心Render core编辑Editing文本与图形Text & graphicscomponents/:开箱即用的组件components/: ready-made components18 个文件18 files609terminal.tsProcessTerminal · 协议协商ProcessTerminal · negotiation444stdin-buffer.ts字节流 → 完整序列bytes → whole sequences1,401keys.tsparseKey · matchesKey320keybindings.ts可配置的按键 idconfigurable key ids65native-platform.ts剪贴板 · 修饰键(可选)clipboard · modifiers (opt.)53program-status.tsOSC 7501 程序状态OSC 7501 program status1,493tui.tsComponent · TuiBase · 覆盖层Component · TuiBase · overlays655tui-main-screen.tsregular:行级差分regular: line diff1,784tui-alt-screen.tsfullscreen:备用屏fullscreen: alt screen449layout.ts布局帧 · 滚动条layout frame · scrollbars327alt-screen-search.ts全屏内搜索in-screen search82wheel-scroll.ts滚轮加速wheel acceleration2,472editor.ts多行编辑器multi-line editor861autocomplete.ts/ 命令 · @ 文件 · 路径/ commands · @ files · paths28undo-stack.ts快照式撤销snapshot undo46kill-ring.tsEmacs 式 kill/yankEmacs kill / yank138fuzzy.ts模糊匹配打分fuzzy scoring117word-navigation.ts按词移动word motion1,397utils.tsvisibleWidth · 截断 · 换行visibleWidth · truncate · wrap1,025markdown.tsMarkdown → ANSI1,506latex.tsLaTeX → Unicode763terminal-image.tsKitty / iTerm2 图片Kitty / iTerm2 images367colors.ts颜色转换colour conversion91terminal-colors.ts前景/背景色探测fg / bg detectionalt-screen-flash 51box 172cancellable-loader 40editor 2,472h-stack 44image 167input 494loader 101markdown 1,025mouse-region 33scroll-view 224select-list 273settings-list 328spacer 28stack 154text 113truncated-text 65v-stack 33标签 = 行数tag = lines of code终端terminal渲染render编辑editing文本 · 图形text · graphics

要点Key points

  • 最大的文件是 components/editor.ts:多行编辑、撤销、kill ring、历史、粘贴折叠与补全菜单都在这里。The largest file is components/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's visibleWidth() / truncateToWidth() handle ANSI sequences and wide characters; every component relies on them to respect the width contract.
  • native-platform.ts 加载可选的原生模块(剪贴板、修饰键状态);没有时功能降级而不报错。native-platform.ts loads an optional native module (clipboard, modifier state); without it features degrade instead of failing.

工作原理How it works

  1. 创建 ProcessTerminal,再用它创建 TuiMainScreen 或 TuiAltScreen。Create a ProcessTerminal, then a TuiMainScreen or TuiAltScreen on top of it.
  2. addChild() 组件、setFocus() 输入组件,start()。addChild() components, setFocus() the input component, start().
  3. 状态变化后调用 requestRender();输入由 TUI 交给获得焦点的组件。Call requestRender() after state changes; the TUI routes input to the focused component.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/index.tsL182export …包入口package entry
packages/tui/src/tui.tsL495TuiBase两种屏幕的共同基类base of both screens
packages/tui/src/terminal.tsL150ProcessTerminal真实终端the real terminal
packages/tui/src/utils.tsL250visibleWidth()可见宽度visible width
packages/tui/src/components/editor.tsL296Editor最大的组件the largest component
02pi-tui

公共 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.tsContainer CURSOR_MARKER组件模型component model
packages/tui/src/tui-main-screen.tsTuiMainScreenregular 屏幕regular screen
packages/tui/src/tui-alt-screen.tsTuiAltScreenfullscreen 屏幕fullscreen screen
packages/tui/src/terminal.tsProcessTerminal真实终端the real terminal
packages/tui/src/components/editor.tsEditor多行编辑器multi-line editor
packages/tui/src/autocomplete.tsCombinedAutocompleteProvider/ · @ · 路径补全/ · @ · path completion
packages/tui/src/keys.tsmatchesKey parseKey按键识别key recognition
packages/tui/src/keybindings.tsKeybindingsManager getKeybindings键位bindings
packages/tui/src/utils.tsvisibleWidth 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
markedMarkedToken Tokens
packages/tui/src/autocomplete.tsCombinedAutocompleteProviderAutocompleteItem AutocompleteProvider AutocompleteSuggestions SlashCommand
packages/tui/src/colors.tsbackgroundAnsi colorToHex colorToOkhsl colorToOklch colorToRgb foregroundAnsi indexedColor mixColors okhslColor oklchColor parseColor rgbColor styleText styleTextWithAnsiColor ColorMixSpace IndexedColor OkhslChannels OklchChannels OklchColorValue RgbColorValue TerminalColorMode TextAttributes TextStyle
packages/tui/src/components/box.tsBox—
packages/tui/src/components/cancellable-loader.tsCancellableLoader—
packages/tui/src/components/editor.tsEditorEditorOptions EditorTheme
packages/tui/src/components/h-stack.tsHStack—
packages/tui/src/components/image.tsImage setImageTranscoderImageOptions ImageTheme ImageTranscoder
packages/tui/src/components/input.tsInput—
packages/tui/src/components/loader.tsLoaderLoaderIndicatorOptions
packages/tui/src/components/markdown.tsMarkdownDefaultTextStyle MarkdownOptions MarkdownTheme
packages/tui/src/components/mouse-region.tsMouseRegionMouseRegionHandler
packages/tui/src/components/scroll-view.tsScrollViewScrollViewOptions ScrollViewScrollbar ScrollViewScrollToOptions
packages/tui/src/components/select-list.tsSelectListSelectItem SelectListLayoutOptions SelectListTheme SelectListTruncatePrimaryContext
packages/tui/src/components/settings-list.tsSettingsListSettingItem SettingsListTheme
packages/tui/src/components/spacer.tsSpacer—
packages/tui/src/components/text.tsText—
packages/tui/src/components/truncated-text.tsTruncatedText—
packages/tui/src/components/v-stack.tsVStackStackChild StackEntry StackEntryOptions StackOptions
packages/tui/src/editor-component.ts—EditorComponent
packages/tui/src/fuzzy.tsfuzzyFilter fuzzyMatchFuzzyMatch
packages/tui/src/keybindings.tsgetKeybindings KeybindingsManager setKeybindings TUI_KEYBINDINGSKeybinding KeybindingConflict KeybindingDefinition KeybindingDefinitions Keybindings KeybindingsConfig
packages/tui/src/keys.tsdecodeKittyPrintable isKeyRelease isKeyRepeat isKittyProtocolActive Key matchesKey parseKey setKittyProtocolActiveKeyEventType KeyId
packages/tui/src/latex.tsrenderLatexRenderLatexOptions
packages/tui/src/native-platform.tsgetNativeClipboardNativeClipboard
packages/tui/src/oklab.tsoklabToOkhslLightness—
packages/tui/src/program-status.tsformatProgramStatusProgramStatus
packages/tui/src/stdin-buffer.tsStdinBufferStdinBufferEventMap StdinBufferOptions
packages/tui/src/terminal.tsisAppleTerminalSession ProcessTerminalTerminal
packages/tui/src/terminal-colors.tsparseTerminalColorSchemeReportRgbColor TerminalColorScheme TerminalColors
packages/tui/src/terminal-image.tsallocateImageId calculateImageRows deleteAllKittyImages deleteKittyImage detectCapabilities encodeITerm2 encodeKitty getCapabilities getCellDimensions getGifDimensions getImageDimensions getJpegDimensions getPngDimensions getTerminalColorMode +8CellDimensions ImageDimensions ImageProtocol ImageRenderOptions TerminalCapabilities
packages/tui/src/tui.tsContainer CURSOR_MARKER compositeTuiLine isFocusable isViewportTUIComponent Focusable OverlayAnchor OverlayBounds OverlayHandle OverlayMargin OverlayOptions OverlayUnfocusOptions SizeValue TUI +9
packages/tui/src/tui-alt-screen.tsTuiAltScreenTuiAltScreenOptions
packages/tui/src/tui-main-screen.tsTuiMainScreenTuiMainScreenRenderState
packages/tui/src/utils.tsgetOsc8LinkAtColumn sliceByColumn stripTerminalSequences truncateToWidth visibleWidth wrapTextWithAnsi—
packages/tui/src/wheel-scroll.ts—WheelScrollLines

由构建脚本解析 export { … } from 语句生成。Generated by parsing the export { … } from statements at build time.

03pi-tui

组件模型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.

接口、一棵组件树示例、每帧的约定与内置组件Interfaces, an example tree, the per-frame contract and built-in components
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
The component model: render(width) returns lines例:交互模式的组件树e.g. the interactive-mode tree每一帧的约定The contract for every frame内置组件Built-in components18 个,按用途18, by purpose«interface»Component+render(width): string[]+handleInput?(data): void+handleMouse?(event)+wantsKeyRelease?: boolean+invalidate(): void«interface»Focusable+focused: boolean«class»Container+children: Component[]+addChild() · removeChild() · clear()+render(w): concat(child.render(w))«interface»TUI extends Component+mode: "regular" | "fullscreen"+setFocus(c) · showOverlay(c, opts)+requestRender(force?) · renderNow()+… 25 members in totalTuiAltScreen根 = Containerroot = ContainerText标题与提示title & hintsScrollView消息列表(Markdown…)messages (Markdown…)Container扩展 widgetsextension widgetsEditorfocused = trueText页脚:模型 · tokenfooter: model · tokensboxstackh-stackv-stackspacerscroll-viewmouse-regiontexttruncated-textmarkdownimageinputeditorselect-listsettings-listloadercancellable-loaderalt-screen-flashrender(width) 自上而下,各子组件的行拼在一起render(width) top-down; child lines are concatenated每行可见宽度 ≤ widthEvery line's visible width ≤ width否则抛错,并写 pi-tui-crash.logotherwise it throws and writes pi-tui-crash.logfocused 组件在光标处输出 CURSOR_MARKERThe focused component emits CURSOR_MARKER"\x1b_pi:c\x07":零宽 APC 序列"\x1b_pi:c\x07": a zero-width APC sequenceTUI 找到标记并删掉,再移动硬件光标TUI finds and strips it, then moves the hardware cursor输入法候选框因此跟着光标走so IME candidate windows follow the cursor每行末尾追加 SEGMENT_RESETEach line gets SEGMENT_RESET appended\x1b[0m\x1b]8;;\x07:样式和超链接不会串行\x1b[0m\x1b]8;;\x07: no style or link bleeds to the next lineinvalidate():主题变化时清缓存invalidate(): drop caches on theme change下一次 render 从头计算the next render starts from scratch布局layout内容content输入input反馈feedback12345接口 / 组件interfaces / components获得焦点focused约定contract

要点Key points

  • 每一行的可见宽度不能超过 width;超过时 TUI 抛错并写 pi-tui-crash.log,而不是让终端自动换行弄乱画面。No line may be wider than width; if one is, the TUI throws and writes pi-tui-crash.log instead of letting the terminal wrap and corrupt the screen.
  • 获得焦点的组件(Focusable.focused = true)在光标处输出零宽的 CURSOR_MARKER;TUI 找到它、删掉它,再把硬件光标移过去——输入法候选框因此出现在正确位置。The focused component (Focusable.focused = true) emits a zero-width CURSOR_MARKER at 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 协议的按键释放事件会被过滤,组件需要时再显式打开。wantsKeyRelease defaults to false: Kitty key-release events are filtered unless a component opts in.

工作原理How it works

  1. 实现 render(width):用 visibleWidth() / truncateToWidth() 保证每行不超宽。Implement render(width): use visibleWidth() / truncateToWidth() so no line is too wide.
  2. 需要输入就实现 handleInput(data),用 matchesKey(data, "ctrl+c") 等判断按键。For input, implement handleInput(data) and test keys with matchesKey(data, "ctrl+c") and friends.
  3. 缓存渲染结果时实现 invalidate(),主题变化时 TUI 会调用它。If you cache rendered output, implement invalidate(); the TUI calls it when the theme changes.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/tui.tsL117Component组件接口component interface
packages/tui/src/tui.tsL180Focusable焦点focus
packages/tui/src/tui.tsL196CURSOR_MARKER光标标记cursor marker
packages/tui/src/tui.tsL347Container子组件拼接child concatenation
packages/tui/src/tui.tsL453TUITUI 接口TUI interface
04pi-tui

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.

从按键 / 事件到终端输出的一帧One frame, from keypress or event to terminal output
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
TUI renderingstdin批量到达的字节bytes arrive in batchesProcessTerminalStdinBuffer 切分按键StdinBuffer splits keys焦点组件focused ComponenthandleInput(data)requestRender()nextTick 合并coalesced on nextTickscheduleRender()帧间隔 ≥ 16 ms≥ 16 ms between framesagent 事件agent eventsInteractiveMode 更新InteractiveMode updatesdoRender()render(width) → string[]合成 overlaycomposite overlays弹窗、选择器叠在上面dialogs · selectors on top对比 previousLinesdiff previousLines找出变化的行find changed lines只写变化的行write changed linesCSI ?2026h … ?2026l> fix the failing test● read math.test.ts● bash npm test ✓ 41 passed ✓ 1 fixedDone. The test now…──────────────────────▌render(width): string[]handleInput?(data)invalidate()force = true高亮 = 本帧重写的行highlighted = rewritten this frame组件契约COMPONENT CONTRACT123456789一帧的路径one frame异步事件async eventforce:重置并立即全量重绘force: reset + immediate full redraw

要点Key points

  • Container 组合子组件;可选的 handleInput / handleMouse 处理输入。Container composes children; optional handleInput / handleMouse handle input.
  • 两种屏幕:TuiMainScreen(主屏,保留 scrollback)与 TuiAltScreen(备用屏全屏)。Two screens: TuiMainScreen (main buffer, keeps scrollback) and TuiAltScreen (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

  1. ProcessTerminal 用 StdinBuffer 把批量到达的输入拆成完整的按键序列,交给获得焦点的组件。ProcessTerminal uses StdinBuffer to split batched input into complete key sequences for the focused component.
  2. requestRender() 在 process.nextTick 中合并请求,scheduleRender() 保证两帧间隔 ≥ MIN_RENDER_INTERVAL_MS(16 ms);force 会立即渲染。requestRender() coalesces on process.nextTick; scheduleRender() keeps frames ≥ MIN_RENDER_INTERVAL_MS (16 ms) apart; force renders immediately.
  3. TuiMainScreen.doRender() 合成 overlay 后与 previousLines 比较,在同步输出(CSI ?2026h)中只写出变化的行。TuiMainScreen.doRender() composites overlays, diffs against previousLines, and writes only changed lines inside synchronized output (CSI ?2026h).

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/tui.tsL117Component最小组件接口minimal component interface
packages/tui/src/tui.tsL990requestRender()批量与节流batching and throttling
packages/tui/src/tui-main-screen.tsL124TuiMainScreen差分渲染differential rendering
packages/tui/src/terminal.tsL150ProcessTerminal终端 I/Oterminal I/O
packages/coding-agent/src/modes/interactive/tui-renderer.tsL22createInteractiveTui()选择屏幕类型picks the screen type
05pi-tui

差分渲染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.

渲染调度、帧准备与 regular 屏幕的 7 个判断Render scheduling, frame preparation and the regular screen's seven checks
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Differential rendering: what follows requestRender()调度Scheduling合并请求 · 每帧至少间隔 16mscoalesced · at least 16 ms between frames准备一帧(两种屏幕共用)Preparing a frame (both screens)TuiMainScreen.doRender()regular 模式:在终端滚动区里原地更新regular mode: update in place in the scrollbackrequestRender()已请求就直接返回no-op if already pendingnextTick同一 tick 内合并coalesce this tickscheduleRender()setTimeout(16ms − 距上一帧)setTimeout(16 ms − since last)doRender()子类实现:per subclass:main / alt screenhandleInput(data)requestImmediateRender()nextTick 直接渲染,取消等待中的定时器render on nextTick, cancel any pending timerrender(width)组件树 → 行tree → linescompositeOverlays覆盖层拼进行内splice overlays inextractCursorPosition找到并删掉标记find & strip markerapplyLineResets行尾 SEGMENT_RESETSEGMENT_RESET per line宽度检查width check超宽 → 抛错too wide → throw首帧?first frame?previousLines = []全量输出full renderfullRender(false)宽度变了?width changed?previousWidth清屏重绘clear + redrawfullRender(true)高度变了?height changed?Termux 除外not on Termux清屏重绘clear + redrawfullRender(true)内容变短?shrank?clearOnShrink清屏重绘clear + redrawfullRender(true)没有变化?nothing changed?逐行比较compare lines只移光标move cursor only只删了行?only deletions?first ≥ new.length清掉尾部行clear tail lines在视口上方?above viewport?first < viewportTop清屏重绘clear + redrawfullRender(true)否则:局部重写else: partial writefirstChanged … lastChanged\x1b[?2026h … ?2026l定时器到期timer fires按键延迟敏感keys are latency-sensitive比较前把变化区间扩大到覆盖整张 Kitty 图片;所有输出都包在同步输出 ?2026h … ?2026l 里,避免闪烁。The changed range is widened to cover whole Kitty images; every write is wrapped in synchronized output ?2026h … ?2026l to avoid flicker.fullRender(true) = 清屏 + 清滚动区(\x1b[2J\x1b[H\x1b[3J)后输出全部行;TUI.fullRedraws 计数。fullRender(true) = clear screen and scrollback (\x1b[2J\x1b[H\x1b[3J), then write every line; counted in TUI.fullRedraws.否 → 下一个判断no → next check是 → 全量重绘yes → full redraw是 → 轻量处理yes → light path

要点Key points

  • 宽度变化、高度变化(Termux 除外)、内容变短(开启 clearOnShrink 时)以及变化发生在视口上方时,会清屏重绘——终端滚动区里已经滚出去的行无法原地修改。A width change, a height change (except on Termux), shrinking content (with clearOnShrink) 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.fullRedraws counts full redraws, handy for spotting components that cause needless redraws.

工作原理How it works

  1. 准备一帧:render(width) → compositeOverlays() → extractCursorPosition() → applyLineResets()。Prepare a frame: render(width) → compositeOverlays() → extractCursorPosition() → applyLineResets().
  2. 依次判断是否需要全量重绘(见图中 7 个判断)。Check, in order, whether a full redraw is needed (the seven checks in the figure).
  3. 否则移动光标到 firstChanged,逐行写到 lastChanged,每行先 \x1b[2K 清除。Otherwise move to firstChanged and write each line through lastChanged, clearing each with \x1b[2K first.
  4. 最后把硬件光标放到 CURSOR_MARKER 的位置。Finally place the hardware cursor where CURSOR_MARKER was.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/tui.tsL473requestRender()合并与节流coalesce and throttle
packages/tui/src/tui.tsL507MIN_RENDER_INTERVAL_MS16ms16 ms
packages/tui/src/tui.tsL1001requestImmediateRender()输入后立即渲染render right after input
packages/tui/src/tui-main-screen.tsL124TuiMainScreenregular 屏幕the regular screen
packages/tui/src/tui-main-screen.tsL247doRender()差分逻辑the diff logic
06pi-tui

两种屏幕: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.

regular 与 fullscreen 的对比及备用屏的一帧Regular vs fullscreen, and one alternate-screen frame
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Two screens: regular (main) and fullscreen (alternate)TuiMainScreenmode = "regular"TuiAltScreenmode = "fullscreen" · 进入 \x1b[?1049hmode = "fullscreen" · enters \x1b[?1049hrenderLayoutFrame(root)布局帧:盒子 · 滚动视图layout frame: boxes · scroll viewsapplySearchHighlights搜索命中高亮search hitscompositeScrollToEndIndicator"回到底部"提示"jump to end" pillcompositeOverlays覆盖层overlaysapplySelection鼠标选区mouse selectioncompositeFlashes短暂提示transient flashesextractCursorPosition · applyLineResets光标 · 行尾重置 · 裁到 widthcursor · resets · clip to width与 previousScreen 逐行比较diff rows against previousScreen尺寸变化 → 整屏重绘size change → full redraw\x1b[row;1H\x1b[2K …只写变化的行 · 包在 ?2026 中changed rows only · inside ?2026内容写进终端自己的滚动区Content goes into the terminal's own scrollback往上翻就是历史,退出后仍在scroll up for history; it stays after exit按行差分(见上一张图)Line diff (see the previous figure)只重写 firstChanged…lastChangedrewrites only firstChanged…lastChanged不抓鼠标No mouse capture终端原生选择与复制照常native selection and copy still work视口之上的变化只能清屏重绘Changes above the viewport force a full redrawrows/cols 变化同理so do rows / cols changesKitty 图片按行预留Kitty images reserve rows变化区间会扩到整张图diff ranges widen to whole images选择CHOOSING# pi-coding-agent: settings.json"tuiMode": "regular" | "fullscreen"# 默认 fullscreen(getTuiMode)# default: fullscreen (getTuiMode)鼠标mouse?1000 ?1002 ?1003 ?1004 ?1006滚轮wheel加速,最多 6 行/次accelerated, ≤ 6 lines选择复制select & copyOSC 52搜索searchalt-screen-search.ts布局layoutHStack · VStack · ScrollView退出exit\x1b[?1049l12345regularfullscreen输出output

要点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 uses HStack / VStack / ScrollView with basis / grow / shrink; the regular screen does without.

工作原理How it works

  1. TuiAltScreen.start() 写 \x1b[?1049h 进入备用屏并开启鼠标。TuiAltScreen.start() writes \x1b[?1049h to enter the alternate screen and enables the mouse.
  2. doRender() 调 renderLayoutFrame(root, width, height) 得到布局与行。doRender() calls renderLayoutFrame(root, width, height) for the layout and lines.
  3. 与 previousScreen 逐行比较;尺寸变化时整屏重绘。Compare row by row with previousScreen; redraw the whole screen when the size changes.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/tui-alt-screen.tsL202TuiAltScreenfullscreen 屏幕the fullscreen screen
packages/tui/src/tui-alt-screen.tsL1678doRender()备用屏一帧one alt-screen frame
packages/tui/src/layout.tsL379renderLayoutFrame()布局帧layout frame
packages/tui/src/wheel-scroll.tsL11MAX_AUTO_LINES滚轮加速上限wheel acceleration cap
packages/coding-agent/src/core/settings-manager.tsL1371getTuiMode()默认 fullscreendefaults to fullscreen
07pi-tui

输入与按键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.

从 stdin 字节到组件的 8 步输入分发Eight-step input dispatch from stdin bytes to the component
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Input: from stdin bytes to focused.handleInput()TuiBase.handleTerminalInput(data)依次尝试,可被消费in order; may be consumedkeys.ts把序列认成按键名turn sequences into key namesKeybindingsManager47 个 tui.* 按键 id47 tui.* key idsprocess.stdin原始字节raw bytesProcessTerminalsetRawMode · ?2004h 括号粘贴setRawMode · ?2004h bracketed pasteKitty 键盘协议 flags = 7Kitty keyboard protocol, flags = 7StdinBuffer拆成完整转义序列 · 粘贴整段保留split into whole sequences · paste kept wholeESC 10ms(SSH 100ms)· 序列 50msESC 10 ms (SSH 100 ms) · sequence 50 ms终端颜色回复terminal colour replyOSC 10 / 11 / 4配色方案通知colour-scheme report深/浅色切换dark / light switchaddInputListener()consume 或改写 dataconsume or rewrite data单元格尺寸回复cell-size reply图片用for imagesshift+ctrl+d调试回调 onDebugonDebug hook覆盖层焦点修复overlay focus repairvisible() 变化后after visible() changes过滤按键释放drop key releases除非 wantsKeyReleaseunless wantsKeyReleasefocused.handleInput(data)然后立即渲染then render immediately序列举例SAMPLE SEQUENCES\x1b[A up\x1b[13;2u shift+enter\x1b[99;5:3u ctrl+c release\x1b[200~ … \x1b[201~ paste\x1b]11;rgb:… bg colour replyKitty 查询 \x1b[>7u\x1b[?u 无回应时,If the Kitty query \x1b[>7u\x1b[?u gets no reply在 DA 回复后改用 modifyOtherKeys \x1b[>4;2mbefore DA, fall back to modifyOtherKeys \x1b[>4;2mmatchesKey(data, "ctrl+c")Kitty CSI-u · 旧式 · modifyOtherKeysKitty CSI-u · legacy · modifyOtherKeysparseKey(data)→ "shift+enter" 等→ "shift+enter" etc.isKeyRelease(data)Kitty 事件类型 3Kitty event type 3decodeKittyPrintable(data)CSI-u → 可打印字符CSI-u → printable texttui.input.*4tui.select.*6tui.editor.*23tui.altScreen.*14input.submitenterinput.newLineshift+enter / ctrl+jinput.copyctrl+ceditor.undoctrl+-editor.yankctrl+y用户覆盖:~/.pi/agent/keybindings.jsonOverrides: ~/.pi/agent/keybindings.json12345678终端层terminal layerTUI 分发TUI dispatch组件 · 按键component · keys

要点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 with PI_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

  1. ProcessTerminal.start() 打开 raw 模式与括号粘贴,并发送协议查询。ProcessTerminal.start() enables raw mode and bracketed paste and sends the protocol queries.
  2. StdinBuffer 输出完整序列;括号粘贴内容作为一整段交付。StdinBuffer emits whole sequences; bracketed-paste content is delivered as one chunk.
  3. handleTerminalInput(data) 走完 8 步,调用 focused.handleInput(data),再立即渲染。handleTerminalInput(data) runs the eight steps, calls focused.handleInput(data), then renders immediately.
  4. 组件里用 getKeybindings().matches(data, id) 或 matchesKey(data, "ctrl+c") 判断。Inside components, test with getKeybindings().matches(data, id) or matchesKey(data, "ctrl+c").

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/stdin-buffer.tsL24StdinBuffer序列重组sequence reassembly
packages/tui/src/terminal.tsL136resolveEscapeTimeoutMs()ESC 超时ESC timeout
packages/tui/src/tui.tsL1044handleTerminalInput()8 步分发eight-step dispatch
packages/tui/src/keys.tsL820matchesKey()按键匹配key matching
packages/tui/src/keybindings.tsL71TUI_KEYBINDINGS默认键位default bindings
packages/tui/src/keybindings.tsL231KeybindingsManager可覆盖的绑定overridable bindings
08pi-tui

编辑器与自动补全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.

Editor 的状态、按键分支顺序与自动补全Editor state, key-branch order and autocomplete
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
The Editor and autocomplete细节DetailsEditor.handleInput(data)自上而下尝试,命中即返回tried top to bottom; first match returns自动补全AutocompleteCombinedAutocompleteProvider«class» implements Component, FocusableEditor−state: { lines[], cursorLine, cursorCol }−undoStack: UndoStack<EditorState>−killRing: KillRing−history: string[] (≤ 100)−pastes: Map<id, text>+onSubmit?(text) · onChange?(text)+getText() · setText() · insertTextAtCursor()+setAutocompleteProvider(p)+addToHistory(text)跳转模式jump modejumpForward括号粘贴bracketed paste\x1b[200~交给上层to parentctrl+c撤销undoctrl+-补全菜单completion menuup · down · tab · enterTab 触发Tab triggerforce: true删除delete字符 · 单词 · 到行首尾char · word · linekill ringctrl+y · alt+y历史historyup · down光标移动cursor行首尾 · 按词line · word换行new lineshift+enter提交submitenter → onSubmit其余:插入字符else: insert textdecodeKittyPrintable/行首的斜杠命令slash command at line startfuzzyFilter(commands)@文件附件file attachmentfd 搜索 · 防抖 20msfd search · 20 ms debounceTab路径补全path completionforce: true · 读目录force: true · readdirSelectList弹出在编辑器上方 · ↑↓ 选择pops above the editor · ↑↓ to pickapplyCompletion(…)→ { lines, cursorLine, cursorCol }大段粘贴折叠成标记Large pastes collapse to a marker[paste #1 +42 lines]阈值threshold> 10 行或 > 1000 字符> 10 lines or > 1000 chars提交时on submit标记换回原文markers expand back行尾 \ + Entertrailing \ + Enter插入换行而不是提交inserts a newline instead撤销undoUndoStack 保存状态快照UndoStack keeps state snapshots历史history最多 100 条up to 100 entrieskill ring按行/按词删除的文本,连续删除合并line / word kills; consecutive ones merge确认confirm提供者接口:triggerCharacters? · getSuggestions(lines, line, col, { signal, force }) · applyCompletion(…)Provider interface: triggerCharacters? · getSuggestions(lines, line, col, { signal, force }) · applyCompletion(…)12345678910111213编辑器状态 / 提交editor state / submit按键分支key branches补全completion

要点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-in CombinedAutocompleteProvider covers 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

  1. handleInput(data):跳转模式 → 括号粘贴 → ctrl+c 交给上层 → 撤销 → 补全菜单 → … → 提交 → 插入字符。handleInput(data): jump mode → bracketed paste → ctrl+c to the parent → undo → completion menu → … → submit → insert text.
  2. 输入触发字符后调用 provider.getSuggestions(lines, line, col, { signal }),结果显示在 SelectList 中。After a trigger character, provider.getSuggestions(lines, line, col, { signal }) runs and results show in a SelectList.
  3. 确认后 applyCompletion() 返回新的文本与光标位置。On confirm, applyCompletion() returns the new text and cursor position.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/components/editor.tsL296Editor编辑器the editor
packages/tui/src/components/editor.tsL695handleInput()按键分支顺序key-branch order
packages/tui/src/components/editor.tsL1302paste markers粘贴折叠阈值paste-fold threshold
packages/tui/src/autocomplete.tsL271AutocompleteProvider补全接口completion interface
packages/tui/src/autocomplete.tsL303CombinedAutocompleteProvider/ · @ · 路径/ · @ · paths
packages/tui/src/kill-ring.tsL8KillRingkill / yankkill / yank
09pi-tui

覆盖层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.

覆盖层选项、9 个锚点、合成步骤与焦点栈Overlay options, nine anchors, compositing and the focus stack
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Overlays: showOverlay(component, options)9 个锚点9 anchors终端可用区域(去掉 margin)terminal area minus margin每一帧Every framecompositeOverlays()焦点Focus覆盖层是一个栈overlays form a stack«interface»OverlayOptions+width?: number | "50%"+minWidth?: number+maxHeight?: number | "%"+anchor?: 9 values+offsetX? · offsetY?+row? · col?: number | "%"+margin?: number | { top … }+visible?(w, h): boolean+nonCapturing?: boolean«returned»OverlayHandle+hide()+setHidden()+isHidden()+focus()+unfocus()+isFocused()+getBounds()筛选可见的filter visiblevisible(w, h) · 未隐藏visible(w, h) · not hidden解析宽度resolve width% / minWidth / margincomponent.render(width)截到 maxHeightcut to maxHeight解析 row / colresolve row / col用实际高度算锚点anchor with real heightcompositeLineAt(…)按列拼进底层行splice into base linesshowOverlay()记住 preFocus,然后抢焦点remember preFocus, take focusnonCapturing: true显示但不抢焦点shown, focus untouchedhide()焦点交给下一个可见覆盖层或 preFocusfocus to next visible overlay or preFocustop-lefttop-centertop-rightleft-centercenterright-centerbottom-leftbottom-centerbottom-right默认:center · 宽 min(80, 可用宽度)default: center · width min(80, available)每次输入前都会复查:focused 覆盖层若已不可见(resize 或 visible() 变 false),焦点改到最上层可见覆盖层或 preFocus。Before every input: if the focused overlay is no longer visible (resize, or visible() turned false), focus moves to the top visible overlay or preFocus.覆盖层按 focusOrder 排序叠放;focus() 把一个覆盖层提到最上层。Overlays stack by focusOrder; focus() brings one to the top.12345API定位placement合成compositing焦点focus

要点Key points

  • 默认居中,宽度为 min(80, 可用宽度);width / maxHeight / row / col 都接受数字或 "50%" 这样的百分比。Default: centred, width min(80, available); width / maxHeight / row / col accept 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 的覆盖层只显示、不抢焦点,适合状态提示。A nonCapturing: true overlay shows without taking focus — good for status pop-ups.

工作原理How it works

  1. showOverlay() 记下当前焦点为 preFocus,把覆盖层压栈并聚焦(nonCapturing 除外)。showOverlay() records the current focus as preFocus, pushes the overlay and focuses it (unless nonCapturing).
  2. 每帧:筛选可见的 → 解析宽度 → 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().
  3. hide() 出栈,焦点交给下一个可见覆盖层或 preFocus。hide() pops it; focus goes to the next visible overlay or preFocus.

关键文件Key files · v1.1.0

文件File符号Symbol作用Role
packages/tui/src/tui.tsL243OverlayOptions位置与尺寸position and size
packages/tui/src/tui.tsL298OverlayHandle控制句柄control handle
packages/tui/src/tui.tsL1204resolveOverlayLayout()布局计算layout maths
packages/tui/src/tui.tsL1339compositeOverlays()按列拼接column splicing
packages/tui/src/tui.tsL203OverlayAnchor9 个锚点nine anchors
10pi-tui

终端能力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.

启动协商、输出序列与按终端识别的能力Start-up negotiation, output sequences and per-terminal capabilities
← 左右滑动查看,点击放大 →← swipe to scroll, tap to enlarge →
Terminal capabilities: negotiation, output sequences, imagesProcessTerminal.start()Terminal 接口 13 个成员Terminal interface: 13 members输出与查询Output & queriesdetectCapabilities()按环境变量识别;PI_IMAGE_PROTOCOL · PI_TRUE_COLOR · PI_HYPERLINKS 可覆盖from env vars; PI_IMAGE_PROTOCOL · PI_TRUE_COLOR · PI_HYPERLINKS overridesetRawMode(true)逐字节读取,不回显byte-wise input, no echo\x1b[?2004h开启括号粘贴bracketed paste on\x1b[>7u \x1b[?u请求 Kitty 键盘协议并查询push Kitty flags 7 and query\x1b[cDA:用作"查询结束"哨兵DA as the end-of-queries sentinel\x1b[>4;2m无 Kitty 回复时的 modifyOtherKeysmodifyOtherKeys when Kitty is silentOSC 0OSC 9;4OSC 7501OSC 10/11OSC 8?2026kittyiterm2Windows:额外打开 VT 输入;stop() 恢复原 raw 状态并关掉以上模式。Windows: also enables VT input; stop() restores raw state and undoes the modes.\x1b]0;title\x07窗口标题window title\x1b]9;4;3 / 0任务栏进度(不确定 / 清除)taskbar progress (busy / clear)idle · working · blocked …程序状态(终端声明支持时)program status (if supported)前景 / 背景色查询fg / bg colour query自动判断深浅主题picks dark or light超链接hyperlinks只在确认支持时启用only where known to work同步输出synchronized output每帧包起来,防闪烁wraps every frame终端TERMINAL图片协议IMAGES真彩色TRUECOLOR超链接HYPERLINKSkitty · ghostty · wezterm · warp✓✓iTerm2✓✓Windows Terminal · alacritty · vscode · zed—✓✓JetBrains · Windows console—✓—tmux—COLORTERM探测probedscreen · 未知终端screen · unknown—COLORTERM—12345启动协商start-up negotiation输出序列output sequences能力识别capability detection

要点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

  1. start(onInput, onResize):raw 模式 → ?2004h → Kitty 查询 → DA。start(onInput, onResize): raw mode → ?2004h → Kitty query → DA.
  2. detectCapabilities() 根据 TERM_PROGRAM、KITTY_WINDOW_ID、TMUX 等变量给出 images / trueColor / hyperlinks。detectCapabilities() derives images / trueColor / hyperlinks from TERM_PROGRAM, KITTY_WINDOW_ID, TMUX and similar variables.
  3. 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.tsL78Terminal终端接口terminal interface
packages/tui/src/terminal.tsL20KITTY_KEYBOARD_PROTOCOL_QUERYKitty 查询Kitty query
packages/tui/src/terminal-image.tsL146detectCapabilities()能力识别capability detection
packages/tui/src/terminal-colors.tsL1terminal colours颜色查询colour queries
packages/tui/src/program-status.tsL8ProgramStatusOSC 7501OSC 7501
11pi-tui

扩展点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.
12pi-tui

测试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).

13pi-tui

交叉链接Cross-links#