1package browser
2
3import (
4 "fmt"
5 "log"
6 "strings"
7 "sync"
8)
9
10type TreeSnapshot struct {
11 Label string
12 Lines []string
13}
14
15type WorkspaceDiagnostics struct {
16 mu sync.Mutex
17 enabled bool
18 snapshots []TreeSnapshot
19}
20
21func NewWorkspaceDiagnostics(enabled bool) *WorkspaceDiagnostics {
22 return &WorkspaceDiagnostics{enabled: enabled}
23}
24
25func (d *WorkspaceDiagnostics) Enabled() bool {
26 if d == nil {
27 return false
28 }
29 d.mu.Lock()
30 defer d.mu.Unlock()
31 return d.enabled
32}
33
34func (d *WorkspaceDiagnostics) SetEnabled(enabled bool) {
35 if d == nil {
36 return
37 }
38 d.mu.Lock()
39 d.enabled = enabled
40 d.mu.Unlock()
41}
42
43func (d *WorkspaceDiagnostics) Capture(label string, root *paneNode) TreeSnapshot {
44 if d == nil {
45 return TreeSnapshot{}
46 }
47
48 d.mu.Lock()
49 enabled := d.enabled
50 d.mu.Unlock()
51 if !enabled {
52 return TreeSnapshot{}
53 }
54
55 snapshot := TreeSnapshot{Label: label}
56 visited := make(map[*paneNode]bool)
57 buildTreeSnapshot(root, 0, "", visited, &snapshot.Lines)
58 if len(snapshot.Lines) == 0 {
59 snapshot.Lines = append(snapshot.Lines, "<empty workspace>")
60 }
61
62 d.mu.Lock()
63 d.snapshots = append(d.snapshots, snapshot)
64 d.mu.Unlock()
65
66 log.Printf("[pane-close] snapshot %s\n%s", snapshot.Label, strings.Join(snapshot.Lines, "\n"))
67 return snapshot
68}
69
70func (d *WorkspaceDiagnostics) Snapshots() []TreeSnapshot {
71 if d == nil {
72 return nil
73 }
74 d.mu.Lock()
75 defer d.mu.Unlock()
76
77 out := make([]TreeSnapshot, len(d.snapshots))
78 copy(out, d.snapshots)
79 return out
80}
81
82func buildTreeSnapshot(node *paneNode, depth int, prefix string, visited map[*paneNode]bool, lines *[]string) {
83 indent := strings.Repeat(" ", depth)
84 if node == nil {
85 *lines = append(*lines, fmt.Sprintf("%s%s<nil>", indent, prefix))
86 return
87 }
88
89 if depth > 64 {
90 *lines = append(*lines, fmt.Sprintf("%s%s<max depth reached>", indent, prefix))
91 return
92 }
93
94 if visited[node] {
95 *lines = append(*lines, fmt.Sprintf("%s%s<cycle node=%p>", indent, prefix, node))
96 return
97 }
98
99 visited[node] = true
100 *lines = append(*lines, fmt.Sprintf("%s%s%s", indent, prefix, describePaneNode(node)))
101
102 if node.isStacked {
103 for idx, child := range node.stackedPanes {
104 childPrefix := fmt.Sprintf("[%d] ", idx)
105 buildTreeSnapshot(child, depth+1, childPrefix, visited, lines)
106 }
107 return
108 }
109
110 if node.left != nil || node.right != nil {
111 buildTreeSnapshot(node.left, depth+1, "L: ", visited, lines)
112 buildTreeSnapshot(node.right, depth+1, "R: ", visited, lines)
113 }
114}
115
116func describePaneNode(node *paneNode) string {
117 if node == nil {
118 return "<nil>"
119 }
120
121 parts := []string{fmt.Sprintf("node=%p", node)}
122
123 if node.isLeaf {
124 parts = append(parts, "leaf")
125 if node.pane != nil && node.pane.ID() != "" {
126 parts = append(parts, fmt.Sprintf("pane=%s", node.pane.ID()))
127 }
128 if node.isPopup {
129 parts = append(parts, "popup")
130 }
131 } else if node.isStacked {
132 parts = append(parts, fmt.Sprintf("stack size=%d active=%d", len(node.stackedPanes), node.activeStackIndex))
133 } else {
134 parts = append(parts, fmt.Sprintf("split orientation=%d", node.orientation))
135 }
136
137 if node.container != 0 {
138 parts = append(parts, fmt.Sprintf("widget=%#x", node.container))
139 }
140
141 if node.parent != nil {
142 parts = append(parts, fmt.Sprintf("parent=%p", node.parent))
143 }
144
145 if !node.isLeaf && !node.isStacked {
146 if node.left != nil {
147 parts = append(parts, fmt.Sprintf("left=%p", node.left))
148 }
149 if node.right != nil {
150 parts = append(parts, fmt.Sprintf("right=%p", node.right))
151 }
152 }
153
154 return strings.Join(parts, " ")
155}