123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- package ansi
- import (
- "regexp"
- "strings"
- "unicode"
- )
- func expand(text string) [][]string {
- r := regexp.MustCompile(`(?s)((?:\x1b\[.*?m)*)(.)(?:\x1b\[0m)?`)
- return r.FindAllStringSubmatch(text, -1)
- }
- func collapse(expanded [][]string) string {
- output := ""
- for _, match := range expanded {
- output += match[0]
- }
- return output
- }
- func Apply(text string, style string) string {
- expanded := expand(text)
- result := ""
- for _, match := range expanded {
- prefix := match[1]
- letter := match[2]
- if letter == "\n" {
- result += "\n"
- continue
- }
- result += "\x1b[" + style + "m" + prefix + letter + "\x1b[0m"
- }
- return result
- }
- func Indent(text string, prefix string, includeFirst bool) string {
- expanded := expand(text)
- result := ""
- if includeFirst {
- result = prefix
- }
- for _, match := range expanded {
- full := match[0]
- letter := match[2]
- if letter == "\n" {
- result += "\n" + prefix
- continue
- }
- result += full
- }
- return result
- }
- const suffix = " "
- func Pad(text string, length int) string {
- expanded := expand(text)
- result := ""
- lineLength := 0
- for _, match := range expanded {
- full := match[0]
- letter := match[2]
- if letter == "\n" {
- amount := length - lineLength
- if amount <= 0 {
- result += "\n"
- lineLength = 0
- continue
- }
- result += strings.Repeat(suffix, amount) + "\n"
- lineLength = 0
- continue
- }
- lineLength += 1
- result += full
- }
-
- amount := length - lineLength
- if amount > 0 {
- result += strings.Repeat(suffix, amount)
- }
- return result
- }
- func Wrap(text string, length int) string {
- expanded := expand(text)
- result := []string{}
- var line, space, word string
- var lineLength, spaceLength, wordLength int
- for _, match := range expanded {
- full := match[0]
- letter := match[2]
- if !unicode.IsSpace([]rune(letter)[0]) {
- if wordLength == length {
-
- result = append(result, word)
- line = ""
- lineLength = 0
- space = ""
- spaceLength = 0
- word = ""
- wordLength = 0
- }
- if lineLength+spaceLength+wordLength >= length {
-
- result = append(result, line)
- line = ""
- lineLength = 0
- space = ""
- spaceLength = 0
- }
- word += full
- wordLength += 1
- continue
- }
-
- if wordLength > 0 {
- line += space + word
- lineLength += spaceLength + wordLength
- space = ""
- spaceLength = 0
- word = ""
- wordLength = 0
- }
- if letter == "\n" {
-
- if lineLength+spaceLength <= length {
- line += space
- lineLength += spaceLength
- }
-
- result = append(result, line)
- line = ""
- lineLength = 0
- space = ""
- spaceLength = 0
- word = ""
- wordLength = 0
- } else {
- space += full
- spaceLength += 1
- }
- }
-
- if wordLength > 0 {
- line += space + word
- lineLength += spaceLength + wordLength
- }
- finalLetter := ""
- if len(expanded) > 0 {
- finalLetter = expanded[len(expanded)-1][2]
- }
- if lineLength > 0 || finalLetter == "\n" {
- result = append(result, line)
- }
- return strings.Join(result, "\n")
- }
- func DumbWrap(text string, width int) string {
- expanded := expand(text)
- result := ""
- currentLineLength := 0
- for _, match := range expanded {
- full := match[0]
- letter := match[2]
- if letter == "\n" {
- currentLineLength = 0
- result += "\n"
- continue
- }
- if currentLineLength == width {
- currentLineLength = 0
- result += "\n"
- }
- result += full
- currentLineLength += 1
- }
- return result
- }
- func Snip(text string, width, height int, ellipsis string) string {
- snipped := make([]string, 0, height)
-
- lines := strings.Split(text, "\n")
- requiresEllipsis := false
- if len(lines) <= height {
- height = len(lines)
- } else {
- requiresEllipsis = true
- }
-
- for i := height - 1; i >= 0; i -= 1 {
- line := expand(lines[i])
- if len(snipped) == 0 {
- if lineIsOnlyWhitespace(line) {
- requiresEllipsis = true
- continue
- }
-
- if len(line) == width && requiresEllipsis {
- line = line[:len(line)-1]
- }
- }
- snipped = append([]string{collapse(line)}, snipped...)
- }
- output := strings.Join(snipped, "\n")
- if requiresEllipsis {
- output += ellipsis
- }
- return output
- }
- func lineIsOnlyWhitespace(expanded [][]string) bool {
- for _, match := range expanded {
- if !unicode.IsSpace([]rune(match[2])[0]) {
- return false
- }
- }
- return true
- }
- func Height(text string) uint {
- return uint(strings.Count(text, "\n")) + 1
- }
- func CenterVertically(prefix, centered, suffix string, height uint) string {
- prefixHeight, centeredHeight, suffixHeight := Height(prefix), Height(centered), Height(suffix)
- if height <= centeredHeight {
- return strings.Join(strings.Split(centered, "\n")[:height], "\n")
- }
- totalBufferSize := height - centeredHeight
- topBufferSize := totalBufferSize / 2
- bottomBufferSize := topBufferSize + totalBufferSize%2
- if topBufferSize > prefixHeight {
- prefix = strings.Repeat("\n", int(topBufferSize-prefixHeight)) + prefix
- } else if topBufferSize < prefixHeight {
- prefix = strings.Join(strings.Split(prefix, "\n")[prefixHeight-topBufferSize:], "\n")
- }
- if bottomBufferSize > suffixHeight {
- suffix += strings.Repeat("\n", int(bottomBufferSize-suffixHeight))
- } else if bottomBufferSize < suffixHeight {
- suffix = strings.Join(strings.Split(suffix, "\n")[:bottomBufferSize], "\n")
- }
- return prefix + "\n" + centered + "\n" + suffix
- }
- func ReplaceLastLine(original, replacement string) string {
- if strings.Contains(replacement, "\n") {
- panic("new version of last line cannot contain a newline")
- }
- var lastIndex = strings.LastIndex(original, "\n")
- if lastIndex == -1 {
- lastIndex = 0
- }
- return original[:lastIndex] + "\n" + replacement
- }
- func SetLength(text string, length int, ellipsis string) string {
- text = Squash(Scrub(text))
- runes := []rune(text)
- if length == 0 {
- return ""
- }
- if len(runes) > length {
- return string(runes[:length-1]) + ellipsis
- }
- if len(runes) < length {
- return string(runes) + strings.Repeat(" ", length-len(runes))
- }
- return text
- }
- func Squash(text string) string {
- return strings.ReplaceAll(text, "\n", " ")
- }
- func Scrub(text string) string {
- text = strings.ReplaceAll(text, "\t", " ")
- text = strings.Map(func(input rune) rune {
- if input != '\n' && unicode.IsControl(input) {
- return -1
- }
- return input
- }, text)
- return text
- }
|