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) <[email protected]>
This commit is contained in:
Stefan Haller
2026-10-10 13:44:43 +02:00
co-authored by Claude Opus 5.5
parent 99f92d00ef
commit abc16e904c
2 changed files with 31 additions and 5 deletions
+6 -5
View File
@@ -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
+25
View File
@@ -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.