aboutsummaryrefslogtreecommitdiff
path: root/vendor/maunium.net/go/tview/util.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/tview/util.go')
-rw-r--r--vendor/maunium.net/go/tview/util.go456
1 files changed, 456 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/tview/util.go b/vendor/maunium.net/go/tview/util.go
new file mode 100644
index 0000000..f49c35f
--- /dev/null
+++ b/vendor/maunium.net/go/tview/util.go
@@ -0,0 +1,456 @@
+package tview
+
+import (
+ "math"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode"
+
+ "maunium.net/go/tcell"
+ runewidth "github.com/mattn/go-runewidth"
+)
+
+// Text alignment within a box.
+const (
+ AlignLeft = iota
+ AlignCenter
+ AlignRight
+)
+
+// Semigraphical runes.
+const (
+ GraphicsHoriBar = '\u2500'
+ GraphicsVertBar = '\u2502'
+ GraphicsTopLeftCorner = '\u250c'
+ GraphicsTopRightCorner = '\u2510'
+ GraphicsBottomLeftCorner = '\u2514'
+ GraphicsBottomRightCorner = '\u2518'
+ GraphicsLeftT = '\u251c'
+ GraphicsRightT = '\u2524'
+ GraphicsTopT = '\u252c'
+ GraphicsBottomT = '\u2534'
+ GraphicsCross = '\u253c'
+ GraphicsDbVertBar = '\u2550'
+ GraphicsDbHorBar = '\u2551'
+ GraphicsDbTopLeftCorner = '\u2554'
+ GraphicsDbTopRightCorner = '\u2557'
+ GraphicsDbBottomRightCorner = '\u255d'
+ GraphicsDbBottomLeftCorner = '\u255a'
+ GraphicsEllipsis = '\u2026'
+)
+
+// joints maps combinations of two graphical runes to the rune that results
+// when joining the two in the same screen cell. The keys of this map are
+// two-rune strings where the value of the first rune is lower than the value
+// of the second rune. Identical runes are not contained.
+var joints = map[string]rune{
+ "\u2500\u2502": GraphicsCross,
+ "\u2500\u250c": GraphicsTopT,
+ "\u2500\u2510": GraphicsTopT,
+ "\u2500\u2514": GraphicsBottomT,
+ "\u2500\u2518": GraphicsBottomT,
+ "\u2500\u251c": GraphicsCross,
+ "\u2500\u2524": GraphicsCross,
+ "\u2500\u252c": GraphicsTopT,
+ "\u2500\u2534": GraphicsBottomT,
+ "\u2500\u253c": GraphicsCross,
+ "\u2502\u250c": GraphicsLeftT,
+ "\u2502\u2510": GraphicsRightT,
+ "\u2502\u2514": GraphicsLeftT,
+ "\u2502\u2518": GraphicsRightT,
+ "\u2502\u251c": GraphicsLeftT,
+ "\u2502\u2524": GraphicsRightT,
+ "\u2502\u252c": GraphicsCross,
+ "\u2502\u2534": GraphicsCross,
+ "\u2502\u253c": GraphicsCross,
+ "\u250c\u2510": GraphicsTopT,
+ "\u250c\u2514": GraphicsLeftT,
+ "\u250c\u2518": GraphicsCross,
+ "\u250c\u251c": GraphicsLeftT,
+ "\u250c\u2524": GraphicsCross,
+ "\u250c\u252c": GraphicsTopT,
+ "\u250c\u2534": GraphicsCross,
+ "\u250c\u253c": GraphicsCross,
+ "\u2510\u2514": GraphicsCross,
+ "\u2510\u2518": GraphicsRightT,
+ "\u2510\u251c": GraphicsCross,
+ "\u2510\u2524": GraphicsRightT,
+ "\u2510\u252c": GraphicsTopT,
+ "\u2510\u2534": GraphicsCross,
+ "\u2510\u253c": GraphicsCross,
+ "\u2514\u2518": GraphicsBottomT,
+ "\u2514\u251c": GraphicsLeftT,
+ "\u2514\u2524": GraphicsCross,
+ "\u2514\u252c": GraphicsCross,
+ "\u2514\u2534": GraphicsBottomT,
+ "\u2514\u253c": GraphicsCross,
+ "\u2518\u251c": GraphicsCross,
+ "\u2518\u2524": GraphicsRightT,
+ "\u2518\u252c": GraphicsCross,
+ "\u2518\u2534": GraphicsBottomT,
+ "\u2518\u253c": GraphicsCross,
+ "\u251c\u2524": GraphicsCross,
+ "\u251c\u252c": GraphicsCross,
+ "\u251c\u2534": GraphicsCross,
+ "\u251c\u253c": GraphicsCross,
+ "\u2524\u252c": GraphicsCross,
+ "\u2524\u2534": GraphicsCross,
+ "\u2524\u253c": GraphicsCross,
+ "\u252c\u2534": GraphicsCross,
+ "\u252c\u253c": GraphicsCross,
+ "\u2534\u253c": GraphicsCross,
+}
+
+// Common regular expressions.
+var (
+ colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
+ regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
+ escapePattern = regexp.MustCompile(`\[("[a-zA-Z0-9_,;: \-\.]*"|[a-zA-Z]+|#[0-9a-zA-Z]{6})\[(\[*)\]`)
+ boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
+ spacePattern = regexp.MustCompile(`\s+`)
+)
+
+// Predefined InputField acceptance functions.
+var (
+ // InputFieldInteger accepts integers.
+ InputFieldInteger func(text string, ch rune) bool
+
+ // InputFieldFloat accepts floating-point numbers.
+ InputFieldFloat func(text string, ch rune) bool
+
+ // InputFieldMaxLength returns an input field accept handler which accepts
+ // input strings up to a given length. Use it like this:
+ //
+ // inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters.
+ InputFieldMaxLength func(maxLength int) func(text string, ch rune) bool
+)
+
+// Package initialization.
+func init() {
+ // Initialize the predefined input field handlers.
+ InputFieldInteger = func(text string, ch rune) bool {
+ if text == "-" {
+ return true
+ }
+ _, err := strconv.Atoi(text)
+ return err == nil
+ }
+ InputFieldFloat = func(text string, ch rune) bool {
+ if text == "-" || text == "." || text == "-." {
+ return true
+ }
+ _, err := strconv.ParseFloat(text, 64)
+ return err == nil
+ }
+ InputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool {
+ return func(text string, ch rune) bool {
+ return len([]rune(text)) <= maxLength
+ }
+ }
+}
+
+// Print prints text onto the screen into the given box at (x,y,maxWidth,1),
+// not exceeding that box. "align" is one of AlignLeft, AlignCenter, or
+// AlignRight. The screen's background color will not be changed.
+//
+// You can change the text color mid-text by inserting a color tag. See the
+// package description for details.
+//
+// Returns the number of actual runes printed (not including color tags) and the
+// actual width used for the printed runes.
+func Print(screen tcell.Screen, text string, x, y, maxWidth, align int, color tcell.Color) (int, int) {
+ if maxWidth < 0 {
+ return 0, 0
+ }
+
+ // Get positions of color and escape tags. Remove them from original string.
+ colorIndices := colorPattern.FindAllStringIndex(text, -1)
+ colors := colorPattern.FindAllStringSubmatch(text, -1)
+ escapeIndices := escapePattern.FindAllStringIndex(text, -1)
+ strippedText := escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]")
+
+ // We deal with runes, not with bytes.
+ runes := []rune(strippedText)
+
+ // This helper function takes positions for a substring of "runes" and a start
+ // color and returns the substring with the original tags and the new start
+ // color.
+ substring := func(from, to int, color tcell.Color) (string, tcell.Color) {
+ var colorPos, escapePos, runePos, startPos int
+ for pos := range text {
+ // Handle color tags.
+ if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
+ if pos == colorIndices[colorPos][1]-1 {
+ if runePos <= from {
+ color = tcell.GetColor(colors[colorPos][1])
+ }
+ colorPos++
+ }
+ continue
+ }
+
+ // Handle escape tags.
+ if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
+ if pos == escapeIndices[escapePos][1]-1 {
+ escapePos++
+ } else if pos == escapeIndices[escapePos][1]-2 {
+ continue
+ }
+ }
+
+ // Check boundaries.
+ if runePos == from {
+ startPos = pos
+ } else if runePos >= to {
+ return text[startPos:pos], color
+ }
+
+ runePos++
+ }
+
+ return text[startPos:], color
+ }
+
+ // We want to reduce everything to AlignLeft.
+ if align == AlignRight {
+ width := 0
+ start := len(runes)
+ for index := start - 1; index >= 0; index-- {
+ w := runewidth.RuneWidth(runes[index])
+ if width+w > maxWidth {
+ break
+ }
+ width += w
+ start = index
+ }
+ text, color = substring(start, len(runes), color)
+ return Print(screen, text, x+maxWidth-width, y, width, AlignLeft, color)
+ } else if align == AlignCenter {
+ width := runewidth.StringWidth(strippedText)
+ if width == maxWidth {
+ // Use the exact space.
+ return Print(screen, text, x, y, maxWidth, AlignLeft, color)
+ } else if width < maxWidth {
+ // We have more space than we need.
+ half := (maxWidth - width) / 2
+ return Print(screen, text, x+half, y, maxWidth-half, AlignLeft, color)
+ } else {
+ // Chop off runes until we have a perfect fit.
+ var choppedLeft, choppedRight, leftIndex, rightIndex int
+ rightIndex = len(runes) - 1
+ for rightIndex > leftIndex && width-choppedLeft-choppedRight > maxWidth {
+ leftWidth := runewidth.RuneWidth(runes[leftIndex])
+ rightWidth := runewidth.RuneWidth(runes[rightIndex])
+ if choppedLeft < choppedRight {
+ choppedLeft += leftWidth
+ leftIndex++
+ } else {
+ choppedRight += rightWidth
+ rightIndex--
+ }
+ }
+ text, color = substring(leftIndex, rightIndex, color)
+ return Print(screen, text, x, y, maxWidth, AlignLeft, color)
+ }
+ }
+
+ // Draw text.
+ drawn := 0
+ drawnWidth := 0
+ var colorPos, escapePos int
+ for pos, ch := range text {
+ // Handle color tags.
+ if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
+ if pos == colorIndices[colorPos][1]-1 {
+ color = tcell.GetColor(colors[colorPos][1])
+ colorPos++
+ }
+ continue
+ }
+
+ // Handle escape tags.
+ if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
+ if pos == escapeIndices[escapePos][1]-1 {
+ escapePos++
+ } else if pos == escapeIndices[escapePos][1]-2 {
+ continue
+ }
+ }
+
+ // Check if we have enough space for this rune.
+ chWidth := runewidth.RuneWidth(ch)
+ if drawnWidth+chWidth > maxWidth {
+ break
+ }
+ finalX := x + drawnWidth
+
+ // Print the rune.
+ _, _, style, _ := screen.GetContent(finalX, y)
+ style = style.Foreground(color)
+ for offset := 0; offset < chWidth; offset++ {
+ // To avoid undesired effects, we place the same character in all cells.
+ screen.SetContent(finalX+offset, y, ch, nil, style)
+ }
+
+ drawn++
+ drawnWidth += chWidth
+ }
+
+ return drawn, drawnWidth
+}
+
+// PrintSimple prints white text to the screen at the given position.
+func PrintSimple(screen tcell.Screen, text string, x, y int) {
+ Print(screen, text, x, y, math.MaxInt32, AlignLeft, Styles.PrimaryTextColor)
+}
+
+// StringWidth returns the width of the given string needed to print it on
+// screen. The text may contain color tags which are not counted.
+func StringWidth(text string) int {
+ return runewidth.StringWidth(escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]"))
+}
+
+// WordWrap splits a text such that each resulting line does not exceed the
+// given screen width. Possible split points are after any punctuation or
+// whitespace. Whitespace after split points will be dropped.
+//
+// This function considers color tags to have no width.
+//
+// Text is always split at newline characters ('\n').
+func WordWrap(text string, width int) (lines []string) {
+ // Strip color tags.
+ strippedText := escapePattern.ReplaceAllString(colorPattern.ReplaceAllString(text, ""), "[$1$2]")
+
+ // Keep track of color tags and escape patterns so we can restore the original
+ // indices.
+ colorTagIndices := colorPattern.FindAllStringIndex(text, -1)
+ escapeIndices := escapePattern.FindAllStringIndex(text, -1)
+
+ // Find candidate breakpoints.
+ breakPoints := boundaryPattern.FindAllStringIndex(strippedText, -1)
+
+ // This helper function adds a new line to the result slice. The provided
+ // positions are in stripped index space.
+ addLine := func(from, to int) {
+ // Shift indices back to original index space.
+ var colorTagIndex, escapeIndex int
+ for colorTagIndex < len(colorTagIndices) && to >= colorTagIndices[colorTagIndex][0] ||
+ escapeIndex < len(escapeIndices) && to >= escapeIndices[escapeIndex][0] {
+ past := 0
+ if colorTagIndex < len(colorTagIndices) {
+ tagWidth := colorTagIndices[colorTagIndex][1] - colorTagIndices[colorTagIndex][0]
+ if colorTagIndices[colorTagIndex][0] < from {
+ from += tagWidth
+ to += tagWidth
+ colorTagIndex++
+ } else if colorTagIndices[colorTagIndex][0] < to {
+ to += tagWidth
+ colorTagIndex++
+ } else {
+ past++
+ }
+ } else {
+ past++
+ }
+ if escapeIndex < len(escapeIndices) {
+ tagWidth := escapeIndices[escapeIndex][1] - escapeIndices[escapeIndex][0]
+ if escapeIndices[escapeIndex][0] < from {
+ from += tagWidth
+ to += tagWidth
+ escapeIndex++
+ } else if escapeIndices[escapeIndex][0] < to {
+ to += tagWidth
+ escapeIndex++
+ } else {
+ past++
+ }
+ } else {
+ past++
+ }
+ if past == 2 {
+ break // All other indices are beyond the requested string.
+ }
+ }
+ lines = append(lines, text[from:to])
+ }
+
+ // Determine final breakpoints.
+ var start, lastEnd, newStart, breakPoint int
+ for {
+ // What's our candidate string?
+ var candidate string
+ if breakPoint < len(breakPoints) {
+ candidate = text[start:breakPoints[breakPoint][1]]
+ } else {
+ candidate = text[start:]
+ }
+ candidate = strings.TrimRightFunc(candidate, unicode.IsSpace)
+
+ if runewidth.StringWidth(candidate) >= width {
+ // We're past the available width.
+ if lastEnd > start {
+ // Use the previous candidate.
+ addLine(start, lastEnd)
+ start = newStart
+ } else {
+ // We have no previous candidate. Make a hard break.
+ var lineWidth int
+ for index, ch := range text {
+ if index < start {
+ continue
+ }
+ chWidth := runewidth.RuneWidth(ch)
+ if lineWidth > 0 && lineWidth+chWidth >= width {
+ addLine(start, index)
+ start = index
+ break
+ }
+ lineWidth += chWidth
+ }
+ }
+ } else {
+ // We haven't hit the right border yet.
+ if breakPoint >= len(breakPoints) {
+ // It's the last line. We're done.
+ if len(candidate) > 0 {
+ addLine(start, len(strippedText))
+ }
+ break
+ } else {
+ // We have a new candidate.
+ lastEnd = start + len(candidate)
+ newStart = breakPoints[breakPoint][1]
+ breakPoint++
+ }
+ }
+ }
+
+ return
+}
+
+// PrintJoinedBorder prints a border graphics rune into the screen at the given
+// position with the given color, joining it with any existing border graphics
+// rune. Background colors are preserved. At this point, only regular single
+// line borders are supported.
+func PrintJoinedBorder(screen tcell.Screen, x, y int, ch rune, color tcell.Color) {
+ previous, _, style, _ := screen.GetContent(x, y)
+ style = style.Foreground(color)
+
+ // What's the resulting rune?
+ var result rune
+ if ch == previous {
+ result = ch
+ } else {
+ if ch < previous {
+ previous, ch = ch, previous
+ }
+ result = joints[string(previous)+string(ch)]
+ }
+ if result == 0 {
+ result = ch
+ }
+
+ // We only print something if we have something.
+ screen.SetContent(x, y, result, nil, style)
+}