You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP CLI应用含ANSI转义码文本的可靠换行方案问询

解决含ANSI转义码的CLI文本按实际长度换行问题

核心问题在于wordwrap()和strlen()会把非打印的ANSI转义码计入字符长度,导致换行位置偏移。要解决这个问题,需要分离样式代码与可见文本,基于纯文本长度计算换行位置,同时保证样式在换行后能正确延续。

实现思路

  1. 将带ANSI样式的文本拆分为「ANSI样式段」和「纯文本段」两类片段;
  2. 跟踪当前生效的所有ANSI样式;
  3. 逐段处理纯文本,累计可见长度,达到80字符时插入换行,并将当前样式自动附加到新行开头;
  4. 最后输出剩余内容。

具体代码实现

首先实现一个辅助函数,拆分ANSI文本为样式和纯文本片段:

function splitAnsiText(string $text): array
{
    $segments = [];
    // 匹配ANSI控制序列(CSI,即\x1b[开头的样式码)
    $pattern = '/(\x1b\[[0-9;]*m)|([^\x1b]+)/';
    preg_match_all($pattern, $text, $matches, PREG_SET_ORDER);
    
    foreach ($matches as $match) {
        if (isset($match[1]) && $match[1] !== '') {
            $segments[] = ['type' => 'ansi', 'content' => $match[1]];
        } elseif (isset($match[2]) && $match[2] !== '') {
            $segments[] = ['type' => 'text', 'content' => $match[2]];
        }
    }
    return $segments;
}

然后修改你的line()方法,实现基于可见长度的换行逻辑:

public function line(string $text): void
{
    $segments = splitAnsiText($text);
    $currentLine = '';
    $currentVisibleLength = 0;
    $currentAnsiStyles = []; // 记录当前生效的所有ANSI样式

    foreach ($segments as $segment) {
        if ($segment['type'] === 'ansi') {
            // 保存当前样式,并追加到当前行
            $currentAnsiStyles[] = $segment['content'];
            $currentLine .= $segment['content'];
            continue;
        }

        // 处理纯文本片段,逐段填充到当前行
        $remainingText = $segment['content'];
        $remainingLength = strlen($remainingText);

        while ($remainingLength > 0) {
            $availableSpace = 80 - $currentVisibleLength;

            // 当前行已满,换行并保留样式
            if ($availableSpace <= 0) {
                $this->rawLine($currentLine);
                $currentLine = implode('', $currentAnsiStyles);
                $currentVisibleLength = 0;
                $availableSpace = 80;
            }

            // 截取能放入当前行的文本长度
            $takeLength = min($remainingLength, $availableSpace);
            $takeText = substr($remainingText, 0, $takeLength);

            $currentLine .= $takeText;
            $currentVisibleLength += $takeLength;

            // 更新剩余文本
            $remainingText = substr($remainingText, $takeLength);
            $remainingLength = strlen($remainingText);
        }
    }

    // 输出最后一行剩余内容
    if ($currentLine !== '') {
        $this->rawLine($currentLine);
    }
}

说明

  • 该方法会自动处理样式延续:比如文本中途设置了红色加粗,换行后新行的文本会继续保持红色加粗,直到遇到重置样式的ANSI码(如\x1b[0m);
  • 正则表达式匹配了常见的ANSI CSI序列,覆盖大多数文本样式需求(颜色、加粗、下划线等);
  • 不需要依赖第三方库,纯原生PHP实现,适合轻量CLI应用。

内容的提问来源于stack exchange,提问作者NightHawk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 20:15:37