# @f5-sales-demo/pi-tui 中文文档

规范地址：https://npmcn.com/package/%40f5-sales-demo/pi-tui/

版本：23\.10\.13
npm 元数据更新时间：2026-11-13T03:57:30.420Z
中文译文更新时间：2026-11-14T07:33:20.912Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40f5-sales-demo/pi-tui
原文对照：https://npmcn.com/package/%40f5-sales-demo/pi-tui/?lang=original
项目主页：https://github.com/f5-sales-demo/xcsh
源码仓库：https://github.com/f5-sales-demo/xcsh

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：481 kB，40 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：3 个
- 首次发布：2026-06-26T15:15:29.250Z
- 最新版本发布：2026-11-13T03:57:30.089Z
- 近 30 天下载量：8121（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：450（近 12 个月 450 个）

## 中文简介

终端用户界面库,用于高效文本应用程序的差分渲染

## 安装

```sh
npm install @f5-sales-demo/pi-tui
```

也可以使用：`pnpm add @f5-sales-demo/pi-tui`、`yarn add @f5-sales-demo/pi-tui`、`bun add @f5-sales-demo/pi-tui`

## README

# @f5-sales-demo/pi-tui (英语)

最小终端UI框架,有差分渲染和同步输出,用于flicker无交互CLI应用程序.

## 特征

-   **区别对待**: 三战略渲染系统,只更新变化
-   **同步输出**: CSI 2026用于原子屏幕更新(无闪烁)
-   **括号粘贴模式**: 正确处理大于10行粘贴的大型粘贴
-   **基于构成部分**: 具有渲染( ) 方法的简单组件接口
-   **主题支持**: 组件接受自定义式的主题界面
-   **内建组件**: 文本、 截断文本、 输入、 编辑器、 Markdown、 装入器、 选择列表、 设置列表、 空间器、 图像、 框、 容器
-   **内含图像**: 在支持 Kitty 或 iTerm2 图形协议的终端中渲染图像
-   **自动完整支持**: 文件路径和斜线命令

## 快速启动

```typescript
import { TUI, Text, Editor, ProcessTerminal } from "@f5-sales-demo/pi-tui";

// Create terminal
const terminal = new ProcessTerminal();

// Create TUI
const tui = new TUI(terminal);

// Add components
tui.addChild(new Text("Welcome to my app!"));

const editor = new Editor(editorTheme);
editor.onSubmit = (text) => {
 console.log("Submitted:", text);
 tui.addChild(new Text(`You said: ${text}`));
};
tui.addChild(editor);

// Start
tui.start();
```

## 核心 API

### 图瓦卢

管理组件和渲染的主要容器.

```typescript
const tui = new TUI(terminal);
tui.addChild(component);
tui.removeChild(component);
tui.start();
tui.stop();
tui.requestRender(); // Request a re-render

// Global debug key handler (Shift+Ctrl+D)
tui.onDebug = () => console.log("Debug triggered");
```

### 组件接口

所有组成部分执行:

```typescript
interface Component {
 render(width: number): string[];
 handleInput?(data: string): void;
 invalidate?(): void;
}
```

| 方法 | 说明 |
| --- | --- |
| `render(width)` | 返回一组字符串,每行一个。 每行 **不得超过 `width`** 否则TUI会出错. 使用 `truncateToWidth()` 或人工包装以确保这一点. |
| `handleInput?(data)` | 当组件有焦点并接收键盘输入时调用. 这个 `data` 字符串包含原始终端输入(可能包括ANSI逃生序列). |
| `invalidate?()` | 请求清除缓存的状态 。 部件应该从零开始在下一个 `render()` 电话. |

## 内建组件

### 集装箱

儿童组.

```typescript
const container = new Container();
container.addChild(component);
container.removeChild(component);
```

### 方框

适用于所有儿童的贴花和背景颜色的容器.

```typescript
const box = new Box(
 1, // paddingX (default: 1)
 1, // paddingY (default: 1)
 (text) => chalk.bgGray(text), // optional background function
);
box.addChild(new Text("Content"));
box.setBgFn((text) => chalk.bgBlue(text)); // Change background dynamically
```

### 文本

显示多行文字并用字包和粘贴.

```typescript
const text = new Text(
 "Hello World", // text content
 1, // paddingX (default: 1)
 1, // paddingY (default: 1)
 (text) => chalk.bgGray(text), // optional background function
);
text.setText("Updated text");
text.setCustomBgFn((text) => chalk.bgBlue(text));
```

