Skip to content

feat: restore wheel-scroll tab/pane navigation without rate limiting#109

Merged
GeneralD merged 1 commit into
mainfrom
feat/108-restore-wheel-scroll
Jun 20, 2026
Merged

feat: restore wheel-scroll tab/pane navigation without rate limiting#109
GeneralD merged 1 commit into
mainfrom
feat/108-restore-wheel-scroll

Conversation

@GeneralD

@GeneralD GeneralD commented Jun 20, 2026

Copy link
Copy Markdown
Owner

type breaking scope diff files tests review

Closes #108.

Background

#104 deleted wheel-scroll tab/pane navigation wholesale when it rolled back the buggy timing-based cooldown (the scroll_cooldown_ms limiter that #83 / #96 / #100 each tried and never got right). Only the limiter was the problem — the navigation itself was wanted. This restores the navigation on its own, without the rate limiter.

What changed

scroll is back as a ScrollMode enum — tab (default) / pane / off:

  • tab — scroll up = next tab, down = previous, wrapping at the ends (zellij's stock tab-bar direction, but wrap instead of clamp).
  • pane — walks the focused pane forward / backward in reading order (top→bottom, then left→right), crossing tab boundaries, wrapping globally.
  • off — the wheel is inert.

One wheel event maps to exactly one step. No Gate, no cooldown window, no Timer subscription — none of the machinery that made the old limiter flaky.

Known trade-off

zellij hands the plugin ScrollUp / ScrollDown with no device identity, so a stepless device (Magic Mouse, trackpad) reports a single flick as a burst of events and steps several tabs / panes at once. That is the accepted cost of dropping the limiter: users who dislike it set scroll "off" and navigate by keyboard, rather than living with a timing heuristic that never reconciled notched wheels and stepless devices. See #108 for the rationale.

Design

  • Pure traversal math (next_tab / next_pane / step, plus the ScrollMode / ScrollDir enums) lives in a new dependency-free scroll module — no zellij types, unit-tested off-wasm, mirroring the renderer's discipline.
  • lib.rs holds only the thin host-call dispatch (switch_tab_to / focus_terminal_pane) behind the two Mouse::ScrollUp/Down arms.
  • Pane ordering reuses the minimap's tiled-pane filter via projection::pane_ids_in_reading_order, so the wheel and the minimap can never disagree on which panes exist.

Permission safety

Rides the existing ChangeApplicationState grant — no new permission. Existing installs gain this on update without a re-grant, so it can't trigger the silent-freeze trap (zellij#4982).

Tests

scroll.rs 100% / config.rs 99.58% / projection.rs 100% / lib.rs 99.25% line coverage; 263 native unit tests pass, clean wasm build, clean clippy.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added scroll configuration option for the tab-bar plugin to control mouse-wheel behavior. Choose from three modes: switch between tabs (tab), navigate panes across tab boundaries (pane), or disable wheel handling entirely (off). Each wheel event triggers exactly one action with automatic wrapping at boundaries. No additional permissions required.

…108)

