Show HN: 一个纯 PHP 实现的终端模拟器
soloterm/screen
一个用纯 PHP 编写的终端模拟器。
许可协议
Solo Screen
Screen 是一个用纯 PHP 编写的终端模拟器。它为 Solo for Laravel 提供支持,并且可以用于在任何 PHP 应用程序中构建富文本的用户界面。
注意
Screen 是一个旨在集成到 PHP 应用程序中的库,它不是一个独立的终端应用程序。
关于终端模拟器
终端模拟器是一种软件,可以复制经典硬件计算机终端的功能。它可以处理文本的输入和输出,以及特殊的控制序列 (ANSI escape codes),这些控制序列控制格式化、光标移动和其他终端功能。
终端模拟器会解释这些转义序列来实现:
- 定位光标
- 设置文本颜色和样式 (粗体、下划线等)
- 清除屏幕的部分内容
- 处理特殊的字符集
- 以及更多
Screen 使用纯 PHP 实现此功能,允许开发人员构建终端用户界面,而无需依赖外部依赖项或原生代码。
为什么会有这个东西
Screen 最初是为了解决 Solo for Laravel 中的一个特定问题而创建的。
Solo 提供了一个 TUI (文本用户界面),该界面在单独的面板中同时运行多个进程,类似于 tmux。但是,当这些进程输出用于光标移动和屏幕操作的 ANSI escape codes 时,它们可能会“突破”其视觉容器并干扰界面的其他部分。
为了解决这个问题,Screen 创建了一个虚拟终端缓冲区,其中:
- 所有 ANSI 操作(光标移动、颜色更改、屏幕清除)都在一个隔离的环境中安全地进行。
- 在处理完所有操作后,捕获最终的渲染状态。
- 仅将最终的视觉输出显示给用户的终端。
这种方法可以完全控制终端输出的渲染方式,确保复杂的 ANSI 操作保持在其指定的区域内。虽然最初是为 Solo 构建的,但 Screen 已经发展成为一个独立的库,可以在任何需要终端模拟的 PHP 应用程序中使用。
功能特性
- 纯 PHP 实现:只有一个依赖项 (Grapheme,另一个 Solo 库)
- 全面的 ANSI 支持:处理光标定位、文本样式和屏幕操作
- Unicode/Multibyte 支持:正确处理 UTF-8 字符,包括表情符号和宽字符
- 缓冲区管理:维护用于文本内容和样式的单独缓冲区
- 字符宽度处理:正确计算 CJK 和其他双倍宽度字符的显示宽度
- 滚动:支持垂直滚动以及正确的内容管理
安装
通过 Composer 安装:
composer require soloterm/screen
要求
- PHP 8.1 或更高版本
- mbstring 扩展
基本用法
这是一个使用 Screen 的简单示例:
use SoloTerm\Screen\Screen;
// 创建一个具有尺寸(列,行)的屏幕
$screen = new Screen(80, 24);
// 写入文本和 ANSI escape sequences
$screen->write("Hello, \e[1;32mWorld!\e[0m");
// 移动光标并添加更多文本
$screen->write("\e[5;10HPositioned text");
// 获取渲染的内容
echo $screen->output();
核心概念
Screen 运行的几个关键组件:
Screen
协调所有功能的主类。它负责光标定位、内容写入以及渲染最终输出。
$screen = new Screen(80, 24); // 宽度, 高度
$screen->write("Text and ANSI codes");
Buffers
Screen 使用多种缓冲区类型来跟踪内容和样式:
- PrintableBuffer:存储可见字符并处理宽度计算
- AnsiBuffer:跟踪样式信息(颜色、粗体、下划线等)
ANSI 处理
Screen 正确处理 ANSI escape sequences 以进行以下操作:
- 光标移动(上、下、左、右、绝对定位)
- 文本样式(颜色、粗体、斜体、下划线)
- 屏幕清除和行操作
- 滚动
高级功能
光标定位
// 将光标移动到位置(第 5 行,第 10 列)
$screen->write("\e[5;10H");
// 向上移动光标 3 行
$screen->write("\e[3A");
// 保存和恢复光标位置
$screen->write("\e7"); // 保存
$screen->write("More text");
$screen->write("\e8"); // 恢复
文本样式
// 粗体红色文本
$screen->write("\e[1;31mImportant message\e[0m");
// 背景颜色
$screen->write("\e[44mBlue background\e[0m");
// 256 色支持
$screen->write("\e[38;5;208mOrange text\e[0m");
// RGB 颜色
$screen->write("\e[38;2;255;100;0mCustom color\e[0m");
屏幕操作
// 清除屏幕
$screen->write("\e[2J");
// 从光标清除到行尾
$screen->write("\e[0K");
// 插入行
$screen->write("\e[2L");
// 向上滚动
$screen->write("\e[2S");
自定义集成
您可以通过设置回调来响应终端查询:
$screen->respondToQueriesVia(function($response) {
// 处理响应(如光标位置)
echo $response;
});
注意
这仍然是一个正在进行的工作。我们需要更多测试/用例。
示例:构建一个简单的 UI
use SoloTerm\Screen\Screen;
$screen = new Screen(80, 24);
// 绘制边框
$screen->write("┌" . str_repeat("─", 78) . "┐\n");
for ($i = 0; $i < 22; $i++) {
$screen->write("│" . str_repeat("", 78) . "│\n");
}
$screen->write("└" . str_repeat("─", 78) . "┘");
// 添加标题
$screen->write("\e[1;30H\e[1;36mMy Application\e[0m");
// 添加一些内容
$screen->write("\e[5;5HWelcome to the application!");
$screen->write("\e[7;5HPress 'q' to quit.");
// 渲染
echo $screen->output();
处理 unicode 和宽字符
Screen 正确处理 Unicode 字符,包括占用多列的 emoji 和 CJK 字符:
$screen->write("Regular text: Hello");
$screen->write("\nWide characters: 你好世界");
$screen->write("\nEmoji: 🚀 👨👩👧👦 🌍");
测试
Screen 包含一个全面的测试套件,该套件具有独特的视觉比较系统:
composer test
可视化测试
Screen 采用了一种创新的基于屏幕截图的测试方法(请参阅 ComparesVisually
特性),该方法验证了视觉输出:
- 测试在真实的终端 (iTerm) 中渲染内容
- 它捕获终端输出的屏幕截图
- 它通过 Screen 模拟器运行相同的内容
- 它捕获模拟输出的屏幕截图
- 它逐像素比较屏幕截图以确保准确性
这种测试策略确保 Screen 的仿真能够准确地匹配真实终端的行为,尤其是在涉及以下方面的复杂场景中:
- 多字节字符
- 复杂的 ANSI 格式
- 光标移动
- 滚动行为
- 自动换行
对于没有屏幕截图功能的环境,测试可以回退到基于 fixture 的比较,从而使测试套件对于 CI/CD 管道具有通用性。
要为所有测试启用屏幕截图,请使用以下命令:
ENABLE_SCREENSHOT_TESTING=1 composer test
要仅为尚未具有 fixture 的测试启用屏幕截图,请使用以下命令:
ENABLE_SCREENSHOT_TESTING=2 composer test
贡献
欢迎贡献!请随时提交 pull request。
许可协议
The MIT License (MIT).
支持
This is free! If you want to support me:
- Sponsor my open source work: aaronfrancis.com/backstage
- Check out my courses:
- Help spread the word about things I make
Credits
Solo Screen was developed by Aaron Francis. If you like it, please let me know!
- Twitter: https://twitter.com/aarondfrancis
- Website: https://aaronfrancis.com
- YouTube: https://youtube.com/@aarondfrancis
- GitHub: https://github.com/aarondfrancis/solo