Skip to content

fix: correct active pane border indicator for all split layouts#255

Closed
LizardLiang wants to merge 1 commit into
psmux:masterfrom
LizardLiang:fix/pane-border-indicator
Closed

fix: correct active pane border indicator for all split layouts#255
LizardLiang wants to merge 1 commit into
psmux:masterfrom
LizardLiang:fix/pane-border-indicator

Conversation

@LizardLiang

@LizardLiang LizardLiang commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

The pane-active-border-style indicator is not visible when panes are split, and behaves incorrectly in nested layouts.

Repro

  1. Start psmux
  2. Split into left and right panes
  3. Switch focus between panes

Expected

The border segment adjacent to the focused pane should be highlighted with pane-active-border-style. For exactly 2 panes, the shared separator is split at the midpoint so each half reflects its adjacent pane's active state (matching tmux pane-border-indicators colour behaviour).

Actual

No visible change when switching focus — the separator appears the same color regardless of which pane is active. In nested layouts (e.g. left pane + top-right / bottom-right), the separators are either all highlighted or split at an arbitrary midpoint unrelated to the focused pane's position.

Root cause

Two separate issues:

1. Post-render re-coloring pass overwrites everything

After render_json draws the separators, a post-render pass re-colors all border characters based on adjacency to the active pane rect. For a simple 2-pane split, the separator is always geometrically adjacent to whichever pane is active — so the entire separator is always painted with active_border_style, producing no visible change when switching focus.

Fix

Re-coloring pass: Restrict the post-render pass to junction characters only (┼ ├ ┤ ┬ ┴). This lets render_json own the style of straight and cells without being overwritten. Keeping and in the pass causes it to overwrite the midpoint styles computed by render_json for the 2-pane case, so removal is necessary.

Separator rendering: Unify the logic into two cases:

  • Exactly 2 panes (whole window): Use the midpoint half-highlight for the shared separator, matching tmux's pane-border-indicators colour behaviour.
  • 3+ panes: Use per-cell adjacency — each cell of the separator is highlighted only if the active pane directly borders that specific cell. This ensures only the segments forming the actual border of the focused pane are highlighted, regardless of layout complexity.

LayoutJson::count_leaves() is added to compute the total pane count. When zoomed, total_panes is set to 1 to reflect that only one pane is visible and to skip the traversal.


Current State
image

After change
image

3+ pane still working as usaul
image

@LizardLiang LizardLiang force-pushed the fix/pane-border-indicator branch from 766d619 to 8e0aa25 Compare April 23, 2026 08:16
The post-render re-coloring pass was painting every │ and ─ character
based on adjacency to the active pane rect, causing the entire shared
separator to always appear active regardless of which pane had focus.

Changes:
- Restrict re-coloring pass to junction characters only (┼ ├ ┤ ┬ ┴),
  letting render_json own the style of straight │ and ─ cells
- Unify separator rendering: midpoint half-highlight only for exactly
  2 total panes (matches tmux pane-border-indicators behaviour);
  per-cell adjacency for 3+ panes so only the cells bordering the
  active pane are highlighted
- Add LayoutJson::count_leaves() in layout.rs; use zoomed=true shortcut
  (total_panes=1) to avoid traversal and clarify zoom semantics
@LizardLiang LizardLiang force-pushed the fix/pane-border-indicator branch from 8e0aa25 to 79a6432 Compare April 23, 2026 08:20
@LizardLiang LizardLiang marked this pull request as ready for review April 23, 2026 08:25
psmux pushed a commit that referenced this pull request Apr 25, 2026
Ports the fix from PR #255 to current master (which had refactored
render_json -> render_layout_json to module level).

For 3+ pane layouts, the previous "both_leaves" half-highlight path
incorrectly colored separators between non-active leaf children as if
their adjacent leaf were active, regardless of whether the active pane
was actually elsewhere in the tree.

This change:
* Adds LayoutJson::count_leaves() to compute total pane count.
* Threads total_panes through render_layout_json and gates the legacy
  half-highlight path with `total_panes == 2` so it only applies to
  simple two-pane splits.
* For 3+ panes, separator cells are colored per-cell based on adjacency
  to active_rect (the existing else branch), giving correct highlighting
  for arbitrary nested layouts.
* When zoomed, total_panes is forced to 1 to skip traversal.
* Restricts the post-render re-coloring pass to junction characters
  (┼ ├ ┤ ┬ ┴) only. Straight │ and ─ separators are now already colored
  correctly per-cell by render_layout_json, so re-coloring them was
  clobbering the new behavior for 3+ pane layouts.

Also updates the two other call sites (preview.rs choose-tree previews
and main.rs render-test diagnostic) to pass total_panes.

Tests:
* tests-rs/test_pr255_active_border.rs - 7 unit tests covering
  count_leaves for 1/2/3/4/5-pane layouts and a TestBackend render
  regression test asserting that for a 3-pane H[active, V[inactive,
  inactive]] layout, no '─' cell on the right side is colored as active.
