Skip to content

feat: Vim tab with Vimium-C-style link hints and smooth scrolling - #968

Open
imvasen wants to merge 3 commits into
crittermike:masterfrom
imvasen:feat/vim-mode
Open

imvasen wants to merge 3 commits into
crittermike:masterfrom
imvasen:feat/vim-mode

Conversation

@imvasen

@imvasen imvasen commented Sep 11, 2026

Copy link
Copy Markdown

What this does

Adds a Vim tab to the options page and rebuilds the two features behind it —
link hints and scrolling — to match the behaviour of Vimium-C.

Three parts:

1. Vim tab (new screen)

Global settings for the Vim-style actions, stored as vimSettings in sync storage
(useVimSettings). Changes save automatically and reach open tabs right away.

  • Link hints: hint keys, pick mode (type the label / type the link text), wait for Enter,
    detection level, look inside web components, hint scrollable areas, skip covered elements
  • Scrolling: smooth scrolling, scroll step, large scroll step, animation duration

Per-shortcut hintChars and smoothScrolling still win over the global values.

2. Link hint detection — finds far more targets

src/utils/hint-targets.ts replaces the old fixed CSS selector:

  • Geometry reads getClientRects() instead of the bounding box, so a link wrapped over two
    lines gets its hint on the first line box instead of over empty space. A wrapper whose own
    boxes are empty descends into floated or positioned children instead of being dropped.
  • Clickability is layered: known tags, then attributes (onclick, ARIA role, ng-click,
    jsaction, tabindex), then fuzzy signals (class-name patterns, computed cursor: pointer,
    scrollable containers).
  • Open shadow roots are traversed, image maps expand to one hint per <area>, and covered
    elements are dropped with a hit test that fails open.
  • A weak signal inside an already-hinted ancestor is suppressed, so a cursor: pointer card
    gets one hint and not one per child.
  • The costly work is capped (MAX_ELEMENTS, MAX_COMPUTED_STYLES) to stay quick on large pages.

link-hints.ts keeps the overlay and adds filter mode (type the link text, pick a number,
optional wait for Enter), blue hints for scrollable panes, and a pointer/mouse event sequence
before the native click for sites that ignore a bare click().

3. Scrolling — runs in the page

src/utils/smooth-scroll.ts moves scrolling out of the background script. Every scroll
keypress used to cost a message round trip plus a chrome.scripting.executeScript injection,
and CSS behavior: 'smooth' restarted its animation on each repeat — that is the stutter you
feel when you hold a key.

The new scroller runs one requestAnimationFrame loop per element and axis, and a repeat
keypress adds to the running animation instead of restarting it. Reversing direction
cancels the old motion. It also walks up to the real scrollable ancestor and remembers the
last container scrolled, instead of only looking at document.activeElement.

Scroll actions moved into contentScriptActions in the background script and fall back to the
old injected handlers only when no content script answers.

Licensing

Vimium-C is Apache-2.0 (MIT for code before 2023-07-15), which is compatible with this MIT
project. No Vimium-C source is copied — its content scripts are written against a private
runtime and build-time constant folding, so these are reimplementations of the techniques.
NOTICE.md records the attribution, a file-by-file table of what came from where, and the
changes made relative to Vimium-C.

Behaviour changes for existing users

  • Scroll step is now 100px (was 50) and smooth scrolling is on by default, matching
    Vimium-C. Both are adjustable in the Vim tab.
  • An explicit per-shortcut smooth-scrolling choice still overrides the global setting, so
    anyone who turned it off keeps that.

Not included

  • Hints inside iframes — the cross-frame coordination is a separate piece of work.
  • Vimium-C's MAIN-world addEventListener hook for true click-listener detection. It is the
    single biggest remaining win, but it patches a DOM builtin on every page, so it deserves its
    own PR and an opt-in setting.

Testing

  • 840 unit tests pass (80 new across hint-targets, smooth-scroll and vim-settings).
  • Verified in headless Chrome on a synthetic page, not only in jsdom:
    • all target types hinted, including a cursor: pointer-only div, a shadow-DOM button,
      role="button", an onclick span and a scrollable pane
    • a full-page overlay drops hints to 0
    • one j moves exactly 100px; ten fast presses sample 61 → 140 → 243 → … → 943 and settle
      at exactly 1000, so motion is continuous with no dropped or doubled steps
    • a 250px step set in the Vim tab moves 250px; a per-shortcut smoothScrolling: false
      scrolls instantly
    • filter mode shows numeric labels, typing plain narrows to one match, Enter opens it
    • "Scroll to bottom" lands exactly at maximum scroll
  • Production build clean. Vim tab screenshotted in light and dark, and added to
    npm run visual-review.

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.

1 participant