mirror of
https://github.com/rivo/tview.git
synced 2025-04-28 13:48:53 +08:00
Rewrote TextView's reindex() and Draw() to introduce text alignment and
word wrapping
This commit is contained in:
parent
38d663c267
commit
1e78c506cb
@ -62,6 +62,8 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio
|
|||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
|
- v0.5 (2018-01-13)
|
||||||
|
- `TextView` now has word wrapping and text alignment
|
||||||
- v0.4 (2018-01-12)
|
- v0.4 (2018-01-12)
|
||||||
- `TextView` now accepts color tags with any W3C color (including RGB hex values).
|
- `TextView` now accepts color tags with any W3C color (including RGB hex values).
|
||||||
- Support for wide unicode characters.
|
- Support for wide unicode characters.
|
||||||
|
@ -24,6 +24,7 @@ func main() {
|
|||||||
textView := tview.NewTextView().
|
textView := tview.NewTextView().
|
||||||
SetDynamicColors(true).
|
SetDynamicColors(true).
|
||||||
SetRegions(true).
|
SetRegions(true).
|
||||||
|
SetWordWrap(true).
|
||||||
SetChangedFunc(func() {
|
SetChangedFunc(func() {
|
||||||
app.Draw()
|
app.Draw()
|
||||||
})
|
})
|
||||||
|
330
textview.go
330
textview.go
@ -2,7 +2,6 @@ package tview
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -13,20 +12,24 @@ import (
|
|||||||
|
|
||||||
// Regular expressions commonly used throughout the TextView class.
|
// Regular expressions commonly used throughout the TextView class.
|
||||||
var (
|
var (
|
||||||
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
|
colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
|
||||||
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
regionPattern = regexp.MustCompile(`\["([a-zA-Z0-9_,;: \-\.]*)"\]`)
|
||||||
|
boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
|
||||||
|
spacePattern = regexp.MustCompile(`\s+`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TabSize is the number of spaces to be drawn for a tab character.
|
// TabSize is the number of spaces with which a tab character will be replaced.
|
||||||
var TabSize = 4
|
var TabSize = 4
|
||||||
|
|
||||||
// textViewIndex contains information about each line displayed in the text
|
// textViewIndex contains information about each line displayed in the text
|
||||||
// view.
|
// view.
|
||||||
type textViewIndex struct {
|
type textViewIndex struct {
|
||||||
Line int // The index into the "buffer" variable.
|
Line int // The index into the "buffer" variable.
|
||||||
Pos int // The index into the "buffer" string.
|
Pos int // The index into the "buffer" string (byte position).
|
||||||
Color tcell.Color // The starting color.
|
NextPos int // The (byte) index of the next character in this buffer line.
|
||||||
Region string // The starting region ID.
|
Width int // The screen width of this line.
|
||||||
|
Color tcell.Color // The starting color.
|
||||||
|
Region string // The starting region ID.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextView is a box which displays text. It implements the io.Writer interface
|
// TextView is a box which displays text. It implements the io.Writer interface
|
||||||
@ -103,6 +106,9 @@ type TextView struct {
|
|||||||
// to be re-indexed.
|
// to be re-indexed.
|
||||||
index []*textViewIndex
|
index []*textViewIndex
|
||||||
|
|
||||||
|
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
|
||||||
|
align int
|
||||||
|
|
||||||
// Indices into the "index" slice which correspond to the first line of the
|
// Indices into the "index" slice which correspond to the first line of the
|
||||||
// first highlight and the last line of the last highlight. This is calculated
|
// first highlight and the last line of the last highlight. This is calculated
|
||||||
// during re-indexing. Set to -1 if there is no current highlight.
|
// during re-indexing. Set to -1 if there is no current highlight.
|
||||||
@ -111,10 +117,10 @@ type TextView struct {
|
|||||||
// A set of region IDs that are currently highlighted.
|
// A set of region IDs that are currently highlighted.
|
||||||
highlights map[string]struct{}
|
highlights map[string]struct{}
|
||||||
|
|
||||||
// The display width for which the index is created.
|
// The last width for which the current table is drawn.
|
||||||
indexWidth int
|
lastWidth int
|
||||||
|
|
||||||
// The width of the longest line in the index (not the buffer).
|
// The screen width of the longest line in the index (not the buffer).
|
||||||
longestLine int
|
longestLine int
|
||||||
|
|
||||||
// The index of the first line shown in the text view.
|
// The index of the first line shown in the text view.
|
||||||
@ -138,6 +144,10 @@ type TextView struct {
|
|||||||
// width are discarded.
|
// width are discarded.
|
||||||
wrap bool
|
wrap bool
|
||||||
|
|
||||||
|
// If set to true and if wrap is also true, lines are split at spaces or
|
||||||
|
// after punctuation characters.
|
||||||
|
wordWrap bool
|
||||||
|
|
||||||
// The (starting) color of the text.
|
// The (starting) color of the text.
|
||||||
textColor tcell.Color
|
textColor tcell.Color
|
||||||
|
|
||||||
@ -172,6 +182,7 @@ func NewTextView() *TextView {
|
|||||||
highlights: make(map[string]struct{}),
|
highlights: make(map[string]struct{}),
|
||||||
lineOffset: -1,
|
lineOffset: -1,
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
|
align: AlignLeft,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
textColor: Styles.PrimaryTextColor,
|
textColor: Styles.PrimaryTextColor,
|
||||||
dynamicColors: false,
|
dynamicColors: false,
|
||||||
@ -199,6 +210,29 @@ func (t *TextView) SetWrap(wrap bool) *TextView {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetWordWrap sets the flag that, if true and if the "wrap" flag is also true
|
||||||
|
// (see SetWrap()), wraps the line at spaces or after punctuation marks. Note
|
||||||
|
// that trailing spaces will not be printed.
|
||||||
|
//
|
||||||
|
// This flag is ignored if the "wrap" flag is false.
|
||||||
|
func (t *TextView) SetWordWrap(wrapOnWords bool) *TextView {
|
||||||
|
if t.wordWrap != wrapOnWords {
|
||||||
|
t.index = nil
|
||||||
|
}
|
||||||
|
t.wordWrap = wrapOnWords
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTextAlign sets the text alignment within the text view. This must be
|
||||||
|
// either AlignLeft, AlignCenter, or AlignRight.
|
||||||
|
func (t *TextView) SetTextAlign(align int) *TextView {
|
||||||
|
if t.align != align {
|
||||||
|
t.index = nil
|
||||||
|
}
|
||||||
|
t.align = align
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// SetTextColor sets the initial color of the text (which can be changed
|
// SetTextColor sets the initial color of the text (which can be changed
|
||||||
// dynamically by sending color strings in square brackets to the text view if
|
// dynamically by sending color strings in square brackets to the text view if
|
||||||
// dynamic colors are enabled).
|
// dynamic colors are enabled).
|
||||||
@ -220,6 +254,9 @@ func (t *TextView) SetDynamicColors(dynamic bool) *TextView {
|
|||||||
// SetRegions sets the flag that allows to define regions in the text. See class
|
// SetRegions sets the flag that allows to define regions in the text. See class
|
||||||
// description for details.
|
// description for details.
|
||||||
func (t *TextView) SetRegions(regions bool) *TextView {
|
func (t *TextView) SetRegions(regions bool) *TextView {
|
||||||
|
if t.regions != regions {
|
||||||
|
t.index = nil
|
||||||
|
}
|
||||||
t.regions = regions
|
t.regions = regions
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
@ -273,6 +310,7 @@ func (t *TextView) Highlight(regionIDs ...string) *TextView {
|
|||||||
}
|
}
|
||||||
t.highlights[id] = struct{}{}
|
t.highlights[id] = struct{}{}
|
||||||
}
|
}
|
||||||
|
t.index = nil
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +412,9 @@ func (t *TextView) GetRegionText(regionID string) string {
|
|||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write lets us implement the io.Writer interface.
|
// Write lets us implement the io.Writer interface. Tab characters will be
|
||||||
|
// replaced with TabSize space characters. A "\n" or "\r\n" will be interpreted
|
||||||
|
// as a new line.
|
||||||
func (t *TextView) Write(p []byte) (n int, err error) {
|
func (t *TextView) Write(p []byte) (n int, err error) {
|
||||||
// Notify at the end.
|
// Notify at the end.
|
||||||
if t.changed != nil {
|
if t.changed != nil {
|
||||||
@ -396,7 +436,7 @@ func (t *TextView) Write(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
// If we have a trailing open dynamic color, exclude it.
|
// If we have a trailing open dynamic color, exclude it.
|
||||||
if t.dynamicColors {
|
if t.dynamicColors {
|
||||||
openColor := regexp.MustCompile(`\[[a-z]+$`)
|
openColor := regexp.MustCompile(`\[([a-zA-Z]*|#[0-9a-zA-Z]*)$`)
|
||||||
location := openColor.FindIndex(newBytes)
|
location := openColor.FindIndex(newBytes)
|
||||||
if location != nil {
|
if location != nil {
|
||||||
t.recentBytes = newBytes[location[0]:]
|
t.recentBytes = newBytes[location[0]:]
|
||||||
@ -404,8 +444,19 @@ func (t *TextView) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a trailing open region, exclude it.
|
||||||
|
if t.regions {
|
||||||
|
openRegion := regexp.MustCompile(`\["[a-zA-Z0-9_,;: \-\.]*"?$`)
|
||||||
|
location := openRegion.FindIndex(newBytes)
|
||||||
|
if location != nil {
|
||||||
|
t.recentBytes = newBytes[location[0]:]
|
||||||
|
newBytes = newBytes[:location[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Transform the new bytes into strings.
|
// Transform the new bytes into strings.
|
||||||
newLine := regexp.MustCompile(`\r?\n`)
|
newLine := regexp.MustCompile(`\r?\n`)
|
||||||
|
newBytes = bytes.Replace(newBytes, []byte{'\t'}, bytes.Repeat([]byte{' '}, TabSize), -1)
|
||||||
for index, line := range newLine.Split(string(newBytes), -1) {
|
for index, line := range newLine.Split(string(newBytes), -1) {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
if len(t.buffer) == 0 {
|
if len(t.buffer) == 0 {
|
||||||
@ -429,23 +480,25 @@ func (t *TextView) Write(p []byte) (n int, err error) {
|
|||||||
// into the buffer from which on we will print text. It will also contain the
|
// into the buffer from which on we will print text. It will also contain the
|
||||||
// color with which the line starts.
|
// color with which the line starts.
|
||||||
func (t *TextView) reindexBuffer(width int) {
|
func (t *TextView) reindexBuffer(width int) {
|
||||||
if t.index != nil && width == t.indexWidth {
|
if t.index != nil {
|
||||||
return // Nothing has changed. We can still use the current index.
|
return // Nothing has changed. We can still use the current index.
|
||||||
}
|
}
|
||||||
t.index = nil
|
t.index = nil
|
||||||
t.fromHighlight, t.toHighlight = -1, -1
|
t.fromHighlight, t.toHighlight = -1, -1
|
||||||
|
|
||||||
var (
|
// If there's no space, there's no index.
|
||||||
regionID string
|
if width < 1 {
|
||||||
highlighted bool
|
return
|
||||||
)
|
|
||||||
t.longestLine = 0
|
|
||||||
color := t.textColor
|
|
||||||
if !t.wrap {
|
|
||||||
width = math.MaxInt32
|
|
||||||
}
|
}
|
||||||
for index, str := range t.buffer {
|
|
||||||
// Find all color tags in this line.
|
// Initial states.
|
||||||
|
regionID := ""
|
||||||
|
var highlighted bool
|
||||||
|
color := t.textColor
|
||||||
|
|
||||||
|
// Go through each line in the buffer.
|
||||||
|
for bufferIndex, str := range t.buffer {
|
||||||
|
// Find all color tags in this line. Then remove them.
|
||||||
var (
|
var (
|
||||||
colorTagIndices [][]int
|
colorTagIndices [][]int
|
||||||
colorTags [][]string
|
colorTags [][]string
|
||||||
@ -453,9 +506,10 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
if t.dynamicColors {
|
if t.dynamicColors {
|
||||||
colorTagIndices = colorPattern.FindAllStringIndex(str, -1)
|
colorTagIndices = colorPattern.FindAllStringIndex(str, -1)
|
||||||
colorTags = colorPattern.FindAllStringSubmatch(str, -1)
|
colorTags = colorPattern.FindAllStringSubmatch(str, -1)
|
||||||
|
str = colorPattern.ReplaceAllString(str, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all regions in this line.
|
// Find all regions in this line. Then remove them.
|
||||||
var (
|
var (
|
||||||
regionIndices [][]int
|
regionIndices [][]int
|
||||||
regions [][]string
|
regions [][]string
|
||||||
@ -463,89 +517,109 @@ func (t *TextView) reindexBuffer(width int) {
|
|||||||
if t.regions {
|
if t.regions {
|
||||||
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
regionIndices = regionPattern.FindAllStringIndex(str, -1)
|
||||||
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
regions = regionPattern.FindAllStringSubmatch(str, -1)
|
||||||
|
str = regionPattern.ReplaceAllString(str, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We also keep a reference to empty lines.
|
// Split the line if required.
|
||||||
if len(str) == 0 {
|
var splitLines []string
|
||||||
t.index = append(t.index, &textViewIndex{
|
if t.wrap && len(str) > 0 {
|
||||||
Line: index,
|
for len(str) > 0 {
|
||||||
Pos: 0,
|
extract := runewidth.Truncate(str, width, "")
|
||||||
|
if t.wordWrap && len(extract) < len(str) {
|
||||||
|
// Add any spaces from the next line.
|
||||||
|
if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 {
|
||||||
|
extract = str[:len(extract)+spaces[1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can we split before the mandatory end?
|
||||||
|
matches := boundaryPattern.FindAllStringIndex(extract, -1)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
// Yes. Let's split there.
|
||||||
|
extract = extract[:matches[len(matches)-1][1]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
splitLines = append(splitLines, extract)
|
||||||
|
str = str[len(extract):]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No need to split the line.
|
||||||
|
splitLines = []string{str}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create index from split lines.
|
||||||
|
var startPos, originalPos, colorPos, regionPos int
|
||||||
|
for _, splitLine := range splitLines {
|
||||||
|
line := &textViewIndex{
|
||||||
|
Line: bufferIndex,
|
||||||
|
Pos: originalPos,
|
||||||
Color: color,
|
Color: color,
|
||||||
Region: regionID,
|
Region: regionID,
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Break down the line.
|
|
||||||
var currentTag, currentRegion, currentWidth int
|
|
||||||
for pos, ch := range str {
|
|
||||||
// Skip any color tags.
|
|
||||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
|
||||||
if pos == colorTagIndices[currentTag][1]-1 {
|
|
||||||
color = tcell.GetColor(colorTags[currentTag][1])
|
|
||||||
currentTag++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check regions.
|
// Shift original position with tags.
|
||||||
if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
|
lineWidth := 0
|
||||||
if pos == regionIndices[currentRegion][1]-1 {
|
for index, ch := range splitLine {
|
||||||
// We're done with this region.
|
// Get the width of the current rune.
|
||||||
regionID = regions[currentRegion][1]
|
lineWidth += runewidth.RuneWidth(ch)
|
||||||
|
|
||||||
// Is this region highlighted?
|
// Process color tags.
|
||||||
|
for colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+index {
|
||||||
|
originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||||
|
color = tcell.GetColor(colorTags[colorPos][1])
|
||||||
|
colorPos++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process region tags.
|
||||||
|
for regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+index {
|
||||||
|
originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||||
|
regionID = regions[regionPos][1]
|
||||||
_, highlighted = t.highlights[regionID]
|
_, highlighted = t.highlights[regionID]
|
||||||
|
|
||||||
currentRegion++
|
// Update highlight range.
|
||||||
}
|
if highlighted {
|
||||||
continue
|
line := len(t.index)
|
||||||
}
|
if t.fromHighlight < 0 {
|
||||||
|
t.fromHighlight, t.toHighlight = line, line
|
||||||
|
} else if line > t.toHighlight {
|
||||||
|
t.toHighlight = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the width of the current rune.
|
regionPos++
|
||||||
chWidth := runewidth.RuneWidth(ch)
|
|
||||||
if ch == '\t' {
|
|
||||||
chWidth = TabSize
|
|
||||||
}
|
|
||||||
if chWidth == 0 {
|
|
||||||
continue // Skip width-less runes.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this line.
|
|
||||||
if currentWidth == 0 {
|
|
||||||
t.index = append(t.index, &textViewIndex{
|
|
||||||
Line: index,
|
|
||||||
Pos: pos,
|
|
||||||
Color: color,
|
|
||||||
Region: regionID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update highlight range.
|
|
||||||
if highlighted {
|
|
||||||
line := len(t.index) - 1
|
|
||||||
if t.fromHighlight < 0 {
|
|
||||||
t.fromHighlight, t.toHighlight = line, line
|
|
||||||
} else if line > t.toHighlight {
|
|
||||||
t.toHighlight = line
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proceed.
|
// Advance to next line.
|
||||||
currentWidth += chWidth
|
startPos += len(splitLine)
|
||||||
|
originalPos += len(splitLine)
|
||||||
|
|
||||||
// Have we crossed the width?
|
// Append this line.
|
||||||
if t.wrap && currentWidth >= width {
|
line.NextPos = originalPos
|
||||||
currentWidth = 0
|
line.Width = lineWidth
|
||||||
}
|
t.index = append(t.index, line)
|
||||||
|
}
|
||||||
|
|
||||||
// Do we have a new maximum width?
|
// Word-wrapped lines may have trailing whitespace. Remove it.
|
||||||
if currentWidth > t.longestLine {
|
if t.wrap && t.wordWrap {
|
||||||
t.longestLine = currentWidth
|
for _, line := range t.index {
|
||||||
|
str := t.buffer[line.Line][line.Pos:line.NextPos]
|
||||||
|
spaces := spacePattern.FindAllStringIndex(str, -1)
|
||||||
|
if spaces != nil && spaces[len(spaces)-1][1] == len(str) {
|
||||||
|
oldNextPos := line.NextPos
|
||||||
|
line.NextPos -= spaces[len(spaces)-1][1] - spaces[len(spaces)-1][0]
|
||||||
|
line.Width -= runewidth.StringWidth(t.buffer[line.Line][line.NextPos:oldNextPos])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.indexWidth = width
|
// Calculate longest line.
|
||||||
|
t.longestLine = 0
|
||||||
|
for _, line := range t.index {
|
||||||
|
if line.Width > t.longestLine {
|
||||||
|
t.longestLine = line.Width
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws this primitive onto the screen.
|
// Draw draws this primitive onto the screen.
|
||||||
@ -558,9 +632,20 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
x, y, width, height := t.GetInnerRect()
|
x, y, width, height := t.GetInnerRect()
|
||||||
t.pageSize = height
|
t.pageSize = height
|
||||||
|
|
||||||
|
// If the width has changed, we need to reindex.
|
||||||
|
if width != t.lastWidth {
|
||||||
|
t.index = nil
|
||||||
|
}
|
||||||
|
t.lastWidth = width
|
||||||
|
|
||||||
// Re-index.
|
// Re-index.
|
||||||
t.reindexBuffer(width)
|
t.reindexBuffer(width)
|
||||||
|
|
||||||
|
// If we don't have an index, there's nothing to draw.
|
||||||
|
if t.index == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Move to highlighted regions.
|
// Move to highlighted regions.
|
||||||
if t.regions && t.scrollToHighlights && t.fromHighlight >= 0 {
|
if t.regions && t.scrollToHighlights && t.fromHighlight >= 0 {
|
||||||
// Do we fit the entire height?
|
// Do we fit the entire height?
|
||||||
@ -586,11 +671,32 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust column offset.
|
// Adjust column offset.
|
||||||
if t.columnOffset+width > t.longestLine {
|
if t.align == AlignLeft {
|
||||||
t.columnOffset = t.longestLine - width
|
if t.columnOffset+width > t.longestLine {
|
||||||
}
|
t.columnOffset = t.longestLine - width
|
||||||
if t.columnOffset < 0 {
|
}
|
||||||
t.columnOffset = 0
|
if t.columnOffset < 0 {
|
||||||
|
t.columnOffset = 0
|
||||||
|
}
|
||||||
|
} else if t.align == AlignRight {
|
||||||
|
if t.columnOffset-width < -t.longestLine {
|
||||||
|
t.columnOffset = width - t.longestLine
|
||||||
|
}
|
||||||
|
if t.columnOffset > 0 {
|
||||||
|
t.columnOffset = 0
|
||||||
|
}
|
||||||
|
} else { // AlignCenter.
|
||||||
|
half := (t.longestLine - width) / 2
|
||||||
|
if half > 0 {
|
||||||
|
if t.columnOffset > half {
|
||||||
|
t.columnOffset = half
|
||||||
|
}
|
||||||
|
if t.columnOffset < -half {
|
||||||
|
t.columnOffset = -half
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.columnOffset = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the buffer.
|
// Draw the buffer.
|
||||||
@ -602,7 +708,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
|
|
||||||
// Get the text for this line.
|
// Get the text for this line.
|
||||||
index := t.index[line]
|
index := t.index[line]
|
||||||
text := t.buffer[index.Line][index.Pos:]
|
text := t.buffer[index.Line][index.Pos:index.NextPos]
|
||||||
color := index.Color
|
color := index.Color
|
||||||
regionID := index.Region
|
regionID := index.Region
|
||||||
|
|
||||||
@ -626,8 +732,22 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
regions = regionPattern.FindAllStringSubmatch(text, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print one line.
|
// Calculate the position of the line.
|
||||||
var currentTag, currentRegion, skip, posX int
|
var skip, posX int
|
||||||
|
if t.align == AlignLeft {
|
||||||
|
posX = -t.columnOffset
|
||||||
|
} else if t.align == AlignRight {
|
||||||
|
posX = width - index.Width - t.columnOffset
|
||||||
|
} else { // AlignCenter.
|
||||||
|
posX = (width-index.Width)/2 - t.columnOffset
|
||||||
|
}
|
||||||
|
if posX < 0 {
|
||||||
|
skip = -posX
|
||||||
|
posX = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the line.
|
||||||
|
var currentTag, currentRegion, skipped int
|
||||||
for pos, ch := range text {
|
for pos, ch := range text {
|
||||||
// Get the color.
|
// Get the color.
|
||||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
||||||
@ -647,19 +767,15 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip to the right.
|
// Determine the width of this rune.
|
||||||
if !t.wrap && skip < t.columnOffset {
|
chWidth := runewidth.RuneWidth(ch)
|
||||||
skip++
|
if chWidth == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of this rune.
|
// Skip to the right.
|
||||||
chWidth := runewidth.RuneWidth(ch)
|
if !t.wrap && skipped < skip {
|
||||||
if ch == '\t' {
|
skipped += chWidth
|
||||||
chWidth = TabSize
|
|
||||||
ch = ' '
|
|
||||||
}
|
|
||||||
if chWidth == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user