* tests/test_pr255_active_border.ps1 - 12 PowerShell E2E + TUI
  verification tests covering 1/2/3/4-pane creation, select-pane on
  each pane, geometry, zoom toggle, and capture-pane on all panes.

Co-authored-by: LizardLiang <shotup0101@gmail.com>

Thanks @LizardLiang for the contribution!
@psmux

psmux commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Thank you so much for this fix, @LizardLiang! Your analysis is spot on. The legacy both_leaves half-highlight path was indeed coloring inner separators as if their adjacent leaf were active, even when the active pane lived elsewhere in the layout tree. The count_leaves + total_panes == 2 gate is exactly the right approach.

Master had refactored the inner render_json function out to module level (render_layout_json in src/client.rs) which made GitHub mark this PR as conflicting. Rather than ask you to rebase, I ported your changes onto current master and merged them in commit 70e8602 with you credited as co-author.

What landed:

  • LayoutJson::count_leaves() exactly as you wrote it.
  • total_panes threaded through render_layout_json and the legacy half-highlight branches gated with total_panes == 2.
  • total_panes = 1 when zoomed, to skip traversal.
  • The post-render re-coloring pass restricted to junction characters (┼ ├ ┤ ┬ ┴) only, so the new per-cell coloring of and is no longer clobbered for 3+ pane layouts.
  • The two other call sites (preview.rs choose-tree previews, main.rs render-test) updated to pass total_panes.

I also added regression tests:

  • tests-rs/test_pr255_active_border.rs (7 Rust unit tests) including a TestBackend render assertion that for a 3-pane H[active, V[inactive, inactive]] layout, no cell on the right side is colored as active.
  • tests/test_pr255_active_border.ps1 (12 PowerShell E2E + TUI verification tests) covering 1/2/3/4-pane creation, select-pane cycling, geometry, zoom toggle, and capture-pane on all panes.

Full regression: 1828 / 1828 Rust tests pass, all PowerShell E2E suites pass.

Closing this PR since the fix is now in master. Huge thanks again, this was a clean, well reasoned change!

@psmux

psmux commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Merged via 70e8602 (with you credited as co-author). Thanks again @LizardLiang!

@psmux psmux closed this Apr 25, 2026
psmux added a commit that referenced this pull request Apr 25, 2026
Ports the fix from PR #255 to current master (which had refactored
render_json -> render_layout_json to module level).

For 3+ pane layouts, the previous "both_leaves" half-highlight path
incorrectly colored separators between non-active leaf children as if
their adjacent leaf were active, regardless of whether the active pane
was actually elsewhere in the tree.

This change:
* Adds LayoutJson::count_leaves() to compute total pane count.
* Threads total_panes through render_layout_json and gates the legacy
  half-highlight path with `total_panes == 2` so it only applies to
  simple two-pane splits.
* For 3+ panes, separator cells are colored per-cell based on adjacency
  to active_rect (the existing else branch), giving correct highlighting
  for arbitrary nested layouts.
* When zoomed, total_panes is forced to 1 to skip traversal.
* Restricts the post-render re-coloring pass to junction characters
  (┼ ├ ┤ ┬ ┴) only. Straight │ and ─ separators are now already colored
  correctly per-cell by render_layout_json, so re-coloring them was
  clobbering the new behavior for 3+ pane layouts.

Also updates the two other call sites (preview.rs choose-tree previews
and main.rs render-test diagnostic) to pass total_panes.

Tests:
* tests-rs/test_pr255_active_border.rs - 7 unit tests covering
  count_leaves for 1/2/3/4/5-pane layouts and a TestBackend render
  regression test asserting that for a 3-pane H[active, V[inactive,
  inactive]] layout, no '─' cell on the right side is colored as active.
* tests/test_pr255_active_border.ps1 - 12 PowerShell E2E + TUI
  verification tests covering 1/2/3/4-pane creation, select-pane on
  each pane, geometry, zoom toggle, and capture-pane on all panes.

Co-authored-by: LizardLiang <shotup0101@gmail.com>

Thanks @LizardLiang for the contribution!
psmux added a commit that referenced this pull request Apr 25, 2026
…view

Renders the same render_layout_json the live TUI uses to a TestBackend
(via the hidden _render-preview command), parses the ANSI output, and
proves per-cell color correctness for a 3-pane H[active=%1, V[%2, %3]]
layout when each pane is activated in turn.

8 assertions including:
* Inner horizontal separator (between inactive %2 and %3) has 0 active
  cells when %1 is active. Before PR #255, half would be green.
* When %2 is activated, the same separator becomes fully active.
* Outer vertical separator: per-cell adjacency works (upper half active
  when %2 is active, lower half inactive).
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.

2 participants