Skip to content

barewalker/herdr-jump

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

herdr-jump

Fuzzy-jump to — or move — any pane across all Herdr workspaces (spaces), in one key.

Herdr's indexed switches (prefix+1..9) only reach tabs within the current workspace, and there's no built-in "focus a specific tab/pane in another space" action. Moving a pane between spaces has no keybinding at all — it's CLI-only (herdr pane move). herdr-jump fills both gaps: it lists every pane across every workspace and lets you pick one with fzf.

$ herdr-jump

  enter: jump to it   ctrl-o: move it elsewhere
jump>
  rhesca / RTT / claude [idle]
  rhesca / TCM1002 / claude [idle]
  private / vecview / claude [working] *
  private / notes / shell [unknown]

(* marks the pane you're currently in.) enter focuses the selected pane, switching space and tab as needed. ctrl-o moves it instead — same list, so one key covers both. (ctrl-m can't be used for this: terminals send it as enter.)

$ herdr-jump --move                      # or ctrl-o in the list above

move which pane> …                       # same list
move private / notes / shell to>         # then: where?
  rhesca / RTT
  rhesca / TCM1002
  private / vecview
  + new tab in rhesca
  + new tab in private
  + new space

Picking a tab splits it and drops the pane in — enter splits to the right, ctrl-s splits downwards. + new tab puts the pane on a fresh tab in that space, + new space gives it a space of its own. Focus follows the pane unless you pass --stay. If the move empties the pane's old tab or space, Herdr closes them for you.

A tab Herdr creates on its own is named after its number, so herdr pane move on its own leaves a moved pane on a tab called "4". Both new-tab destinations carry the source tab's name over instead (--label / --tab-label), unless that name was itself just an auto-assigned number.

Requirements

  • herdr (the CLI and socket API of the running session)
  • fzf
  • python3

Install

Drop the script on your PATH and make it executable:

curl -fsSL https://raw.githubusercontent.com/barewalker/herdr-jump/main/herdr-jump \
  -o ~/.local/bin/herdr-jump
chmod +x ~/.local/bin/herdr-jump

Then bind it to keys with custom commands in ~/.config/herdr/config.toml:

[[keys.command]]
key = "prefix+f"
type = "pane"
command = "herdr-jump"        # use an absolute path if ~/.local/bin isn't on the command's PATH
description = "jump to any pane across all spaces"

[[keys.command]]
key = "prefix+m"
type = "pane"
command = "herdr-jump --move"
description = "move a pane to another space"

Apply it live (no restart):

herdr server reload-config

Now prefix f (e.g. ctrl+z f) opens the switcher and prefix m the mover.

Why type = "pane" and not "popup"? A popup restores focus to the invoking pane when it closes, which races with the focus change this script performs — over a remote/laggy client (herdr --remote, or SSH into the host) that desync can leave the client unresponsive until you detach and reattach. type = "pane" avoids it.

Usage

herdr-jump [--move] [--stay]

  (no args)   pick a pane across all spaces and focus it
  --move      start on the same list, but move the pane instead of jumping
  --stay      when moving, leave focus where it is instead of following the pane

--move only decides what enter does on the first list; ctrl-o gets you there from a plain herdr-jump anyway. Binding both keys is just a shortcut for "I already know I want to move something".

How it works

It's a plain script wired to a key via Herdr's custom-command keybinding — not a Herdr plugin. Everything happens outside Herdr's process:

  1. [[keys.command]] type = "pane" runs the script in a temporary pane (which gives fzf a TTY) and closes it on exit.
  2. The script calls herdr workspace/tab/pane list (live JSON over the socket) and formats one row per pane as <pane_id>|<ws_id>|<tab_id>\t<space> / <tab> / <label> [status], piped to fzf with the id column hidden (--with-nth=2). The pane the script itself runs in ($HERDR_PANE_ID) is left out — it's a meaningless choice, and a broken one to move. fzf --expect=ctrl-o reports which key ended the selection, which is how one list serves both jumping and moving.
  3. Jumping focuses the target explicitly: space, then tab, then pane. Each step matters — focusing a pane does not pull its space and tab along — and the explicit path is more deterministic over a laggy/remote client than letting one call resolve against whichever pane the UI thinks is active.
  4. Moving builds a second list from the same live state (every tab except the pane's own, plus a "new tab" row per space and a "new space" row) and runs the matching herdr pane move variant. A moved pane gets a new pane id, so the id to focus afterwards is read back out of the move result.

Because it re-reads live state every time, it never holds stale IDs and nothing is hardcoded.

Why the socket for the last focus step

The CLI's only direct-pane focus is herdr agent focus <target>, which resolves agents — on a pane with no agent running (a plain shell) it fails with agent_not_found, leaving you on the right tab but the wrong pane. The socket API has a plain pane.focus, so the script sends that one request itself over $HERDR_SOCKET_PATH (one request per connection — the server closes it after replying) and falls back to the CLI if the socket isn't reachable.

Customizing

herdr-jump is an ordinary script — edit it to show more columns (cwd, process), change the sort order, or drop the + new space destination if you never want it. Rebind the keys in config.toml and herdr server reload-config.

License

MIT — see LICENSE.

About

Fuzzy-jump to any pane across all Herdr workspaces in one key

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages