tmux: window move should insert, not swap #1

Closed
opened 2026-03-26 02:53:43 +00:00 by forgejo_admin · 4 comments
Contributor

Type

Bug

Lineage

standalone — discovered during tmux daily use

Repo

ldraney/tmux-custom (GitHub)

What Broke

tmux-pain-control plugin binds prefix + < and prefix + > to swap-window -d -t -1 / swap-window -d -t +1. This swaps two windows' positions rather than inserting and shifting. Creating a new window (position 5) and pressing < to move it to position 1 causes window 1 to teleport to position 5 — disrupting the entire window order.

Repro Steps

  1. Open tmux with windows [1:mail, 2:code, 3:logs, 4:ssh]
  2. Create new window — becomes 5:new
  3. Press prefix + < repeatedly to move window 5 to position 1
  4. Observe: each press swaps two windows — window at position 1 ends up at position 5 instead of shifting right

Expected Behavior

prefix + < should bubble the current window left one position at a time via adjacent swap. Windows not directly involved in the swap should remain in their original order. Result: [NEW, mail, code, logs, ssh] — everything shifts right by one.

Environment

  • Platform: Arch Linux (archbox)
  • tmux with TPM, plugins: tmux-sensible, tmux-pain-control, catppuccin/tmux
  • Config: ~/tmux-custom/.tmux.conf

Acceptance Criteria

  • prefix + < moves current window left one position without displacing non-adjacent windows
  • prefix + > moves current window right one position without displacing non-adjacent windows
  • Repeatable (-r) — mash key to slide through multiple positions
  • Works with renumber-windows on (line 72 in config)
  • dotfiles — project
  • tmux-pain-control — upstream plugin providing the bindings being overridden
### Type Bug ### Lineage standalone — discovered during tmux daily use ### Repo `ldraney/tmux-custom` (GitHub) ### What Broke `tmux-pain-control` plugin binds `prefix + <` and `prefix + >` to `swap-window -d -t -1` / `swap-window -d -t +1`. This **swaps** two windows' positions rather than inserting and shifting. Creating a new window (position 5) and pressing `<` to move it to position 1 causes window 1 to teleport to position 5 — disrupting the entire window order. ### Repro Steps 1. Open tmux with windows `[1:mail, 2:code, 3:logs, 4:ssh]` 2. Create new window — becomes `5:new` 3. Press `prefix + <` repeatedly to move window 5 to position 1 4. Observe: each press swaps two windows — window at position 1 ends up at position 5 instead of shifting right ### Expected Behavior `prefix + <` should bubble the current window left one position at a time via adjacent swap. Windows not directly involved in the swap should remain in their original order. Result: `[NEW, mail, code, logs, ssh]` — everything shifts right by one. ### Environment - Platform: Arch Linux (archbox) - tmux with TPM, plugins: tmux-sensible, tmux-pain-control, catppuccin/tmux - Config: `~/tmux-custom/.tmux.conf` ### Acceptance Criteria - [ ] `prefix + <` moves current window left one position without displacing non-adjacent windows - [ ] `prefix + >` moves current window right one position without displacing non-adjacent windows - [ ] Repeatable (`-r`) — mash key to slide through multiple positions - [ ] Works with `renumber-windows on` (line 72 in config) ### Related - `dotfiles` — project - `tmux-pain-control` — upstream plugin providing the bindings being overridden
Author
Contributor

Scope Review: NEEDS_REFINEMENT

Review note: review-358-2026-03-25
Ticket scope is solid on context and acceptance criteria but missing two sections critical for agent execution.

  • File Targets missing: Agent needs to know the fix goes in ~/tmux-custom/.tmux.conf AFTER the TPM init line (line 95), and must NOT modify the plugin file directly.
  • Constraints missing: Plugin load order is the key gotcha -- any override placed before run '~/.tmux/plugins/tpm/tpm' will be silently clobbered by tmux-pain-control. This must be documented explicitly.
## Scope Review: NEEDS_REFINEMENT Review note: `review-358-2026-03-25` Ticket scope is solid on context and acceptance criteria but missing two sections critical for agent execution. - **File Targets missing**: Agent needs to know the fix goes in `~/tmux-custom/.tmux.conf` AFTER the TPM init line (line 95), and must NOT modify the plugin file directly. - **Constraints missing**: Plugin load order is the key gotcha -- any override placed before `run '~/.tmux/plugins/tpm/tpm'` will be silently clobbered by tmux-pain-control. This must be documented explicitly.
Author
Contributor

Scope Refinement (from review-358-2026-03-25)

Adding the two sections flagged by ticket review:


File Targets

  • ~/tmux-custom/.tmux.conf — add override bindings after line 95 (run '~/.tmux/plugins/tpm/tpm')

Constraints

  • TPM load order: Plugin scripts execute during the run '~/.tmux/plugins/tpm/tpm' line. Any keybindings defined before this line that conflict with plugin bindings will be silently overwritten. The < / > overrides MUST come after the TPM run line to take effect.
  • The existing post-TPM settings block starts at line 98 (set -g automatic-rename off). New bindings should follow this pattern.
