Skip to content

fix: mouse scroll snaps back instead of scrolling freely #191

@oobagi

Description

@oobagi

Problem

When scrolling with the mouse wheel, the viewport snaps back to the cursor position instead of scrolling freely. Users can only navigate with arrow keys. This affects both edit and view mode:

  • Edit mode: Every keystroke calls updateViewport()SetContent(), which resets scroll position via GotoBottom() when yOffset > maxYOffset(). The auto-scroll-to-cursor logic then forces the viewport back to the cursor.
  • View mode: Toggling a checklist checkbox calls updateViewport(), which triggers the same SetContent() reset path.

Context

The root cause is in the forked viewport at fork/bubbles/viewport/viewport.go:259-261:

if m.YOffset() > m.maxYOffset() {
    m.GotoBottom()
}

This runs inside SetContentLines() every time content is set. In edit mode, editor.go:1272-1273 calls SetContent() on every update, and the auto-scroll logic at lines 1285-1358 then overrides any manual scroll position.

View mode correctly skips auto-scroll (editor.go:1277-1280) but still hits the SetContent reset when checklist items are toggled.

Possible approaches

Approach A: Track manual scroll state

  • How: Add a userScrolled flag to the viewport. Set it on mouse wheel events, clear it on cursor movement / keystrokes. Skip auto-scroll-to-cursor when the flag is set. Preserve yOffset in SetContent() when flag is set.
  • Pros: Clean separation of manual vs automatic scrolling
  • Cons: Need to decide exactly when to clear the flag
  • Files: fork/bubbles/viewport/viewport.go, internal/editor/editor.go

Approach B: Save/restore yOffset around SetContent

  • How: Before calling SetContent(), save current yOffset. After setting content, restore it (clamped to new maxYOffset). Remove the GotoBottom() call from SetContentLines().
  • Pros: Simpler, no new state to manage
  • Cons: May cause viewport to show blank space if content shrinks
  • Files: fork/bubbles/viewport/viewport.go, internal/editor/editor.go

Approach C: Hybrid — preserve offset + debounce auto-scroll

  • How: Remove GotoBottom() from SetContentLines(), preserve yOffset. In edit mode, only auto-scroll to cursor after a keystroke/cursor-move, not on every updateViewport() call.
  • Pros: Best UX — scroll stays put until user types
  • Cons: Slightly more complex, needs careful event routing
  • Files: fork/bubbles/viewport/viewport.go, internal/editor/editor.go

Tasks

  • Remove or gate the GotoBottom() call in SetContentLines() (viewport.go:259-261)
  • Preserve yOffset across SetContent() calls (clamp to new max)
  • Add userScrolled state to viewport, set on wheel events
  • Gate auto-scroll-to-cursor in edit mode behind cursor-movement check
  • Ensure view mode scroll position is never reset by updateViewport()
  • Test manual scroll + typing interplay in edit mode
  • Test manual scroll + checklist toggle in view mode

Test plan

  • Edit mode: scroll down with mouse, type a character — viewport should not snap back
  • Edit mode: scroll down with mouse, press arrow key — viewport scrolls to follow cursor
  • View mode: scroll down with mouse, toggle checklist — viewport stays at scroll position
  • View mode: scroll to bottom, content is shorter than viewport — no blank space shown
  • Long document: scroll to middle, resize terminal — viewport stays near same content
  • Page Up/Down keys still work correctly after manual scroll

Scope

Type: bug
Size: medium

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions