diff --git a/src/terminal.go b/src/terminal.go index 69735d9..02c8f1f 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -46,6 +46,7 @@ type itemLine struct { current bool selected bool label string + width int result Result } @@ -678,13 +679,17 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) { } // Avoid unnecessary redraw - newLine := itemLine{current, selected, label, *result} - if t.prevLines[i] == newLine { + newLine := itemLine{current: current, selected: selected, label: label, result: *result, width: 0} + prevLine := t.prevLines[i] + if prevLine.current == newLine.current && + prevLine.selected == newLine.selected && + prevLine.label == newLine.label && + prevLine.result == newLine.result { 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) if current { if selected { @@ -692,15 +697,22 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) { } else { 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 { if selected { t.window.CPrint(tui.ColSelected, t.strong, ">") } else { 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) { @@ -745,17 +757,10 @@ func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) { } func (t *Terminal) overflow(runes []rune, max int) bool { - l := 0 - for _, r := range runes { - l += util.RuneWidth(r, l, t.tabstop) - if l > max { - return true - } - } - return false + return t.displayWidthWithLimit(runes, 0, max) > max } -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 // 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) maxWidth := t.window.Width() - 3 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 { // Stri.. 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) t.window.CPrint(col1, attr, substr) } + return displayWidth } func numLinesMax(str string, max int) int { diff --git a/src/tui/light.go b/src/tui/light.go index 2e99aa3..075fd6b 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -32,13 +32,18 @@ func openTtyIn() *os.File { return in } -// FIXME: Need better handling of non-displayable characters 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) runes := []rune{} for len(bytes) > 0 { 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, '?') } else { runes = append(runes, r) @@ -553,6 +558,10 @@ func (r *LightRenderer) DoesAutoWrap() bool { return true } +func (r *LightRenderer) IsOptimized() bool { + return false +} + func (r *LightRenderer) NewWindow(top int, left int, width int, height int, border bool) Window { w := &LightWindow{ renderer: r, @@ -594,6 +603,10 @@ func (w *LightWindow) stderr(str string) { w.renderer.stderr(str) } +func (w *LightWindow) stderrInternal(str string, allowNLCR bool) { + w.renderer.stderrInternal(str, allowNLCR) +} + func (w *LightWindow) Top() int { return w.top } @@ -703,7 +716,7 @@ func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) { } else { w.csiColor(pair.Fg(), pair.Bg(), attr) } - w.stderr(text) + w.stderrInternal(text, false) 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) { defer w.csi("m") } - w.stderr(text) + w.stderrInternal(text, false) } type wrappedLine struct { @@ -754,7 +767,7 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn { } return FillNextLine } - w.stderr(wl.text) + w.stderrInternal(wl.text, false) w.posx += wl.displayWidth if j < len(lines)-1 || i < len(allLines)-1 { if w.posy+1 >= w.height { diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go index db7cd83..0978ea8 100644 --- a/src/tui/ncurses.go +++ b/src/tui/ncurses.go @@ -279,6 +279,10 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool { return true } +func (r *FullscreenRenderer) IsOptimized() bool { + return true +} + func (w *CursesWindow) Fill(str string) FillReturn { if C.waddstr(w.impl, C.CString(str)) == C.OK { return FillContinue diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 8de6fe3..c898a38 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -158,6 +158,10 @@ func (r *FullscreenRenderer) DoesAutoWrap() bool { return false } +func (r *FullscreenRenderer) IsOptimized() bool { + return false +} + func (r *FullscreenRenderer) Clear() { _screen.Sync() _screen.Clear() diff --git a/src/tui/tui.go b/src/tui/tui.go index 4760a38..fd4a21e 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -204,6 +204,7 @@ type Renderer interface { MaxX() int MaxY() int DoesAutoWrap() bool + IsOptimized() bool NewWindow(top int, left int, width int, height int, border bool) Window }