Use synchronized update mode to simplify and enhance rendering

pull/3713/head
Junegunn Choi 2 months ago
parent 62963dcefd
commit 544a0bb542
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
0.50.0
------
- Eliminated any flickering of the screen and simplified the code using [synchronized update mode](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036)
- TODO: Not all terminals support this mode. We can detect if the current terminal supports this. But it means we can't simplify the code using the mode.
0.49.0 0.49.0
------ ------
- Ingestion performance improved by around 40% (more or less depending on options) - Ingestion performance improved by around 40% (more or less depending on options)

@ -130,7 +130,6 @@ type previewed struct {
offset int offset int
filled bool filled bool
image bool image bool
wipe bool
wireframe bool wireframe bool
} }
@ -145,8 +144,6 @@ type itemLine struct {
selected bool selected bool
label string label string
queryLen int queryLen int
width int
bar bool
result Result result Result
} }
@ -768,7 +765,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
initialPreviewOpts: opts.Preview, initialPreviewOpts: opts.Preview,
previewOpts: opts.Preview, previewOpts: opts.Preview,
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}}, previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
previewed: previewed{0, 0, 0, false, false, false, false}, previewed: previewed{0, 0, 0, false, false, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@ -1836,12 +1833,11 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
// Avoid unnecessary redraw // Avoid unnecessary redraw
newLine := itemLine{offset: line, current: current, selected: selected, label: label, newLine := itemLine{offset: line, current: current, selected: selected, label: label,
result: result, queryLen: len(t.input), width: 0, bar: bar} result: result, queryLen: len(t.input)}
prevLine := t.prevLines[i] prevLine := t.prevLines[i]
forceRedraw := prevLine.offset != newLine.offset forceRedraw := prevLine.offset != newLine.offset
printBar := func() { printBar := func() {
if len(t.scrollbar) > 0 && (bar != prevLine.bar || forceRedraw) { if len(t.scrollbar) > 0 {
t.prevLines[i].bar = bar
t.move(line, t.window.Width()-1, true) t.move(line, t.window.Width()-1, true)
if bar { if bar {
t.window.CPrint(tui.ColScrollbar, t.scrollbar) t.window.CPrint(tui.ColScrollbar, t.scrollbar)
@ -1859,7 +1855,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
return return
} }
t.move(line, 0, forceRedraw) t.move(line, 0, true)
if current { if current {
if len(label) == 0 { if len(label) == 0 {
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty) t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
@ -1871,7 +1867,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
} else { } else {
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty) t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
} }
newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true) t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else { } else {
if len(label) == 0 { if len(label) == 0 {
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty) t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
@ -1883,11 +1879,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
} else { } else {
t.window.Print(t.markerEmpty) t.window.Print(t.markerEmpty)
} }
newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true) t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
}
fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 {
t.window.Print(strings.Repeat(" ", fillSpaces))
} }
printBar() printBar()
t.prevLines[i] = newLine t.prevLines[i] = newLine
@ -1931,7 +1923,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max return t.displayWidthWithLimit(runes, 0, max) > max
} }
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int { func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) {
item := result.item item := result.item
// Overflow // Overflow
@ -2009,11 +2001,9 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth)) offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
} }
} }
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
} }
t.printColoredString(t.window, text, offsets, colBase) t.printColoredString(t.window, text, offsets, colBase)
return displayWidth
} }
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) { func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
@ -2072,21 +2062,11 @@ func (t *Terminal) renderPreviewSpinner() {
} }
func (t *Terminal) renderPreviewArea(unchanged bool) { func (t *Terminal) renderPreviewArea(unchanged bool) {
if t.previewed.wipe && t.previewed.version != t.previewer.version { if unchanged {
t.previewed.wipe = false
t.pwindow.Erase()
} else if unchanged {
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
} else { } else {
t.previewed.filled = false t.previewed.filled = false
// We don't erase the window here to avoid flickering during scroll. t.pwindow.Erase()
// However, tcell renderer uses double-buffering technique and there's no
// flickering. So we just erase the window and make the rest of the code
// simpler.
if !t.pwindow.EraseMaybe() {
t.pwindow.DrawBorder()
t.pwindow.Move(0, 0)
}
} }
height := t.pwindow.Height() height := t.pwindow.Height()
@ -2173,7 +2153,6 @@ Loop:
isItermImage := strings.HasPrefix(passThrough, "\x1b]1337;") isItermImage := strings.HasPrefix(passThrough, "\x1b]1337;")
isImage := isSixel || isItermImage isImage := isSixel || isItermImage
if isImage { if isImage {
t.previewed.wipe = true
// NOTE: We don't have a good way to get the height of an iTerm image, // NOTE: We don't have a good way to get the height of an iTerm image,
// so we assume that it requires the full height of the preview // so we assume that it requires the full height of the preview
// window. // window.

@ -71,7 +71,7 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() { func (r *LightRenderer) flush() {
if r.queued.Len() > 0 { if r.queued.Len() > 0 {
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h") fmt.Fprint(os.Stderr, "\x1b[?2026h"+r.queued.String()+"\x1b[?2026l")
r.queued.Reset() r.queued.Reset()
} }
} }
@ -1128,7 +1128,3 @@ func (w *LightWindow) Erase() {
w.FinishFill() w.FinishFill()
w.Move(0, 0) w.Move(0, 0)
} }
func (w *LightWindow) EraseMaybe() bool {
return false
}

@ -565,11 +565,6 @@ func (w *TcellWindow) Erase() {
w.drawBorder(false) w.drawBorder(false)
} }
func (w *TcellWindow) EraseMaybe() bool {
w.Erase()
return true
}
func (w *TcellWindow) Enclose(y int, x int) bool { func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) && return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height) y >= w.top && y < (w.top+w.height)

@ -530,7 +530,6 @@ type Window interface {
Fill(text string) FillReturn Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase() Erase()
EraseMaybe() bool
} }
type FullscreenRenderer struct { type FullscreenRenderer struct {

Loading…
Cancel
Save