### 截断文本

单行文本可切换以适应取景口宽度. 用于状态行和标题 .

```typescript
const truncated = new TruncatedText(
 "This is a very long line that will be truncated...",
 0, // paddingX (default: 0)
 0, // paddingY (default: 0)
);
```

### 投入

单行文字输入并横向滚动.

```typescript
const input = new Input();
input.onSubmit = (value) => console.log(value);
input.setValue("initial");
input.getValue();
```

**密钥绑定 :**

-   `Enter` - 提交
-   `Ctrl+A` 编号 : `Ctrl+E` - 线路始末
-   `Ctrl+W` 或者说 `Alt+Backspace` - 向后删除单词
-   `Ctrl+U` - 删除为行首
-   `Ctrl+K` - 删除到行尾
-   `Ctrl+Left` 编号 : `Ctrl+Right` - 文字导航
-   `Alt+Left` 编号 : `Alt+Right` - 文字导航
-   箭头密钥, 后空格, 按预期删除工作

### 编辑器

多行文本编辑器,具有自动完成,文件完成和粘贴处理功能.

```typescript
interface SymbolTheme {
 cursor: string;
 ellipsis: string;
 boxRound: {
  topLeft: string;
  topRight: string;
  bottomLeft: string;
  bottomRight: string;
  horizontal: string;
  vertical: string;
 };
 boxSharp: {
  topLeft: string;
  topRight: string;
  bottomLeft: string;
  bottomRight: string;
  horizontal: string;
  vertical: string;
  teeDown: string;
  teeUp: string;
  teeLeft: string;
  teeRight: string;
  cross: string;
 };
 table: {
  topLeft: string;
  topRight: string;
  bottomLeft: string;
  bottomRight: string;
  horizontal: string;
  vertical: string;
  teeDown: string;
  teeUp: string;
  teeLeft: string;
  teeRight: string;
  cross: string;
 };
 quoteBorder: string;
 hrChar: string;
 spinnerFrames: string[];
}

interface EditorTheme {
 borderColor: (str: string) => string;
 selectList: SelectListTheme;
 symbols: SymbolTheme;
}

const editor = new Editor(theme);
editor.onSubmit = (text) => console.log(text);
editor.onChange = (text) => console.log("Changed:", text);
editor.disableSubmit = true; // Disable submit temporarily
editor.setAutocompleteProvider(provider);
editor.borderColor = (s) => chalk.blue(s); // Change border dynamically
```

**特征 :**

-   多行编辑并装有字包
-   斜线命令自动完成( 类型) `/`(中文(简体) )
-   文件路径自动完成( 按 `Tab`(中文(简体) )
-   大型粘贴处理( > 10行创建) `[paste #1 +50 lines]` 标记)
-   水平行上方/下方编辑器
-   假光标渲染( 隐藏真实光标)

**密钥绑定 :**

-   `Enter` - 提交
-   `Shift+Enter`, (中文(简体) ), `Ctrl+Enter`,或 `Alt+Enter` - 新线(依赖地铁,Alt+Enter最可靠)
-   `Tab` - 自动完成
-   `Ctrl+K` - 删除行
-   `Alt+D` 编号 : `Alt+Delete` - 删除前面的字
-   `Ctrl+A` 编号 : `Ctrl+E` - 线路始末
-   `Ctrl+-` - 撤销上次编辑
-   箭头密钥, 后空格, 按预期删除工作

### 标记

以语法加亮和主题支持来标记下.

```typescript
interface MarkdownTheme {
 heading: (text: string) => string;
 link: (text: string) => string;
 linkUrl: (text: string) => string;
 code: (text: string) => string;
 codeBlock: (text: string) => string;
 codeBlockBorder: (text: string) => string;
 quote: (text: string) => string;
 quoteBorder: (text: string) => string;
 hr: (text: string) => string;
 listBullet: (text: string) => string;
 bold: (text: string) => string;
 italic: (text: string) => string;
 strikethrough: (text: string) => string;
 underline: (text: string) => string;
 highlightCode?: (code: string, lang?: string) => string[];
 symbols: SymbolTheme;
}

interface DefaultTextStyle {
 color?: (text: string) => string;
 bgColor?: (text: string) => string;
 bold?: boolean;
 italic?: boolean;
 strikethrough?: boolean;
 underline?: boolean;
}

const md = new Markdown(
 "# Hello\n\nSome **bold** text",
 1, // paddingX
 1, // paddingY
 theme, // MarkdownTheme
 defaultStyle, // optional DefaultTextStyle
 2, // optional code block indent (spaces)
);
md.setText("Updated markdown");
```

