feat(stash): capture windows opened while the stash is shown
- commit
- 34856b1f41b2988fb255b0f9ca82a8bd7544d5e7
- author
- brice · · 2026-10-02 15:06 UTC
- parent
- b87e882
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +96 -3
M docs/config.md +1 -0
| @@ -26,6 +26,7 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). | |||
| 26 | 26 | | `stash.width` | `80` | Width of the selected stashed window, over the tiles, in percent of the usable width; 10 to 90 (`80%` works too). The rest is split between both sides, where the neighbors show. Its height is 80% of the usable height | | |
| 27 | 27 | | `stash.gap` | `2` | Space between the selected stashed window and its neighbors, in percent of the usable width; 0 to 10. The neighbors show from there to the screen edge: with the defaults, 8% of each. A gap as wide as the side margin hides them | | |
| 28 | 28 | | `stash.dim` | `0.5` | Black veil over the stash neighbors, borders included; opacity 0 to 1 | | |
| 29 | + | | `stash.capture` | `on` | `on`: a window opened while the stash is shown joins its end, selected; `toggle-window-stash` tiles it as a new column. `off`: new windows always tile | | |
| 29 | 30 | | `border.active` / `border.inactive` | `#808080` / `#111111` | Colors for focused / other window lines; only the focused output lights up | | |
| 30 | 31 | | `layout.gaps` | `0` | Space between windows | | |
| 31 | 32 | | `layout.max-columns` | `2` | Columns that share the screen before scrolling | | |
| @@ -26,6 +26,7 @@ NeferWL reads `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). | |||||
| 26 | | `stash.width` | `80` | Width of the selected stashed window, over the tiles, in percent of the usable width; 10 to 90 (`80%` works too). The rest is split between both sides, where the neighbors show. Its height is 80% of the usable height | | 26 | | `stash.width` | `80` | Width of the selected stashed window, over the tiles, in percent of the usable width; 10 to 90 (`80%` works too). The rest is split between both sides, where the neighbors show. Its height is 80% of the usable height | | ||
| 27 | | `stash.gap` | `2` | Space between the selected stashed window and its neighbors, in percent of the usable width; 0 to 10. The neighbors show from there to the screen edge: with the defaults, 8% of each. A gap as wide as the side margin hides them | | 27 | | `stash.gap` | `2` | Space between the selected stashed window and its neighbors, in percent of the usable width; 0 to 10. The neighbors show from there to the screen edge: with the defaults, 8% of each. A gap as wide as the side margin hides them | | ||
| 28 | | `stash.dim` | `0.5` | Black veil over the stash neighbors, borders included; opacity 0 to 1 | | 28 | | `stash.dim` | `0.5` | Black veil over the stash neighbors, borders included; opacity 0 to 1 | | ||
| 29 | + | | `stash.capture` | `on` | `on`: a window opened while the stash is shown joins its end, selected; `toggle-window-stash` tiles it as a new column. `off`: new windows always tile | | |||
| 29 | | `border.active` / `border.inactive` | `#808080` / `#111111` | Colors for focused / other window lines; only the focused output lights up | | 30 | | `border.active` / `border.inactive` | `#808080` / `#111111` | Colors for focused / other window lines; only the focused output lights up | | ||
| 30 | | `layout.gaps` | `0` | Space between windows | | 31 | | `layout.gaps` | `0` | Space between windows | | ||
| 31 | | `layout.max-columns` | `2` | Columns that share the screen before scrolling | | 32 | | `layout.max-columns` | `2` | Columns that share the screen before scrolling | | ||
M internal/adapters/config/config.go +7 -0
| @@ -112,6 +112,7 @@ func Defaults() ports.Config { | |||
| 112 | 112 | c.Stash.Width = 80 | |
| 113 | 113 | c.Stash.Gap = 2 | |
| 114 | 114 | c.Stash.Dim = 0.5 | |
| 115 | + | c.Stash.Capture = true | |
| 115 | 116 | c.Border.Width = 2 | |
| 116 | 117 | c.Border.Active = "#808080" | |
| 117 | 118 | c.Border.Inactive = "#111111" | |
| @@ -504,6 +505,12 @@ func set(c *ports.Config, key, v string) error { | |||
| 504 | 505 | return fmt.Errorf("must be between 0 and 1") | |
| 505 | 506 | } | |
| 506 | 507 | c.Stash.Dim = dim | |
| 508 | + | case "stash.capture": | |
| 509 | + | b, err := onOff(v) | |
| 510 | + | if err != nil { | |
| 511 | + | return err | |
| 512 | + | } | |
| 513 | + | c.Stash.Capture = b | |
| 507 | 514 | case "border.width": | |
| 508 | 515 | n, err := strconv.Atoi(v) | |
| 509 | 516 | if err != nil || n < 0 || n > 32 { | |
| @@ -112,6 +112,7 @@ func Defaults() ports.Config { | |||||
| 112 | c.Stash.Width = 80 | 112 | c.Stash.Width = 80 | ||
| 113 | c.Stash.Gap = 2 | 113 | c.Stash.Gap = 2 | ||
| 114 | c.Stash.Dim = 0.5 | 114 | c.Stash.Dim = 0.5 | ||
| 115 | + | c.Stash.Capture = true | |||
| 115 | c.Border.Width = 2 | 116 | c.Border.Width = 2 | ||
| 116 | c.Border.Active = "#808080" | 117 | c.Border.Active = "#808080" | ||
| 117 | c.Border.Inactive = "#111111" | 118 | c.Border.Inactive = "#111111" | ||
| @@ -504,6 +505,12 @@ func set(c *ports.Config, key, v string) error { | |||||
| 504 | return fmt.Errorf("must be between 0 and 1") | 505 | return fmt.Errorf("must be between 0 and 1") | ||
| 505 | } | 506 | } | ||
| 506 | c.Stash.Dim = dim | 507 | c.Stash.Dim = dim | ||
| 508 | + | case "stash.capture": | |||
| 509 | + | b, err := onOff(v) | |||
| 510 | + | if err != nil { | |||
| 511 | + | return err | |||
| 512 | + | } | |||
| 513 | + | c.Stash.Capture = b | |||
| 507 | case "border.width": | 514 | case "border.width": | ||
| 508 | n, err := strconv.Atoi(v) | 515 | n, err := strconv.Atoi(v) | ||
| 509 | if err != nil || n < 0 || n > 32 { | 516 | if err != nil || n < 0 || n > 32 { | ||
M internal/adapters/config/config_test.go +12 -0
| @@ -487,6 +487,18 @@ func TestStash(t *testing.T) { | |||
| 487 | 487 | } | |
| 488 | 488 | } | |
| 489 | 489 | ||
| 490 | + | func TestStashCapture(t *testing.T) { | |
| 491 | + | if c, _ := parseString(t, ""); !c.Stash.Capture { | |
| 492 | + | t.Fatal("stash.capture is on by default") | |
| 493 | + | } | |
| 494 | + | if c, w := parseString(t, "stash.capture = off"); c.Stash.Capture || len(w) != 0 { | |
| 495 | + | t.Fatalf("off: %v %v", c.Stash.Capture, w) | |
| 496 | + | } | |
| 497 | + | if c, w := parseString(t, "stash.capture = yes"); !c.Stash.Capture || len(w) == 0 { | |
| 498 | + | t.Fatalf("invalid: %v %v", c.Stash.Capture, w) | |
| 499 | + | } | |
| 500 | + | } | |
| 501 | + | ||
| 490 | 502 | func TestVRRFlipGap(t *testing.T) { | |
| 491 | 503 | for _, tc := range []struct { | |
| 492 | 504 | value string | |
| @@ -487,6 +487,18 @@ func TestStash(t *testing.T) { | |||||
| 487 | } | 487 | } | ||
| 488 | } | 488 | } | ||
| 489 | 489 | ||||
| 490 | + | func TestStashCapture(t *testing.T) { | |||
| 491 | + | if c, _ := parseString(t, ""); !c.Stash.Capture { | |||
| 492 | + | t.Fatal("stash.capture is on by default") | |||
| 493 | + | } | |||
| 494 | + | if c, w := parseString(t, "stash.capture = off"); c.Stash.Capture || len(w) != 0 { | |||
| 495 | + | t.Fatalf("off: %v %v", c.Stash.Capture, w) | |||
| 496 | + | } | |||
| 497 | + | if c, w := parseString(t, "stash.capture = yes"); !c.Stash.Capture || len(w) == 0 { | |||
| 498 | + | t.Fatalf("invalid: %v %v", c.Stash.Capture, w) | |||
| 499 | + | } | |||
| 500 | + | } | |||
| 501 | + | ||||
| 490 | func TestVRRFlipGap(t *testing.T) { | 502 | func TestVRRFlipGap(t *testing.T) { | ||
| 491 | for _, tc := range []struct { | 503 | for _, tc := range []struct { | ||
| 492 | value string | 504 | value string | ||
M internal/core/monitor.go +11 -2
| @@ -28,6 +28,8 @@ type Monitor struct { | |||
| 28 | 28 | named []NamedWorkspace | |
| 29 | 29 | // followMove shows the target workspace after a move to it. | |
| 30 | 30 | followMove bool | |
| 31 | + | // stashCapture puts new windows in the shown stash (stash.capture). | |
| 32 | + | stashCapture bool | |
| 31 | 33 | // switchOff slides the view between numbered workspaces: the view is | |
| 32 | 34 | // at Active+switchOff while a swipe follows the fingers or its spring | |
| 33 | 35 | // (switchMotion) lands. | |
| @@ -294,12 +296,15 @@ func (m *Monitor) find(id WindowID) (*Workspace, int) { | |||
| 294 | 296 | return nil, -1 | |
| 295 | 297 | } | |
| 296 | 298 | ||
| 297 | - | // AddWindow places a new window on the workspace on screen (see arrive). | |
| 299 | + | // AddWindow places a new window on the workspace on screen; with | |
| 300 | + | // stash.capture, in its stash when shown. | |
| 298 | 301 | func (m *Monitor) AddWindow(id WindowID) { | |
| 299 | 302 | if w, _ := m.find(id); w != nil { | |
| 300 | 303 | return | |
| 301 | 304 | } | |
| 302 | - | m.Current().AddWindow(id) | |
| 305 | + | if w := m.Current(); !m.stashCapture || !w.captureStash(id) { | |
| 306 | + | w.AddWindow(id) | |
| 307 | + | } | |
| 303 | 308 | m.normalize() | |
| 304 | 309 | } | |
| 305 | 310 | ||
| @@ -468,6 +473,10 @@ func (m *Monitor) SetStash(width, gap int) { | |||
| 468 | 473 | } | |
| 469 | 474 | m.each(func(w *Workspace) { w.stashWidth, w.stashGap = width, min(max(gap, 0), 10) }) | |
| 470 | 475 | } | |
| 476 | + | ||
| 477 | + | // SetStashCapture makes new windows join the shown stash (stash.capture). | |
| 478 | + | func (m *Monitor) SetStashCapture(on bool) { m.stashCapture = on } | |
| 479 | + | ||
| 471 | 480 | func (m *Monitor) SetPresets(v []Width) { m.each(func(w *Workspace) { w.SetPresets(v) }) } | |
| 472 | 481 | ||
| 473 | 482 | // SetFollowMove makes moves to another workspace show it (focus.follow-move). | |
| @@ -28,6 +28,8 @@ type Monitor struct { | |||||
| 28 | named []NamedWorkspace | 28 | named []NamedWorkspace | ||
| 29 | // followMove shows the target workspace after a move to it. | 29 | // followMove shows the target workspace after a move to it. | ||
| 30 | followMove bool | 30 | followMove bool | ||
| 31 | + | // stashCapture puts new windows in the shown stash (stash.capture). | |||
| 32 | + | stashCapture bool | |||
| 31 | // switchOff slides the view between numbered workspaces: the view is | 33 | // switchOff slides the view between numbered workspaces: the view is | ||
| 32 | // at Active+switchOff while a swipe follows the fingers or its spring | 34 | // at Active+switchOff while a swipe follows the fingers or its spring | ||
| 33 | // (switchMotion) lands. | 35 | // (switchMotion) lands. | ||
| @@ -294,12 +296,15 @@ func (m *Monitor) find(id WindowID) (*Workspace, int) { | |||||
| 294 | return nil, -1 | 296 | return nil, -1 | ||
| 295 | } | 297 | } | ||
| 296 | 298 | ||||
| 297 | - | // AddWindow places a new window on the workspace on screen (see arrive). | 299 | + | // AddWindow places a new window on the workspace on screen; with |
| 300 | + | // stash.capture, in its stash when shown. | |||
| 298 | func (m *Monitor) AddWindow(id WindowID) { | 301 | func (m *Monitor) AddWindow(id WindowID) { | ||
| 299 | if w, _ := m.find(id); w != nil { | 302 | if w, _ := m.find(id); w != nil { | ||
| 300 | return | 303 | return | ||
| 301 | } | 304 | } | ||
| 302 | - | m.Current().AddWindow(id) | 305 | + | if w := m.Current(); !m.stashCapture || !w.captureStash(id) { |
| 306 | + | w.AddWindow(id) | |||
| 307 | + | } | |||
| 303 | m.normalize() | 308 | m.normalize() | ||
| 304 | } | 309 | } | ||
| 305 | 310 | ||||
| @@ -468,6 +473,10 @@ func (m *Monitor) SetStash(width, gap int) { | |||||
| 468 | } | 473 | } | ||
| 469 | m.each(func(w *Workspace) { w.stashWidth, w.stashGap = width, min(max(gap, 0), 10) }) | 474 | m.each(func(w *Workspace) { w.stashWidth, w.stashGap = width, min(max(gap, 0), 10) }) | ||
| 470 | } | 475 | } | ||
| 476 | + | ||||
| 477 | + | // SetStashCapture makes new windows join the shown stash (stash.capture). | |||
| 478 | + | func (m *Monitor) SetStashCapture(on bool) { m.stashCapture = on } | |||
| 479 | + | ||||
| 471 | func (m *Monitor) SetPresets(v []Width) { m.each(func(w *Workspace) { w.SetPresets(v) }) } | 480 | func (m *Monitor) SetPresets(v []Width) { m.each(func(w *Workspace) { w.SetPresets(v) }) } | ||
| 472 | 481 | ||||
| 473 | // SetFollowMove makes moves to another workspace show it (focus.follow-move). | 482 | // SetFollowMove makes moves to another workspace show it (focus.follow-move). | ||
M internal/core/screens.go +1 -0
| @@ -395,6 +395,7 @@ func (c *Core) settings(m *Monitor) { | |||
| 395 | 395 | m.SetGaps(c.cfg.Layout.Gaps) | |
| 396 | 396 | m.SetBorder(c.cfg.Border.Width) | |
| 397 | 397 | m.SetStash(c.cfg.Stash.Width, c.cfg.Stash.Gap) | |
| 398 | + | m.SetStashCapture(c.cfg.Stash.Capture) | |
| 398 | 399 | m.SetFollowMove(c.cfg.Focus.FollowMove) | |
| 399 | 400 | } | |
| 400 | 401 | ||
| @@ -395,6 +395,7 @@ func (c *Core) settings(m *Monitor) { | |||||
| 395 | m.SetGaps(c.cfg.Layout.Gaps) | 395 | m.SetGaps(c.cfg.Layout.Gaps) | ||
| 396 | m.SetBorder(c.cfg.Border.Width) | 396 | m.SetBorder(c.cfg.Border.Width) | ||
| 397 | m.SetStash(c.cfg.Stash.Width, c.cfg.Stash.Gap) | 397 | m.SetStash(c.cfg.Stash.Width, c.cfg.Stash.Gap) | ||
| 398 | + | m.SetStashCapture(c.cfg.Stash.Capture) | |||
| 398 | m.SetFollowMove(c.cfg.Focus.FollowMove) | 399 | m.SetFollowMove(c.cfg.Focus.FollowMove) | ||
| 399 | } | 400 | } | ||
| 400 | 401 | ||||
M internal/core/stash.go +14 -0
| @@ -67,6 +67,20 @@ func (w *Workspace) addStash(f Float) { | |||
| 67 | 67 | w.showStash() | |
| 68 | 68 | } | |
| 69 | 69 | ||
| 70 | + | // captureStash puts a new window id in the shown stash, selected, and | |
| 71 | + | // reports whether it did. Like a new tile, it leaves a focused native | |
| 72 | + | // float focused. Under a covering fullscreen window the stash is not on | |
| 73 | + | // screen: the window tiles as usual. | |
| 74 | + | func (w *Workspace) captureStash(id WindowID) bool { | |
| 75 | + | if id == 0 || w.stashHidden || len(w.Stash) == 0 || w.cover() != 0 { | |
| 76 | + | return false | |
| 77 | + | } | |
| 78 | + | // No former column: toggle-window-stash tiles it as a new column. | |
| 79 | + | w.Stash = append(w.Stash, Float{ID: id}) | |
| 80 | + | w.stashAt, w.stashFocus = len(w.Stash)-1, true | |
| 81 | + | return true | |
| 82 | + | } | |
| 83 | + | ||
| 70 | 84 | // showStash shows the stash and gives it the focus; a member whose | |
| 71 | 85 | // fullscreen waited while hidden is fullscreen again, selected, as if it | |
| 72 | 86 | // asked now. | |
| @@ -67,6 +67,20 @@ func (w *Workspace) addStash(f Float) { | |||||
| 67 | w.showStash() | 67 | w.showStash() | ||
| 68 | } | 68 | } | ||
| 69 | 69 | ||||
| 70 | + | // captureStash puts a new window id in the shown stash, selected, and | |||
| 71 | + | // reports whether it did. Like a new tile, it leaves a focused native | |||
| 72 | + | // float focused. Under a covering fullscreen window the stash is not on | |||
| 73 | + | // screen: the window tiles as usual. | |||
| 74 | + | func (w *Workspace) captureStash(id WindowID) bool { | |||
| 75 | + | if id == 0 || w.stashHidden || len(w.Stash) == 0 || w.cover() != 0 { | |||
| 76 | + | return false | |||
| 77 | + | } | |||
| 78 | + | // No former column: toggle-window-stash tiles it as a new column. | |||
| 79 | + | w.Stash = append(w.Stash, Float{ID: id}) | |||
| 80 | + | w.stashAt, w.stashFocus = len(w.Stash)-1, true | |||
| 81 | + | return true | |||
| 82 | + | } | |||
| 83 | + | ||||
| 70 | // showStash shows the stash and gives it the focus; a member whose | 84 | // showStash shows the stash and gives it the focus; a member whose | ||
| 71 | // fullscreen waited while hidden is fullscreen again, selected, as if it | 85 | // fullscreen waited while hidden is fullscreen again, selected, as if it | ||
| 72 | // asked now. | 86 | // asked now. | ||
M internal/core/stash_test.go +47 -0
| @@ -196,6 +196,53 @@ func TestStashFromRemovedNamedWorkspace(t *testing.T) { | |||
| 196 | 196 | } | |
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | + | // With stash.capture, a window opened while the stash is shown joins its | |
| 200 | + | // end, selected, and takes the focus unless a native dialog has it. A | |
| 201 | + | // hidden stash, a covering fullscreen window or capture off let it tile. | |
| 202 | + | func TestStashCapture(t *testing.T) { | |
| 203 | + | for _, tc := range []struct { | |
| 204 | + | name string | |
| 205 | + | overflow Overflow | |
| 206 | + | setup func(*Monitor) | |
| 207 | + | captured bool | |
| 208 | + | focused WindowID | |
| 209 | + | }{ | |
| 210 | + | {"shown", OverflowScroll, func(*Monitor) {}, true, 4}, | |
| 211 | + | {"shown fixed", OverflowFixed, func(*Monitor) {}, true, 4}, | |
| 212 | + | {"dialog keeps focus", OverflowScroll, func(m *Monitor) { m.AddFloating(9, 10, 10) }, true, 9}, | |
| 213 | + | {"hidden", OverflowScroll, func(m *Monitor) { m.Current().ToggleStashVisible() }, false, 4}, | |
| 214 | + | {"under fullscreen", OverflowScroll, func(m *Monitor) { m.Current().SetFullscreen(3, true) }, false, 3}, | |
| 215 | + | {"off", OverflowScroll, func(m *Monitor) { m.SetStashCapture(false) }, false, 3}, | |
| 216 | + | } { | |
| 217 | + | t.Run(tc.name, func(t *testing.T) { | |
| 218 | + | m := stashMonitor(tc.overflow) | |
| 219 | + | m.SetStashCapture(true) | |
| 220 | + | tc.setup(m) | |
| 221 | + | w := m.Current() | |
| 222 | + | m.AddWindow(4) | |
| 223 | + | if captured := w.stashIndex(4) == 2 && w.stashAt == 2; captured != tc.captured { | |
| 224 | + | t.Fatalf("captured %v, stash %+v", captured, w.Stash) | |
| 225 | + | } | |
| 226 | + | if id, _ := w.Focused(); id != tc.focused { | |
| 227 | + | t.Fatalf("focused %d", id) | |
| 228 | + | } | |
| 229 | + | }) | |
| 230 | + | } | |
| 231 | + | } | |
| 232 | + | ||
| 233 | + | // A captured window has no former column: toggle-window-stash tiles it as | |
| 234 | + | // a new column after the focused one. | |
| 235 | + | func TestStashCaptureUnstash(t *testing.T) { | |
| 236 | + | m := stashMonitor(OverflowScroll) | |
| 237 | + | m.SetStashCapture(true) | |
| 238 | + | w := m.Current() | |
| 239 | + | m.AddWindow(4) | |
| 240 | + | w.ToggleWindowStash() | |
| 241 | + | if len(w.Columns) != 2 || w.Columns[1].Windows[0] != 4 || w.Focus != 1 || w.stashIndex(4) >= 0 { | |
| 242 | + | t.Fatalf("columns %+v focus %d stash %+v", w.Columns, w.Focus, w.Stash) | |
| 243 | + | } | |
| 244 | + | } | |
| 245 | + | ||
| 199 | 246 | // stash.width sets the selected window's width; the neighbors sit | |
| 200 | 247 | // stash.gap beside it, in what the margins show. | |
| 201 | 248 | func TestStashWidth(t *testing.T) { | |
| @@ -196,6 +196,53 @@ func TestStashFromRemovedNamedWorkspace(t *testing.T) { | |||||
| 196 | } | 196 | } | ||
| 197 | } | 197 | } | ||
| 198 | 198 | ||||
| 199 | + | // With stash.capture, a window opened while the stash is shown joins its | |||
| 200 | + | // end, selected, and takes the focus unless a native dialog has it. A | |||
| 201 | + | // hidden stash, a covering fullscreen window or capture off let it tile. | |||
| 202 | + | func TestStashCapture(t *testing.T) { | |||
| 203 | + | for _, tc := range []struct { | |||
| 204 | + | name string | |||
| 205 | + | overflow Overflow | |||
| 206 | + | setup func(*Monitor) | |||
| 207 | + | captured bool | |||
| 208 | + | focused WindowID | |||
| 209 | + | }{ | |||
| 210 | + | {"shown", OverflowScroll, func(*Monitor) {}, true, 4}, | |||
| 211 | + | {"shown fixed", OverflowFixed, func(*Monitor) {}, true, 4}, | |||
| 212 | + | {"dialog keeps focus", OverflowScroll, func(m *Monitor) { m.AddFloating(9, 10, 10) }, true, 9}, | |||
| 213 | + | {"hidden", OverflowScroll, func(m *Monitor) { m.Current().ToggleStashVisible() }, false, 4}, | |||
| 214 | + | {"under fullscreen", OverflowScroll, func(m *Monitor) { m.Current().SetFullscreen(3, true) }, false, 3}, | |||
| 215 | + | {"off", OverflowScroll, func(m *Monitor) { m.SetStashCapture(false) }, false, 3}, | |||
| 216 | + | } { | |||
| 217 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 218 | + | m := stashMonitor(tc.overflow) | |||
| 219 | + | m.SetStashCapture(true) | |||
| 220 | + | tc.setup(m) | |||
| 221 | + | w := m.Current() | |||
| 222 | + | m.AddWindow(4) | |||
| 223 | + | if captured := w.stashIndex(4) == 2 && w.stashAt == 2; captured != tc.captured { | |||
| 224 | + | t.Fatalf("captured %v, stash %+v", captured, w.Stash) | |||
| 225 | + | } | |||
| 226 | + | if id, _ := w.Focused(); id != tc.focused { | |||
| 227 | + | t.Fatalf("focused %d", id) | |||
| 228 | + | } | |||
| 229 | + | }) | |||
| 230 | + | } | |||
| 231 | + | } | |||
| 232 | + | ||||
| 233 | + | // A captured window has no former column: toggle-window-stash tiles it as | |||
| 234 | + | // a new column after the focused one. | |||
| 235 | + | func TestStashCaptureUnstash(t *testing.T) { | |||
| 236 | + | m := stashMonitor(OverflowScroll) | |||
| 237 | + | m.SetStashCapture(true) | |||
| 238 | + | w := m.Current() | |||
| 239 | + | m.AddWindow(4) | |||
| 240 | + | w.ToggleWindowStash() | |||
| 241 | + | if len(w.Columns) != 2 || w.Columns[1].Windows[0] != 4 || w.Focus != 1 || w.stashIndex(4) >= 0 { | |||
| 242 | + | t.Fatalf("columns %+v focus %d stash %+v", w.Columns, w.Focus, w.Stash) | |||
| 243 | + | } | |||
| 244 | + | } | |||
| 245 | + | ||||
| 199 | // stash.width sets the selected window's width; the neighbors sit | 246 | // stash.width sets the selected window's width; the neighbors sit | ||
| 200 | // stash.gap beside it, in what the margins show. | 247 | // stash.gap beside it, in what the margins show. | ||
| 201 | func TestStashWidth(t *testing.T) { | 248 | func TestStashWidth(t *testing.T) { | ||
M internal/ports/config.go +3 -1
| @@ -81,10 +81,12 @@ type Config struct { | |||
| 81 | 81 | // Stash is the strip of windows set aside by toggle-window-stash: | |
| 82 | 82 | // Width is the width of its selected window (10 to 90) and Gap the | |
| 83 | 83 | // space between it and its neighbors (0 to 10), in percent of the | |
| 84 | - | // usable width; Dim darkens the neighbors, 0 to 1. | |
| 84 | + | // usable width; Dim darkens the neighbors, 0 to 1. Capture puts new | |
| 85 | + | // windows in the stash while it is shown. | |
| 85 | 86 | Stash struct { | |
| 86 | 87 | Width, Gap int | |
| 87 | 88 | Dim float64 | |
| 89 | + | Capture bool | |
| 88 | 90 | } | |
| 89 | 91 | // Border is drawn inside the window edge; Width 0 disables it. | |
| 90 | 92 | Border struct { | |
| @@ -81,10 +81,12 @@ type Config struct { | |||||
| 81 | // Stash is the strip of windows set aside by toggle-window-stash: | 81 | // Stash is the strip of windows set aside by toggle-window-stash: | ||
| 82 | // Width is the width of its selected window (10 to 90) and Gap the | 82 | // Width is the width of its selected window (10 to 90) and Gap the | ||
| 83 | // space between it and its neighbors (0 to 10), in percent of the | 83 | // space between it and its neighbors (0 to 10), in percent of the | ||
| 84 | - | // usable width; Dim darkens the neighbors, 0 to 1. | 84 | + | // usable width; Dim darkens the neighbors, 0 to 1. Capture puts new |
| 85 | + | // windows in the stash while it is shown. | |||
| 85 | Stash struct { | 86 | Stash struct { | ||
| 86 | Width, Gap int | 87 | Width, Gap int | ||
| 87 | Dim float64 | 88 | Dim float64 | ||
| 89 | + | Capture bool | |||
| 88 | } | 90 | } | ||
| 89 | // Border is drawn inside the window edge; Width 0 disables it. | 91 | // Border is drawn inside the window edge; Width 0 disables it. | ||
| 90 | Border struct { | 92 | Border struct { | ||