111 lines
2.2 KiB
Go
111 lines
2.2 KiB
Go
package terminal
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"unicode/utf8"
|
||
)
|
||
|
||
const textLimit = 64 << 10
|
||
|
||
func Text(b []byte) string {
|
||
s := plainText(b)
|
||
if len(s) > textLimit {
|
||
s = s[len(s)-textLimit:]
|
||
s = s[strings.IndexByte(s, '\n')+1:]
|
||
}
|
||
return s
|
||
}
|
||
|
||
// Lines reads a range of the retained output. Positive offsets are one-based;
|
||
// negative offsets count back from the last line.
|
||
func Lines(b []byte, offset, limit int) string {
|
||
lines := strings.Split(strings.TrimSuffix(plainText(b), "\n"), "\n")
|
||
start := 0
|
||
if offset < 0 {
|
||
start = len(lines) + offset
|
||
} else {
|
||
start = offset - 1
|
||
}
|
||
start = min(max(0, start), len(lines))
|
||
end := start + min(limit, len(lines)-start)
|
||
if start == end {
|
||
return fmt.Sprintf("[no lines in range; %d retained lines]", len(lines))
|
||
}
|
||
return fmt.Sprintf("[lines %d–%d of %d retained lines]\n", min(start+1, end), end, len(lines)) + strings.Join(lines[start:end], "\n")
|
||
}
|
||
|
||
func plainText(b []byte) string {
|
||
out := strings.Builder{}
|
||
line := []rune{}
|
||
col := 0
|
||
flush := func() {
|
||
out.WriteString(strings.TrimRight(string(line), " "))
|
||
out.WriteByte('\n')
|
||
line = line[:0]
|
||
col = 0
|
||
}
|
||
for i := 0; i < len(b); {
|
||
c := b[i]
|
||
switch {
|
||
case c == 0x1b:
|
||
end, final := skipEscape(b, i)
|
||
if final == 'K' {
|
||
line = line[:min(col, len(line))]
|
||
}
|
||
i = end + 1
|
||
continue
|
||
case c == '\n':
|
||
flush()
|
||
case c == '\r':
|
||
col = 0
|
||
case c == '\b':
|
||
col = max(0, col-1)
|
||
case c == '\t' || c >= 0x20 && c != 0x7f:
|
||
r, n := utf8.DecodeRune(b[i:])
|
||
for len(line) < col {
|
||
line = append(line, ' ')
|
||
}
|
||
if col < len(line) {
|
||
line[col] = r
|
||
} else {
|
||
line = append(line, r)
|
||
}
|
||
col++
|
||
i += n
|
||
continue
|
||
}
|
||
i++
|
||
}
|
||
return out.String() + strings.TrimRight(string(line), " ")
|
||
}
|
||
|
||
func skipEscape(b []byte, i int) (int, byte) {
|
||
if i+1 >= len(b) {
|
||
return i, 0
|
||
}
|
||
i++
|
||
switch b[i] {
|
||
case '[':
|
||
for i++; i < len(b); i++ {
|
||
if b[i] >= 0x40 && b[i] <= 0x7e {
|
||
return i, b[i]
|
||
}
|
||
}
|
||
return len(b) - 1, 0
|
||
case ']', 'P', '_', '^':
|
||
for i++; i < len(b); i++ {
|
||
if b[i] == 0x07 {
|
||
return i, 0
|
||
}
|
||
if b[i] == 0x1b && i+1 < len(b) && b[i+1] == '\\' {
|
||
return i + 1, 0
|
||
}
|
||
}
|
||
return len(b) - 1, 0
|
||
case '(', ')', '*', '+', '#', '%':
|
||
return min(i+1, len(b)-1), 0
|
||
}
|
||
return i, 0
|
||
}
|