0
0
mirror of https://github.com/crazy-max/diun.git synced 2024-12-22 19:38:28 +00:00
crazy-max_diun/vendor/github.com/jedib0t/go-pretty/v6/text/wrap.go
dependabot[bot] 6f7b5b313d
chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.5.9 to 6.6.5
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.5.9 to 6.6.5.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.5.9...v6.6.5)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-15 14:33:08 +00:00

235 lines
6.4 KiB
Go

package text
import (
"strings"
)
// WrapHard wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Breaks all words that go beyond the line boundary.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapHard(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := StringWidthWithoutEscSequences(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapHard(paragraph, wrapLen, out)
}
return out.String()
}
// WrapSoft wraps a string to the given length using a newline. Handles strings
// with ANSI escape sequences (such as text color) without breaking the text
// formatting. Tries to move words that go beyond the line boundary to the next
// line.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapSoft(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := StringWidthWithoutEscSequences(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, paragraph := range strings.Split(str, "\n\n") {
if idx > 0 {
out.WriteString("\n\n")
}
wrapSoft(paragraph, wrapLen, out)
}
return out.String()
}
// WrapText is very similar to WrapHard except for one minor difference. Unlike
// WrapHard which discards line-breaks and respects only paragraph-breaks, this
// function respects line-breaks too.
//
// For examples, refer to the unit-tests or GoDoc examples.
func WrapText(str string, wrapLen int) string {
if wrapLen <= 0 {
return ""
}
str = strings.Replace(str, "\t", " ", -1)
sLen := StringWidthWithoutEscSequences(str)
if sLen <= wrapLen {
return str
}
out := &strings.Builder{}
out.Grow(sLen + (sLen / wrapLen))
for idx, line := range strings.Split(str, "\n") {
if idx > 0 {
out.WriteString("\n")
}
wrapHard(line, wrapLen, out)
}
return out.String()
}
func appendChar(char rune, wrapLen int, lineLen *int, inEscSeq bool, lastSeenEscSeq string, out *strings.Builder) {
// handle reaching the end of the line as dictated by wrapLen or by finding
// a newline character
if (*lineLen == wrapLen && !inEscSeq && char != '\n') || (char == '\n') {
if lastSeenEscSeq != "" {
// terminate escape sequence and the line; and restart the escape
// sequence in the next line
out.WriteString(EscapeReset)
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
} else {
// just start a new line
out.WriteRune('\n')
}
// reset line index to 0th character
*lineLen = 0
}
// if the rune is not a new line, output it
if char != '\n' {
out.WriteRune(char)
// increment the line index if not in the middle of an escape sequence
if !inEscSeq {
*lineLen += RuneWidth(char)
}
}
}
func appendWord(word string, lineIdx *int, lastSeenEscSeq string, wrapLen int, out *strings.Builder) {
inEscSeq := false
for _, char := range word {
if char == EscapeStartRune {
inEscSeq = true
lastSeenEscSeq = ""
}
if inEscSeq {
lastSeenEscSeq += string(char)
}
appendChar(char, wrapLen, lineIdx, inEscSeq, lastSeenEscSeq, out)
if inEscSeq && char == EscapeStopRune {
inEscSeq = false
}
if lastSeenEscSeq == EscapeReset {
lastSeenEscSeq = ""
}
}
}
func terminateLine(wrapLen int, lineLen *int, lastSeenEscSeq string, out *strings.Builder) {
if *lineLen < wrapLen {
out.WriteString(strings.Repeat(" ", wrapLen-*lineLen))
}
// something is already on the line; terminate it
if lastSeenEscSeq != "" {
out.WriteString(EscapeReset)
}
out.WriteRune('\n')
out.WriteString(lastSeenEscSeq)
*lineLen = 0
}
func terminateOutput(lastSeenEscSeq string, out *strings.Builder) {
if lastSeenEscSeq != "" && lastSeenEscSeq != EscapeReset && !strings.HasSuffix(out.String(), EscapeReset) {
out.WriteString(EscapeReset)
}
}
func wrapHard(paragraph string, wrapLen int, out *strings.Builder) {
esp := escSeqParser{}
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
if openEscSeq := esp.ParseString(word); openEscSeq != "" {
lastSeenEscSeq = openEscSeq
}
if lineLen > 0 {
out.WriteRune(' ')
lineLen++
}
wordLen := StringWidthWithoutEscSequences(word)
if lineLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(word)
lineLen += wordLen
} else { // word doesn't fit within the line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}
func wrapSoft(paragraph string, wrapLen int, out *strings.Builder) {
esp := escSeqParser{}
lineLen, lastSeenEscSeq := 0, ""
words := strings.Fields(paragraph)
for wordIdx, word := range words {
if openEscSeq := esp.ParseString(word); openEscSeq != "" {
lastSeenEscSeq = openEscSeq
}
spacing, spacingLen := wrapSoftSpacing(lineLen)
wordLen := StringWidthWithoutEscSequences(word)
if lineLen+spacingLen+wordLen <= wrapLen { // word fits within the line
out.WriteString(spacing)
out.WriteString(word)
lineLen += spacingLen + wordLen
} else { // word doesn't fit within the line
lineLen = wrapSoftLastWordInLine(wrapLen, lineLen, lastSeenEscSeq, wordLen, word, out)
}
// end of line; but more words incoming
if lineLen == wrapLen && wordIdx < len(words)-1 {
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
}
terminateOutput(lastSeenEscSeq, out)
}
func wrapSoftLastWordInLine(wrapLen int, lineLen int, lastSeenEscSeq string, wordLen int, word string, out *strings.Builder) int {
if lineLen > 0 { // something is already on the line; terminate it
terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
}
if wordLen <= wrapLen { // word fits within a single line
out.WriteString(word)
lineLen = wordLen
} else { // word doesn't fit within a single line; hard-wrap
appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
}
return lineLen
}
func wrapSoftSpacing(lineLen int) (string, int) {
spacing, spacingLen := "", 0
if lineLen > 0 {
spacing, spacingLen = " ", 1
}
return spacing, spacingLen
}