From 0bce007abec55ad59d69e94c9e02f71b34f7b431 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 18 Aug 2026 17:05:49 +0200 Subject: [PATCH] Report a wrapped selection by the lines of content it covers A test asks which lines of a view are selected; a wrapping view's cursor and range anchor answer in view lines, which count the segments each line is drawn as. Going through the segment-to-line mapping keeps the answer in the terms the question was asked in, and a line the selection covers several segments of is reported once. Co-authored-by: Claude Opus 5 (1M context) --- pkg/gocui/view.go | 29 ++++++++++++++++++++++++++--- pkg/gocui/view_test.go | 6 ------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 6fbbecdb6..7bb0b7ca5 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -1985,11 +1985,12 @@ func (v *View) SelectedLine() string { v.writeMutex.Lock() defer v.writeMutex.Unlock() - if len(v.buf.lines) == 0 { + idx, ok := v.bufferLineForViewLine(v.SelectedLineIdx()) + if !ok { return "" } - return v.lineContentAtIdx(v.SelectedLineIdx()) + return v.lineContentAtIdx(idx) } // expected to only be used in tests @@ -2004,8 +2005,17 @@ func (v *View) SelectedLines() []string { startIdx, endIdx := v.SelectedLineRange() lines := make([]string, 0, endIdx-startIdx+1) + previous := -1 for i := startIdx; i <= endIdx; i++ { - lines = append(lines, v.lineContentAtIdx(i)) + // The selection is in view lines, which count the segments a wrapped line + // is drawn as; a line the selection covers several segments of is still + // the one line it is. + idx, ok := v.bufferLineForViewLine(i) + if !ok || idx == previous { + continue + } + previous = idx + lines = append(lines, v.lineContentAtIdx(idx)) } return lines @@ -2015,6 +2025,19 @@ func (v *View) lineContentAtIdx(idx int) string { return v.buf.lines[idx].cells.String() } +// bufferLineForViewLine maps a view line index, which counts the wrapped +// segments of the lines it draws, to the index of the line of content it is a +// segment of. Only call this with a lock on writeMutex. +func (v *View) bufferLineForViewLine(y int) (int, bool) { + v.refreshViewLinesIfNeeded() + + if y < 0 || y >= len(v.viewLines) { + return 0, false + } + + return v.viewLines[y].linesY, true +} + func (v *View) SelectedPoint() (int, int) { cx, cy := v.Cursor() ox, oy := v.Origin() diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index adbfb952b..2b2d50fd2 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -837,15 +837,9 @@ func TestSelectedLinesOfWrappedContent(t *testing.T) { // The cursor on the wrapped line's second half is on that line. v.FocusPoint(0, 3, false) - /* EXPECTED: assert.Equal(t, "a line that wraps", v.SelectedLine()) - ACTUAL: */ - assert.Equal(t, "four", v.SelectedLine()) // A range over both halves of the wrapped line covers one line of content. v.SetRangeSelectStart(2) - /* EXPECTED: assert.Equal(t, []string{"a line that wraps"}, v.SelectedLines()) - ACTUAL: */ - assert.Equal(t, []string{"a line that wraps", "four"}, v.SelectedLines()) }