**特征 :**

-   标题、粗体、斜体、代码块、列表、链接、块词
-   HTML 标记作为纯文本渲染
-   可选语法高亮通过 `highlightCode`
-   铺设支持
-   为性能进行缓冲

### 装入器

动画装入自旋器.

```typescript
const loader = new Loader(
 tui, // TUI instance for render updates
 (s) => chalk.cyan(s), // spinner color function
 (s) => chalk.gray(s), // message color function
 "Loading...", // message (default: "Loading...")
);
loader.start();
loader.setMessage("Still loading...");
loader.stop();
```

### 可切换连接器

以 Escape 密钥处理方式扩展加载器, 并中止用于取消ASync 操作的信号 .

```typescript
const loader = new CancellableLoader(
 tui, // TUI instance for render updates
 (s) => chalk.cyan(s), // spinner color function
 (s) => chalk.gray(s), // message color function
 "Working...", // message
);
loader.onAbort = () => done(null); // Called when user presses Escape
doAsyncWork(loader.signal).then(done);
```

**属性 :**

-   `signal: AbortSignal` - 用户按出逃时中止
-   `aborted: boolean` - 装载器是否中止
-   `onAbort?: () => void` - 用户按出逃时召回

### 选择列表

与键盘导航交互选择列表 .

```typescript
interface SelectItem {
 value: string;
 label: string;
 description?: string;
}

interface SelectListTheme {
 selectedPrefix: (text: string) => string;
 selectedText: (text: string) => string;
 description: (text: string) => string;
 scrollInfo: (text: string) => string;
 noMatch: (text: string) => string;
 symbols: SymbolTheme;
}

const list = new SelectList(
 [
  { value: "opt1", label: "Option 1", description: "First option" },
  { value: "opt2", label: "Option 2", description: "Second option" },
 ],
 5, // maxVisible
 theme, // SelectListTheme
 { presentation: "compact-with-selected-detail" },
);

list.onSelect = (item) => console.log("Selected:", item);
list.onCancel = () => console.log("Cancelled");
list.onSelectionChange = (item) => console.log("Highlighted:", item);
list.setFilter("opt"); // Filter items
```

每张列表都会选择一个展示合同. 使用 `compact-with-selected-detail` 用于密集的一行; 当 选中的标签或描述不符合 , `SelectList` 将其完整的包裹值添加到行下. 使用 `wrapped-prose` 当每个可见的项应该包起来而不是切换时.

**控件 :**

-   箭头密钥: 导航
-   输入: 选择
-   逃脱: 取消

### 设置列表

设置带有值循环和子菜单的面板 .

```typescript
interface SettingItem {
 id: string;
 label: string;
 description?: string;
 currentValue: string;
 values?: string[]; // If provided, Enter/Space cycles through these
 submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
}

interface SettingsListTheme {
 label: (text: string, selected: boolean) => string;
 value: (text: string, selected: boolean) => string;
 description: (text: string) => string;
 cursor: string;
 hint: (text: string) => string;
}

const settings = new SettingsList(
 [
  { id: "theme", label: "Theme", currentValue: "dark", values: ["dark", "light"] },
  { id: "model", label: "Model", currentValue: "gpt-4", submenu: (val, done) => modelSelector },
 ],
 10, // maxVisible
 theme, // SettingsListTheme
 (id, newValue) => console.log(`${id} changed to ${newValue}`),
 () => console.log("Cancelled"),
);
settings.updateValue("theme", "light");
```

**控件 :**

-   箭头密钥: 导航
-   输入/ 空间: 激活( 周期值或打开子菜单)
-   逃脱: 取消

### 空间器

垂直间距的空行.

```typescript
const spacer = new Spacer(2); // 2 empty lines (default: 1)
```

### 图像

将支持 Kitty 图形协议( Kitty, Ghostty, Wez Term) 或 iTerm2 内置图像的终端内置图像 。 返回到不支持的终端上的文本占位符 。 在Herdr内部,图像需要服务器提供 `HERDR_KITTY_GRAPHICS=1` 能力。 赫德中已失效的图形即使外端终端支持图像,也会选择文本回落.