### Scope Refinement (from review-358-2026-03-25) Adding the two sections flagged by ticket review: --- ### File Targets - `~/tmux-custom/.tmux.conf` — add override bindings **after line 95** (`run '~/.tmux/plugins/tpm/tpm'`) ### Constraints - **TPM load order:** Plugin scripts execute during the `run '~/.tmux/plugins/tpm/tpm'` line. Any keybindings defined before this line that conflict with plugin bindings will be silently overwritten. The `<` / `>` overrides MUST come after the TPM `run` line to take effect. - The existing post-TPM settings block starts at line 98 (`set -g automatic-rename off`). New bindings should follow this pattern.
Author
Contributor

Scope Review: READY

Review note: review-358-2026-03-25-v2
Re-review after scope refinement. Both File Targets and Constraints sections verified against codebase -- all line references accurate. Ticket is agent-executable.

  • File Targets: ~/tmux-custom/.tmux.conf line 95 (TPM init), line 98 (post-TPM block) -- both verified exact match
  • Constraints: TPM load order documented correctly -- overrides after run line confirmed necessary
  • No dependencies, no blast radius concerns
  • Previous NEEDS_REFINEMENT findings fully resolved
## Scope Review: READY Review note: `review-358-2026-03-25-v2` Re-review after scope refinement. Both File Targets and Constraints sections verified against codebase -- all line references accurate. Ticket is agent-executable. - File Targets: `~/tmux-custom/.tmux.conf` line 95 (TPM init), line 98 (post-TPM block) -- both verified exact match - Constraints: TPM load order documented correctly -- overrides after `run` line confirmed necessary - No dependencies, no blast radius concerns - Previous NEEDS_REFINEMENT findings fully resolved
Author
Contributor

Code Review: Lines 100-104 of .tmux.conf

DOMAIN REVIEW (tmux configuration)

Change under review (lines 100-104):

# Override pain-control's swap-window bindings with bubble-sort behavior
# pain-control uses -d which prevents following the window, breaking repeat.
# Without -d, focus follows the moved window so -r (repeat) bubbles correctly.
bind-key -r "<" swap-window -t -1
bind-key -r ">" swap-window -t +1

What pain-control does (lines 30-31 of pain_control.tmux):

tmux bind-key -r "<" swap-window -d -t -1
tmux bind-key -r ">" swap-window -d -t +1

The -d flag in pain-control means "don't follow the moved window." After a swap, focus stays at the original index rather than following the window to its new position. This breaks repeat behavior: pressing < twice in a row swaps the window at the original index twice, which swaps it left then back right -- a no-op. The override correctly removes -d so focus follows the window, making successive presses bubble the window further in one direction.

ACCEPTANCE CRITERIA VERIFICATION

Criterion Status Notes
prefix + < moves window left one position PASS swap-window -t -1 swaps with the window at index-1
prefix + > moves window right one position PASS swap-window -t +1 swaps with the window at index+1
Non-adjacent windows are not displaced PASS Each swap only involves the current window and its immediate neighbor. Bubble-sort semantics: only pairwise adjacent swaps occur.
Repeatable (-r) PASS -r flag present on both bindings. Without -d, focus follows the window, so repeat key presses continue moving in the same direction.
Works with renumber-windows on (line 72) PASS renumber-windows only triggers on window creation/destruction, not on swap-window. Index swaps are direct and unaffected.

PLACEMENT

Correctly placed after TPM init at line 95. This is necessary because pain-control's window_move_bindings() runs during TPM plugin initialization. Bindings defined before run '~/.tmux/plugins/tpm/tpm' would be overwritten by the plugin. Lines 97-98 and 100-104 are both post-plugin overrides, grouped with the explanatory comment at line 97.

BLOCKERS

None.

NITS

  1. Comment accuracy: The comment says "bubble-sort behavior" which is technically correct (pairwise adjacent swaps are the core operation of bubble sort). The term could be confusing to someone unfamiliar with the algorithm. Consider "slide" or "shift" as a more intuitive label, but this is purely cosmetic.

  2. Issue title vs. implementation: The issue says "insert, not swap" but the implementation uses swap-window. This is not a bug -- swapping with the adjacent window IS insertion for a single-step move, and repeated swaps produce insertion-slide behavior. The distinction only matters for multi-position jumps (e.g., moving window 5 to position 1 in one command), which is not what these keybindings do. Worth noting in the issue close message for future reference.

  3. Edge case -- boundary behavior: When the current window is already at the leftmost position, swap-window -t -1 wraps around (swaps with the highest-indexed window). Same for rightmost with +1. This is default tmux behavior and may be surprising. Not a regression from pain-control (same behavior), but worth documenting if users report unexpected wrapping.

SOP COMPLIANCE

