ansiwrap.go

v0.7.0
Doc Versions Source
1
package logreader
2
3
import (
4
	"regexp"
5
	"strings"
6
7
	"github.com/charmbracelet/x/ansi"
8
)
9
10
var sgrRe = regexp.MustCompile(`\x1b\[[0-9;]*m`)
11
12
const resetSeq = "\x1b[0m"
13
14
// ansiWrap hard-wraps s at maxW display columns (ANSI-aware), then ensures
15
// each output line carries its own SGR state so that the viewport can render
16
// any line independently without relying on prior lines' escape codes.
17
func ansiWrap(s string, maxW int) string {
18
	if maxW <= 0 {
19
		return s
20
	}
21
	wrapped := ansi.Hardwrap(s, maxW, true)
22
	lines := strings.Split(wrapped, "\n")
23
24
	var lastSGR string
25
	var b strings.Builder
26
	b.Grow(len(wrapped) + len(lines)*20)
27
28
	for i, line := range lines {
29
		if i > 0 {
30
			b.WriteByte('\n')
31
		}
32
33
		// If this line doesn't start with an SGR and we have one pending,
34
		// re-emit it so the line is self-contained.
35
		if lastSGR != "" && lastSGR != resetSeq && !strings.HasPrefix(line, "\x1b[") {
36
			b.WriteString(lastSGR)
37
		}
38
		b.WriteString(line)
39
40
		// Track all SGR sequences in this line; keep the last one.
41
		if matches := sgrRe.FindAllString(line, -1); len(matches) > 0 {
42
			lastSGR = matches[len(matches)-1]
43
		}
44
	}
45
46
	return b.String()
47
}
48

Source Files