```typescript
interface ImageTheme {
 fallbackColor: (str: string) => string;
}

interface ImageOptions {
 maxWidthCells?: number;
 maxHeightCells?: number;
 filename?: string;
}

const image = new Image(
 base64Data, // base64-encoded image data
 "image/png", // MIME type
 theme, // ImageTheme
 options, // optional ImageOptions
);
tui.addChild(image);
```

所支持的格式:PNG,JPEG,GIF,WebP. 从图像头自动解析尺寸 .

## 自动完成

### 组合自动完成器

支持斜线命令和文件路径.

```typescript
import { CombinedAutocompleteProvider } from "@f5-sales-demo/pi-tui";
import { getProjectDir } from "@f5-sales-demo/pi-utils";

const provider = new CombinedAutocompleteProvider(
 [
  { name: "help", description: "Show help" },
  { name: "clear", description: "Clear screen" },
  { name: "delete", description: "Delete last message" },
 ],
 getProjectDir(), // base path for file completion
);

editor.setAutocompleteProvider(provider);
```

**特征 :**

-   类型 `/` 显示斜线命令
-   新闻 `Tab` 文件路径补全
-   与 `~/`, (中文(简体) ), `./`, (中文(简体) ), `../`,以及 `@` 前缀
-   可附加文件的过滤器 `@` 前缀

## 密钥检测

用于检测键盘输入的帮助器功能( 支持 Kitty 键盘协议 ):

```typescript
import {
 isEnter,
 isEscape,
 isTab,
 isShiftTab,
 isArrowUp,
 isArrowDown,
 isArrowLeft,
 isArrowRight,
 isCtrlA,
 isCtrlC,
 isCtrlE,
 isCtrlK,
 isCtrlO,
 isCtrlP,
 isCtrlLeft,
 isCtrlRight,
 isAltLeft,
 isAltRight,
 isShiftEnter,
 isAltEnter,
 isShiftCtrlO,
 isShiftCtrlD,
 isShiftCtrlP,
 isBackspace,
 isDelete,
 isHome,
 isEnd,
 // ... and more
} from "@f5-sales-demo/pi-tui";

if (isCtrlC(data)) {
 process.exit(0);
}
```

## 区别对待

TUI使用三个渲染策略:

1.  **第一个**: 输出所有行而不清理回滚
2.  **视图端口上方的宽度已更改或更改**: 清除屏幕并全部重置
3.  **普通更新**: 将光标移动到第一个已更改行上, 清除到结束, 渲染已更改行

所有更新都包含在 **同步输出** (单位:千美元)`\x1b[?2026h` . `\x1b[?2026l`用于原子,无闪光渲染.

## 终端接口

TUI 与执行 `Terminal` 接口 :

```typescript
interface Terminal {
 start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void): void;
 stop(): void;
 write(data: string): void;
 get columns(): number;
 get rows(): number;
 moveBy(lines: number): void;
 hideCursor(): void;
 showCursor(): void;
 clearLine(): void;
 clearFromCursor(): void;
 clearScreen(): void;
}
```

**内建实施:**

-   `ProcessTerminal` - 用途 `process.stdin/stdout`
-   `VirtualTerminal` - 测试(用途) `@xterm/headless`(中文(简体) )

## 公用事业

```typescript
import { Ellipsis, visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@f5-sales-demo/pi-tui";

// Get visible width of string (ignoring ANSI codes, uses Bun.stringWidth)
const width = visibleWidth("\x1b[31mHello\x1b[0m"); // 5

// Truncate string to width (preserving ANSI codes, adds ellipsis)
const truncated = truncateToWidth("Hello World", 8); // "Hello…" (default: Ellipsis.Unicode)

// Truncate without ellipsis
const truncatedNoEllipsis = truncateToWidth("Hello World", 8, Ellipsis.Omit); // "Hello Wo"

// Wrap text to width (Bun.wrapAnsi word wrap, trims line ends, preserves ANSI)
const lines = wrapTextWithAnsi("This is a long line that needs wrapping", 20);
// ["This is a long line", "that needs wrapping"]
```

## 创建自定义组件

当创建自定义组件时, **返回的每行 `render()` 不得超过 `width` 参数**。如果有线比终端宽,则TUI会出错.

### 处理输入

使用密钥检测工具处理键盘输入 :

