Files
lazygit/pkg/utils/lines.go
Stefan Haller 1f2cb35cc9 Refactor: move wrapMessageToWidth to utils/lines.go
to make it more generally usable by clients other than ConfirmationHelper, which
we will do later in this branch. Rename it to WrapViewLinesToWidth while we're
at it.

Add tests; in particular, add a sanity check that we wrap lines the same way as
gocui does. The tests that are added here are the same ones as in gocui for its
lineWrap function, but we'll extend them a bit in later commits in this branch.
2024-12-23 12:24:09 +01:00

158 lines
4.1 KiB
Go

package utils
import (
"bytes"
"strings"
"github.com/mattn/go-runewidth"
)
// SplitLines takes a multiline string and splits it on newlines
// currently we are also stripping \r's which may have adverse effects for
// windows users (but no issues have been raised yet)
func SplitLines(multilineString string) []string {
multilineString = strings.Replace(multilineString, "\r", "", -1)
if multilineString == "" || multilineString == "\n" {
return make([]string, 0)
}
lines := strings.Split(multilineString, "\n")
if lines[len(lines)-1] == "" {
return lines[:len(lines)-1]
}
return lines
}
func SplitNul(str string) []string {
if str == "" {
return make([]string, 0)
}
str = strings.TrimSuffix(str, "\x00")
return strings.Split(str, "\x00")
}
// NormalizeLinefeeds - Removes all Windows and Mac style line feeds
func NormalizeLinefeeds(str string) string {
str = strings.Replace(str, "\r\n", "\n", -1)
str = strings.Replace(str, "\r", "", -1)
return str
}
// EscapeSpecialChars - Replaces all special chars like \n with \\n
func EscapeSpecialChars(str string) string {
return strings.NewReplacer(
"\n", "\\n",
"\r", "\\r",
"\t", "\\t",
"\b", "\\b",
"\f", "\\f",
"\v", "\\v",
).Replace(str)
}
func dropCR(data []byte) []byte {
if len(data) > 0 && data[len(data)-1] == '\r' {
return data[0 : len(data)-1]
}
return data
}
// ScanLinesAndTruncateWhenLongerThanBuffer returns a split function that can be
// used with bufio.Scanner.Split(). It is very similar to bufio.ScanLines,
// except that it will truncate lines that are longer than the scanner's read
// buffer (whereas bufio.ScanLines will return an error in that case, which is
// often difficult to handle).
//
// If you are using your own buffer for the scanner, you must set maxBufferSize
// to the same value as the max parameter that you passed to scanner.Buffer().
// Otherwise, maxBufferSize must be set to bufio.MaxScanTokenSize.
func ScanLinesAndTruncateWhenLongerThanBuffer(maxBufferSize int) func(data []byte, atEOF bool) (int, []byte, error) {
skipOverRemainderOfLongLine := false
return func(data []byte, atEOF bool) (int, []byte, error) {
if atEOF && len(data) == 0 {
// Done
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
if skipOverRemainderOfLongLine {
skipOverRemainderOfLongLine = false
return i + 1, nil, nil
}
return i + 1, dropCR(data[0:i]), nil
}
if atEOF {
if skipOverRemainderOfLongLine {
return len(data), nil, nil
}
return len(data), dropCR(data), nil
}
// Buffer is full, so we can't get more data
if len(data) >= maxBufferSize {
if skipOverRemainderOfLongLine {
return len(data), nil, nil
}
skipOverRemainderOfLongLine = true
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
}
// Wrap lines to a given width.
// If wrap is false, the text is returned as is.
// This code needs to behave the same as `gocui.lineWrap` does.
func WrapViewLinesToWidth(wrap bool, text string, width int) []string {
lines := strings.Split(text, "\n")
if !wrap {
return lines
}
wrappedLines := make([]string, 0, len(lines))
for _, line := range lines {
n := 0
offset := 0
lastWhitespaceIndex := -1
for i, currChr := range line {
rw := runewidth.RuneWidth(currChr)
n += rw
if n > width {
if currChr == ' ' {
wrappedLines = append(wrappedLines, line[offset:i])
offset = i + 1
n = 0
} else if currChr == '-' {
wrappedLines = append(wrappedLines, line[offset:i])
offset = i
n = rw
} else if lastWhitespaceIndex != -1 {
if line[lastWhitespaceIndex] == '-' {
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
} else {
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
}
offset = lastWhitespaceIndex + 1
n = runewidth.StringWidth(line[offset : i+1])
} else {
wrappedLines = append(wrappedLines, line[offset:i])
offset = i
n = rw
}
lastWhitespaceIndex = -1
} else if currChr == ' ' || currChr == '-' {
lastWhitespaceIndex = i
}
}
wrappedLines = append(wrappedLines, line[offset:])
}
return wrappedLines
}