-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathMainWindow.SolutionManager.vb
More file actions
287 lines (256 loc) · 14.4 KB
/
Copy pathMainWindow.SolutionManager.vb
File metadata and controls
287 lines (256 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
' MainWindow.SolutionManager.vb - Multi-project .sln solution loading
Imports Gtk
Imports System
Imports System.Threading.Tasks
Imports SimpleIDE.Managers
Imports SimpleIDE.Utilities
Imports SimpleIDE.Widgets
Imports SimpleIDE.Models
Partial Public Class MainWindow
''' <summary>
''' Owns every ProjectManager instance for the currently loaded solution (one per member
''' project) once a .sln has been opened; Nothing when only a plain single .vbproj is open
''' </summary>
Private pSolutionManager As SolutionManager
''' <summary>
''' One-shot flag consumed by OnProjectFileListLoaded (MainWindow.ProjectManager.vb) - set
''' just before the startup project's own LoadProjectEnhanced call inside
''' LoadSolutionEnhanced, so that handler can skip populating Project Explorer with the
''' startup project's single-project tree (which would otherwise be visible for the ~1
''' second before OnStartupProjectAllFilesParsed replaces it with the real multi-root
''' solution tree - a visible "loads the project, then the solution" flash). Consumed
''' (reset to False) the moment it's checked, so any LATER plain single-project open
''' (even after a solution was previously loaded) is unaffected
''' </summary>
Private pSolutionStartupLoadPending As Boolean = False
''' <summary>
''' Handles the "Open Solution..." menu command
''' </summary>
Public Sub OnOpenSolution(vSender As Object, vArgs As EventArgs)
Try
' Close all open editors from the current project (prompts to save/discard, may cancel)
If Not CloseAllTabs() Then Return
Dim lDialog As New FileChooserDialog(
"Select Solution File",
Me,
FileChooserAction.Open,
"Cancel", ResponseType.Cancel,
"Open", ResponseType.Accept
)
Dim lSolutionFilter As New FileFilter()
lSolutionFilter.Name = "Solution Files (*.sln)"
lSolutionFilter.AddPattern("*.sln")
lDialog.AddFilter(lSolutionFilter)
If lDialog.Run() = CInt(ResponseType.Accept) Then
LoadSolutionEnhanced(lDialog.FileName)
End If
lDialog.Destroy()
Catch ex As Exception
Console.WriteLine($"OnOpenSolution error: {ex.Message}")
ShowError("Open solution error", ex.Message)
End Try
End Sub
''' <summary>
''' Loads a .sln solution - every member project into its own SolutionManager-owned
''' ProjectManager instance, plus the startup (first-listed) project through the existing,
''' unmodified single-project open flow so every already-wired widget/handler (Project
''' Explorer, Object Explorer, editor tabs) behaves exactly as it does for a plain project
''' open today
''' </summary>
''' <remarks>
''' Deliberately loads the startup project TWICE - once (lightly, synchronously) inside
''' SolutionManager purely to compute the cross-project dependency graph, and once (fully,
''' asynchronously) via the existing LoadProjectEnhanced, which is what actually drives the
''' visible UI. Doing this instead of reusing MainWindow's own pProjectManager instance
''' inside SolutionManager avoids a race between the two independent load paths (one
''' synchronous on the UI thread, one asynchronous on a background Task) touching the same
''' object concurrently.
'''
''' SolutionManager.LoadSolution() itself is NOT lightweight - ProjectManager.LoadProject()
''' (called once per member project) fully Roslyn-parses every one of that project's source
''' files synchronously (EnsureAllFilesLoaded), so calling it directly on the UI thread
''' would freeze the window for the whole solution load and never let the "Loading
''' projects..." placeholder (ShowSolutionLoadingPlaceholder) actually paint. Run on a
''' background Task instead, with a progress callback marshaled back to the UI thread via
''' Application.Invoke for status-bar feedback; pSolutionManager itself is only assigned
''' once back on the UI thread with a fully-populated instance, so nothing else in the app
''' can observe a half-initialized SolutionManager mid-load.
''' </remarks>
Private Sub LoadSolutionEnhanced(vSolutionPath As String)
Try
#If DEBUG Then
Console.WriteLine($"LoadSolutionEnhanced: Loading solution: {vSolutionPath}")
#End If
Dim lSolutionName As String = System.IO.Path.GetFileNameWithoutExtension(vSolutionPath)
UpdateStatusBar($"Opening solution '{lSolutionName}'...")
pProjectExplorer?.ShowSolutionLoadingPlaceholder(lSolutionName)
ShowProgressBar(True)
UpdateProgressBar(0)
Dim lNewSolutionManager As New SolutionManager()
Task.Run(Sub()
Try
Dim lSuccess As Boolean = lNewSolutionManager.LoadSolution(vSolutionPath, Nothing,
Sub(vProjectName As String, vIndex As Integer, vTotal As Integer)
Application.Invoke(Sub()
Try
UpdateStatusBar($"Loading project {vIndex} of {vTotal}: {vProjectName}...")
UpdateProgressBar(CInt((vIndex - 1) * 100.0 / vTotal))
Catch ex As Exception
Console.WriteLine($"LoadSolutionEnhanced progress callback error: {ex.Message}")
End Try
End Sub)
End Sub)
Application.Invoke(Sub()
Try
If Not lSuccess OrElse lNewSolutionManager.AllProjects.Count = 0 Then
ShowProgressBar(False)
pProjectExplorer?.ClearProject()
UpdateStatusBar("Open Solution Failed")
ShowError("Open Solution Failed", $"Failed to load any projects from: {vSolutionPath}")
Return
End If
pSolutionManager = lNewSolutionManager
#If DEBUG Then
Console.WriteLine($"LoadSolutionEnhanced: Loaded {pSolutionManager.AllProjects.Count} project(s), dependency order:")
#End If
for each lMember in pSolutionManager.AllProjects
#If DEBUG Then
Console.WriteLine($" {lMember.CurrentProjectName} ({lMember.CurrentProjectPath})")
#End If
Next
UpdateProgressBar(100)
ShowProgressBar(False)
UpdateStatusBar($"Solution loaded: {pSolutionManager.AllProjects.Count} project(s) - parsing source files...")
Dim lStartupPath As String = pSolutionManager.StartupProject?.CurrentProjectPath
If Not String.IsNullOrEmpty(lStartupPath) Then
' LoadProjectEnhanced's own async load fires ProjectFileListLoaded
' partway through - OnProjectFileListLoaded skips populating
' Project Explorer with the startup project's own single-project
' tree while pSolutionStartupLoadPending is set (see its own doc
' comment), leaving the "Loading projects..." placeholder up
' until AllFilesParseCompleted fires and the real multi-root
' solution tree replaces it wholesale
' RemoveHandler first: if a prior solution load's own AddHandler
' here was never matched by a RemoveHandler (e.g. that load's
' AllFilesParseCompleted never fired), this call would otherwise
' double-subscribe OnStartupProjectAllFilesParsed
RemoveHandler pProjectManager.AllFilesParseCompleted, AddressOf OnStartupProjectAllFilesParsed
AddHandler pProjectManager.AllFilesParseCompleted, AddressOf OnStartupProjectAllFilesParsed
pSolutionStartupLoadPending = True
LoadProjectEnhanced(lStartupPath)
End If
Catch ex As Exception
Console.WriteLine($"LoadSolutionEnhanced (completion) error: {ex.Message}")
ShowProgressBar(False)
ShowError("Open Solution Error", ex.Message)
End Try
End Sub)
Catch ex As Exception
Application.Invoke(Sub()
Console.WriteLine($"LoadSolutionEnhanced (background) error: {ex.Message}")
ShowProgressBar(False)
pProjectExplorer?.ClearProject()
UpdateStatusBar("Open Solution Failed")
ShowError("Open Solution Error", ex.Message)
End Sub)
End Try
End Sub)
Catch ex As Exception
Console.WriteLine($"LoadSolutionEnhanced error: {ex.Message}")
ShowError("Open Solution Error", ex.Message)
End Try
End Sub
''' <summary>
''' One-shot handler that applies the full multi-root solution tree to Project Explorer
''' once the startup project's own single-project load (kicked off by LoadSolutionEnhanced)
''' has fully finished
''' </summary>
''' <remarks>
''' Confirmed via direct testing (stack-trace-dumping LoadProjectFromManager itself) that
''' a plain Application.Invoke here is NOT sufficient: OnProjectFileListLoaded's own
''' Application.Invoke-queued LoadProjectFromManager() call (queued much earlier, when
''' ProjectFileListLoaded first raises) does not reliably run before this one, because
''' several existing code paths in this app (ThemeManager.ForceGlobalRefresh,
''' UpdateProgressBar) call Gtk.Application.RunIteration() reentrantly from inside other
''' Application.Invoke callbacks to force synchronous UI updates during long operations -
''' this breaks the normal FIFO ordering GTK's invoke queue would otherwise guarantee, so
''' a callback queued earlier can end up processed later than one queued after it. A short
''' settling delay (matching the existing GLib.Timeout.Add(1000, ...) already used in
''' OnAllFilesParseCompleted for the same "let the reentrant pumping finish" reason) is
''' the pragmatic fix - reordering the reentrant RunIteration calls themselves would be a
''' much larger, riskier change unrelated to solution support.
''' </remarks>
Private Sub OnStartupProjectAllFilesParsed(vFileCount As Integer, vTotalMilliseconds As Double)
Try
RemoveHandler pProjectManager.AllFilesParseCompleted, AddressOf OnStartupProjectAllFilesParsed
Dim lSolutionManager As SolutionManager = pSolutionManager
If lSolutionManager IsNot Nothing Then
GLib.Timeout.Add(750, New GLib.TimeoutHandler(Function()
Try
pProjectExplorer?.LoadSolutionFromManager(lSolutionManager)
pObjectExplorer?.SetSolutionManager(lSolutionManager)
pFindPanel?.SetSolutionManager(lSolutionManager)
pGitPanel?.SetSolutionManager(lSolutionManager)
UpdateStatusBar($"Solution ready: {lSolutionManager.AllProjects.Count} project(s) loaded")
Catch ex As Exception
Console.WriteLine($"OnStartupProjectAllFilesParsed (settled apply) error: {ex.Message}")
End Try
Return False
End Function))
End If
Catch ex As Exception
Console.WriteLine($"OnStartupProjectAllFilesParsed error: {ex.Message}")
End Try
End Sub
''' <summary>
''' Handles the "Solution Settings..." context-menu request from Project Explorer's
''' solution root node
''' </summary>
Private Sub OnSolutionSettingsRequested()
Try
OpenSolutionSettingsTab()
Catch ex As Exception
Console.WriteLine($"OnSolutionSettingsRequested error: {ex.Message}")
End Try
End Sub
''' <summary>
''' Opens (or switches to) the read-only Solution Settings tab showing every loaded
''' project in build/dependency order
''' </summary>
''' <remarks>
''' Follows the same pattern as OpenAssemblySettingsEditor (MainWindow.AssemblySettings.vb) -
''' a settings view, not a text editor, so it deliberately does NOT implement IEditor;
''' registered in pOpenTabs with Editor = Nothing and IsSpecialTab = True purely so it
''' participates in normal tab close/dispose handling
''' </remarks>
Private Sub OpenSolutionSettingsTab()
Try
If pSolutionManager Is Nothing Then
ShowError("No Solution Loaded", "Open a solution first (File > Open Solution...).")
Return
End If
for each lTabEntry in pOpenTabs
If lTabEntry.Key = "Solution Settings" Then
SwitchToTab(lTabEntry.Key)
Return
End If
Next
Dim lSettingsTab As New SolutionSettingsTab(pSolutionManager, pThemeManager)
Dim lTabInfo As New TabInfo() With {
.FilePath = "Solution Settings",
.Editor = Nothing,
.EditorContainer = lSettingsTab,
.Modified = False,
.IsSpecialTab = True
}
Dim lPageIndex As Integer = pNotebook.AppendPage(lSettingsTab, "Solution Settings")
pNotebook.ShowAll()
pNotebook.CurrentPage = lPageIndex
pOpenTabs("Solution Settings") = lTabInfo
UpdateStatusBar("Opened solution settings")
Catch ex As Exception
Console.WriteLine($"OpenSolutionSettingsTab error: {ex.Message}")
ShowError("Solution Settings Error", ex.Message)
End Try
End Sub
End Class