Lock the view and guard the line index when reading a hyperlink

HyperLinkInLine read v.lines/v.viewLines without holding writeMutex, so it
could race a concurrent re-render rebuilding the buffer. It also indexed
v.lines by viewLines[y].linesY after only checking y against len(viewLines);
since refreshViewLinesIfNeeded overwrites viewLines in place without
truncating, the tail can hold stale entries pointing past a shrunk v.lines,
giving an out-of-range panic while a shorter diff is still loading.

Take writeMutex (as the sibling view methods do) and bounds-check linesY
against len(v.lines), returning "no link" rather than panicking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-08-08 12:58:59 +02:00
co-authored by Claude Opus 4.8
parent 1b70ac6fa6
commit 70b80d1375
+16 -1
View File
@@ -1654,11 +1654,26 @@ func (v *View) Word(x, y int) (string, bool) {
}
func (v *View) HyperLinkInLine(y int, urlScheme string) (string, 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 "", false
}
for _, c := range v.lines[v.viewLines[y].linesY].cells {
// 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 "", false
}
for _, c := range v.lines[linesY].cells {
if strings.HasPrefix(c.hyperlink, urlScheme) {
return c.hyperlink, true
}