From 86c9e6a20a86fb9b77c817cdbe0373189582086d Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 8 Aug 2026 19:05:06 +0200 Subject: [PATCH] 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) --- pkg/gocui/view.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index e05446207..66d0cde3a 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -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 "" }