From 165afd4652fb1c5f83ec0b5c1b10a007a2a6b90a Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 6 Jun 2026 15:31:39 +0200 Subject: [PATCH] Add View.BufferLineForViewLine to map a view line to its buffer line The diff-line parser needs to walk the unwrapped diff buffer upward from a clicked/selected row, but the row index it's handed is a view line index (which counts wrapped lines). Expose the existing internal mapping (viewLines[y].linesY) so callers can translate, with the same lock and stale-tail guard that HyperLinkInLine uses against a concurrent shorter re-render. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/gocui/view.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 2333cfc16..dfbbfd0c8 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -1675,6 +1675,33 @@ func (v *View) HyperLinkInLine(y int, urlScheme string) (string, bool) { return "", false } +// BufferLineForViewLine maps a view line index (which counts wrapped lines) to +// the index of the corresponding line in the unwrapped internal buffer (as +// returned by BufferLines). Several view lines can map to the same buffer line +// when wrapping is on. Returns false if the view line is out of range. +func (v *View) BufferLineForViewLine(y int) (int, bool) { + // Take the lock so we don't race a concurrent re-render that is rebuilding + // the buffer. + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.refreshViewLinesIfNeeded() + + if y < 0 || y >= len(v.viewLines) { + return 0, false + } + + // refreshViewLinesIfNeeded overwrites viewLines in place without truncating, + // so while a shorter re-render is loading, the tail of viewLines can still + // hold stale entries pointing past the (shrunk) v.lines. Guard against that. + linesY := v.viewLines[y].linesY + if linesY >= len(v.lines) { + return 0, false + } + + return linesY, true +} + // indexFunc allows to split lines by words taking into account spaces // and 0. func indexFunc(r rune) bool {