diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index 695ab0f4e..b10edd387 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -2693,6 +2693,37 @@ func (v *View) ScrollRight(amount int) { v.clearHover() } +// ClampOriginXToContent scrolls the view back to the left if it is scrolled further +// than its content needs, so that the end of the widest line is at the right edge. +// A view that doesn't wrap keeps its horizontal scroll position when its content +// changes, and the new content may not reach as far. +func (v *View) ClampOriginXToContent() { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + if v.ox == 0 { + return + } + + v.refreshViewLinesIfNeeded() + widest := 0 + for _, vline := range v.viewLines { + width := 0 + for _, c := range vline.line { + width += c.width + } + widest = max(widest, width) + } + + contentWidth := v.InnerWidth() - v.inclusionGutterWidth() + newOx := min(v.ox, max(0, widest-contentWidth)) + if newOx != v.ox { + v.SetOriginX(newOx) + + v.clearHover() + } +} + func (v *View) adjustDownwardScrollAmount(scrollHeight int) int { _, oy := v.Origin() y := oy diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index 3a5ba5814..b123ea3ad 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -1351,6 +1351,36 @@ func TestInclusionGutterWithHorizontallyScrolledContent(t *testing.T) { assert.Equal(t, " 3456789a", row(2)) } +// A view scrolled further right than its content needs goes back as far as it takes +// to put the end of the widest line at the right edge, and no further. +func TestClampOriginXToContent(t *testing.T) { + v := NewView("name", 0, 0, 11, 5, OutputNormal) // InnerWidth 10 + v.InclusionGutterMarker = "✓" + + // The widest line is 16 wide. + v.writeString("0123456789abcdef\nshort\n") + + v.SetOriginX(4) + v.ClampOriginXToContent() + assert.Equal(t, 4, v.OriginX(), "the widest line still reaches the right edge") + + v.SetOriginX(10) + v.ClampOriginXToContent() + assert.Equal(t, 6, v.OriginX()) + + // The gutter leaves the content eight columns. + v.SetInclusionGutter(true, []bool{false, false}) + v.SetOriginX(10) + v.ClampOriginXToContent() + assert.Equal(t, 8, v.OriginX()) + + // Content that fits needs no scrolling at all. + v.Clear() + v.writeString("short\n") + v.ClampOriginXToContent() + assert.Equal(t, 0, v.OriginX()) +} + // 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. diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index 5b87a9d59..114c27705 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -235,6 +235,7 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { view.SetOriginY(newOriginY) } + view.ClampOriginXToContent() gui.updateDiffPaneDecorations(view, true) gui.clampDiffSelectionToContent(view)