detached baa0832

refactor(core): share spawn dispatch and workspace placement

Core.dispatch publishes the pending flag, sends the requests and cancels the
claims of those not issued; spawnEmpty and spawnStash both use it. place
handles terminal and stash claims in one case.
commit
baa0832f0cb43c99c03cf02c1f891a2da46ed4df
author
brice · · 2026-10-14 20:36 UTC
parent
f3346fa
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +25 -28
M internal/core/core.go +18 -8
@@ -1247,19 +1247,29 @@ func (c *Core) spawnEmpty(ctx context.Context) error {
12471247 }
12481248 first := c.firstTerminalResolved
12491249 terms := c.fillEmpty()
1250- err := c.publishPending(ctx)
1251- for i, req := range terms {
1250+ issued, err := c.dispatch(ctx, terms)
1251+ if len(terms) > 0 && issued == 0 {
1252+ c.firstTerminalResolved = first
1253+ }
1254+ return err
1255+}
1256+
1257+// dispatch sends reqs to the launcher and reports how many it issued.
1258+// Wayland reads claim tokens only while it knows spawns are pending, so the
1259+// flag is published before the first send. From the first request that is
1260+// not issued (publication failed, or the gate suppressed the send), the
1261+// rest are not sent and their claims are cancelled.
1262+func (c *Core) dispatch(ctx context.Context, reqs []ports.SpawnRequest) (issued int, err error) {
1263+ err = c.publishPending(ctx)
1264+ for i, req := range reqs {
12521265 if err != nil || !c.trySpawn(ctx, req) {
1253- for _, suppressed := range terms[i:] {
1266+ for _, suppressed := range reqs[i:] {
12541267 c.cancelClaim(suppressed)
12551268 }
1256- if i == 0 {
1257- c.firstTerminalResolved = first
1258- }
1259- return err
1269+ return i, err
12601270 }
12611271 }
1262- return err
1272+ return len(reqs), err
12631273 }
12641274
12651275 // activate brings a window forward for a valid xdg-activation token. The
@@ -1247,19 +1247,29 @@ func (c *Core) spawnEmpty(ctx context.Context) error {
1247 }1247 }
1248 first := c.firstTerminalResolved1248 first := c.firstTerminalResolved
1249 terms := c.fillEmpty()1249 terms := c.fillEmpty()
1250- err := c.publishPending(ctx)1250+ issued, err := c.dispatch(ctx, terms)
1251- for i, req := range terms {1251+ if len(terms) > 0 && issued == 0 {
1252+ c.firstTerminalResolved = first
1253+ }
1254+ return err
1255+}
1256+
1257+// dispatch sends reqs to the launcher and reports how many it issued.
1258+// Wayland reads claim tokens only while it knows spawns are pending, so the
1259+// flag is published before the first send. From the first request that is
1260+// not issued (publication failed, or the gate suppressed the send), the
1261+// rest are not sent and their claims are cancelled.
1262+func (c *Core) dispatch(ctx context.Context, reqs []ports.SpawnRequest) (issued int, err error) {
1263+ err = c.publishPending(ctx)
1264+ for i, req := range reqs {
1252 if err != nil || !c.trySpawn(ctx, req) {1265 if err != nil || !c.trySpawn(ctx, req) {
1253- for _, suppressed := range terms[i:] {1266+ for _, suppressed := range reqs[i:] {
1254 c.cancelClaim(suppressed)1267 c.cancelClaim(suppressed)
1255 }1268 }
1256- if i == 0 {1269+ return i, err
1257- c.firstTerminalResolved = first
1258- }
1259- return err
1260 }1270 }
1261 }1271 }
1262- return err1272+ return len(reqs), err
1263 }1273 }
1264 1274
1265 // activate brings a window forward for a valid xdg-activation token. The1275 // activate brings a window forward for a valid xdg-activation token. The
M internal/core/input.go +1 -9
@@ -398,15 +398,7 @@ func (c *Core) spawnStash(ctx context.Context, w *Workspace, commands [][]string
398398 for i, argv := range commands {
399399 reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now})
400400 }
401- err := c.publishPending(ctx)
402- for i, req := range reqs {
403- if err != nil || !c.trySpawn(ctx, req) {
404- for _, suppressed := range reqs[i:] {
405- c.cancelClaim(suppressed)
406- }
407- break
408- }
409- }
401+ _, err := c.dispatch(ctx, reqs)
410402 return err
411403 }
412404
@@ -398,15 +398,7 @@ func (c *Core) spawnStash(ctx context.Context, w *Workspace, commands [][]string
398 for i, argv := range commands {398 for i, argv := range commands {
399 reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now})399 reqs[i] = c.placement.request(argv, spawnTarget{kind: spawnStash, ws: w, at: now})
400 }400 }
401- err := c.publishPending(ctx)401+ _, err := c.dispatch(ctx, reqs)
402- for i, req := range reqs {
403- if err != nil || !c.trySpawn(ctx, req) {
404- for _, suppressed := range reqs[i:] {
405- c.cancelClaim(suppressed)
406- }
407- break
408- }
409- }
410 return err402 return err
411 }403 }
412 404
M internal/core/spawn_placement.go +6 -11
@@ -112,20 +112,15 @@ func (p *spawnPlacement) place(c *Core, v ports.WindowMapped) {
112112 p.expireWorkspaceClaims(c.now())
113113 if target, ok := p.pending[v.Slot]; ok {
114114 switch target.kind {
115- case spawnTerminal:
115+ case spawnTerminal, spawnStash:
116116 delete(p.pending, v.Slot)
117117 for _, s := range c.screens {
118118 if s.mon.has(target.ws) {
119- target.ws.AddWindow(v.ID)
120- s.mon.normalize()
121- return
122- }
123- }
124- case spawnStash:
125- delete(p.pending, v.Slot)
126- for _, s := range c.screens {
127- if s.mon.has(target.ws) {
128- target.ws.stashSpawned(v.ID)
119+ if target.kind == spawnStash {
120+ target.ws.stashSpawned(v.ID)
121+ } else {
122+ target.ws.AddWindow(v.ID)
123+ }
129124 s.mon.normalize()
130125 return
131126 }
@@ -112,20 +112,15 @@ func (p *spawnPlacement) place(c *Core, v ports.WindowMapped) {
112 p.expireWorkspaceClaims(c.now())112 p.expireWorkspaceClaims(c.now())
113 if target, ok := p.pending[v.Slot]; ok {113 if target, ok := p.pending[v.Slot]; ok {
114 switch target.kind {114 switch target.kind {
115- case spawnTerminal:115+ case spawnTerminal, spawnStash:
116 delete(p.pending, v.Slot)116 delete(p.pending, v.Slot)
117 for _, s := range c.screens {117 for _, s := range c.screens {
118 if s.mon.has(target.ws) {118 if s.mon.has(target.ws) {
119- target.ws.AddWindow(v.ID)119+ if target.kind == spawnStash {
120- s.mon.normalize()120+ target.ws.stashSpawned(v.ID)
121- return121+ } else {
122- }122+ target.ws.AddWindow(v.ID)
123- }123+ }
124- case spawnStash:
125- delete(p.pending, v.Slot)
126- for _, s := range c.screens {
127- if s.mon.has(target.ws) {
128- target.ws.stashSpawned(v.ID)
129 s.mon.normalize()124 s.mon.normalize()
130 return125 return
131 }126 }

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