Lock the view while reading viewLines on the event-handling thread

hyperlinkAt (the click path) and onMouseMove/findHyperlinkAt (hover) read
v.viewLines without holding writeMutex, unlike every other reader. They run
on the event-handling goroutine, so a re-render on the task goroutine can
shrink or rebuild viewLines between the bounds check and the indexing,
causing an out-of-range panic (observed: "index out of range [60] with
length 0" while hovering during a diff re-render).

Take writeMutex for the duration, like the other viewLines readers do, so
the check and the access see the same slice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-08-08 19:05:06 +02:00
parent e3fe321080
commit 86c9e6a20a

View file

@ -2098,6 +2098,9 @@ func (v *View) onMouseMove(x int, y int) {
return
}
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
// newCx and newCy are relative to the view port, i.e. to the visible area of the view
newCx := x - v.x0 - 1
newCy := y - v.y0 - 1
@ -2119,6 +2122,9 @@ func (v *View) onMouseMove(x int, y int) {
// hyperlinkAt returns the hyperlink at the given position of the view's
// content, or an empty string if there is none.
func (v *View) hyperlinkAt(x, y int) string {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
if y < 0 || y >= len(v.viewLines) || x < 0 || x >= len(v.viewLines[y].line) {
return ""
}