Skip to content

Opt-in edge spillover: carry focus into the neighbouring tab / workspace - #10

Open
imagirom wants to merge 1 commit into
paulbkim-dev:mainfrom
imagirom:main
Open

Opt-in edge spillover: carry focus into the neighbouring tab / workspace#10
imagirom wants to merge 1 commit into
paulbkim-dev:mainfrom
imagirom:main

Conversation

@imagirom

@imagirom imagirom commented Aug 3, 2026

Copy link
Copy Markdown

Both the code and the PR description have largely been AI generated. I have read through both, and understood most of the code.
I have been using the feature for a couple of days without issues.


Closes #6.

Ctrl+h/j/k/l stops dead at the edge of the pane layout. With HERDR_NAV_SPILL=1 the motion carries on into the neighbouring container instead, i3-style:

  • Ctrl+h / Ctrl+l at the left/right edge → previous/next tab in this workspace
  • Ctrl+k / Ctrl+j at the top/bottom edge → previous/next workspace

Both wrap, and focus then walks to the far side of the layout it lands in, so crossing rightwards puts you on the leftmost pane and the move reads as one continuous motion. Off by default, as agreed in #6.

Implementation notes

  • No extra probe. herdr pane focus already reports focus.changed along with the tab and workspace the move happened in, so the edge is found by simply attempting the move. One CLI call in the common case, and no dependency on pane edges.
  • Pane targeting. Rebased on Target the invoking pane instead of --current #8 and follows it: the move targets the invoking pane. The walk afterwards is the one place that stays on --current, deliberately — focusing the tab/workspace has already put the globally focused pane inside the container we just entered, which is what we want to walk from, while $HERDR_PANE_ID is back in the container we left.
  • Stepping order. Tabs and workspaces are stepped in the order tab list / workspace list return them — their on-screen order. Indexing by .number looks right and isn't: for tabs it's an id, not a position, so it stays put when a tab is reordered and closing a tab leaves a hole (on 0.7.5, closing tab 2 leaves 1, 3, 4, 5). Workspace order isn't creation order either — a new local workspace can appear before existing remote ones.
  • Editor side. editor/vim.vim and editor/nvim.lua called herdr pane focus directly, so crossing out of a Vim split at a layout edge skipped all of this. They now call navigate.sh --no-detect when the file still sits in a plugin checkout, and keep the direct call otherwise — so cp editor/nvim.lua ~/.config/nvim/after/plugin/ behaves exactly as it does today. --no-detect skips the Vim check, which is what stops the key being handed straight back to Vim.

Compatibility

Behaviour is unchanged with HERDR_NAV_SPILL unset. Without jq it degrades to plain pane focus, same as the existing Vim detection. A single tab / single workspace is a no-op, and without $HERDR_PANE_ID it falls back to global focus as #8 does. min_herdr_version is unchanged — the only new CLI surface is tab list / workspace list / tab focus / workspace focus.

Testing

Manual, on herdr 0.7.5 (Linux), in a scratch workspace with a side-by-side tab, a stacked tab and a single-pane tab:

  • all four directions at layout edges, plus wrap-around at both ends of both axes
  • movement inside a multi-pane tab — spillover must not fire, and doesn't
  • single-tab and single-workspace no-ops
  • from inside Vim and Neovim splits, both from a checkout and with nvim.lua copied elsewhere (falls back to plain pane focus, still crosses panes)
  • jq off $PATH; HERDR_NAV_SPILL unset; $HERDR_PANE_ID unset; bad or missing direction argument exits non-zero

Unrelated, spotted while rebasing

#8 switched editor/nvim.lua to --pane "$HERDR_PANE_ID" but left the same --current call in editor/vim.vim. I've left it alone to keep this PR to one thing — happy to fix it here or in a separate PR, whichever you prefer.

Thanks @inadicis for raising it — happy to take you up on the logic review.

Ctrl+h/j/k/l stops dead at the edge of the pane layout. With
HERDR_NAV_SPILL=1 the motion carries on into the neighbouring container
instead, i3-style:

  left/right at the horizontal edge -> previous/next tab in this workspace
  up/down    at the vertical edge   -> previous/next workspace

Both wrap, and focus then walks to the far side of the layout it lands in,
so crossing rightwards puts you on the leftmost pane and the whole thing
reads as one continuous motion. Off by default, same as
HERDR_NAV_PASSTHROUGH_RE.

`pane focus` already reports whether focus changed and which tab and
workspace it happened in, so the edge is found by simply attempting the
move: no separate probe, and one CLI call in the common case.

The move targets the invoking pane, following e1ba690. The walk afterwards
is the one place that stays on --current, deliberately: focusing the tab or
workspace has already put the globally focused pane inside the container we
just entered, which is what we want to walk from -- $HERDR_PANE_ID is back
in the container we left.

Tabs and workspaces are stepped in the order herdr lists them, which is
their on-screen order. Indexing by .number would be wrong for tabs -- that
is an id, not a position: it stays put when a tab is reordered, and closing
a tab leaves a hole in the sequence.

The editor side called `herdr pane focus` directly, so crossing out of a
Vim split at a layout edge skipped all of this. It now goes through
navigate.sh --no-detect when the file still sits in a plugin checkout, and
keeps the direct call for when nvim.lua has been copied into after/plugin,
as the README suggests. Without jq it degrades to plain focus, as before.
@paulbkim-dev

Copy link
Copy Markdown
Owner

I will take a look over the weekend

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.

seemless workspace navigation

2 participants