detached b1ff1a9

fix(layout): share lines only within the usable area

A cascade band above a bottom panel took the next band, which starts under
the panel, for a neighbor and drew a line along the panel.
commit
b1ff1a9e163218ea065a7657832dfd4e072b7f14
author
brice · · 2026-10-07 14:05 UTC
parent
62026dc
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +27 -1
M internal/core/layout.go +3 -1
@@ -1658,7 +1658,9 @@ func (w *Workspace) appendLayout(dst []Placement) []Placement {
16581658 tiles = append(tiles, Placement{ID: id, Rect: r, Fullscreen: full, Focused: id == focusedID, Hidden: hidden})
16591659 }
16601660 }
1661- setVisibleNeighbors(tiles, gap, w.Output)
1661+ // Lines are shared within the usable area: a tile under a panel (the
1662+ // next cascade band starts right below a bottom one) is no neighbor.
1663+ setVisibleNeighbors(tiles, gap, w.Usable)
16621664 // tiles lives in the workspace's scratch until they are copied below.
16631665 w.tileBuf = tiles
16641666 // The column/stash group sits between demoted covering floats and
@@ -1658,7 +1658,9 @@ func (w *Workspace) appendLayout(dst []Placement) []Placement {
1658 tiles = append(tiles, Placement{ID: id, Rect: r, Fullscreen: full, Focused: id == focusedID, Hidden: hidden})1658 tiles = append(tiles, Placement{ID: id, Rect: r, Fullscreen: full, Focused: id == focusedID, Hidden: hidden})
1659 }1659 }
1660 }1660 }
1661- setVisibleNeighbors(tiles, gap, w.Output)1661+ // Lines are shared within the usable area: a tile under a panel (the
1662+ // next cascade band starts right below a bottom one) is no neighbor.
1663+ setVisibleNeighbors(tiles, gap, w.Usable)
1662 // tiles lives in the workspace's scratch until they are copied below.1664 // tiles lives in the workspace's scratch until they are copied below.
1663 w.tileBuf = tiles1665 w.tileBuf = tiles
1664 // The column/stash group sits between demoted covering floats and1666 // The column/stash group sits between demoted covering floats and
M internal/core/separators_test.go +24 -0
@@ -147,6 +147,30 @@ func TestSeparatorsScroll(t *testing.T) {
147147 }
148148 }
149149
150+// A cascade band above a bottom panel: the next band starts under the
151+// panel, so the focused tile gets no line along the panel, only the one it
152+// shares with its band neighbor.
153+func TestSeparatorsCascadeBottomPanel(t *testing.T) {
154+ w := &Workspace{Overflow: OverflowCascade, MaxColumns: 2}
155+ w.SetOutput(900, 600)
156+ w.SetUsable(Rect{W: 900, H: 580})
157+ for id := WindowID(1); id <= 3; id++ {
158+ w.AddWindow(id)
159+ }
160+ w.FocusID(1)
161+ ps := w.Layout()
162+ if p := previewOf(t, ps, 1); p.Neighbors != ports.SideRight {
163+ t.Fatalf("neighbors %04b, want right only", p.Neighbors)
164+ }
165+ seps := separators(ps, 2, 0, w.Output, true)
166+ if got := colorAt(seps, 200, 578); got != "" {
167+ t.Fatalf("line along the panel: %v", seps)
168+ }
169+ if got := colorAt(seps, 449, 100); got != "lit" {
170+ t.Fatalf("shared line (449,100) = %q; %v", got, seps)
171+ }
172+}
173+
150174 // With gaps each tile has its own line: the focused one lights it whole.
151175 func TestSeparatorsGaps(t *testing.T) {
152176 ps := []Placement{{ID: 1, Rect: Rect{X: 0, W: 16, H: 16}, Focused: true}, {ID: 2, Rect: Rect{X: 20, W: 16, H: 16}}}
@@ -147,6 +147,30 @@ func TestSeparatorsScroll(t *testing.T) {
147 }147 }
148 }148 }
149 149
150+// A cascade band above a bottom panel: the next band starts under the
151+// panel, so the focused tile gets no line along the panel, only the one it
152+// shares with its band neighbor.
153+func TestSeparatorsCascadeBottomPanel(t *testing.T) {
154+ w := &Workspace{Overflow: OverflowCascade, MaxColumns: 2}
155+ w.SetOutput(900, 600)
156+ w.SetUsable(Rect{W: 900, H: 580})
157+ for id := WindowID(1); id <= 3; id++ {
158+ w.AddWindow(id)
159+ }
160+ w.FocusID(1)
161+ ps := w.Layout()
162+ if p := previewOf(t, ps, 1); p.Neighbors != ports.SideRight {
163+ t.Fatalf("neighbors %04b, want right only", p.Neighbors)
164+ }
165+ seps := separators(ps, 2, 0, w.Output, true)
166+ if got := colorAt(seps, 200, 578); got != "" {
167+ t.Fatalf("line along the panel: %v", seps)
168+ }
169+ if got := colorAt(seps, 449, 100); got != "lit" {
170+ t.Fatalf("shared line (449,100) = %q; %v", got, seps)
171+ }
172+}
173+
150 // With gaps each tile has its own line: the focused one lights it whole.174 // With gaps each tile has its own line: the focused one lights it whole.
151 func TestSeparatorsGaps(t *testing.T) {175 func TestSeparatorsGaps(t *testing.T) {
152 ps := []Placement{{ID: 1, Rect: Rect{X: 0, W: 16, H: 16}, Focused: true}, {ID: 2, Rect: Rect{X: 20, W: 16, H: 16}}}176 ps := []Placement{{ID: 1, Rect: Rect{X: 0, W: 16, H: 16}, Focused: true}, {ID: 2, Rect: Rect{X: 20, W: 16, H: 16}}}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 26

Tags 10

↑↓ moveenter openesc close