v0.15.0 9006957

workspace_operations.go RawHistoryFolder

1// workspace_operations.go - Workspace operation methods with validation and safety
2package browser
3
4import (
5	"fmt"
6	"github.com/bnema/dumber/internal/logging"
7	"github.com/bnema/dumber/pkg/webkit"
8)
9
10// SplitPane performs a split operation with validation and safety checks
11func (wm *WorkspaceManager) SplitPane(target *paneNode, direction string) (*paneNode, error) {
12	if wm == nil {
13		return nil, fmt.Errorf("workspace manager is nil")
14	}
15
16	// Validate direction parameter
17	validDirections := map[string]bool{
18		DirectionLeft:  true,
19		DirectionRight: true,
20		DirectionUp:    true,
21		DirectionDown:  true,
22	}
23	if !validDirections[direction] {
24		return nil, fmt.Errorf("invalid split direction '%s', expected one of: left, right, up, down", direction)
25	}
26
27	logging.Debug(fmt.Sprintf("[workspace] Starting split operation: target=%p direction=%s", target, direction))
28
29	// Step 1: Validate geometry constraints (only in DebugBasic+)
30	if wm.debugLevel >= DebugBasic {
31		validation := wm.geometryValidator.ValidateSplit(target, direction)
32		if !validation.IsValid {
33			if wm.debugLevel == DebugFull {
34				return nil, fmt.Errorf("split validation failed: %s", validation.Reason)
35			}
36			logging.Error(fmt.Sprintf("[workspace] WARNING: Split validation failed but allowing operation: %s", validation.Reason))
37		}
38
39		// Log if re-validation will be needed due to pending widget allocation
40		if validation.RequiresRevalidation {
41			logging.Info(fmt.Sprintf("[workspace] Split validation passed with pending allocation - operation will proceed"))
42		}
43
44		// Step 3: Validate tree invariants before operation
45		if err := wm.treeValidator.ValidateTree(wm.root, "before_split"); err != nil {
46			if wm.debugLevel == DebugFull {
47				return nil, fmt.Errorf("tree validation failed before split: %w", err)
48			}
49			logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before split but allowing operation: %v", err))
50		}
51	}
52
53	// Step 2: Execute directly if we're already on the GTK main thread
54	if webkit.IsMainThread() {
55		logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing split directly"))
56		newNode, err := wm.splitNode(target, direction, nil)
57		if err != nil {
58			return nil, err
59		}
60
61		if wm.debugLevel >= DebugBasic {
62			if err := wm.treeValidator.ValidateTree(wm.root, "after_split"); err != nil {
63				logging.Error(fmt.Sprintf("[workspace] Tree validation failed after split: %v", err))
64			}
65		}
66
67		// Tree rebalancing is only needed after close promotions. Splits have correct allocation from GTK.
68
69		logging.Info(fmt.Sprintf("[workspace] Split operation completed successfully (direct execution): newNode=%p", newNode))
70		return newNode, nil
71	}
72
73	// Step 3: Not on main thread, marshal via IdleAdd
74	logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling split via IdleAdd"))
75	var newNode *paneNode
76	var splitErr error
77	done := make(chan struct{})
78
79	_ = webkit.IdleAdd(func() bool {
80		newNode, splitErr = wm.splitNode(target, direction, nil)
81		if splitErr == nil {
82			if wm.debugLevel >= DebugBasic {
83				if verr := wm.treeValidator.ValidateTree(wm.root, "after_split"); verr != nil {
84					logging.Error(fmt.Sprintf("[workspace] Tree validation failed after split: %v", verr))
85				}
86			}
87		}
88		close(done)
89		return false
90	})
91
92	<-done
93
94	if splitErr != nil {
95		return nil, splitErr
96	}
97
98	logging.Info(fmt.Sprintf("[workspace] Split operation completed successfully: newNode=%p", newNode))
99	return newNode, nil
100}
101
102// ClosePane performs a close operation with validation and safety checks
103func (wm *WorkspaceManager) ClosePane(node *paneNode) error {
104	if wm == nil {
105		return fmt.Errorf("workspace manager is nil")
106	}
107
108	logging.Info(fmt.Sprintf("[workspace] Starting close operation: node=%p", node))
109	wm.paneCloseLogf("start bulletproof close node=%p", node)
110	wm.dumpTreeState("before_close")
111
112	// Step 1: Validate tree invariants before operation (only in DebugBasic+)
113	if wm.debugLevel >= DebugBasic {
114		if err := wm.treeValidator.ValidateTree(wm.root, "before_close"); err != nil {
115			if wm.debugLevel == DebugFull {
116				return fmt.Errorf("tree validation failed before close: %w", err)
117			}
118			logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before close but allowing operation: %v", err))
119		}
120	}
121
122	// Step 3: Check if this is a stacked pane and use enhanced lifecycle management
123	if node.parent != nil && node.parent.isStacked {
124		logging.Debug(fmt.Sprintf("[workspace] Using enhanced stack lifecycle management for close"))
125		return wm.stackLifecycleManager.CloseStackedPaneWithLifecycle(node)
126	}
127
128	// Step 4: Execute directly when already on the GTK main thread
129	if webkit.IsMainThread() {
130		logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing close directly"))
131
132		wm.paneCloseLogf("invoking closePane node=%p", node)
133		promoted, err := wm.closePane(node)
134		if err != nil {
135			wm.paneCloseLogf("closePane failed node=%p err=%v", node, err)
136			wm.dumpTreeState("after_close_error")
137			return err
138		}
139
140		if wm.debugLevel >= DebugBasic {
141			if err := wm.treeValidator.ValidateTree(wm.root, "after_close"); err != nil {
142				logging.Error(fmt.Sprintf("[workspace] Tree validation failed after close: %v", err))
143			}
144		}
145		wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)
146		wm.dumpTreeState("after_close_success")
147
148		if wm.treeRebalancer != nil {
149			if err := wm.treeRebalancer.RebalanceAfterClose(node, promoted); err != nil {
150				logging.Error(fmt.Sprintf("[workspace] Tree rebalancing failed after close: %v", err))
151			}
152		}
153
154		logging.Info(fmt.Sprintf("[workspace] Close operation completed successfully (direct execution)"))
155		return nil
156	}
157
158	// Step 5: Not on main thread, marshal via IdleAdd
159	logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling close via IdleAdd"))
160	var promoted *paneNode
161	var closeErr error
162	done := make(chan struct{})
163
164	_ = webkit.IdleAdd(func() bool {
165		wm.paneCloseLogf("invoking closePane node=%p", node)
166		promoted, closeErr = wm.closePane(node)
167		if closeErr != nil {
168			wm.paneCloseLogf("closePane failed node=%p err=%v", node, closeErr)
169			wm.dumpTreeState("after_close_error")
170		} else {
171			if wm.debugLevel >= DebugBasic {
172				if verr := wm.treeValidator.ValidateTree(wm.root, "after_close"); verr != nil {
173					logging.Error(fmt.Sprintf("[workspace] Tree validation failed after close: %v", verr))
174				}
175			}
176			wm.paneCloseLogf("closePane succeeded node=%p promoted=%p root=%p", node, promoted, wm.root)
177			wm.dumpTreeState("after_close_success")
178
179			if wm.treeRebalancer != nil {
180				if rerr := wm.treeRebalancer.RebalanceAfterClose(node, promoted); rerr != nil {
181					logging.Error(fmt.Sprintf("[workspace] Tree rebalancing failed after close: %v", rerr))
182				}
183			}
184		}
185		close(done)
186		return false
187	})
188
189	<-done
190
191	if closeErr != nil {
192		return closeErr
193	}
194
195	logging.Info(fmt.Sprintf("[workspace] Close operation completed successfully"))
196	return nil
197}
198
199// StackPane performs a stack operation with validation and safety checks
200func (wm *WorkspaceManager) StackPane(target *paneNode) (*paneNode, error) {
201	if wm == nil {
202		return nil, fmt.Errorf("workspace manager is nil")
203	}
204
205	logging.Info(fmt.Sprintf("[workspace] Starting stack operation: target=%p", target))
206
207	// Step 1: Validate stack operation constraints (only in DebugBasic+)
208	if wm.debugLevel >= DebugBasic {
209		validation := wm.geometryValidator.ValidateStackOperation(target)
210		if !validation.IsValid {
211			if wm.debugLevel == DebugFull {
212				return nil, fmt.Errorf("stack validation failed: %s", validation.Reason)
213			}
214			logging.Error(fmt.Sprintf("[workspace] WARNING: Stack validation failed but allowing operation: %s", validation.Reason))
215		}
216
217		// Log if re-validation will be needed due to pending widget allocation
218		if validation.RequiresRevalidation {
219			logging.Info(fmt.Sprintf("[workspace] Stack validation passed with pending allocation - operation will proceed"))
220		}
221	}
222
223	// Step 2: Validate tree invariants before operation (only in DebugBasic+)
224	if wm.debugLevel >= DebugBasic {
225		if err := wm.treeValidator.ValidateTree(wm.root, "before_stack"); err != nil {
226			if wm.debugLevel == DebugFull {
227				return nil, fmt.Errorf("tree validation failed before stack: %w", err)
228			}
229			logging.Error(fmt.Sprintf("[workspace] WARNING: Tree validation failed before stack but allowing operation: %v", err))
230		}
231	}
232
233	// Step 3: Execute directly if already on the GTK main thread
234	if webkit.IsMainThread() {
235		logging.Info(fmt.Sprintf("[workspace] Already on main thread, executing stack directly"))
236		newNode, err := wm.stackedPaneManager.StackPane(target)
237		if err != nil {
238			return nil, err
239		}
240
241		if wm.debugLevel >= DebugBasic {
242			if err := wm.treeValidator.ValidateTree(wm.root, "after_stack"); err != nil {
243				logging.Error(fmt.Sprintf("[workspace] Tree validation failed after stack: %v", err))
244			}
245		}
246
247		logging.Info(fmt.Sprintf("[workspace] Stack operation completed successfully (direct execution): newNode=%p", newNode))
248		return newNode, nil
249	}
250
251	// Step 4: Not on main thread, marshal via IdleAdd
252	logging.Info(fmt.Sprintf("[workspace] Not on main thread, marshalling stack via IdleAdd"))
253	var newNode *paneNode
254	var stackErr error
255	done := make(chan struct{})
256
257	_ = webkit.IdleAdd(func() bool {
258		newNode, stackErr = wm.stackedPaneManager.StackPane(target)
259		if stackErr == nil {
260			if wm.debugLevel >= DebugBasic {
261				if verr := wm.treeValidator.ValidateTree(wm.root, "after_stack"); verr != nil {
262					logging.Error(fmt.Sprintf("[workspace] Tree validation failed after stack: %v", verr))
263				}
264			}
265		}
266		close(done)
267		return false
268	})
269
270	<-done
271
272	if stackErr != nil {
273		return nil, stackErr
274	}
275
276	logging.Info(fmt.Sprintf("[workspace] Stack operation completed successfully: newNode=%p", newNode))
277	return newNode, nil
278}
279
280// EnableEnhancedMode enables all enhanced validation features
281func (wm *WorkspaceManager) EnableEnhancedMode() {
282	if wm.treeValidator != nil {
283		wm.treeValidator.Enable()
284	}
285	if wm.treeRebalancer != nil {
286		wm.treeRebalancer.Enable()
287	}
288	logging.Info(fmt.Sprintf("[workspace] Enhanced validation mode enabled"))
289}
290
291// DisableEnhancedMode disables enhanced features for performance
292func (wm *WorkspaceManager) DisableEnhancedMode() {
293	if wm.treeValidator != nil {
294		wm.treeValidator.Disable()
295	}
296	if wm.treeRebalancer != nil {
297		wm.treeRebalancer.Disable()
298	}
299	logging.Info(fmt.Sprintf("[workspace] Enhanced validation mode disabled"))
300}
301
302// SetEnhancedDebugMode enables/disables debug mode for all enhanced validation components
303func (wm *WorkspaceManager) SetEnhancedDebugMode(debug bool) {
304	if wm.treeValidator != nil {
305		wm.treeValidator.SetDebugMode(debug)
306	}
307	if wm.geometryValidator != nil {
308		wm.geometryValidator.SetDebugMode(debug)
309	}
310	logging.Debug(fmt.Sprintf("[workspace] Enhanced debug mode set to: %v", debug))
311}
312
313// GetEnhancedStats returns comprehensive statistics about all enhanced validation components
314func (wm *WorkspaceManager) GetEnhancedStats() map[string]interface{} {
315	stats := make(map[string]interface{})
316
317	if wm.treeValidator != nil {
318		stats["tree_validation"] = wm.treeValidator.GetValidationStats()
319	}
320
321	if wm.treeRebalancer != nil {
322		stats["tree_rebalancing"] = wm.treeRebalancer.GetRebalancingStats()
323	}
324
325	// Geometry validator no longer tracks stats - simplified
326
327	return stats
328}
329
330// ValidateWorkspaceIntegrity performs a comprehensive validation of the workspace
331func (wm *WorkspaceManager) ValidateWorkspaceIntegrity() error {
332	logging.Info(fmt.Sprintf("[workspace] Performing comprehensive workspace integrity check"))
333
334	// Tree structure validation
335	if wm.treeValidator != nil {
336		if err := wm.treeValidator.ValidateTree(wm.root, "integrity_check"); err != nil {
337			return fmt.Errorf("tree validation failed: %w", err)
338		}
339	}
340
341	// Geometry validation simplified - ValidateWorkspaceLayout removed
342
343	logging.Info(fmt.Sprintf("[workspace] Workspace integrity check completed successfully"))
344	return nil
345}
346
347// ShutdownEnhancedComponents gracefully shuts down all enhanced validation components
348func (wm *WorkspaceManager) ShutdownEnhancedComponents() {
349	logging.Info(fmt.Sprintf("[workspace] Shutting down enhanced validation components"))
350
351	// Other components don't require explicit shutdown currently
352	logging.Info(fmt.Sprintf("[workspace] Enhanced validation components shutdown complete"))
353}

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 8

Tags 42

↑↓ moveenter openesc close