终端输出内容截断以适应窗口的方法 - Golang实现

终端输出内容截断以适应窗口的方法 - Golang实现 我正在尝试构建一个Charm应用程序,用于显示Shell程序的标准输出和标准错误。

我在决定如何裁剪输出以适应窗口时遇到了问题。哪个包提供了能够正确处理ANSI转义码、表情符号和其他特殊字符的文本裁剪函数,以便我能将程序的标准输出裁剪到窗口大小?此外,还需要处理所有ANSI颜色转义码的结束,以防止颜色溢出到边框等区域。我希望在应用程序中尽可能少地处理终端特定的操作。

1 回复

更多关于终端输出内容截断以适应窗口的方法 - Golang实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理终端输出截断并保持ANSI转义码完整性,推荐使用muesli/ansi包。它专门设计用于处理ANSI转义序列,包括颜色、样式和光标控制,同时正确计算字符串的显示宽度(支持表情符号和宽字符)。

以下是实现终端输出截断的示例:

package main

import (
    "strings"
    "github.com/muesli/reflow/ansi"
    "github.com/muesli/reflow/truncate"
)

// 截断带ANSI转义码的文本到指定宽度
func truncateWithANSI(text string, maxWidth int) string {
    // 使用ansi包处理转义序列
    truncated := truncate.StringWithTail(text, uint(maxWidth), "")
    
    // 确保ANSI转义码正确闭合
    return ansi.Truncate(truncated, maxWidth, "")
}

// 更完整的实现,处理多行文本
func wrapAndTruncateOutput(output string, width, height int) []string {
    lines := strings.Split(output, "\n")
    result := make([]string, 0, height)
    
    for i, line := range lines {
        if i >= height {
            break
        }
        
        // 处理ANSI转义码并截断到窗口宽度
        truncated := ansi.Truncate(line, width, "")
        result = append(result, truncated)
    }
    
    return result
}

// 处理颜色转义码的完整示例
func processTerminalOutput(rawOutput string, termWidth, termHeight int) string {
    // 分割为行
    lines := strings.Split(rawOutput, "\n")
    processedLines := make([]string, 0, termHeight)
    
    for i := 0; i < len(lines) && i < termHeight; i++ {
        line := lines[i]
        
        // 计算实际显示宽度(忽略ANSI转义码)
        displayWidth := ansi.PrintableRuneWidth(line)
        
        if displayWidth > termWidth {
            // 截断并添加省略号
            line = ansi.Truncate(line, termWidth, "…")
        }
        
        // 确保行尾没有未闭合的ANSI转义码
        line = ensureANSIClosed(line)
        processedLines = append(processedLines, line)
    }
    
    return strings.Join(processedLines, "\n")
}

// 确保所有ANSI转义码正确闭合
func ensureANSIClosed(s string) string {
    // 检查是否有未闭合的颜色转义码
    openCodes := countOpenANSICodes(s)
    if openCodes > 0 {
        s += "\033[0m" // 重置所有属性
    }
    return s
}

// 计算未闭合的ANSI转义码数量
func countOpenANSICodes(s string) int {
    count := 0
    inEscape := false
    escapeSeq := ""
    
    for _, r := range s {
        if r == '\033' {
            inEscape = true
            escapeSeq = ""
        } else if inEscape {
            escapeSeq += string(r)
            if r == 'm' {
                inEscape = false
                // 检查是否是重置码
                if escapeSeq == "[0m" {
                    count = 0
                } else {
                    count++
                }
            }
        }
    }
    
    return count
}

// 使用Charm的lipgloss进行更高级的处理
import "github.com/charmbracelet/lipgloss"

func truncateWithLipgloss(text string, width int) string {
    style := lipgloss.NewStyle().Width(width).MaxWidth(width)
    return style.Render(text)
}

对于Charm应用程序,还可以直接使用lipgloss进行样式化截断:

package main

import (
    "github.com/charmbracelet/lipgloss"
)

func main() {
    // 示例:处理带ANSI颜色的输出
    coloredOutput := "\033[31mHello \033[32mWorld\033[0m"
    
    // 使用lipgloss自动处理截断和ANSI转义码
    style := lipgloss.NewStyle().
        Width(10).
        MaxWidth(10).
        Border(lipgloss.RoundedBorder())
    
    rendered := style.Render(coloredOutput)
    // rendered会自动处理ANSI转义码的截断和闭合
}

关键点:

  1. muesli/ansi正确处理ANSI转义序列的显示宽度计算
  2. truncate.StringWithTail进行安全截断
  3. 自动处理颜色转义码的闭合,防止颜色溢出
  4. 支持表情符号和宽字符的正确宽度计算

这种方法最小化了终端特定操作的处理,让专门的库处理ANSI转义码的复杂性。

回到顶部