diff options
Diffstat (limited to 'vendor/github.com/MichaelMure/go-term-text')
14 files changed, 0 insertions, 1110 deletions
diff --git a/vendor/github.com/MichaelMure/go-term-text/.gitignore b/vendor/github.com/MichaelMure/go-term-text/.gitignore deleted file mode 100644 index 9f11b755..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea/ diff --git a/vendor/github.com/MichaelMure/go-term-text/.travis.yml b/vendor/github.com/MichaelMure/go-term-text/.travis.yml deleted file mode 100644 index c5db9518..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go - -go: - - 1.11.x - - 1.12.x - - 1.13.x - -env: - - GO111MODULE=on - -script: - - go build - - go test -v -bench=. -race -coverprofile=coverage.txt -covermode=atomic ./... - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/MichaelMure/go-term-text/LICENSE b/vendor/github.com/MichaelMure/go-term-text/LICENSE deleted file mode 100644 index 5ba12bf4..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Michael Muré - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/MichaelMure/go-term-text/Readme.md b/vendor/github.com/MichaelMure/go-term-text/Readme.md deleted file mode 100644 index 25722ee9..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/Readme.md +++ /dev/null @@ -1,74 +0,0 @@ -# go-term-text - -[![Build Status](https://travis-ci.org/MichaelMure/go-term-text.svg?branch=master)](https://travis-ci.org/MichaelMure/go-term-text) -[![GoDoc](https://godoc.org/github.com/MichaelMure/go-term-text?status.svg)](https://godoc.org/github.com/MichaelMure/go-term-text) -[![Go Report Card](https://goreportcard.com/badge/github.com/MichaelMure/go-term-text)](https://goreportcard.com/report/github.com/MichaelMure/go-term-text) -[![codecov](https://codecov.io/gh/MichaelMure/go-term-text/branch/master/graph/badge.svg)](https://codecov.io/gh/MichaelMure/go-term-text) -[![GitHub license](https://img.shields.io/github/license/MichaelMure/go-term-text.svg)](https://github.com/MichaelMure/go-term-text/blob/master/LICENSE) -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/the-git-bug/Lobby) - -`go-term-text` is a go package implementing a collection of algorithms to help format and manipulate text for the terminal. - -In particular, `go-term-text`: -- support wide characters (chinese, japanese ...) and emoji -- handle properly ANSI escape sequences - -Included algorithms cover: -- wrapping with padding and indentation -- padding -- text length -- trimming -- alignment -- escape sequence extraction and reapplication -- escape sequence snapshot and simplification -- truncation - -## Example - -```go -package main - -import ( - "fmt" - "strings" - - "github.com/MichaelMure/go-term-text" -) - -func main() { - input := "The \x1b[1mLorem ipsum\x1b[0m text is typically composed of " + - "pseudo-Latin words. It is commonly used as \x1b[3mplaceholder\x1b[0m" + - " text to examine or demonstrate the \x1b[9mvisual effects\x1b[0m of " + - "various graphic design. 一只 A Quick \x1b[31m敏捷的狐 Fox " + - "狸跳过了\x1b[0mDog一只懒狗。" - - output, n := text.Wrap(input, 60, - text.WrapIndent("\x1b[34m<-indent-> \x1b[0m"), - text.WrapPad("\x1b[33m<-pad-> \x1b[0m"), - ) - - fmt.Printf("output has %d lines\n\n", n) - - fmt.Println("|" + strings.Repeat("-", 58) + "|") - fmt.Println(output) - fmt.Println("|" + strings.Repeat("-", 58) + "|") -} -``` - -This will print: - -![example output](/img/example.png) - -For more details, have a look at the [GoDoc](https://godoc.org/github.com/MichaelMure/go-term-text). - -## Origin - -This package has been extracted from the [git-bug](https://github.com/MichaelMure/git-bug) project. As such, its aim is to support this project and not to provide an all-in-one solution. Contributions as welcome though. - -## Contribute - -PRs accepted. - -## License - -MIT diff --git a/vendor/github.com/MichaelMure/go-term-text/align.go b/vendor/github.com/MichaelMure/go-term-text/align.go deleted file mode 100644 index 8262a4de..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/align.go +++ /dev/null @@ -1,67 +0,0 @@ -package text - -import ( - "strings" -) - -type Alignment int - -const ( - NoAlign Alignment = iota - AlignLeft - AlignCenter - AlignRight -) - -// LineAlign align the given line as asked and apply the needed padding to match the given -// lineWidth, while ignoring the terminal escape sequences. -// If the given lineWidth is too small to fit the given line, it's returned without -// padding, overflowing lineWidth. -func LineAlign(line string, lineWidth int, align Alignment) string { - switch align { - case NoAlign: - return line - case AlignLeft: - return LineAlignLeft(line, lineWidth) - case AlignCenter: - return LineAlignCenter(line, lineWidth) - case AlignRight: - return LineAlignRight(line, lineWidth) - } - panic("unknown alignment") -} - -// LineAlignLeft align the given line on the left while ignoring the terminal escape sequences. -// If the given lineWidth is too small to fit the given line, it's returned without -// padding, overflowing lineWidth. -func LineAlignLeft(line string, lineWidth int) string { - return TrimSpace(line) -} - -// LineAlignCenter align the given line on the center and apply the needed left -// padding, while ignoring the terminal escape sequences. -// If the given lineWidth is too small to fit the given line, it's returned without -// padding, overflowing lineWidth. -func LineAlignCenter(line string, lineWidth int) string { - trimmed := TrimSpace(line) - totalPadLen := lineWidth - Len(trimmed) - if totalPadLen < 0 { - totalPadLen = 0 - } - pad := strings.Repeat(" ", totalPadLen/2) - return pad + trimmed -} - -// LineAlignRight align the given line on the right and apply the needed left -// padding to match the given lineWidth, while ignoring the terminal escape sequences. -// If the given lineWidth is too small to fit the given line, it's returned without -// padding, overflowing lineWidth. -func LineAlignRight(line string, lineWidth int) string { - trimmed := TrimSpace(line) - padLen := lineWidth - Len(trimmed) - if padLen < 0 { - padLen = 0 - } - pad := strings.Repeat(" ", padLen) - return pad + trimmed -} diff --git a/vendor/github.com/MichaelMure/go-term-text/escape_state.go b/vendor/github.com/MichaelMure/go-term-text/escape_state.go deleted file mode 100644 index cec35c26..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/escape_state.go +++ /dev/null @@ -1,265 +0,0 @@ -package text - -import ( - "fmt" - "strconv" - "strings" -) - -const Escape = '\x1b' - -type EscapeState struct { - Bold bool - Dim bool - Italic bool - Underlined bool - Blink bool - Reverse bool - Hidden bool - CrossedOut bool - - FgColor Color - BgColor Color -} - -type Color interface { - Codes() []string -} - -func (es *EscapeState) Witness(s string) { - inEscape := false - var start int - - runes := []rune(s) - - for i, r := range runes { - if r == Escape { - inEscape = true - start = i - continue - } - if inEscape { - if r == 'm' { - inEscape = false - es.witnessCode(string(runes[start+1 : i])) - } - continue - } - } -} - -func (es *EscapeState) witnessCode(s string) { - if s == "" { - return - } - if s == "[" { - es.reset() - return - } - if len(s) < 2 { - return - } - if s[0] != '[' { - return - } - - s = s[1:] - split := strings.Split(s, ";") - - dequeue := func() { - split = split[1:] - } - - color := func(ground int) Color { - if len(split) < 1 { - // the whole sequence is broken, ignoring the rest - return nil - } - - subCode := split[0] - dequeue() - - switch subCode { - case "2": - if len(split) < 3 { - return nil - } - r, err := strconv.Atoi(split[0]) - dequeue() - if err != nil { - return nil - } - g, err := strconv.Atoi(split[0]) - dequeue() - if err != nil { - return nil - } - b, err := strconv.Atoi(split[0]) - dequeue() - if err != nil { - return nil - } - return &ColorRGB{ground: ground, R: r, G: g, B: b} - - case "5": - if len(split) < 1 { - return nil - } - index, err := strconv.Atoi(split[0]) - dequeue() - if err != nil { - return nil - } - return &Color256{ground: ground, Index: index} - - } - return nil - } - - for len(split) > 0 { - code, err := strconv.Atoi(split[0]) - if err != nil { - return - } - dequeue() - - switch { - case code == 0: - es.reset() - - case code == 1: - es.Bold = true - case code == 2: - es.Dim = true - case code == 3: - es.Italic = true - case code == 4: - es.Underlined = true - case code == 5: - es.Blink = true - // case code == 6: - case code == 7: - es.Reverse = true - case code == 8: - es.Hidden = true - case code == 9: - es.CrossedOut = true - - case code == 21: - es.Bold = false - case code == 22: - es.Dim = false - case code == 23: - es.Italic = false - case code == 24: - es.Underlined = false - case code == 25: - es.Blink = false - // case code == 26: - case code == 27: - es.Reverse = false - case code == 28: - es.Hidden = false - case code == 29: - es.CrossedOut = false - - case (code >= 30 && code <= 37) || code == 39 || (code >= 90 && code <= 97): - es.FgColor = ColorIndex(code) - - case (code >= 40 && code <= 47) || code == 49 || (code >= 100 && code <= 107): - es.BgColor = ColorIndex(code) - - case code == 38: - es.FgColor = color(code) - if es.FgColor == nil { - return - } - - case code == 48: - es.BgColor = color(code) - if es.BgColor == nil { - return - } - } - } -} - -func (es *EscapeState) reset() { - *es = EscapeState{} -} - -func (es *EscapeState) String() string { - var codes []string - - if es.Bold { - codes = append(codes, strconv.Itoa(1)) - } - if es.Dim { - codes = append(codes, strconv.Itoa(2)) - } - if es.Italic { - codes = append(codes, strconv.Itoa(3)) - } - if es.Underlined { - codes = append(codes, strconv.Itoa(4)) - } - if es.Blink { - codes = append(codes, strconv.Itoa(5)) - } - if es.Reverse { - codes = append(codes, strconv.Itoa(7)) - } - if es.Hidden { - codes = append(codes, strconv.Itoa(8)) - } - if es.CrossedOut { - codes = append(codes, strconv.Itoa(9)) - } - - if es.FgColor != nil { - codes = append(codes, es.FgColor.Codes()...) - } - if es.BgColor != nil { - codes = append(codes, es.BgColor.Codes()...) - } - - if len(codes) == 0 { - return "\x1b[0m" - } - - return fmt.Sprintf("\x1b[%sm", strings.Join(codes, ";")) -} - -type ColorIndex int - -func (cInd ColorIndex) Codes() []string { - return []string{strconv.Itoa(int(cInd))} -} - -type Color256 struct { - ground int - Index int -} - -func (c256 Color256) Codes() []string { - return []string{ - strconv.Itoa(c256.ground), - "5", - strconv.Itoa(c256.Index), - } -} - -type ColorRGB struct { - ground int - R, G, B int -} - -func (cRGB ColorRGB) Codes() []string { - return []string{ - strconv.Itoa(cRGB.ground), - "2", - strconv.Itoa(cRGB.R), - strconv.Itoa(cRGB.G), - strconv.Itoa(cRGB.B), - } -} diff --git a/vendor/github.com/MichaelMure/go-term-text/escapes.go b/vendor/github.com/MichaelMure/go-term-text/escapes.go deleted file mode 100644 index 19f78c92..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/escapes.go +++ /dev/null @@ -1,95 +0,0 @@ -package text - -import ( - "strings" - "unicode/utf8" -) - -// EscapeItem hold the description of terminal escapes in a line. -// 'item' is the actual escape command -// 'pos' is the index in the rune array where the 'item' shall be inserted back. -// For example, the escape item in "F\x1b33mox" is {"\x1b33m", 1}. -type EscapeItem struct { - Item string - Pos int -} - -// ExtractTermEscapes extract terminal escapes out of a line and returns a new -// line without terminal escapes and a slice of escape items. The terminal escapes -// can be inserted back into the new line at rune index 'item.pos' to recover the -// original line. -// -// Required: The line shall not contain "\n" -func ExtractTermEscapes(line string) (string, []EscapeItem) { - var termEscapes []EscapeItem - var line1 strings.Builder - - pos := 0 - item := "" - occupiedRuneCount := 0 - inEscape := false - for i, r := range []rune(line) { - if r == '\x1b' { - pos = i - item = string(r) - inEscape = true - continue - } - if inEscape { - item += string(r) - if r == 'm' { - termEscapes = append(termEscapes, EscapeItem{item, pos - occupiedRuneCount}) - occupiedRuneCount += utf8.RuneCountInString(item) - inEscape = false - } - continue - } - line1.WriteRune(r) - } - - return line1.String(), termEscapes -} - -// ApplyTermEscapes apply the extracted terminal escapes to the edited line. -// Escape sequences need to be ordered by their position. -// If the position is < 0, the escape is applied at the beginning of the line. -// If the position is > len(line), the escape is applied at the end of the line. -func ApplyTermEscapes(line string, escapes []EscapeItem) string { - if len(escapes) == 0 { - return line - } - - var out strings.Builder - - currPos := 0 - currItem := 0 - for _, r := range line { - for currItem < len(escapes) && currPos >= escapes[currItem].Pos { - out.WriteString(escapes[currItem].Item) - currItem++ - } - out.WriteRune(r) - currPos++ - } - - // Don't forget the trailing escapes, if any. - for currItem < len(escapes) { - out.WriteString(escapes[currItem].Item) - currItem++ - } - - return out.String() -} - -// OffsetEscapes is a utility function to offset the position of a -// collection of EscapeItem. -func OffsetEscapes(escapes []EscapeItem, offset int) []EscapeItem { - result := make([]EscapeItem, len(escapes)) - for i, e := range escapes { - result[i] = EscapeItem{ - Item: e.Item, - Pos: e.Pos + offset, - } - } - return result -} diff --git a/vendor/github.com/MichaelMure/go-term-text/go.mod b/vendor/github.com/MichaelMure/go-term-text/go.mod deleted file mode 100644 index a03c8606..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/MichaelMure/go-term-text - -go 1.11 - -require ( - github.com/mattn/go-runewidth v0.0.6 - github.com/stretchr/testify v1.3.0 -) diff --git a/vendor/github.com/MichaelMure/go-term-text/go.sum b/vendor/github.com/MichaelMure/go-term-text/go.sum deleted file mode 100644 index a119b457..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/go.sum +++ /dev/null @@ -1,9 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/github.com/MichaelMure/go-term-text/left_pad.go b/vendor/github.com/MichaelMure/go-term-text/left_pad.go deleted file mode 100644 index a63fedb9..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/left_pad.go +++ /dev/null @@ -1,50 +0,0 @@ -package text - -import ( - "bytes" - "strings" - - "github.com/mattn/go-runewidth" -) - -// LeftPadMaxLine pads a line on the left by a specified amount and pads the -// string on the right to fill the maxLength. -// If the given string is too long, it is truncated with an ellipsis. -// Handle properly terminal color escape code -func LeftPadMaxLine(line string, length, leftPad int) string { - cleaned, escapes := ExtractTermEscapes(line) - - scrWidth := runewidth.StringWidth(cleaned) - // truncate and ellipse if needed - if scrWidth+leftPad > length { - cleaned = runewidth.Truncate(cleaned, length-leftPad, "…") - } else if scrWidth+leftPad < length { - cleaned = runewidth.FillRight(cleaned, length-leftPad) - } - - rightPart := ApplyTermEscapes(cleaned, escapes) - pad := strings.Repeat(" ", leftPad) - - return pad + rightPart -} - -// LeftPad left pad each line of the given text -func LeftPadLines(text string, leftPad int) string { - var result bytes.Buffer - - pad := strings.Repeat(" ", leftPad) - - lines := strings.Split(text, "\n") - - for i, line := range lines { - result.WriteString(pad) - result.WriteString(line) - - // no additional line break at the end - if i < len(lines)-1 { - result.WriteString("\n") - } - } - - return result.String() -} diff --git a/vendor/github.com/MichaelMure/go-term-text/len.go b/vendor/github.com/MichaelMure/go-term-text/len.go deleted file mode 100644 index c6bcaeac..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/len.go +++ /dev/null @@ -1,45 +0,0 @@ -package text - -import ( - "strings" - - "github.com/mattn/go-runewidth" -) - -// Len return the length of a string in a terminal, while ignoring the terminal -// escape sequences. -func Len(text string) int { - length := 0 - escape := false - - for _, char := range text { - if char == '\x1b' { - escape = true - } - if !escape { - length += runewidth.RuneWidth(char) - } - if char == 'm' { - escape = false - } - } - - return length -} - -// MaxLineLen return the length in a terminal of the longest line, while -// ignoring the terminal escape sequences. -func MaxLineLen(text string) int { - lines := strings.Split(text, "\n") - - max := 0 - - for _, line := range lines { - length := Len(line) - if length > max { - max = length - } - } - - return max -} diff --git a/vendor/github.com/MichaelMure/go-term-text/trim.go b/vendor/github.com/MichaelMure/go-term-text/trim.go deleted file mode 100644 index eaf2ca0c..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/trim.go +++ /dev/null @@ -1,28 +0,0 @@ -package text - -import ( - "strings" - "unicode" -) - -// TrimSpace remove the leading and trailing whitespace while ignoring the -// terminal escape sequences. -// Returns the number of trimmed space on both side. -func TrimSpace(line string) string { - cleaned, escapes := ExtractTermEscapes(line) - - // trim left while counting - left := 0 - trimmed := strings.TrimLeftFunc(cleaned, func(r rune) bool { - if unicode.IsSpace(r) { - left++ - return true - } - return false - }) - - trimmed = strings.TrimRightFunc(trimmed, unicode.IsSpace) - - escapes = OffsetEscapes(escapes, -left) - return ApplyTermEscapes(trimmed, escapes) -} diff --git a/vendor/github.com/MichaelMure/go-term-text/truncate.go b/vendor/github.com/MichaelMure/go-term-text/truncate.go deleted file mode 100644 index b51bb39e..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/truncate.go +++ /dev/null @@ -1,24 +0,0 @@ -package text - -import "github.com/mattn/go-runewidth" - -// TruncateMax truncate a line if its length is greater -// than the given length. Otherwise, the line is returned -// as is. If truncating occur, an ellipsis is inserted at -// the end. -// Handle properly terminal color escape code -func TruncateMax(line string, length int) string { - if length <= 0 { - return "…" - } - - l := Len(line) - if l <= length || l == 0 { - return line - } - - cleaned, escapes := ExtractTermEscapes(line) - truncated := runewidth.Truncate(cleaned, length-1, "") - - return ApplyTermEscapes(truncated, escapes) + "…" -} diff --git a/vendor/github.com/MichaelMure/go-term-text/wrap.go b/vendor/github.com/MichaelMure/go-term-text/wrap.go deleted file mode 100644 index eba9e0b8..00000000 --- a/vendor/github.com/MichaelMure/go-term-text/wrap.go +++ /dev/null @@ -1,407 +0,0 @@ -package text - -import ( - "strings" - - "github.com/mattn/go-runewidth" -) - -// Force runewidth not to treat ambiguous runes as wide chars, so that things -// like unicode ellipsis/up/down/left/right glyphs can have correct runewidth -// and can be displayed correctly in terminals. -func init() { - runewidth.DefaultCondition.EastAsianWidth = false -} - -type wrapOpts struct { - indent string - pad string - align Alignment -} - -// WrapOption is a functional option for the Wrap() function -type WrapOption func(opts *wrapOpts) - -// WrapPad configure the padding with a string for Wrap() -func WrapPad(pad string) WrapOption { - return func(opts *wrapOpts) { - opts.pad = pad - } -} - -// WrapPadded configure the padding with a number of space characters for Wrap() -func WrapPadded(padLen int) WrapOption { - return func(opts *wrapOpts) { - opts.pad = strings.Repeat(" ", padLen) - } -} - -// WrapPad configure the indentation on the first line for Wrap() -func WrapIndent(indent string) WrapOption { - return func(opts *wrapOpts) { - opts.indent = indent - } -} - -// WrapAlign configure the text alignment for Wrap() -func WrapAlign(align Alignment) WrapOption { - return func(opts *wrapOpts) { - opts.align = align - } -} - -// allWrapOpts compile the set of WrapOption into a final wrapOpts -// from the default values. -func allWrapOpts(opts []WrapOption) *wrapOpts { - wrapOpts := &wrapOpts{ - indent: "", - pad: "", - align: NoAlign, - } - for _, opt := range opts { - opt(wrapOpts) - } - if wrapOpts.indent == "" { - wrapOpts.indent = wrapOpts.pad - } - return wrapOpts -} - -// Wrap a text for a given line size. -// Handle properly terminal color escape code -// Options are accepted to configure things like indent, padding or alignment. -// Return the wrapped text and the number of lines -func Wrap(text string, lineWidth int, opts ...WrapOption) (string, int) { - wrapOpts := allWrapOpts(opts) - - if lineWidth <= 0 { - return "", 1 - } - - var lines []string - nbLine := 0 - - if len(wrapOpts.indent) >= lineWidth { - // fallback rendering - lines = append(lines, strings.Repeat("⭬", lineWidth)) - nbLine++ - wrapOpts.indent = wrapOpts.pad - } - if len(wrapOpts.pad) >= lineWidth { - // fallback rendering - line := strings.Repeat("⭬", lineWidth) - return strings.Repeat(line+"\n", 5), 5 - } - - // Start with the indent - padStr := wrapOpts.indent - padLen := Len(wrapOpts.indent) - - // tabs are formatted as 4 spaces - text = strings.Replace(text, "\t", " ", -1) - - // NOTE: text is first segmented into lines so that softwrapLine can handle. - for i, line := range strings.Split(text, "\n") { - // on the second line, use the padding instead - if i == 1 { - padStr = wrapOpts.pad - padLen = Len(wrapOpts.pad) - } - - if line == "" || strings.TrimSpace(line) == "" { - // nothing in the line, we just add the non-empty part of the padding - lines = append(lines, strings.TrimRight(padStr, " ")) - nbLine++ - continue - } - - wrapped := softwrapLine(line, lineWidth-padLen) - split := strings.Split(wrapped, "\n") - - if i == 0 && len(split) > 1 { - // the very first line got wrapped - // that means we need to switch to the normal padding - // use the first wrapped line, ignore everything else and - // wrap the remaining of the line with the normal padding. - - content := LineAlign(strings.TrimRight(split[0], " "), lineWidth-padLen, wrapOpts.align) - lines = append(lines, padStr+content) - nbLine++ - line = strings.TrimPrefix(line, split[0]) - line = strings.TrimLeft(line, " ") - - padStr = wrapOpts.pad - padLen = Len(wrapOpts.pad) - wrapped = softwrapLine(line, lineWidth-padLen) - split = strings.Split(wrapped, "\n") - } - - for j, seg := range split { - if j == 0 { - // keep the left padding of the wrapped line - content := LineAlign(strings.TrimRight(seg, " "), lineWidth-padLen, wrapOpts.align) - lines = append(lines, padStr+content) - } else { - content := LineAlign(strings.TrimSpace(seg), lineWidth-padLen, wrapOpts.align) - lines = append(lines, padStr+content) - } - nbLine++ - } - } - - return strings.Join(lines, "\n"), nbLine -} - -// WrapLeftPadded wrap a text for a given line size with a left padding. -// Handle properly terminal color escape code -func WrapLeftPadded(text string, lineWidth int, leftPad int) (string, int) { - return Wrap(text, lineWidth, WrapPadded(leftPad)) -} - -// WrapWithPad wrap a text for a given line size with a custom left padding -// Handle properly terminal color escape code -func WrapWithPad(text string, lineWidth int, pad string) (string, int) { - return Wrap(text, lineWidth, WrapPad(pad)) -} - -// WrapWithPad wrap a text for a given line size with a custom left padding -// This function also align the result depending on the requested alignment. -// Handle properly terminal color escape code -func WrapWithPadAlign(text string, lineWidth int, pad string, align Alignment) (string, int) { - return Wrap(text, lineWidth, WrapPad(pad), WrapAlign(align)) -} - -// WrapWithPadIndent wrap a text for a given line size with a custom left padding -// and a first line indent. The padding is not effective on the first line, indent -// is used instead, which allow to implement indents and outdents. -// Handle properly terminal color escape code -func WrapWithPadIndent(text string, lineWidth int, indent string, pad string) (string, int) { - return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad)) -} - -// WrapWithPadIndentAlign wrap a text for a given line size with a custom left padding -// and a first line indent. The padding is not effective on the first line, indent -// is used instead, which allow to implement indents and outdents. -// This function also align the result depending on the requested alignment. -// Handle properly terminal color escape code -func WrapWithPadIndentAlign(text string, lineWidth int, indent string, pad string, align Alignment) (string, int) { - return Wrap(text, lineWidth, WrapIndent(indent), WrapPad(pad), WrapAlign(align)) -} - -// Break a line into several lines so that each line consumes at most -// 'textWidth' cells. Lines break at groups of white spaces and multibyte -// chars. Nothing is removed from the original text so that it behaves like a -// softwrap. -// -// Required: The line shall not contain '\n' -// -// WRAPPING ALGORITHM: The line is broken into non-breakable chunks, then line -// breaks ("\n") are inserted between these groups so that the total length -// between breaks does not exceed the required width. Words that are longer than -// the textWidth are broken into pieces no longer than textWidth. -func softwrapLine(line string, textWidth int) string { - escaped, escapes := ExtractTermEscapes(line) - - chunks := segmentLine(escaped) - // Reverse the chunk array so we can use it as a stack. - for i, j := 0, len(chunks)-1; i < j; i, j = i+1, j-1 { - chunks[i], chunks[j] = chunks[j], chunks[i] - } - - // for readability, minimal implementation of a stack: - - pop := func() string { - result := chunks[len(chunks)-1] - chunks = chunks[:len(chunks)-1] - return result - } - - push := func(chunk string) { - chunks = append(chunks, chunk) - } - - peek := func() string { - return chunks[len(chunks)-1] - } - - empty := func() bool { - return len(chunks) == 0 - } - - var out strings.Builder - - // helper to write in the output while interleaving the escape - // sequence at the correct places. - // note: the final algorithm will add additional line break in the original - // text. Those line break are *not* fed to this helper so the positions don't - // need to be offset, which make the whole thing much easier. - currPos := 0 - currItem := 0 - outputString := func(s string) { - for _, r := range s { - for currItem < len(escapes) && currPos == escapes[currItem].Pos { - out.WriteString(escapes[currItem].Item) - currItem++ - } - out.WriteRune(r) - currPos++ - } - } - - width := 0 - - for !empty() { - wl := Len(peek()) - - if width+wl <= textWidth { - // the chunk fit in the available space - outputString(pop()) - width += wl - if width == textWidth && !empty() { - // only add line break when there is more chunk to come - out.WriteRune('\n') - width = 0 - } - } else if wl > textWidth { - // words too long for a full line are split to fill the remaining space. - // But if the long words is the first non-space word in the middle of the - // line, preceding spaces shall not be counted in word splitting. - splitWidth := textWidth - width - if strings.HasSuffix(out.String(), "\n"+strings.Repeat(" ", width)) { - splitWidth += width - } - left, right := splitWord(pop(), splitWidth) - // remainder is pushed back to the stack for next round - push(right) - outputString(left) - out.WriteRune('\n') - width = 0 - } else { - // normal line overflow, we add a line break and try again - out.WriteRune('\n') - width = 0 - } - } - - // Don't forget the trailing escapes, if any. - for currItem < len(escapes) && currPos >= escapes[currItem].Pos { - out.WriteString(escapes[currItem].Item) - currItem++ - } - - return out.String() -} - -// Segment a line into chunks, where each chunk consists of chars with the same -// type and is not breakable. -func segmentLine(s string) []string { - var chunks []string - - var word string - wordType := none - flushWord := func() { - chunks = append(chunks, word) - word = "" - wordType = none - } - - for _, r := range s { - // A WIDE_CHAR itself constitutes a chunk. - thisType := runeType(r) - if thisType == wideChar { - if wordType != none { - flushWord() - } - chunks = append(chunks, string(r)) - continue - } - // Other type of chunks starts with a char of that type, and ends with a - // char with different type or end of string. - if thisType != wordType { - if wordType != none { - flushWord() - } - word = string(r) - wordType = thisType - } else { - word += string(r) - } - } - if word != "" { - flushWord() - } - - return chunks -} - -type RuneType int - -// Rune categories -// -// These categories are so defined that each category forms a non-breakable -// chunk. It IS NOT the same as unicode code point categories. -const ( - none RuneType = iota - wideChar - invisible - shortUnicode - space - visibleAscii -) - -// Determine the category of a rune. -func runeType(r rune) RuneType { - rw := runewidth.RuneWidth(r) - if rw > 1 { - return wideChar - } else if rw == 0 { - return invisible - } else if r > 127 { - return shortUnicode - } else if r == ' ' { - return space - } else { - return visibleAscii - } -} - -// splitWord split a word at the given length, while ignoring the terminal escape sequences -func splitWord(word string, length int) (string, string) { - runes := []rune(word) - var result []rune - added := 0 - escape := false - - if length == 0 { - return "", word - } - - for _, r := range runes { - if r == '\x1b' { - escape = true - } - - width := runewidth.RuneWidth(r) - if width+added > length { - // wide character made the length overflow - break - } - - result = append(result, r) - - if !escape { - added += width - if added >= length { - break - } - } - - if r == 'm' { - escape = false - } - } - - leftover := runes[len(result):] - - return string(result), string(leftover) -} |