From abc16e904c5fd81554d4a6e4fd3ac651e340486d Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 9 Oct 2026 08:32:52 +0200 Subject: [PATCH] Keep horizontally scrolled content out of the inclusion gutter If a view that shows the inclusion gutter is scrolled horizontally, its content is drawn over the gutter. The marker of a marked line is overwritten, and the content starts the gutter's width too far to the left. The draw loop skips the cells scrolled past the left edge only until x reaches 0, but the content area starts at the gutter's width. Nothing scrolls such a view horizontally yet. A later commit lets the focused main view scroll sideways, and the gutter shows there while building a custom patch. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/gocui/view.go | 11 ++++++----- pkg/gocui/view_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index f48a4704e..695ab0f4e 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -1878,7 +1878,7 @@ func (v *View) draw(isWindowFocused bool) { var c cell for x < maxX { - if x < 0 && cellIdx >= len(vline.line) { + if x < gutterWidth && cellIdx >= len(vline.line) { // no more characters to write so we're only going to be printing empty cells // past this point x = gutterWidth @@ -1891,8 +1891,9 @@ func (v *View) draw(isWindowFocused bool) { c = vline.line[cellIdx] } - // Skip the cells scrolled out to the left. - if x < 0 && x+c.width <= 0 { + // Skip the cells scrolled past the left edge of the content area; the + // content area starts after the gutter. + if x < gutterWidth && x+c.width <= gutterWidth { x += c.width cellIdx++ continue @@ -1901,10 +1902,10 @@ func (v *View) draw(isWindowFocused bool) { // A terminal can't show half of a double-width character, so one that is cut // off by the left or right edge shows as a blank in the column of it that is // on screen. - if x < 0 || x+c.width > maxX { + if x < gutterWidth || x+c.width > maxX { c.chr = " " c.width = 1 - x = max(x, 0) + x = max(x, gutterWidth) } fgColor := c.fgColor diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index edc800990..3a5ba5814 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -1326,6 +1326,31 @@ func TestClickHyperlinkBesideTheInclusionGutter(t *testing.T) { opened) } +// Content scrolled to the right goes under the gutter, not over it: the gutter +// stays where it is, and the content shows from the scroll position onwards. +func TestInclusionGutterWithHorizontallyScrolledContent(t *testing.T) { + WithSimulationScreen(t, 14, 6) + + v := NewView("name", 0, 0, 11, 5, OutputNormal) // InnerWidth 10 + v.InclusionGutterMarker = "✓" + + v.writeString("0123456789abcdef\n0123456789abcdef\n") + v.SetInclusionGutter(true, []bool{true, false}) + v.SetOriginX(3) + v.draw(true) + + row := func(y int) string { + s := "" + for x := 1; x <= 10; x++ { + chr, _, _ := Screen.Get(x, y) + s += chr + } + return s + } + assert.Equal(t, "✓ 3456789a", row(1)) + assert.Equal(t, " 3456789a", row(2)) +} + // A marked line the view wraps is marked on every segment it is drawn as, so that // the mark doesn't look like it belongs to the first part of the line alone. The // gutter takes its columns out of the width the content wraps in.