1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
package vt
import (
"unicode/utf8"
uv "github.com/charmbracelet/ultraviolet"
"github.com/charmbracelet/x/ansi"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// handlePrint handles printable characters.
func (e *Emulator) handlePrint(r rune) {
if r >= ansi.SP && r < ansi.DEL {
if len(e.grapheme) > 0 {
// If we have a grapheme buffer, flush it before handling the ASCII character.
e.flushGrapheme()
}
e.handleGrapheme(string(r), 1)
} else {
e.grapheme = append(e.grapheme, r)
}
}
// flushGrapheme flushes the current grapheme buffer, if any, and handles the
// grapheme as a single unit.
func (e *Emulator) flushGrapheme() {
if len(e.grapheme) == 0 {
return
}
unicode := e.isModeSet(ansi.UnicodeCoreMode)
gr := string(e.grapheme)
var cl string
var w int
state := -1
for len(gr) > 0 {
cl, gr, w, state = uniseg.FirstGraphemeClusterInString(gr, state)
if !unicode {
//nolint:godox
// TODO: Investigate this further, runewidth.StringWidth doesn't
// report the correct width for some edge cases such as variation
// selectors.
w = 0
for _, r := range cl {
if r >= 0xFE00 && r <= 0xFE0F {
// Variation Selectors 1 - 16
continue
}
if r >= 0xE0100 && r <= 0xE01EF {
// Variation Selectors 17-256
continue
}
w += runewidth.RuneWidth(r)
}
}
e.handleGrapheme(cl, w)
}
e.grapheme = e.grapheme[:0] // Reset the grapheme buffer.
}
// handleGrapheme handles UTF-8 graphemes.
func (e *Emulator) handleGrapheme(content string, width int) {
awm := e.isModeSet(ansi.AutoWrapMode)
cell := uv.Cell{
Content: content,
Width: width,
Style: e.scr.cursorPen(),
Link: e.scr.cursorLink(),
}
x, y := e.scr.CursorPosition()
if e.atPhantom && awm {
// moves cursor down similar to [Terminal.linefeed] except it doesn't
// respects [ansi.LNM] mode.
// This will reset the phantom state i.e. pending wrap state.
e.index()
_, y = e.scr.CursorPosition()
x = 0
}
// Handle character set mappings
if len(content) == 1 { //nolint:nestif
var charset CharSet
c := content[0]
if e.gsingle > 1 && e.gsingle < 4 {
charset = e.charsets[e.gsingle]
e.gsingle = 0
} else if c < 128 {
charset = e.charsets[e.gl]
} else {
charset = e.charsets[e.gr]
}
if charset != nil {
if r, ok := charset[c]; ok {
cell.Content = r
cell.Width = 1
}
}
}
if cell.Width == 1 && len(content) == 1 {
e.lastChar, _ = utf8.DecodeRuneInString(content)
}
e.scr.SetCell(x, y, &cell)
// Handle phantom state at the end of the line
e.atPhantom = awm && x >= e.scr.Width()-1
if !e.atPhantom {
x += cell.Width
}
// NOTE: We don't reset the phantom state here, we handle it up above.
e.scr.setCursor(x, y, false)
}
|