terminal.go RawHistoryFolder
1package core
2
3import (
4 "strings"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// Automatic terminal requests use a SlotEnv token to place their windows
11// on the intended workspace even if focus changes before mapping.
12
13// termRetry is the least time between two terminals for one workspace: a
14// terminal that fails, or maps and exits at once, is not respawned in a loop.
15const termRetry = 5 * time.Second
16
17// keepsTerminal reports whether empty workspaces get a terminal.
18func (c *Core) keepsTerminal() bool {
19 return c.opts.Terminal && c.cfg.Terminal.AutoOpen != "off" && len(c.cfg.Terminal.Command) > 0
20}
21
22// fillEmpty returns the terminals to spawn for empty workspaces on screen.
23// Workspaces with slots are left to them.
24func (c *Core) fillEmpty() []ports.SpawnRequest {
25 now := c.now()
26 c.placement.expireWorkspaceClaims(now)
27 if !c.keepsTerminal() {
28 return nil
29 }
30 var reqs []ports.SpawnRequest
31 for _, s := range c.screens {
32 w := s.mon.Current()
33 if c.cfg.Terminal.AutoOpen == "first" {
34 if c.firstTerminalResolved || s != c.cur() || s.name() == "" {
35 continue
36 }
37 c.firstTerminalResolved = true
38 if w != s.mon.Workspaces[0] || !w.empty() || c.hasSlots(w) {
39 continue
40 }
41 }
42 if s.name() == "" || !w.empty() || c.hasSlots(w) || c.placement.workspacePending(w, spawnTerminal) || now.Sub(w.termAt) < termRetry {
43 continue
44 }
45 c.firstTerminalResolved = true
46 w.termAt = now
47 reqs = append(reqs, c.placement.request(c.cfg.Terminal.Command, spawnTarget{kind: spawnTerminal, ws: w, at: now}))
48 }
49 return reqs
50}
51
52// cancelClaim removes a workspace claim minted for a send suppressed by the
53// gate. A terminal may be requested again at once.
54func (c *Core) cancelClaim(req ports.SpawnRequest) {
55 for _, env := range req.Env {
56 if token, ok := strings.CutPrefix(env, ports.SlotEnv+"="); ok {
57 if target, ok := c.placement.pending[token]; ok && target.kind != spawnSlot {
58 if target.kind == spawnTerminal {
59 target.ws.termAt = time.Time{}
60 }
61 delete(c.placement.pending, token)
62 }
63 }
64 }
65}
66
67func (c *Core) hasSlots(w *Workspace) bool {
68 for key := range c.slots {
69 if w.Name != "" && key.workspace == w.Name {
70 return true
71 }
72 }
73 return false
74}