text.go

  1package text
  2
  3import (
  4	"bytes"
  5	"strings"
  6)
  7
  8// Wrap a text for an exact line size
  9// Handle properly terminal color escape code
 10func Wrap(text string, lineWidth int) (string, int) {
 11	return WrapLeftPadded(text, lineWidth, 0)
 12}
 13
 14// Wrap a text for an exact line size with a left padding
 15// Handle properly terminal color escape code
 16func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) {
 17	var textBuffer bytes.Buffer
 18	var lineBuffer bytes.Buffer
 19	nbLine := 1
 20	firstLine := true
 21	pad := strings.Repeat(" ", leftPad)
 22
 23	// tabs are formatted as 4 spaces
 24	text = strings.Replace(text, "\t", "    ", 4)
 25
 26	for _, line := range strings.Split(text, "\n") {
 27		spaceLeft := lineWidth - leftPad
 28
 29		if !firstLine {
 30			textBuffer.WriteString("\n")
 31			nbLine++
 32		}
 33
 34		firstWord := true
 35
 36		for _, word := range strings.Split(line, " ") {
 37			wordLength := wordLen(word)
 38
 39			if !firstWord {
 40				lineBuffer.WriteString(" ")
 41				spaceLeft -= 1
 42
 43				if spaceLeft <= 0 {
 44					textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
 45					textBuffer.WriteString("\n")
 46					lineBuffer.Reset()
 47					spaceLeft = lineWidth - leftPad
 48					nbLine++
 49					firstLine = false
 50				}
 51			}
 52
 53			// Word fit in the current line
 54			if spaceLeft >= wordLength {
 55				lineBuffer.WriteString(word)
 56				spaceLeft -= wordLength
 57				firstWord = false
 58			} else {
 59				// Break a word longer than a line
 60				if wordLength > lineWidth {
 61					for wordLength > 0 && len(word) > 0 {
 62						l := minInt(spaceLeft, wordLength)
 63						part, leftover := splitWord(word, l)
 64						word = leftover
 65						wordLength = wordLen(word)
 66
 67						lineBuffer.WriteString(part)
 68						textBuffer.WriteString(pad)
 69						textBuffer.Write(lineBuffer.Bytes())
 70						lineBuffer.Reset()
 71
 72						spaceLeft -= l
 73
 74						if spaceLeft <= 0 {
 75							textBuffer.WriteString("\n")
 76							nbLine++
 77							spaceLeft = lineWidth - leftPad
 78						}
 79					}
 80				} else {
 81					// Normal break
 82					textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
 83					textBuffer.WriteString("\n")
 84					lineBuffer.Reset()
 85					lineBuffer.WriteString(word)
 86					firstWord = false
 87					spaceLeft = lineWidth - wordLength
 88					nbLine++
 89				}
 90			}
 91		}
 92
 93		textBuffer.WriteString(pad + strings.TrimRight(lineBuffer.String(), " "))
 94		lineBuffer.Reset()
 95		firstLine = false
 96	}
 97
 98	return textBuffer.String(), nbLine
 99}
100
101func wordLen(word string) int {
102	length := 0
103	escape := false
104
105	for _, char := range word {
106		if char == '\x1b' {
107			escape = true
108		}
109
110		if !escape {
111			length++
112		}
113
114		if char == 'm' {
115			escape = false
116		}
117	}
118
119	return length
120}
121
122func splitWord(word string, length int) (string, string) {
123	result := ""
124	added := 0
125	escape := false
126
127	if length == 0 {
128		return "", word
129	}
130
131	for _, char := range word {
132		if char == '\x1b' {
133			escape = true
134		}
135
136		result += string(char)
137
138		if !escape {
139			added++
140			if added == length {
141				break
142			}
143		}
144
145		if char == 'm' {
146			escape = false
147		}
148	}
149
150	leftover := word[len(result):]
151
152	return result, leftover
153}
154
155func minInt(a, b int) int {
156	if a > b {
157		return b
158	}
159	return a
160}