Optimize LightRenderer for slow terminals

This commit is contained in:
Junegunn Choi 2017-01-16 02:26:36 +09:00
parent 44d3faa048
commit ede7bfb901
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
5 changed files with 50 additions and 21 deletions

View File

@ -46,6 +46,7 @@ type itemLine struct {
current bool current bool
selected bool selected bool
label string label string
width int
result Result result Result
} }
@ -678,13 +679,17 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) {
} }
// Avoid unnecessary redraw // Avoid unnecessary redraw
newLine := itemLine{current, selected, label, *result} newLine := itemLine{current: current, selected: selected, label: label, result: *result, width: 0}
if t.prevLines[i] == newLine { prevLine := t.prevLines[i]
if prevLine.current == newLine.current &&
prevLine.selected == newLine.selected &&
prevLine.label == newLine.label &&
prevLine.result == newLine.result {
return return
} }
t.prevLines[i] = newLine
t.move(line, 0, true) // Optimized renderer can simply erase to the end of the window
t.move(line, 0, t.tui.IsOptimized())
t.window.CPrint(tui.ColCursor, t.strong, label) t.window.CPrint(tui.ColCursor, t.strong, label)
if current { if current {
if selected { if selected {
@ -692,15 +697,22 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) {
} else { } else {
t.window.CPrint(tui.ColCurrent, t.strong, " ") t.window.CPrint(tui.ColCurrent, t.strong, " ")
} }
t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true) newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else { } else {
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColSelected, t.strong, ">")
} else { } else {
t.window.Print(" ") t.window.Print(" ")
} }
t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true) newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true)
} }
if !t.tui.IsOptimized() {
fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 {
t.window.Print(strings.Repeat(" ", fillSpaces))
}
}
t.prevLines[i] = newLine
} }
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, int) { func (t *Terminal) trimRight(runes []rune, width int) ([]rune, int) {
@ -745,17 +757,10 @@ func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
} }
func (t *Terminal) overflow(runes []rune, max int) bool { func (t *Terminal) overflow(runes []rune, max int) bool {
l := 0 return t.displayWidthWithLimit(runes, 0, max) > max
for _, r := range runes {
l += util.RuneWidth(r, l, t.tabstop)
if l > max {
return true
}
}
return false
} }
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) { func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int {
item := result.item item := result.item
// Overflow // Overflow
@ -783,7 +788,8 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current) offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current)
maxWidth := t.window.Width() - 3 maxWidth := t.window.Width() - 3
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
if t.overflow(text, maxWidth) { displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
if displayWidth > maxWidth {
if t.hscroll { if t.hscroll {
// Stri.. // Stri..
if !t.overflow(text[:maxe], maxWidth-2) { if !t.overflow(text[:maxe], maxWidth-2) {
@ -845,6 +851,7 @@ func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.Colo
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(col1, attr, substr) t.window.CPrint(col1, attr, substr)
} }
return displayWidth
} }
func numLinesMax(str string, max int) int { func numLinesMax(str string, max int) int {

View File

@ -32,13 +32,18 @@ func openTtyIn() *os.File {
return in return in
} }
// FIXME: Need better handling of non-displayable characters
func (r *LightRenderer) stderr(str string) { func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true)
}
// FIXME: Need better handling of non-displayable characters
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
bytes := []byte(str) bytes := []byte(str)
runes := []rune{} runes := []rune{}
for len(bytes) > 0 { for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes) r, sz := utf8.DecodeRune(bytes)
if r == utf8.RuneError || r != '\x1b' && r != '\n' && r != '\r' && r < 32 { if r == utf8.RuneError || r < 32 &&
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
runes = append(runes, '?') runes = append(runes, '?')
} else { } else {
runes = append(runes, r) runes = append(runes, r)
@ -553,6 +558,10 @@ func (r *LightRenderer) DoesAutoWrap() bool {
return true return true
} }
func (r *LightRenderer) IsOptimized() bool {
return false
}
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
@ -594,6 +603,10 @@ func (w *LightWindow) stderr(str string) {
w.renderer.stderr(str) w.renderer.stderr(str)
} }
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
w.renderer.stderrInternal(str, allowNLCR)
}
func (w *LightWindow) Top() int { func (w *LightWindow) Top() int {
return w.top return w.top
} }
@ -703,7 +716,7 @@ func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
} else { } else {
w.csiColor(pair.Fg(), pair.Bg(), attr) w.csiColor(pair.Fg(), pair.Bg(), attr)
} }
w.stderr(text) w.stderrInternal(text, false)
w.csi("m") w.csi("m")
} }
@ -711,7 +724,7 @@ func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
if w.csiColor(fg, bg, attr) { if w.csiColor(fg, bg, attr) {
defer w.csi("m") defer w.csi("m")
} }
w.stderr(text) w.stderrInternal(text, false)
} }
type wrappedLine struct { type wrappedLine struct {
@ -754,7 +767,7 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
} }
return FillNextLine return FillNextLine
} }
w.stderr(wl.text) w.stderrInternal(wl.text, false)
w.posx += wl.displayWidth w.posx += wl.displayWidth
if j < len(lines)-1 || i < len(allLines)-1 { if j < len(lines)-1 || i < len(allLines)-1 {
if w.posy+1 >= w.height { if w.posy+1 >= w.height {

View File

@ -279,6 +279,10 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool {
return true return true
} }
func (r *FullscreenRenderer) IsOptimized() bool {
return true
}
func (w *CursesWindow) Fill(str string) FillReturn { func (w *CursesWindow) Fill(str string) FillReturn {
if C.waddstr(w.impl, C.CString(str)) == C.OK { if C.waddstr(w.impl, C.CString(str)) == C.OK {
return FillContinue return FillContinue

View File

@ -158,6 +158,10 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool {
return false return false
} }
func (r *FullscreenRenderer) IsOptimized() bool {
return false
}
func (r *FullscreenRenderer) Clear() { func (r *FullscreenRenderer) Clear() {
_screen.Sync() _screen.Sync()
_screen.Clear() _screen.Clear()

View File

@ -204,6 +204,7 @@ type Renderer interface {
MaxX() int MaxX() int
MaxY() int MaxY() int
DoesAutoWrap() bool DoesAutoWrap() bool
IsOptimized() bool
NewWindow(top int, left int, width int, height int, border bool) Window NewWindow(top int, left int, width int, height int, border bool) Window
} }