8
8
import SwiftUI
9
9
import CodeEditSymbols
10
10
import CodeEditSourceEditor
11
+ import OSLog
11
12
12
13
final class AppDelegate : NSObject , NSApplicationDelegate , ObservableObject {
14
+ private let logger = Logger ( subsystem: Bundle . main. bundleIdentifier ?? " " , category: " AppDelegate " )
13
15
private let updater = SoftwareUpdater ( )
14
16
15
17
@Environment ( \. openWindow)
16
18
var openWindow
17
19
20
+ @LazyService var lspService : LSPService
21
+
18
22
func applicationDidFinishLaunching( _ notification: Notification ) {
19
23
setupServiceContainer ( )
20
24
enableWindowSizeSaveOnQuit ( )
@@ -115,6 +119,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
115
119
}
116
120
}
117
121
122
+ /// Defers the application terminate message until we've finished cleanup.
123
+ ///
124
+ /// All paths _must_ call `NSApplication.shared.reply(toApplicationShouldTerminate: true)` as soon as possible.
125
+ ///
126
+ /// The two things needing deferring are:
127
+ /// - Language server cancellation
128
+ /// - Outstanding document changes.
129
+ ///
130
+ /// Things that don't need deferring (happen immediately):
131
+ /// - Task termination.
132
+ /// These are called immediately if no documents need closing, and are called by
133
+ /// ``documentController(_:didCloseAll:contextInfo:)`` if there are documents we need to defer for.
134
+ ///
135
+ /// See ``terminateLanguageServers()`` and ``documentController(_:didCloseAll:contextInfo:)`` for deferring tasks.
118
136
func applicationShouldTerminate( _ sender: NSApplication ) -> NSApplication . TerminateReply {
119
137
let projects : [ String ] = CodeEditDocumentController . shared. documents
120
138
. compactMap { ( $0 as? WorkspaceDocument ) ? . fileURL? . path }
@@ -128,10 +146,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
128
146
didCloseAllSelector: #selector( documentController ( _: didCloseAll: contextInfo: ) ) ,
129
147
contextInfo: nil
130
148
)
149
+ // `documentController(_:didCloseAll:contextInfo:)` will call `terminateLanguageServers()`
131
150
return . terminateLater
132
151
}
133
152
134
- return . terminateNow
153
+ terminateTasks ( )
154
+ terminateLanguageServers ( )
155
+ return . terminateLater
135
156
}
136
157
137
158
func applicationShouldTerminateAfterLastWindowClosed( _ sender: NSApplication ) -> Bool {
@@ -224,7 +245,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
224
245
225
246
@objc
226
247
func documentController( _ docController: NSDocumentController , didCloseAll: Bool , contextInfo: Any ) {
227
- NSApplication . shared. reply ( toApplicationShouldTerminate: didCloseAll)
248
+ if didCloseAll {
249
+ terminateTasks ( )
250
+ terminateLanguageServers ( )
251
+ }
252
+ }
253
+
254
+ /// Terminates running language servers. Used during app termination to ensure resources are freed.
255
+ private func terminateLanguageServers( ) {
256
+ Task {
257
+ await lspService. stopAllServers ( )
258
+ await MainActor . run {
259
+ NSApplication . shared. reply ( toApplicationShouldTerminate: true )
260
+ }
261
+ }
262
+ }
263
+
264
+ /// Terminates all running tasks. Used during app termination to ensure resources are freed.
265
+ private func terminateTasks( ) {
266
+ let documents = CodeEditDocumentController . shared. documents. compactMap ( { $0 as? WorkspaceDocument } )
267
+ documents. forEach { workspace in
268
+ workspace. taskManager? . stopAllTasks ( )
269
+ }
228
270
}
229
271
230
272
/// Setup all the services into a ServiceContainer for the application to use.
0 commit comments