Fix styled switch-back repaint
This commit is contained in:
@@ -121,6 +121,7 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
@@ -309,6 +310,199 @@ func (e *GhosttyEmulator) SerializeVT() ([]byte, error) {
|
||||
return C.GoBytes(unsafe.Pointer(buf), C.int(n)), nil
|
||||
}
|
||||
|
||||
type styledCellSGR struct {
|
||||
fgSet, bgSet bool
|
||||
fgR, fgG, fgB uint8
|
||||
bgR, bgG, bgB uint8
|
||||
|
||||
bold, italic, faint, blink, inverse, invisible, strikethrough, overline bool
|
||||
underline int
|
||||
}
|
||||
|
||||
func (s styledCellSGR) equal(o styledCellSGR) bool {
|
||||
return s == o
|
||||
}
|
||||
|
||||
func sgrSeq(s styledCellSGR) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("\x1b[0")
|
||||
if s.bold {
|
||||
b.WriteString(";1")
|
||||
}
|
||||
if s.faint {
|
||||
b.WriteString(";2")
|
||||
}
|
||||
if s.italic {
|
||||
b.WriteString(";3")
|
||||
}
|
||||
if s.underline != 0 {
|
||||
b.WriteString(";4")
|
||||
}
|
||||
if s.blink {
|
||||
b.WriteString(";5")
|
||||
}
|
||||
if s.inverse {
|
||||
b.WriteString(";7")
|
||||
}
|
||||
if s.invisible {
|
||||
b.WriteString(";8")
|
||||
}
|
||||
if s.strikethrough {
|
||||
b.WriteString(";9")
|
||||
}
|
||||
if s.overline {
|
||||
b.WriteString(";53")
|
||||
}
|
||||
if s.fgSet {
|
||||
fmt.Fprintf(&b, ";38;2;%d;%d;%d", s.fgR, s.fgG, s.fgB)
|
||||
}
|
||||
if s.bgSet {
|
||||
fmt.Fprintf(&b, ";48;2;%d;%d;%d", s.bgR, s.bgG, s.bgB)
|
||||
}
|
||||
b.WriteByte('m')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (e *GhosttyEmulator) StyledScreenVT() ([]byte, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
if e.closed {
|
||||
return nil, errors.New("vt: emulator closed")
|
||||
}
|
||||
|
||||
var state C.GhosttyRenderState
|
||||
if rc := C.ghostty_render_state_new(nil, &state); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: render_state_new failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
defer C.ghostty_render_state_free(state)
|
||||
if rc := C.ghostty_render_state_update(state, e.term); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: render_state_update failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
|
||||
var rows C.uint16_t
|
||||
if rc := C.ghostty_render_state_get(state, C.GHOSTTY_RENDER_STATE_DATA_ROWS, unsafe.Pointer(&rows)); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: render_state rows failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
|
||||
var iter C.GhosttyRenderStateRowIterator
|
||||
if rc := C.ghostty_render_state_row_iterator_new(nil, &iter); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: row_iterator_new failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
defer C.ghostty_render_state_row_iterator_free(iter)
|
||||
if rc := C.ghostty_render_state_get(state, C.GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR, unsafe.Pointer(&iter)); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: render_state row iterator failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
|
||||
var cells C.GhosttyRenderStateRowCells
|
||||
if rc := C.ghostty_render_state_row_cells_new(nil, &cells); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: row_cells_new failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
defer C.ghostty_render_state_row_cells_free(cells)
|
||||
|
||||
var out strings.Builder
|
||||
for row := 0; row < int(rows) && C.ghostty_render_state_row_iterator_next(iter); row++ {
|
||||
if rc := C.ghostty_render_state_row_get(iter, C.GHOSTTY_RENDER_STATE_ROW_DATA_CELLS, unsafe.Pointer(&cells)); rc != C.GHOSTTY_SUCCESS {
|
||||
return nil, fmt.Errorf("vt: render_state row cells failed: %s", ghosttyResultStr(rc))
|
||||
}
|
||||
|
||||
rowCells := make([]struct {
|
||||
text string
|
||||
sgr styledCellSGR
|
||||
draw bool
|
||||
}, 0, int(e.cols))
|
||||
lastDraw := -1
|
||||
for col := 0; col < int(e.cols) && C.ghostty_render_state_row_cells_next(cells); col++ {
|
||||
var cell C.GhosttyCell
|
||||
_ = C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_RAW, unsafe.Pointer(&cell))
|
||||
var wide C.GhosttyCellWide
|
||||
_ = C.ghostty_cell_get(cell, C.GHOSTTY_CELL_DATA_WIDE, unsafe.Pointer(&wide))
|
||||
if wide == C.GHOSTTY_CELL_WIDE_SPACER_TAIL || wide == C.GHOSTTY_CELL_WIDE_SPACER_HEAD {
|
||||
rowCells = append(rowCells, struct {
|
||||
text string
|
||||
sgr styledCellSGR
|
||||
draw bool
|
||||
}{})
|
||||
continue
|
||||
}
|
||||
|
||||
var style C.GhosttyStyle
|
||||
style.size = C.size_t(unsafe.Sizeof(style))
|
||||
_ = C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_STYLE, unsafe.Pointer(&style))
|
||||
|
||||
sgr := styledCellSGR{
|
||||
bold: bool(style.bold),
|
||||
italic: bool(style.italic),
|
||||
faint: bool(style.faint),
|
||||
blink: bool(style.blink),
|
||||
inverse: bool(style.inverse),
|
||||
invisible: bool(style.invisible),
|
||||
strikethrough: bool(style.strikethrough),
|
||||
overline: bool(style.overline),
|
||||
underline: int(style.underline),
|
||||
}
|
||||
var fg C.GhosttyColorRgb
|
||||
if rc := C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR, unsafe.Pointer(&fg)); rc == C.GHOSTTY_SUCCESS {
|
||||
sgr.fgSet, sgr.fgR, sgr.fgG, sgr.fgB = true, uint8(fg.r), uint8(fg.g), uint8(fg.b)
|
||||
}
|
||||
var bg C.GhosttyColorRgb
|
||||
if rc := C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_BG_COLOR, unsafe.Pointer(&bg)); rc == C.GHOSTTY_SUCCESS {
|
||||
sgr.bgSet, sgr.bgR, sgr.bgG, sgr.bgB = true, uint8(bg.r), uint8(bg.g), uint8(bg.b)
|
||||
}
|
||||
|
||||
var graphemeLen C.uint32_t
|
||||
_ = C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_LEN, unsafe.Pointer(&graphemeLen))
|
||||
text := ""
|
||||
if graphemeLen > 0 {
|
||||
buf := make([]C.uint32_t, int(graphemeLen))
|
||||
_ = C.ghostty_render_state_row_cells_get(cells, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_BUF, unsafe.Pointer(&buf[0]))
|
||||
rs := make([]rune, len(buf))
|
||||
for i, r := range buf {
|
||||
rs[i] = rune(r)
|
||||
}
|
||||
text = string(rs)
|
||||
}
|
||||
|
||||
draw := text != "" || sgr.bgSet
|
||||
if draw {
|
||||
lastDraw = col
|
||||
if text == "" {
|
||||
text = " "
|
||||
}
|
||||
}
|
||||
rowCells = append(rowCells, struct {
|
||||
text string
|
||||
sgr styledCellSGR
|
||||
draw bool
|
||||
}{text: text, sgr: sgr, draw: draw})
|
||||
}
|
||||
if lastDraw < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(&out, "\x1b[%d;1H", row+1)
|
||||
cur := styledCellSGR{}
|
||||
out.WriteString("\x1b[0m")
|
||||
for col := 0; col <= lastDraw && col < len(rowCells); col++ {
|
||||
cell := rowCells[col]
|
||||
if !cell.draw {
|
||||
if !cur.equal(styledCellSGR{}) {
|
||||
cur = styledCellSGR{}
|
||||
out.WriteString("\x1b[0m")
|
||||
}
|
||||
out.WriteByte(' ')
|
||||
continue
|
||||
}
|
||||
if !cell.sgr.equal(cur) {
|
||||
cur = cell.sgr
|
||||
out.WriteString(sgrSeq(cur))
|
||||
}
|
||||
out.WriteString(cell.text)
|
||||
}
|
||||
out.WriteString("\x1b[0m")
|
||||
}
|
||||
return []byte(out.String()), nil
|
||||
}
|
||||
|
||||
func (e *GhosttyEmulator) Cursor() (CursorState, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user