spawn_placement.go RawHistoryFolder
1package core
2
3import (
4 "crypto/rand"
5 "slices"
6 "time"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// spawnPlacement owns pending SlotEnv claims. Slot and terminal retry decisions
12// remain with their respective policies; only this registry mints and claims tokens.
13type spawnPlacement struct {
14 pending map[string]spawnTarget
15 slots map[slotKey]string
16}
17
18// spawnTarget identifies the destination of one pending spawn.
19type spawnTarget struct {
20 slot slotKey
21 terminal *Workspace
22 at time.Time
23}
24
25func newSpawnPlacement() spawnPlacement {
26 return spawnPlacement{pending: make(map[string]spawnTarget), slots: make(map[slotKey]string)}
27}
28
29// request mints a token and records its pending destination.
30func (p *spawnPlacement) request(argv []string, target spawnTarget) ports.SpawnRequest {
31 token := rand.Text()
32 p.pending[token] = target
33 if target.terminal == nil {
34 p.slots[target.slot] = token
35 }
36 return ports.SpawnRequest{Argv: slices.Clone(argv), Env: []string{ports.SlotEnv + "=" + token}}
37}
38
39// dropSlot invalidates the pending token of a slot.
40func (p *spawnPlacement) dropSlot(key slotKey) {
41 if token := p.slots[key]; token != "" {
42 delete(p.pending, token)
43 delete(p.slots, key)
44 }
45}
46
47// slotPending reports whether a slot still awaits a window.
48func (p *spawnPlacement) slotPending(key slotKey) bool { return p.slots[key] != "" }
49
50// anyPending reports whether any spawn still awaits a window.
51func (p *spawnPlacement) anyPending() bool { return len(p.pending) != 0 }
52
53// terminalPending reports whether a workspace awaits an automatic terminal.
54func (p *spawnPlacement) terminalPending(w *Workspace) bool {
55 for _, target := range p.pending {
56 if target.terminal == w {
57 return true
58 }
59 }
60 return false
61}
62
63// expireTerminals drops terminal claims older than the retry interval.
64func (p *spawnPlacement) expireTerminals(now time.Time) {
65 for token, target := range p.pending {
66 if target.terminal != nil && now.Sub(target.at) > termRetry {
67 delete(p.pending, token)
68 }
69 }
70}
71
72// place claims a token once and puts its window in the target workspace.
73// Untagged windows return before any map lookup or allocation.
74func (p *spawnPlacement) place(c *Core, v ports.WindowMapped) {
75 if v.Floating {
76 if s, _ := c.screenOf(v.ID); s == nil {
77 c.cur().mon.AddFloating(v.ID, v.Width, v.Height)
78 }
79 return
80 }
81 if v.Slot != "" {
82 if target, ok := p.pending[v.Slot]; ok {
83 if target.terminal != nil {
84 delete(p.pending, v.Slot)
85 for _, s := range c.screens {
86 if s.mon.has(target.terminal) {
87 target.terminal.AddWindow(v.ID)
88 s.mon.normalize()
89 return
90 }
91 }
92 } else if st := c.slots[target.slot]; st != nil && st.window == 0 {
93 if sc, w := c.byName(target.slot.workspace); w != nil {
94 p.dropSlot(target.slot)
95 st.window = v.ID
96 w.AddSlotWindow(v.ID, target.slot.index, st.width)
97 sc.mon.normalize()
98 return
99 }
100 }
101 }
102 }
103 if s, _ := c.screenOf(v.ID); s == nil {
104 c.cur().mon.AddWindow(v.ID)
105 }
106}