detached c73000c

feat(core): move a fullscreen float to the neighbor monitor

move-column-left/right on a fullscreen floating window sends it to the
neighbor monitor, focused and still fullscreen; with no neighbor it stays.
commit
c73000c5d9f80cb5f1b62302a6631e6e95e64ddb
author
brice · · 2026-10-22 19:45 UTC
parent
dbd9830
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +56 -1
M docs/config.md +1 -1
@@ -308,7 +308,7 @@ The default layout. Columns sit side by side in bands of up to `max-columns`, st
308308 - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back.
309309 - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back.
310310 - A fullscreen window gets a band of its own right below the band it was in (expelled into its own column first when it shares one), so the other columns stay put and focus changes among them leave the fullscreen output alone. Leaving fullscreen keeps the column in that band.
311-- `move-column-left/right` on a fullscreen window sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves.
311+- `move-column-left/right` on a fullscreen window (tiled or floating) sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves.
312312 - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it.
313313
314314 ### Column widths
@@ -308,7 +308,7 @@ The default layout. Columns sit side by side in bands of up to `max-columns`, st
308 - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back.308 - `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back.
309 - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back.309 - `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back.
310 - A fullscreen window gets a band of its own right below the band it was in (expelled into its own column first when it shares one), so the other columns stay put and focus changes among them leave the fullscreen output alone. Leaving fullscreen keeps the column in that band.310 - A fullscreen window gets a band of its own right below the band it was in (expelled into its own column first when it shares one), so the other columns stay put and focus changes among them leave the fullscreen output alone. Leaving fullscreen keeps the column in that band.
311-- `move-column-left/right` on a fullscreen window sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves.311+- `move-column-left/right` on a fullscreen window (tiled or floating) sends it to the neighbor monitor, where it stays fullscreen and focused; with no neighbor monitor nothing moves.
312 - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it.312 - A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it.
313 313
314 ### Column widths314 ### Column widths
M internal/core/action.go +25 -0
@@ -388,6 +388,17 @@ func (c *Core) dispatchAction(a Action) Effect {
388388 if a == ActionMoveColumnLeft {
389389 dir, d = -1, dirLeft
390390 }
391+ // A fullscreen float goes to the neighbor screen, fullscreen there.
392+ if fid, ok := w.Focused(); ok && w.fullscreen == fid && w.floatIndex(fid) >= 0 {
393+ if i := c.neighbor(d); i >= 0 {
394+ from := c.cur()
395+ c.focusScreen = i
396+ moveFloat(w, c.cur().mon.Current(), fid)
397+ from.mon.normalize()
398+ c.cur().mon.normalize()
399+ }
400+ return Effect{}
401+ }
391402 // A fullscreen window leaves for the neighbor screen at once, and
392403 // stays fullscreen there; with no screen, nothing moves.
393404 fid, _ := w.Focused()
@@ -619,3 +630,17 @@ func (w *Workspace) Apply(a Action) Effect {
619630 }
620631 return Effect{}
621632 }
633+
634+// moveFloat moves float id from workspace cur to to, as MoveToWorkspace
635+// does, focuses it there and keeps it fullscreen.
636+func moveFloat(cur, to *Workspace, id WindowID) {
637+ fl := cur.Floats[cur.floatIndex(id)]
638+ if fl.free && fl.back != nil {
639+ fl = fl.rehome()
640+ }
641+ cur.RemoveWindow(id)
642+ to.AddFloating(id, fl.W, fl.H)
643+ to.Floats[len(to.Floats)-1] = fl
644+ to.Activate(id)
645+ to.SetFullscreen(id, true)
646+}
@@ -388,6 +388,17 @@ func (c *Core) dispatchAction(a Action) Effect {
388 if a == ActionMoveColumnLeft {388 if a == ActionMoveColumnLeft {
389 dir, d = -1, dirLeft389 dir, d = -1, dirLeft
390 }390 }
391+ // A fullscreen float goes to the neighbor screen, fullscreen there.
392+ if fid, ok := w.Focused(); ok && w.fullscreen == fid && w.floatIndex(fid) >= 0 {
393+ if i := c.neighbor(d); i >= 0 {
394+ from := c.cur()
395+ c.focusScreen = i
396+ moveFloat(w, c.cur().mon.Current(), fid)
397+ from.mon.normalize()
398+ c.cur().mon.normalize()
399+ }
400+ return Effect{}
401+ }
391 // A fullscreen window leaves for the neighbor screen at once, and402 // A fullscreen window leaves for the neighbor screen at once, and
392 // stays fullscreen there; with no screen, nothing moves.403 // stays fullscreen there; with no screen, nothing moves.
393 fid, _ := w.Focused()404 fid, _ := w.Focused()
@@ -619,3 +630,17 @@ func (w *Workspace) Apply(a Action) Effect {
619 }630 }
620 return Effect{}631 return Effect{}
621 }632 }
633+
634+// moveFloat moves float id from workspace cur to to, as MoveToWorkspace
635+// does, focuses it there and keeps it fullscreen.
636+func moveFloat(cur, to *Workspace, id WindowID) {
637+ fl := cur.Floats[cur.floatIndex(id)]
638+ if fl.free && fl.back != nil {
639+ fl = fl.rehome()
640+ }
641+ cur.RemoveWindow(id)
642+ to.AddFloating(id, fl.W, fl.H)
643+ to.Floats[len(to.Floats)-1] = fl
644+ to.Activate(id)
645+ to.SetFullscreen(id, true)
646+}
M internal/core/cascade_fullscreen_band_test.go +30 -0
@@ -92,3 +92,33 @@ func TestFullscreenColumnNoNeighborStays(t *testing.T) {
9292 t.Fatalf("fullscreen %d", w.fullscreen)
9393 }
9494 }
95+
96+func TestFullscreenFloatMovesToNeighborMonitor(t *testing.T) {
97+ for _, tc := range []struct {
98+ action Action
99+ screen string
100+ }{{ActionMoveColumnLeft, "L"}, {ActionMoveColumnRight, "R"}} {
101+ t.Run(tc.screen, func(t *testing.T) {
102+ c, w := cascadeMovesCore(t, 2, 3, true)
103+ w.AddFloating(9, 5120, 2160)
104+ w.SetFullscreen(9, true)
105+ c.applyAction(tc.action)
106+ dst := screenWorkspace(c, tc.screen)
107+ if w.floatIndex(9) >= 0 || dst.floatIndex(9) < 0 || w.fullscreen != 0 || dst.fullscreen != 9 {
108+ t.Fatalf("float on src %v dst %v, fullscreen src %d dst %d", w.Floats, dst.Floats, w.fullscreen, dst.fullscreen)
109+ }
110+ wantFocus(t, c, tc.screen, 9)
111+ wantColumns(t, w, 1, 2)
112+ })
113+ }
114+}
115+
116+func TestFullscreenFloatNoNeighborStays(t *testing.T) {
117+ c, w := cascadeMovesCore(t, 2, 3, false)
118+ w.AddFloating(9, 800, 600)
119+ w.SetFullscreen(9, true)
120+ c.applyAction(ActionMoveColumnLeft)
121+ if w.floatIndex(9) < 0 || w.fullscreen != 9 {
122+ t.Fatalf("float moved: %v fullscreen %d", w.Floats, w.fullscreen)
123+ }
124+}
@@ -92,3 +92,33 @@ func TestFullscreenColumnNoNeighborStays(t *testing.T) {
92 t.Fatalf("fullscreen %d", w.fullscreen)92 t.Fatalf("fullscreen %d", w.fullscreen)
93 }93 }
94 }94 }
95+
96+func TestFullscreenFloatMovesToNeighborMonitor(t *testing.T) {
97+ for _, tc := range []struct {
98+ action Action
99+ screen string
100+ }{{ActionMoveColumnLeft, "L"}, {ActionMoveColumnRight, "R"}} {
101+ t.Run(tc.screen, func(t *testing.T) {
102+ c, w := cascadeMovesCore(t, 2, 3, true)
103+ w.AddFloating(9, 5120, 2160)
104+ w.SetFullscreen(9, true)
105+ c.applyAction(tc.action)
106+ dst := screenWorkspace(c, tc.screen)
107+ if w.floatIndex(9) >= 0 || dst.floatIndex(9) < 0 || w.fullscreen != 0 || dst.fullscreen != 9 {
108+ t.Fatalf("float on src %v dst %v, fullscreen src %d dst %d", w.Floats, dst.Floats, w.fullscreen, dst.fullscreen)
109+ }
110+ wantFocus(t, c, tc.screen, 9)
111+ wantColumns(t, w, 1, 2)
112+ })
113+ }
114+}
115+
116+func TestFullscreenFloatNoNeighborStays(t *testing.T) {
117+ c, w := cascadeMovesCore(t, 2, 3, false)
118+ w.AddFloating(9, 800, 600)
119+ w.SetFullscreen(9, true)
120+ c.applyAction(ActionMoveColumnLeft)
121+ if w.floatIndex(9) < 0 || w.fullscreen != 9 {
122+ t.Fatalf("float moved: %v fullscreen %d", w.Floats, w.fullscreen)
123+ }
124+}

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 25

Tags 10

↑↓ moveenter openesc close