Fixed handling of combining unicode characters. Fixes #101
This commit is contained in:
parent
d7d44cb0d2
commit
213c37c368
|
@ -248,40 +248,18 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
text := i.text
|
||||
if text == "" && i.placeholder != "" {
|
||||
Print(screen, i.placeholder, x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
|
||||
}
|
||||
|
||||
// Draw entered text.
|
||||
if i.maskCharacter > 0 {
|
||||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||
}
|
||||
fieldWidth-- // We need one cell for the cursor.
|
||||
if fieldWidth < runewidth.StringWidth(text) {
|
||||
runes := []rune(text)
|
||||
for pos := len(runes) - 1; pos >= 0; pos-- {
|
||||
ch := runes[pos]
|
||||
w := runewidth.RuneWidth(ch)
|
||||
if fieldWidth-w < 0 {
|
||||
break
|
||||
}
|
||||
_, _, style, _ := screen.GetContent(x+fieldWidth-w, y)
|
||||
style = style.Foreground(i.fieldTextColor)
|
||||
for w > 0 {
|
||||
fieldWidth--
|
||||
screen.SetContent(x+fieldWidth, y, ch, nil, style)
|
||||
w--
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pos := 0
|
||||
for _, ch := range text {
|
||||
w := runewidth.RuneWidth(ch)
|
||||
_, _, style, _ := screen.GetContent(x+pos, y)
|
||||
style = style.Foreground(i.fieldTextColor)
|
||||
for w > 0 {
|
||||
screen.SetContent(x+pos, y, ch, nil, style)
|
||||
pos++
|
||||
w--
|
||||
}
|
||||
// Draw entered text.
|
||||
if i.maskCharacter > 0 {
|
||||
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
|
||||
} else {
|
||||
text = Escape(text)
|
||||
}
|
||||
fieldWidth-- // We need one cell for the cursor.
|
||||
if fieldWidth < runewidth.StringWidth(text) {
|
||||
Print(screen, text, x, y, fieldWidth, AlignRight, i.fieldTextColor)
|
||||
} else {
|
||||
Print(screen, text, x, y, fieldWidth, AlignLeft, i.fieldTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
128
textview.go
128
textview.go
|
@ -769,50 +769,11 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Print the line.
|
||||
var currentTag, currentRegion, currentEscapeTag, skipped int
|
||||
for pos, ch := range text {
|
||||
// Get the color.
|
||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
||||
if pos == colorTagIndices[currentTag][1]-1 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
|
||||
currentTag++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the region.
|
||||
if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
|
||||
if pos == regionIndices[currentRegion][1]-1 {
|
||||
regionID = regions[currentRegion][1]
|
||||
currentRegion++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
|
||||
if pos == escapeIndices[currentEscapeTag][1]-1 {
|
||||
currentEscapeTag++
|
||||
} else if pos == escapeIndices[currentEscapeTag][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the width of this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if chWidth == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip to the right.
|
||||
if !t.wrap && skipped < skip {
|
||||
skipped += chWidth
|
||||
continue
|
||||
}
|
||||
|
||||
// Stop at the right border.
|
||||
if posX+chWidth > width {
|
||||
break
|
||||
var currentTag, currentRegion, currentEscapeTag, skipped, runeSeqWidth int
|
||||
runeSequence := make([]rune, 0, 10)
|
||||
flush := func() {
|
||||
if len(runeSequence) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Mix the existing style with the new style.
|
||||
|
@ -843,12 +804,85 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Draw the character.
|
||||
for offset := 0; offset < chWidth; offset++ {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ch, nil, style)
|
||||
var comb []rune
|
||||
if len(runeSequence) > 1 {
|
||||
// Allocate space for the combining characters only when necessary.
|
||||
comb = make([]rune, len(runeSequence)-1)
|
||||
copy(comb, runeSequence[1:])
|
||||
}
|
||||
for offset := 0; offset < runeSeqWidth; offset++ {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, runeSequence[0], comb, style)
|
||||
}
|
||||
|
||||
// Advance.
|
||||
posX += chWidth
|
||||
posX += runeSeqWidth
|
||||
runeSequence = runeSequence[:0]
|
||||
runeSeqWidth = 0
|
||||
}
|
||||
for pos, ch := range text {
|
||||
// Get the color.
|
||||
if currentTag < len(colorTags) && pos >= colorTagIndices[currentTag][0] && pos < colorTagIndices[currentTag][1] {
|
||||
flush()
|
||||
if pos == colorTagIndices[currentTag][1]-1 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[currentTag])
|
||||
currentTag++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the region.
|
||||
if currentRegion < len(regionIndices) && pos >= regionIndices[currentRegion][0] && pos < regionIndices[currentRegion][1] {
|
||||
flush()
|
||||
if pos == regionIndices[currentRegion][1]-1 {
|
||||
regionID = regions[currentRegion][1]
|
||||
currentRegion++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if currentEscapeTag < len(escapeIndices) && pos >= escapeIndices[currentEscapeTag][0] && pos < escapeIndices[currentEscapeTag][1] {
|
||||
flush()
|
||||
if pos == escapeIndices[currentEscapeTag][1]-1 {
|
||||
currentEscapeTag++
|
||||
} else if pos == escapeIndices[currentEscapeTag][1]-2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the width of this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if chWidth == 0 {
|
||||
// If this is not a modifier, we treat it as a space character.
|
||||
if len(runeSequence) == 0 {
|
||||
ch = ' '
|
||||
chWidth = 1
|
||||
} else {
|
||||
runeSequence = append(runeSequence, ch)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the right.
|
||||
if !t.wrap && skipped < skip {
|
||||
skipped += chWidth
|
||||
continue
|
||||
}
|
||||
|
||||
// Stop at the right border.
|
||||
if posX+runeSeqWidth+chWidth > width {
|
||||
break
|
||||
}
|
||||
|
||||
// Flush the rune sequence.
|
||||
flush()
|
||||
|
||||
// Queue this rune.
|
||||
runeSequence = append(runeSequence, ch)
|
||||
runeSeqWidth += chWidth
|
||||
}
|
||||
if posX+runeSeqWidth <= width {
|
||||
flush()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
72
util.go
72
util.go
|
@ -361,6 +361,9 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
width += w
|
||||
start = index
|
||||
}
|
||||
for runewidth.RuneWidth(runes[start]) == 0 && start < len(runes) {
|
||||
start++
|
||||
}
|
||||
return printWithStyle(screen, substring(start, len(runes)), x+maxWidth-width, y, width, AlignLeft, style)
|
||||
} else if align == AlignCenter {
|
||||
width := runewidth.StringWidth(strippedText)
|
||||
|
@ -376,12 +379,15 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
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 {
|
||||
leftWidth := runewidth.RuneWidth(runes[leftIndex])
|
||||
choppedLeft += leftWidth
|
||||
leftIndex++
|
||||
for runewidth.RuneWidth(runes[leftIndex]) == 0 && leftIndex < len(runes) && leftIndex < rightIndex {
|
||||
leftIndex++
|
||||
}
|
||||
} else {
|
||||
rightWidth := runewidth.RuneWidth(runes[rightIndex])
|
||||
choppedRight += rightWidth
|
||||
rightIndex--
|
||||
}
|
||||
|
@ -397,9 +403,39 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
colorPos, escapePos int
|
||||
foregroundColor, backgroundColor, attributes string
|
||||
)
|
||||
runeSequence := make([]rune, 0, 10)
|
||||
runeSeqWidth := 0
|
||||
flush := func() {
|
||||
if len(runeSequence) == 0 {
|
||||
return // Nothing to flush.
|
||||
}
|
||||
|
||||
// Print the rune sequence.
|
||||
finalX := x + drawnWidth
|
||||
_, _, finalStyle, _ := screen.GetContent(finalX, y)
|
||||
_, background, _ := finalStyle.Decompose()
|
||||
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
||||
var comb []rune
|
||||
if len(runeSequence) > 1 {
|
||||
// Allocate space for the combining characters only when necessary.
|
||||
comb = make([]rune, len(runeSequence)-1)
|
||||
copy(comb, runeSequence[1:])
|
||||
}
|
||||
for offset := 0; offset < runeSeqWidth; offset++ {
|
||||
// To avoid undesired effects, we place the same character in all cells.
|
||||
screen.SetContent(finalX+offset, y, runeSequence[0], comb, finalStyle)
|
||||
}
|
||||
|
||||
// Advance and reset.
|
||||
drawn += len(runeSequence)
|
||||
drawnWidth += runeSeqWidth
|
||||
runeSequence = runeSequence[:0]
|
||||
runeSeqWidth = 0
|
||||
}
|
||||
for pos, ch := range text {
|
||||
// Handle color tags.
|
||||
if colorPos < len(colorIndices) && pos >= colorIndices[colorPos][0] && pos < colorIndices[colorPos][1] {
|
||||
flush()
|
||||
if pos == colorIndices[colorPos][1]-1 {
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colors[colorPos])
|
||||
colorPos++
|
||||
|
@ -409,6 +445,7 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
|
||||
// Handle escape tags.
|
||||
if escapePos < len(escapeIndices) && pos >= escapeIndices[escapePos][0] && pos < escapeIndices[escapePos][1] {
|
||||
flush()
|
||||
if pos == escapeIndices[escapePos][1]-1 {
|
||||
escapePos++
|
||||
} else if pos == escapeIndices[escapePos][1]-2 {
|
||||
|
@ -419,21 +456,26 @@ func printWithStyle(screen tcell.Screen, text string, x, y, maxWidth, align int,
|
|||
// Check if we have enough space for this rune.
|
||||
chWidth := runewidth.RuneWidth(ch)
|
||||
if drawnWidth+chWidth > maxWidth {
|
||||
break
|
||||
}
|
||||
finalX := x + drawnWidth
|
||||
|
||||
// Print the rune.
|
||||
_, _, finalStyle, _ := screen.GetContent(finalX, y)
|
||||
_, background, _ := finalStyle.Decompose()
|
||||
finalStyle = overlayStyle(background, style, foregroundColor, backgroundColor, attributes)
|
||||
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, finalStyle)
|
||||
break // No. We're done then.
|
||||
}
|
||||
|
||||
drawn++
|
||||
drawnWidth += chWidth
|
||||
// Put this rune in the queue.
|
||||
if chWidth == 0 {
|
||||
// If this is not a modifier, we treat it as a space character.
|
||||
if len(runeSequence) == 0 {
|
||||
ch = ' '
|
||||
chWidth = 1
|
||||
}
|
||||
} else {
|
||||
// We have a character. Flush all previous runes.
|
||||
flush()
|
||||
}
|
||||
runeSequence = append(runeSequence, ch)
|
||||
runeSeqWidth += chWidth
|
||||
|
||||
}
|
||||
if drawnWidth+runeSeqWidth <= maxWidth {
|
||||
flush()
|
||||
}
|
||||
|
||||
return drawn, drawnWidth
|
||||
|
|
Loading…
Reference in New Issue