1package ui
2
3import (
4 "iter"
5 "maps"
6 "slices"
7
8 "github.com/bnema/dumber/internal/domain/entity"
9)
10
11// floatingSessionRegistry owns every floating pane session, keyed by tab and
12// profile session. All lookups over sessions go through it so the ownership
13// rules (one session per key, nil entries ignored, pane-to-tab mapping via
14// the key, never by parsing PaneID) live in one place.
15type floatingSessionRegistry map[floatingSessionKey]*floatingWorkspaceSession
16
17// all yields every non-nil session without allocating. Callers must not add
18// or remove sessions while iterating; use forTabSnapshot for that.
19func (r floatingSessionRegistry) all() iter.Seq2[floatingSessionKey, *floatingWorkspaceSession] {
20 return func(yield func(floatingSessionKey, *floatingWorkspaceSession) bool) {
21 for key, session := range r {
22 if session != nil && !yield(key, session) {
23 return
24 }
25 }
26 }
27}
28
29// forTab yields the non-nil sessions owned by tabID without allocating, so it
30// is safe on hot GTK layout callbacks. Same mutation rule as all.
31func (r floatingSessionRegistry) forTab(tabID entity.TabID) iter.Seq2[floatingSessionKey, *floatingWorkspaceSession] {
32 return func(yield func(floatingSessionKey, *floatingWorkspaceSession) bool) {
33 for key, session := range r.all() {
34 if key.tabID == tabID && !yield(key, session) {
35 return
36 }
37 }
38 }
39}
40
41// forTabSnapshot copies tabID's sessions so the caller may release them.
42func (r floatingSessionRegistry) forTabSnapshot(tabID entity.TabID) map[floatingSessionKey]*floatingWorkspaceSession {
43 return maps.Collect(r.forTab(tabID))
44}
45
46// byPaneID returns the session hosting paneID and its key.
47func (r floatingSessionRegistry) byPaneID(paneID entity.PaneID) (floatingSessionKey, *floatingWorkspaceSession, bool) {
48 if paneID == "" {
49 return floatingSessionKey{}, nil, false
50 }
51 for key, session := range r {
52 if session != nil && session.paneID == paneID {
53 return key, session, true
54 }
55 }
56 return floatingSessionKey{}, nil, false
57}
58
59// visibleForTab returns the visible session of tabID, if any.
60func (r floatingSessionRegistry) visibleForTab(tabID entity.TabID) (floatingSessionKey, *floatingWorkspaceSession, bool) {
61 for key, session := range r {
62 if key.tabID != tabID || session == nil || session.pane == nil {
63 continue
64 }
65 if session.pane.IsVisible() {
66 return key, session, true
67 }
68 }
69 return floatingSessionKey{tabID: tabID}, nil, false
70}
71
72// tabIDs returns the distinct tabs owning at least one session, sorted.
73func (r floatingSessionRegistry) tabIDs() []entity.TabID {
74 ids := make([]entity.TabID, 0, len(r))
75 for key := range r {
76 if !slices.Contains(ids, key.tabID) {
77 ids = append(ids, key.tabID)
78 }
79 }
80 slices.Sort(ids)
81 return ids
82}