This is a dotfiles repo, so full SOP (branch naming, PR template, plan slug) does not strictly apply. Reviewing as a direct config change:

  • No secrets committed
  • No scope creep -- change is narrowly targeted at the keybinding override
  • Comment is descriptive and explains the "why"
  • No PR exists -- change appears to be direct to branch. Recommend creating a PR for traceability if the dotfiles repo follows the same workflow.

PROCESS OBSERVATIONS

Small, surgical change. Low change-failure risk. The override pattern (post-TPM-init rebinding) is clean and maintainable. If more plugin overrides accumulate, consider grouping them under a dedicated section header.

VERDICT: APPROVED

The change correctly satisfies all four acceptance criteria. The removal of -d from pain-control's default bindings is the correct fix, the placement after TPM init is necessary, and renumber-windows on compatibility is confirmed. No blockers.

## Code Review: Lines 100-104 of `.tmux.conf` ### DOMAIN REVIEW (tmux configuration) **Change under review (lines 100-104):** ```tmux # Override pain-control's swap-window bindings with bubble-sort behavior # pain-control uses -d which prevents following the window, breaking repeat. # Without -d, focus follows the moved window so -r (repeat) bubbles correctly. bind-key -r "<" swap-window -t -1 bind-key -r ">" swap-window -t +1 ``` **What pain-control does (lines 30-31 of `pain_control.tmux`):** ```bash tmux bind-key -r "<" swap-window -d -t -1 tmux bind-key -r ">" swap-window -d -t +1 ``` The `-d` flag in pain-control means "don't follow the moved window." After a swap, focus stays at the original index rather than following the window to its new position. This breaks repeat behavior: pressing `<` twice in a row swaps the window at the original index twice, which swaps it left then back right -- a no-op. The override correctly removes `-d` so focus follows the window, making successive presses bubble the window further in one direction. ### ACCEPTANCE CRITERIA VERIFICATION | Criterion | Status | Notes | |-----------|--------|-------| | `prefix + <` moves window left one position | PASS | `swap-window -t -1` swaps with the window at index-1 | | `prefix + >` moves window right one position | PASS | `swap-window -t +1` swaps with the window at index+1 | | Non-adjacent windows are not displaced | PASS | Each swap only involves the current window and its immediate neighbor. Bubble-sort semantics: only pairwise adjacent swaps occur. | | Repeatable (`-r`) | PASS | `-r` flag present on both bindings. Without `-d`, focus follows the window, so repeat key presses continue moving in the same direction. | | Works with `renumber-windows on` (line 72) | PASS | `renumber-windows` only triggers on window creation/destruction, not on `swap-window`. Index swaps are direct and unaffected. | ### PLACEMENT Correctly placed after TPM init at line 95. This is necessary because pain-control's `window_move_bindings()` runs during TPM plugin initialization. Bindings defined before `run '~/.tmux/plugins/tpm/tpm'` would be overwritten by the plugin. Lines 97-98 and 100-104 are both post-plugin overrides, grouped with the explanatory comment at line 97. ### BLOCKERS None. ### NITS 1. **Comment accuracy**: The comment says "bubble-sort behavior" which is technically correct (pairwise adjacent swaps are the core operation of bubble sort). The term could be confusing to someone unfamiliar with the algorithm. Consider "slide" or "shift" as a more intuitive label, but this is purely cosmetic. 2. **Issue title vs. implementation**: The issue says "insert, not swap" but the implementation uses `swap-window`. This is not a bug -- swapping with the adjacent window IS insertion for a single-step move, and repeated swaps produce insertion-slide behavior. The distinction only matters for multi-position jumps (e.g., moving window 5 to position 1 in one command), which is not what these keybindings do. Worth noting in the issue close message for future reference. 3. **Edge case -- boundary behavior**: When the current window is already at the leftmost position, `swap-window -t -1` wraps around (swaps with the highest-indexed window). Same for rightmost with `+1`. This is default tmux behavior and may be surprising. Not a regression from pain-control (same behavior), but worth documenting if users report unexpected wrapping. ### SOP COMPLIANCE This is a dotfiles repo, so full SOP (branch naming, PR template, plan slug) does not strictly apply. Reviewing as a direct config change: - [x] No secrets committed - [x] No scope creep -- change is narrowly targeted at the keybinding override - [x] Comment is descriptive and explains the "why" - [ ] No PR exists -- change appears to be direct to branch. Recommend creating a PR for traceability if the dotfiles repo follows the same workflow. ### PROCESS OBSERVATIONS Small, surgical change. Low change-failure risk. The override pattern (post-TPM-init rebinding) is clean and maintainable. If more plugin overrides accumulate, consider grouping them under a dedicated section header. ### VERDICT: APPROVED The change correctly satisfies all four acceptance criteria. The removal of `-d` from pain-control's default bindings is the correct fix, the placement after TPM init is necessary, and `renumber-windows on` compatibility is confirmed. No blockers.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/dotfiles#1
No description provided.