Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to herdr-automatic-rename are documented here. The format fo

## [Unreleased]

### Added

- Workspace labels can be shown under a shorter name than their directory carries. `WORKSPACE_SUBSTITUTE_SETS` is an ordered list of `sed -E` rewrites applied to the name herdr derives from the directory, so `'s|^worktree-|wt-|'` puts `worktree-feature` in the sidebar as `wt-feature`. The rewrite reaches the label and nothing else: the directory and the Git worktree keep their names, and a tab inside that directory still drops the workspace's name out of its own label rather than re-injecting the long spelling it was shortened to lose.

Only a derived name is rewritten. A name you typed is left alone for good, the same promise the tab opt-out makes, with the same gap in it: herdr exposes no way to tell a typed name from the derivation it happens to match exactly.

Numbering and rewriting are the two reasons the workspace pass runs, so a rewrite reaches a workspace whose numbering was never turned on. A rewrite is derived fresh every pass rather than built out of the label the last one wrote, so deleting the rules puts the derived names back at the next event by itself. With numbering off there is no next pass to do that, and the labels keep their last rewrite until `clear`.

`clear` hands back the derived name too, where it used to strip the number off the rewrite and leave that standing. It is documented as the last step before uninstall, after which the plugin that could have restored the label is gone.

Based on [#15](https://github.com/qu8n/herdr-automatic-rename/pull/15) by @engineersamuel.

## [0.10.0] - 2026-09-10

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ To customize a config, write it to `~/.config/herdr-automatic-rename/config.sh`
| `HIDE_SHELL` | `0` | `1` shows nothing for a plain prompt, so herdr's own number shows through. |
| `ICONS_ENABLED` | `0` | Show Nerd Font glyph in front of the name. |
| `PROGRAM_ALIASES` | none | Rename programs on the tab: `"lazygit=lg"`. |
| `WORKSPACE_SUBSTITUTE_SETS` | none | Rewrite the workspace label herdr derives from the directory: `'s\|^worktree-\|wt-\|'` shows `worktree-feature` as `wt-feature`. Display only, so the directory and the Git worktree keep their names. |
| `MAX_NAME_LEN` `MAX_TITLE_LEN` `MAX_CONTEXT_LEN` `MAX_BRANCH_LEN` | `20` `MAX_NAME_LEN + 8` `12` `12` | Character budget per part of the tab name. |

See [config.example.sh](config.example.sh) for the full configuration details.
Expand Down
166 changes: 143 additions & 23 deletions automatic-rename.sh

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions config.example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@
# Anything else in brackets ("[wip] foo") is left alone, digits are the only
# trigger.

# Ordered `sed -E` rewrites for the name a workspace takes from its directory.
# They change the label herdr shows and nothing else: the directory keeps its
# name, the Git worktree keeps its name, and a tab in that directory goes on
# deduping against the directory's own name rather than the rewrite. Empty by
# default. For example, shorten "worktree-feature" to "wt-feature":
# WORKSPACE_SUBSTITUTE_SETS=(
# 's|^worktree-|wt-|'
# )
#
# Only a name this plugin derived is rewritten. Type a workspace name yourself
# and it is left alone for good, the way the tab opt-out works -- except that
# herdr offers no way to tell a hand-typed name from the derivation it happens
# to match exactly, so a name you type that IS the directory name reads as ours.
#
# The rewrite is derived fresh on every pass rather than built out of the label
# the last one wrote, so deleting the rules puts the derived names back at the
# next herdr event. With workspace numbering off there is no next pass to do it,
# and the labels keep their last rewrite until the `clear` action, which is what
# that action is for.

# ---- naming knobs (only used when NAME_TABS=1) ----

# 1 = put the CONTEXT in front of the program: the directory the pane sits in,
Expand Down
20 changes: 20 additions & 0 deletions naming.sh
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ declare -p SUBSTITUTE_SETS >/dev/null 2>&1 || SUBSTITUTE_SETS=(
's|.*poetry shell.*|poetry|'
)

# The same, for the directory-derived label a WORKSPACE carries. Empty by
# default: a workspace shows the name of the directory it sits in, and a rule
# here is somebody asking for a shorter spelling of it.
#
# Not a naming knob, despite the company it keeps -- it applies whether or not
# tabs are named. It lives in this file because it is string-in / string-out
# like everything else here, and because config.sh has to get first refusal on
# the default (see the `declare -p` note at the top).
declare -p WORKSPACE_SUBSTITUTE_SETS >/dev/null 2>&1 || WORKSPACE_SUBSTITUTE_SETS=()

# Titles that name the agent instead of the work it is doing. An agent sets one
# of these before it has a task (at startup, or once a session is cleared), and a
# tab reading "Claude Code" says less than "claude" does. Matched case-insensitively
Expand Down Expand Up @@ -379,6 +389,16 @@ ar_subst() {
printf '%s' "$s"
}

# ar_ws_subst <string> -> string with WORKSPACE_SUBSTITUTE_SETS applied in order.
# The empty rule list is the common case and forks nothing.
ar_ws_subst() {
local s=$1 expr
for expr in "${WORKSPACE_SUBSTITUTE_SETS[@]}"; do
s=$(printf '%s' "$s" | sed -E "$expr")
done
printf '%s' "$s"
}

# ar_trunc <string> <max> -> the string cut to <max> Unicode codepoints.
#
# Not bytes: bash's ${#s} and ${s:0:$max} count those under a C/POSIX locale
Expand Down
46 changes: 46 additions & 0 deletions tests/test_context.sh
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,50 @@ check_contains "the program names the tab" "$out" "tab rename w1:t1 codex"
check_absent "another agent's transcript is not read" "$out" "Belongs to claude"
teardown

# ======================================================================
# Scenario 14: a workspace rewritten by WORKSPACE_SUBSTITUTE_SETS still swallows
# its own name out of its tabs. The rewrite is the sidebar's alone -- the
# directory is still called what it is called -- so the tabs go on deduping
# against the DERIVED name. Deduping against the rewrite instead would have a
# tab in worktree-feature/ re-inject the long spelling the sidebar was
# shortened to lose ("worktree-feature > nvim" under a "wt-feature" heading).
# ======================================================================
setup
printf "WORKSPACE_SUBSTITUTE_SETS=('s|^worktree-|wt-|')\n" \
>"$HERDR_AUTOMATIC_RENAME_CONFIG"
printf '{"version":7,"collapsed_space_keys":[],"workspaces":[
{"id":"w1","label":null,"identity_cwd":"/home/u/dev/worktree-feature"}]}\n' \
>"$SB/session.json"
fixture snapshot.json <<'JSON'
{"result":{"snapshot":{
"workspaces":[{"workspace_id":"w1","label":"worktree-feature"}],
"tabs":[
{"tab_id":"w1:t1","label":"1","pane_count":1,"focused":true,"workspace_id":"w1"},
{"tab_id":"w1:t2","label":"2","pane_count":1,"focused":false,"workspace_id":"w1"}
],
"panes":[
{"pane_id":"p1","tab_id":"w1:t1","focused":true,"cwd":"/home/u/dev/worktree-feature"},
{"pane_id":"p2","tab_id":"w1:t2","focused":true,"cwd":"/home/u/dev/worktree-feature/web"}
],
"layouts":[
{"tab_id":"w1:t1","focused_pane_id":"p1"},
{"tab_id":"w1:t2","focused_pane_id":"p2"}
]
}}}
JSON
fixture procinfo_p1.json <<'JSON'
{"result":{"process_info":{"foreground_process_group_id":100,
"foreground_processes":[{"pid":100,"argv0":"nvim","cmdline":"nvim README.md"}]}}}
JSON
fixture procinfo_p2.json <<'JSON'
{"result":{"process_info":{"foreground_process_group_id":200,
"foreground_processes":[{"pid":200,"argv0":"nvim","cmdline":"nvim main.go"}]}}}
JSON
HOME=/home/u run_event tab.focused
out=$(log)
check_contains "the workspace wears the rewrite" "$out" "workspace rename w1 wt-feature"
check_contains "and its tabs still drop its directory" "$out" "tab rename w1:t1 nvim"
check_contains "a subdirectory still says which" "$out" "tab rename w1:t2 web${SEP}nvim"
teardown

t_summary
Loading
Loading