返回 DeepSeek-Reasonix
remote_single_surface.go
根目录 / desktop / remote_single_surface.go
1 package main
2
3 import (
4 "context"
5 "fmt"
6 )
7
8 // keepOnlyRemoteVisibleTab applies the workbench/creation one-surface policy
9 // after a remote tab has been registered. Local controllers with active work
10 // are detached exactly like local topic activation; inactive local bindings
11 // are closed, and other remote views drop only their event pumps while their
12 // managed Serve processes remain available in the project tree.
13 //
14 // The caller holds singleSurfaceMu, so local and remote navigation cannot add
15 // another visible surface while the snapshots and registry prune are in flight.
16 func (a *App) keepOnlyRemoteVisibleTab(tabID string) (TabMeta, error) {
17 type localCandidate struct {
18 id string
19 tab *WorkspaceTab
20 }
21
22 meta, remoteCancels, err := func() (TabMeta, []context.CancelFunc, error) {
23 defer a.lockRuntimeMutation("prune-visible-tabs-for-remote")()
24 a.sessionRemovalMu.Lock()
25 defer a.sessionRemovalMu.Unlock()
26
27 a.mu.Lock()
28 candidates := make([]localCandidate, 0, len(a.tabs))
29 for id, tab := range a.tabs {
30 candidates = append(candidates, localCandidate{id: id, tab: tab})
31 }
32 a.mu.Unlock()
33
34 // Snapshot while bindings remain visible and without a.mu: controller
35 // recovery can re-enter App. sessionRemovalMu keeps destructive session
36 // operations away from the same files until the prune commits.
37 for _, candidate := range candidates {
38 if err := a.snapshotTab(candidate.tab); err != nil {
39 return TabMeta{}, nil, fmt.Errorf("save current session before switching tabs: %w", err)
40 }
41 if err := a.saveTabSessionMetaForCurrentSession(candidate.tab); err != nil {
42 return TabMeta{}, nil, fmt.Errorf("save current session metadata before switching tabs: %w", err)
43 }
44 }
45
46 a.mu.Lock()
47 for _, candidate := range candidates {
48 if a.tabs[candidate.id] != candidate.tab {
49 a.mu.Unlock()
50 return TabMeta{}, nil, fmt.Errorf("visible tabs changed while switching; retry")
51 }
52 }
53
54 a.remoteTabMu.Lock()
55 target := a.remoteTabs[tabID]
56 if target == nil {
57 a.remoteTabMu.Unlock()
58 a.mu.Unlock()
59 return TabMeta{}, nil, fmt.Errorf("remote tab %q closed while opening", tabID)
60 }
61 remoteCancels := make([]context.CancelFunc, 0, len(a.remoteTabs)-1)
62 for id, tab := range a.remoteTabs {
63 if id == tabID {
64 continue
65 }
66 if tab.cancel != nil {
67 remoteCancels = append(remoteCancels, tab.cancel)
68 }
69 delete(a.remoteTabs, id)
70 a.forgetRemoteBrowserExecutor(id)
71 }
72 a.remoteTabLayout.activeID = tabID
73 a.remoteTabLayout.order = []string{tabID}
74 a.remoteTabLayout.stripOrder = []string{tabID}
75 meta := remoteTabMetaLocked(target)
76 a.remoteTabMu.Unlock()
77
78 removedLocal := make([]*WorkspaceTab, 0, len(candidates))
79 for _, candidate := range candidates {
80 tab := candidate.tab
81 if tab == nil || a.tabs[candidate.id] != tab {
82 continue
83 }
84 if tab.Ctrl == nil || !tab.hasActiveRuntimeWork() {
85 a.markTabRemovedLocked(tab)
86 }
87 removedLocal = append(removedLocal, tab)
88 delete(a.tabs, candidate.id)
89 }
90 a.activeTabID = ""
91 a.tabOrder = nil
92 dir, entries, activeID, version := a.saveTabsCollectLocked()
93 a.mu.Unlock()
94
95 for _, tab := range removedLocal {
96 a.removeVisibleTabRuntimeAdmissionHeld(tab)
97 }
98 a.saveTabsWrite(dir, entries, activeID, version)
99 return meta, remoteCancels, nil
100 }()
101 if err != nil {
102 return TabMeta{}, err
103 }
104 for _, cancel := range remoteCancels {
105 cancel()
106 }
107 a.emitProjectTreeRuntimeChangedWithLegacy()
108 return meta, nil
109 }
110
110 lines GO