返回 DeepSeek-Reasonix
workspace_removal.go
根目录 / desktop / workspace_removal.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "log/slog"
7 )
8
9 type workspaceTabCandidate struct {
10 id string
11 tab *WorkspaceTab
12 }
13
14 // The caller holds the runtime-mutation and session-removal barriers. Snapshot
15 // off App.mu, then verify that neither a running runtime nor a replacement tab
16 // appeared before durable removal begins.
17 func (a *App) snapshotWorkspaceTabsForRemoval(dir string) ([]workspaceTabCandidate, error) {
18 a.mu.Lock()
19 if a.hasRunningWorkspaceRuntimeLocked(dir) {
20 a.mu.Unlock()
21 return nil, fmt.Errorf("workspace has running sessions; stop them before removing")
22 }
23 candidates := make([]workspaceTabCandidate, 0)
24 for id, tab := range a.tabs {
25 if tabInWorkspace(tab, dir) {
26 candidates = append(candidates, workspaceTabCandidate{id: id, tab: tab})
27 }
28 }
29 a.mu.Unlock()
30
31 snapshotted := make(map[string]*WorkspaceTab, len(candidates))
32 for _, candidate := range candidates {
33 snapshotted[candidate.id] = candidate.tab
34 if err := a.snapshotTab(candidate.tab); err != nil {
35 slog.Warn("desktop: snapshot before removing workspace failed", "tab", candidate.id, "workspace", dir, "err", err)
36 return nil, fmt.Errorf("save current session before removing workspace: %w", err)
37 }
38 }
39
40 a.mu.Lock()
41 defer a.mu.Unlock()
42 if a.hasRunningWorkspaceRuntimeLocked(dir) {
43 return nil, fmt.Errorf("workspace has running sessions; stop them before removing")
44 }
45 for id, tab := range a.tabs {
46 if tabInWorkspace(tab, dir) && snapshotted[id] != tab {
47 return nil, fmt.Errorf("workspace tabs changed while removing; retry")
48 }
49 }
50 return candidates, nil
51 }
52
53 func (a *App) hasRunningWorkspaceRuntimeLocked(dir string) bool {
54 for _, tab := range a.tabs {
55 if tabInWorkspace(tab, dir) && tab.hasActiveRuntimeWork() {
56 return true
57 }
58 }
59 for _, tab := range a.detachedSessions {
60 if tabInWorkspace(tab, dir) && tab.hasActiveRuntimeWork() {
61 return true
62 }
63 }
64 return false
65 }
66
67 func (a *App) hideWorkspaceForRemoval(dir string) error {
68 workspaceID, err := a.resolveDesktopWorkspaceID(a.bootContext(), "project", dir)
69 if err != nil {
70 return err
71 }
72 registry := a.workspaceRegistry()
73 state, err := registry.Load(a.bootContext())
74 if err != nil {
75 return err
76 }
77 wasVisible := state.Workspaces[workspaceID].Visible
78 if wasVisible {
79 if err := registry.SetWorkspaceVisible(a.bootContext(), workspaceID, false); err != nil {
80 return err
81 }
82 }
83 // Both durable sidebar stores must accept removal before runtime bindings
84 // are unlinked. The caller keeps runtime admission frozen throughout.
85 if err := removeProject(dir); err != nil {
86 if wasVisible {
87 if restoreErr := registry.SetWorkspaceVisible(a.bootContext(), workspaceID, true); restoreErr != nil {
88 return errors.Join(err, fmt.Errorf("restore workspace visibility: %w", restoreErr))
89 }
90 }
91 return err
92 }
93 return nil
94 }
95
96 func (a *App) unlinkWorkspaceTabsForRemoval(dir string, candidates []workspaceTabCandidate) (fallback *WorkspaceTab, closeTabs, closeDetached []*WorkspaceTab) {
97 a.mu.Lock()
98 defer a.mu.Unlock()
99 for _, candidate := range candidates {
100 id, tab := candidate.id, candidate.tab
101 if tab == nil || a.tabs[id] != tab || !tabInWorkspace(tab, dir) {
102 continue
103 }
104 a.markTabRemovedLocked(tab)
105 closeTabs = append(closeTabs, tab)
106 delete(a.tabs, id)
107 a.removeTabOrderLocked(id)
108 if a.activeTabID == id {
109 a.activeTabID = ""
110 }
111 }
112 for key, tab := range a.detachedSessions {
113 if !tabInWorkspace(tab, dir) {
114 continue
115 }
116 closeDetached = append(closeDetached, tab)
117 delete(a.detachedSessions, key)
118 }
119 if len(a.tabs) == 0 {
120 fallback = a.createTabEntry("global", globalTabWorkspaceRoot(), "")
121 fallback.TopicTitle = "Global"
122 fallback.sink = &tabEventSink{tabID: fallback.ID, app: a, ctx: a.ctx}
123 a.tabs[fallback.ID] = fallback
124 a.tabOrder = append(a.tabOrder, fallback.ID)
125 a.activeTabID = fallback.ID
126 } else if a.activeTabID == "" {
127 if ordered := a.orderedTabIDsLocked(); len(ordered) > 0 {
128 a.activeTabID = ordered[0]
129 }
130 }
131 a.saveTabsLocked()
132 return fallback, closeTabs, closeDetached
133 }
134
134 lines GO