#104 deleted wheel-scroll navigation wholesale when it rolled back the
buggy timing-based cooldown (#83/#96/#100). Restore the navigation on its
own, dropping the rate limiter that caused the trouble.

`scroll` is a ScrollMode enum {tab (default), pane, off}: `tab` switches
tabs (wrapping), `pane` walks the focused pane in reading order across tab
boundaries (wrapping globally), `off` opts out. One wheel event maps to
exactly one step — no Gate, no cooldown window, no Timer subscription. A
stepless device (Magic Mouse, trackpad) bursts per flick and steps several
at once; users who dislike that set `scroll = off` rather than living with
a limiter that never reconciled both device classes.

The pure traversal math (next_tab / next_pane / step) lives in a new
dependency-free `scroll` module, mirroring the renderer's off-wasm testable
discipline; lib.rs holds only the thin host-call dispatch. Rides the
existing ChangeApplicationState grant — no new permission, so existing
installs gain it on update without a re-grant (freeze-safe, zellij#4982).
Copilot AI review requested due to automatic review settings June 20, 2026 10:41
@GeneralD GeneralD self-assigned this Jun 20, 2026
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea94342d-c27a-4770-bf7e-e308c68eaedd

📥 Commits

Reviewing files that changed from the base of the PR and between f6e082c and 7623a0e.

📒 Files selected for processing (5)
  • README.md
  • src/config.rs
  • src/lib.rs
  • src/projection.rs
  • src/scroll.rs

📝 Walkthrough

Walkthrough

Restores wheel-scroll tab/pane navigation to the zellij tab-bar plugin without any rate limiting. A new scroll config key accepts "tab", "pane", or "off". A new src/scroll.rs module provides traversal math; src/projection.rs gains a reading-order helper; src/config.rs parses the field; and src/lib.rs dispatches wheel events to tab/pane navigation or no-op accordingly.

Changes

Wheel-scroll navigation feature

Layer / File(s) Summary
ScrollDir/ScrollMode types and traversal math
src/scroll.rs
Defines ScrollDir and ScrollMode enums with Default, implements case-sensitive FromStr, and provides next_tab (wrapping tab-index step), next_pane (wrapping pane-id step), and a private step helper, all with unit tests.
pane_ids_in_reading_order helper
src/projection.rs
Adds pane_ids_in_reading_order that filters tiled terminal panes, sorts by (pane_y, pane_x), and returns their IDs; new tests cover ordering and exclusion of chrome panes.
Config: scroll field, parsing, and defaults
src/config.rs
Adds pub scroll: ScrollMode to Config with DEFAULT_SCROLL = ScrollMode::Tab, wires "scroll" key parsing with fallback in from_configuration, and adds/updates unit tests for default and valid/invalid values.
Event dispatch and State scroll navigation
src/lib.rs
Exports the scroll module; adds Mouse::ScrollUp/Down arms to update() returning false; implements scroll(), scroll_tabs(), scroll_panes(), focused_pane_id(), and pane_focus_order() on State; tests cover all scroll modes, missing-anchor no-ops, and update() routing.
README: scroll config documentation
README.md
Adds scroll "tab" to the example KDL snippet and documents the three values, wrap-around semantics, one-event-one-step behavior, and permission requirements.

Sequence Diagram(s)

sequenceDiagram
    participant Zellij
    participant update as update()
    participant scroll as State::scroll()
    participant scroll_tabs as scroll_tabs()
    participant scroll_panes as scroll_panes()

    Zellij->>update: Mouse::ScrollUp / ScrollDown
    update->>scroll: self.scroll(dir)

    alt config.scroll == Tab
        scroll->>scroll_tabs: next_tab(active, count, dir)
        scroll_tabs->>Zellij: switch_tab_to(next_index)
    else config.scroll == Pane
        scroll->>scroll_panes: focused_pane_id() → pane_focus_order()
        scroll_panes->>Zellij: focus_terminal_pane(next_pane_id)
    else config.scroll == Off
        scroll-->>update: no-op
    end

    update-->>Zellij: false (no repaint requested)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #108 (feat: restore wheel-scroll tab/pane navigation without rate limiting) — This PR directly implements the feature requested in #108: restoring ScrollUp/ScrollDown handling without a rate limiter, adding a scroll config key (extended to three modes rather than bool), restoring pane_ids_in_reading_order, and keeping the existing permission set.
  • tracking: mouse interaction on the tab bar #77 — The PR implements the scroll-wheel tab/pane switching item tracked in #77's feature checklist.

Possibly related PRs

  • GeneralD/zellij-tabmap#90: Both PRs add a scroll config field to Config and wire Mouse::ScrollUp/Down in src/lib.rs; the retrieved PR added cooldown/gate logic that this PR deliberately omits.
  • GeneralD/zellij-tabmap#104: That PR removed the entire wheel-scroll navigation feature; this PR restores it at the same code sites (src/lib.rs, src/config.rs, src/projection.rs) without the rate-limiting machinery.
  • GeneralD/zellij-tabmap#97: Both PRs modify the same Mouse::ScrollUp/Down handler in src/lib.rs — the retrieved PR changed cooldown re-arming logic over the same scroll event path this PR now replaces with a direct dispatch.

Poem

🐇 Hop, hop, the wheel goes round,
No timer lost, no lag to be found!
"tab", "pane", or "off" — your pick,
One notch = one step, perfectly slick.
The rabbit scrolls with gleeful cheer,
Each tab glides by, crisp and clear! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main feature: restoring wheel-scroll navigation for tabs/panes without rate limiting, which is the core change across all modified files.
Linked Issues check ✅ Passed The PR fully satisfies issue #108: it restores wheel-scroll navigation (ScrollUp/ScrollDown handlers), implements ScrollMode enum with tab/pane/off modes, restores pane_ids_in_reading_order helper, uses ChangeApplicationState permission, and eliminates all rate-limiting mechanisms as required.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to wheel-scroll navigation restoration and its supporting infrastructure; no unrelated features, config keys (scroll_cooldown_ms), rate limiters, or timer subscriptions were added per requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/108-restore-wheel-scroll

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.17695% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lib.rs 98.37% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores mouse wheel navigation over the tab bar (removed in #104), reintroducing tab/pane traversal without any timing-based rate limiting, and gates the behavior behind a new scroll config mode.

Changes:

  • Adds a new dependency-free scroll module implementing pure traversal logic (ScrollMode, ScrollDir, next_tab, next_pane) with unit tests.
  • Wires Mouse::ScrollUp/ScrollDown in State::update() to dispatch host calls (switch_tab_to, focus_terminal_pane) according to the configured scroll mode.
  • Reintroduces pane reading-order traversal via projection::pane_ids_in_reading_order, and documents the new scroll config key in README.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/scroll.rs New pure traversal module (tab/pane wrapping step logic) with unit tests.
src/projection.rs Adds pane_ids_in_reading_order helper (tiled panes only) plus tests.
src/lib.rs Restores wheel event handling and dispatches to tab/pane navigation based on config.
src/config.rs Adds scroll: ScrollMode config key with default + parsing/tests.
README.md Documents the scroll setting and behavior/trade-offs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@GeneralD GeneralD merged commit 68fdf6a into main Jun 20, 2026
7 checks passed
@GeneralD GeneralD deleted the feat/108-restore-wheel-scroll branch June 20, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: restore wheel-scroll tab/pane navigation without rate limiting

2 participants