From 64fa922ec013079f8f0c90fc9e93c56db3611d30 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 22 Apr 2018 21:25:06 +0300 Subject: Switch to dep --- vendor/maunium.net/go/tview/util.go | 456 ++++++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 vendor/maunium.net/go/tview/util.go (limited to 'vendor/maunium.net/go/tview/util.go') 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) +} -- cgit v1.2.3