```typescript
import { isEnter, isEscape, isArrowUp, isArrowDown, isCtrlC, isTab, isBackspace } from "@f5-sales-demo/pi-tui";
import type { Component } from "@f5-sales-demo/pi-tui";

class MyInteractiveComponent implements Component {
 private selectedIndex = 0;
 private items = ["Option 1", "Option 2", "Option 3"];

 onSelect?: (index: number) => void;
 onCancel?: () => void;

 handleInput(data: string): void {
  if (isArrowUp(data)) {
   this.selectedIndex = Math.max(0, this.selectedIndex - 1);
  } else if (isArrowDown(data)) {
   this.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + 1);
  } else if (isEnter(data)) {
   this.onSelect?.(this.selectedIndex);
  } else if (isEscape(data) || isCtrlC(data)) {
   this.onCancel?.();
  }
 }

 render(width: number): string[] {
  return this.items.map((item, i) => {
   const prefix = i === this.selectedIndex ? "> " : "  ";
   return truncateToWidth(prefix + item, width);
  });
 }
}
```

### 处理行宽

利用所提供的水电设施确保线路符合:

```typescript
import { visibleWidth, truncateToWidth } from "@f5-sales-demo/pi-tui";
import type { Component } from "@f5-sales-demo/pi-tui";

class MyComponent implements Component {
 private text: string;

 constructor(text: string) {
  this.text = text;
 }

 render(width: number): string[] {
  // Option 1: Truncate long lines
  return [truncateToWidth(this.text, width)];

  // Option 2: Check and pad to exact width
  const line = this.text;
  const visible = visibleWidth(line);
  if (visible > width) {
   return [truncateToWidth(line, width)];
  }
  // Pad to exact width (optional, for backgrounds)
  return [line + " ".repeat(width - visible)];
 }
}
```

### ANSI 守则考虑

`visibleWidth()`, (中文(简体) ), `truncateToWidth()`,以及 `wrapTextWithAnsi()` 正确处理 ANSI 逃生代码 :

-   `visibleWidth()` 计算宽度时忽略 ANSI 代码(通过 `Bun.stringWidth`(中文(简体) )
-   `truncateToWidth()` 保存 ANSI 代码并在切换时正确关闭
-   `wrapTextWithAnsi()` 保存 ANSI 代码, 而文字包装和剪切线结束

```typescript
import chalk from "chalk";

const styled = chalk.red("Hello") + " " + chalk.blue("World");
const width = visibleWidth(styled); // 11 (not counting ANSI codes)
const truncated = truncateToWidth(styled, 8); // Red "Hello" + " W..." with proper reset
```

### 缓存

就性能而言,组件应缓存其产出,并仅在必要时重新交付:

```typescript
class CachedComponent implements Component {
 private text: string;
 private cachedWidth?: number;
 private cachedLines?: string[];

 render(width: number): string[] {
  if (this.cachedLines && this.cachedWidth === width) {
   return this.cachedLines;
  }

  const lines = [truncateToWidth(this.text, width)];

  this.cachedWidth = width;
  this.cachedLines = lines;
  return lines;
 }

 invalidate(): void {
  this.cachedWidth = undefined;
  this.cachedLines = undefined;
 }
}
```

## 示例

见 `test/chat-simple.ts` 用于完整聊天接口示例:

-   带有自定义背景颜色的下沉信件
-   回复时正在装入自旋器
-   有自动完成和斜线命令的编辑器
-   信件之间的空格

运行它:

```bash
npx tsx test/chat-simple.ts
```

## 发展

```bash
# Install dependencies (from monorepo root)
npm install

# Run type checking
npm run check

# Run the demo
npx tsx test/chat-simple.ts
```

## 常见问题

### @f5-sales-demo/pi-tui 是什么？

终端用户界面库,用于高效文本应用程序的差分渲染

### 如何安装 @f5-sales-demo/pi-tui？

在项目目录运行 npm install @f5-sales-demo/pi-tui，或使用 pnpm add / yarn add / bun add。

### @f5-sales-demo/pi-tui 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @f5-sales-demo/pi-tui 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @f5-sales-demo/pi-tui 可以商用吗？

@f5-sales-demo/pi-tui 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @f5-sales-demo/pi-tui 的下载量有多少？

根据 npm 官方下载统计，@f5-sales-demo/pi-tui 在 2026/10/14 – 2026/11/12 这 30 天内被下载 8,121 次，日均约 271 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @f5-sales-demo/pi-tui 还在维护吗？

最新版本 23\.10\.13 发布于 2026/11/13（1 天前）；至今共发布 450 个版本，其中过去 12 个月发布了 450 个；首次发布于 2026/06/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

