From 585c7f126d7fe48a9992f3a1d5ae928712186b9a Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 8 Jul 2026 22:29:31 +0200 Subject: [PATCH] Cache each line's wrapping so scrolling doesn't re-wrap the whole buffer refreshViewLinesIfNeeded re-wrapped every line of the buffer whenever the view was tainted. That's cheap for short content, but scrolling a long diff calls it constantly: adjustDownwardScrollAmount queries ViewLinesHeight on every scroll event, and each newly-read line taints the view, so every notch re-wrapped the entire buffer. Wrapping measures each cell's width (uniseg) and allocates per line, so once you'd scrolled far enough down the diff, scrolling turned sluggish - the cost grew with how much had been read. (A CPU profile of scrolling deep in a long diff put 77% of the time in lineWrap, reached almost entirely via ViewLinesHeight rather than draw.) Cache each line's wrapped result on the lineType, keyed by the width it was wrapped at, and only re-wrap lines that have actually changed since the last refresh. A firstDirtyLine index, updated in the same three places that set `tainted` (write, clearViewLines' callers, SetHighlight), marks the lowest line that might have changed; lines below it with a matching cached width reuse their cached wrapping. The cache lives on the line, so it's freed with the line when the view's content is replaced (e.g. selecting a different commit) - it doesn't accumulate across a session. The wrapping cost per scroll now scales with the number of lines just read, not with the total size of the buffer, so scrolling stays smooth no matter how far down you are. Co-Authored-By: Claude Opus 4.8 (1M context) --- cpu.out | Bin 0 -> 22717 bytes pkg/gocui/view.go | 120 ++++++++++++++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 cpu.out diff --git a/cpu.out b/cpu.out new file mode 100644 index 0000000000000000000000000000000000000000..24b4b40a40f2809d660809ebf24b5b82fa20ca33 GIT binary patch literal 22717 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 Ir#|%n09n;AKL7v# literal 0 HcmV?d00001 diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index d93f84954..e4c5a5f98 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -50,6 +50,14 @@ type View struct { // tained is true if the viewLines must be updated tainted bool + // firstDirtyLine is the index of the lowest line in `lines` that has been + // written to or highlighted since viewLines was last refreshed, and whose + // cached wrapping (lineType.wrappedCells) may therefore be stale. Lines + // below it are unchanged and can reuse their cached wrapping instead of + // being re-wrapped, which keeps refreshViewLinesIfNeeded cheap while + // scrolling appends new lines to a long buffer. + firstDirtyLine int + // the last position that the mouse was hovering over; nil if the mouse is outside of // this view, or not hovering over a cell lastHoverPosition *pos @@ -457,6 +465,16 @@ type viewLine struct { type lineType struct { cells cells trailingFillAttributes *trailingFillAttributes + + // wrappedCells caches the result of wrapping `cells` to `wrappedColumns` + // columns, so that unchanged lines don't have to be re-wrapped on every + // refreshViewLinesIfNeeded (which runs on every scroll event, via + // ViewLinesHeight). Wrapping measures every cell's width and allocates, so + // for a long buffer that dominates the cost of scrolling. The cache is used + // only for lines below View.firstDirtyLine whose wrappedColumns still + // matches the current width; nil means nothing is cached yet. + wrappedCells [][]cell + wrappedColumns int } // trailingFillAttributes describes the fg/bg colors that draw() should @@ -815,6 +833,9 @@ func (v *View) Write(p []byte) (n int, err error) { func (v *View) write(p []byte) { v.tainted = true + // write only ever touches lines from v.wy onwards, so any cached wrapping + // below that stays valid. + v.firstDirtyLine = min(v.firstDirtyLine, v.wy) v.clearHover() // Fill with empty cells, if writing outside current view buffer @@ -1358,48 +1379,64 @@ func (v *View) draw() { } func (v *View) refreshViewLinesIfNeeded() { - if v.tainted { - maxX := v.InnerWidth() - lineIdx := 0 - lines := v.lines - for i, line := range lines { - wrap := 0 - if v.Wrap { - wrap = maxX - } - - ls := lineWrap(line.cells, wrap) - for j := range ls { - // Per-segment trailing fill. When the source line opted in - // via '\x1b[K', the LAST wrapped segment uses those colors - // directly; earlier segments use the colors of their own - // last cell, so the trailing area matches the bg active - // where that segment ended rather than bleeding the - // '\x1b[K' bg back across color changes in the line. - var attrs *trailingFillAttributes - if line.trailingFillAttributes != nil { - if j == len(ls)-1 { - attrs = line.trailingFillAttributes - } else if len(ls[j]) > 0 { - last := ls[j][len(ls[j])-1] - attrs = &trailingFillAttributes{fg: last.fgColor, bg: last.bgColor} - } - } - vline := viewLine{ - linesX: j, linesY: i, line: ls[j], - trailingFillAttributes: attrs, - } - - if lineIdx > len(v.viewLines)-1 { - v.viewLines = append(v.viewLines, vline) - } else { - v.viewLines[lineIdx] = vline - } - lineIdx++ - } - } - v.tainted = false + if !v.tainted { + return } + + maxX := v.InnerWidth() + wrap := 0 + if v.Wrap { + wrap = maxX + } + + lineIdx := 0 + lines := v.lines + for i := range lines { + line := &lines[i] + + // Reuse the previously wrapped result for lines that haven't changed + // since the last refresh (i.e. below firstDirtyLine) and were wrapped at + // the current width. Wrapping is expensive and this loop runs on every + // scroll event, so only the lines that were actually just read (or + // re-highlighted) should be wrapped afresh. + if line.wrappedCells == nil || line.wrappedColumns != wrap || i >= v.firstDirtyLine { + line.wrappedCells = lineWrap(line.cells, wrap) + line.wrappedColumns = wrap + } + ls := line.wrappedCells + + for j := range ls { + // Per-segment trailing fill. When the source line opted in + // via '\x1b[K', the LAST wrapped segment uses those colors + // directly; earlier segments use the colors of their own + // last cell, so the trailing area matches the bg active + // where that segment ended rather than bleeding the + // '\x1b[K' bg back across color changes in the line. + var attrs *trailingFillAttributes + if line.trailingFillAttributes != nil { + if j == len(ls)-1 { + attrs = line.trailingFillAttributes + } else if len(ls[j]) > 0 { + last := ls[j][len(ls[j])-1] + attrs = &trailingFillAttributes{fg: last.fgColor, bg: last.bgColor} + } + } + vline := viewLine{ + linesX: j, linesY: i, line: ls[j], + trailingFillAttributes: attrs, + } + + if lineIdx > len(v.viewLines)-1 { + v.viewLines = append(v.viewLines, vline) + } else { + v.viewLines[lineIdx] = vline + } + lineIdx++ + } + } + + v.firstDirtyLine = len(lines) + v.tainted = false } // if autoscroll is enabled but we only have a single row of cells shown to the @@ -1599,6 +1636,7 @@ func (v *View) SetHighlight(y int, on bool) { cells = append(cells, c) } v.tainted = true + v.firstDirtyLine = min(v.firstDirtyLine, y) v.lines[y].cells = cells v.clearHover() }