@@ -17,29 +17,136 @@ import LSPLogging
17
17
import SKCore
18
18
import SKSupport
19
19
import TSCBasic
20
+ import Dispatch
20
21
21
22
/// A thin wrapper over a connection to a clangd server providing build setting handling.
22
23
final class ClangLanguageServerShim : ToolchainLanguageServer {
23
24
24
25
/// The server's request queue, used to serialize requests and responses to `clangd`.
25
26
public let queue : DispatchQueue = DispatchQueue ( label: " clangd-language-server-queue " , qos: . userInitiated)
26
27
27
- let clangd : Connection
28
+ /// The connection to `clangd`. `nil` before `initialize` has been called.
29
+ private var clangd : Connection !
28
30
29
- var capabilities : ServerCapabilities ? = nil
31
+ private var capabilities : ServerCapabilities ? = nil
30
32
31
- let buildSystem : BuildSystem
33
+ private let buildSystem : BuildSystem
32
34
33
- let clang : AbsolutePath ?
35
+ private let clangdPath : AbsolutePath
34
36
35
- /// Creates a language server for the given client using the sourcekitd dylib at the specified path.
36
- public init ( client: Connection , clangd: Connection , buildSystem: BuildSystem ,
37
- clang: AbsolutePath ? ) throws {
38
- self . clangd = clangd
39
- self . buildSystem = buildSystem
40
- self . clang = clang
37
+ private let clangdOptions : [ String ]
38
+
39
+ private let client : MessageHandler
40
+
41
+ private var state : LanguageServerState {
42
+ didSet {
43
+ for handler in stateChangeHandlers {
44
+ handler ( oldValue, state)
45
+ }
46
+ }
47
+ }
48
+
49
+ private var stateChangeHandlers : [ ( _ oldState: LanguageServerState , _ newState: LanguageServerState ) -> Void ] = [ ]
50
+
51
+ /// Ask the parent of this language server to re-open all documents in this language server.
52
+ private let reopenDocuments : ( ) -> Void
53
+
54
+ /// The `InitializeRequest` with which `clangd` was originally initialized.
55
+ /// Stored so we can replay the initialization when clangd crashes.
56
+ private var initializeRequest : InitializeRequest ?
57
+
58
+ public init ( client: MessageHandler ,
59
+ clangdPath: AbsolutePath ,
60
+ buildSettings: BuildSystem ? ,
61
+ clangdOptions: [ String ] ,
62
+ reopenDocuments: @escaping ( ) -> Void
63
+ ) throws {
64
+ self . client = client
65
+ self . buildSystem = buildSettings ?? BuildSystemList ( )
66
+ self . clangdPath = clangdPath
67
+ self . clangdOptions = clangdOptions
68
+ self . state = . connected
69
+ self . reopenDocuments = reopenDocuments
70
+ }
71
+
72
+ func initialize( ) throws {
73
+ let clientToServer : Pipe = Pipe ( )
74
+ let serverToClient : Pipe = Pipe ( )
75
+
76
+ let connection = JSONRPCConnection (
77
+ protocol: MessageRegistry . lspProtocol,
78
+ inFD: serverToClient. fileHandleForReading. fileDescriptor,
79
+ outFD: clientToServer. fileHandleForWriting. fileDescriptor
80
+ )
81
+
82
+ self . clangd = connection
83
+
84
+ connection. start ( receiveHandler: client)
85
+
86
+ let process = Foundation . Process ( )
87
+
88
+ if #available( OSX 10 . 13 , * ) {
89
+ process. executableURL = clangdPath. asURL
90
+ } else {
91
+ process. launchPath = clangdPath. pathString
92
+ }
93
+
94
+ process. arguments = [
95
+ " -compile_args_from=lsp " , // Provide compiler args programmatically.
96
+ " -background-index=false " , // Disable clangd indexing, we use the build
97
+ " -index=false " , // system index store instead.
98
+ ] + clangdOptions
99
+
100
+ process. standardOutput = serverToClient
101
+ process. standardInput = clientToServer
102
+ process. terminationHandler = { [ weak self] process in
103
+ log ( " clangd exited: \( process. terminationReason) \( process. terminationStatus) " )
104
+ connection. close ( )
105
+ if process. terminationStatus != 0 {
106
+ if let self = self {
107
+ self . state = . connectionInterrupted
108
+ self . restartClangd ( )
109
+ }
110
+ }
111
+ }
112
+
113
+ if #available( OSX 10 . 13 , * ) {
114
+ try process. run ( )
115
+ } else {
116
+ process. launch ( )
117
+ }
118
+ }
119
+
120
+ private func restartClangd( ) {
121
+ precondition ( self . state == . connectionInterrupted)
122
+
123
+ guard let initializeRequest = initializeRequest else {
124
+ log ( " clangd crashed before it was sent an InitializeRequest. *Not* trying to restart. " , level: . error)
125
+ return
126
+ }
127
+
128
+ do {
129
+ try self . initialize ( )
130
+ // FIXME: We assume that clangd will return the same capabilites after restarting.
131
+ // Theoretically they could have changed and we would need to inform SourceKitServer about them.
132
+ // But since SourceKitServer more or less ignores them right now anyway, this should be fine for now.
133
+ _ = try self . initializeSync ( initializeRequest)
134
+ self . clientInitialized ( InitializedNotification ( ) )
135
+ self . reopenDocuments ( )
136
+ self . state = . connected
137
+ } catch {
138
+ log ( " Failed to restart clangd after a crash, retrying in 5 seconds: \( error) " , level: . error)
139
+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + . seconds( 5 ) ) { [ weak self] in
140
+ self ? . restartClangd ( )
141
+ }
142
+ }
41
143
}
42
144
145
+ func addStateChangeHandler( handler: @escaping ( LanguageServerState , LanguageServerState ) -> Void ) {
146
+ stateChangeHandlers. append ( handler)
147
+ }
148
+
149
+
43
150
/// Forwards a request to the given connection, taking care of replying to the original request
44
151
/// and cancellation, while providing a callback with the response for additional processing.
45
152
///
@@ -73,6 +180,7 @@ final class ClangLanguageServerShim: ToolchainLanguageServer {
73
180
extension ClangLanguageServerShim {
74
181
75
182
func initializeSync( _ initialize: InitializeRequest ) throws -> InitializeResult {
183
+ self . initializeRequest = initialize
76
184
let result = try clangd. sendSync ( initialize)
77
185
self . capabilities = result. capabilities
78
186
return result
@@ -124,7 +232,7 @@ extension ClangLanguageServerShim {
124
232
if let settings = settings {
125
233
clangd. send ( DidChangeConfigurationNotification ( settings: . clangd(
126
234
ClangWorkspaceSettings (
127
- compilationDatabaseChanges: [ url. path: ClangCompileCommand ( settings, clang: clang ) ] ) ) ) )
235
+ compilationDatabaseChanges: [ url. path: ClangCompileCommand ( settings, clang: clangdPath ) ] ) ) ) )
128
236
}
129
237
}
130
238
@@ -191,60 +299,16 @@ func makeJSONRPCClangServer(
191
299
client: MessageHandler ,
192
300
toolchain: Toolchain ,
193
301
buildSettings: BuildSystem ? ,
194
- clangdOptions: [ String ]
302
+ clangdOptions: [ String ] ,
303
+ reopenDocuments: @escaping ( ) -> Void
195
304
) throws -> ToolchainLanguageServer {
196
305
guard let clangd = toolchain. clangd else {
197
306
preconditionFailure ( " missing clang from toolchain \( toolchain. identifier) " )
198
307
}
199
308
200
- let clientToServer : Pipe = Pipe ( )
201
- let serverToClient : Pipe = Pipe ( )
202
-
203
- let connection = JSONRPCConnection (
204
- protocol: MessageRegistry . lspProtocol,
205
- inFD: serverToClient. fileHandleForReading. fileDescriptor,
206
- outFD: clientToServer. fileHandleForWriting. fileDescriptor
207
- )
208
-
209
- let connectionToClient = LocalConnection ( )
210
-
211
- let shim = try ClangLanguageServerShim (
212
- client: connectionToClient,
213
- clangd: connection,
214
- buildSystem: buildSettings ?? BuildSystemList ( ) ,
215
- clang: toolchain. clang)
216
-
217
- connectionToClient. start ( handler: client)
218
- connection. start ( receiveHandler: client)
219
-
220
- let process = Foundation . Process ( )
221
-
222
- if #available( OSX 10 . 13 , * ) {
223
- process. executableURL = clangd. asURL
224
- } else {
225
- process. launchPath = clangd. pathString
226
- }
227
-
228
- process. arguments = [
229
- " -compile_args_from=lsp " , // Provide compiler args programmatically.
230
- " -background-index=false " , // Disable clangd indexing, we use the build
231
- " -index=false " , // system index store instead.
232
- ] + clangdOptions
233
-
234
- process. standardOutput = serverToClient
235
- process. standardInput = clientToServer
236
- process. terminationHandler = { process in
237
- log ( " clangd exited: \( process. terminationReason) \( process. terminationStatus) " )
238
- connection. close ( )
239
- }
240
-
241
- if #available( OSX 10 . 13 , * ) {
242
- try process. run ( )
243
- } else {
244
- process. launch ( )
245
- }
246
-
247
- return shim
309
+ let server = try ClangLanguageServerShim ( client: client, clangdPath: clangd, buildSettings: buildSettings, clangdOptions: clangdOptions, reopenDocuments: reopenDocuments)
310
+ try server. initialize ( )
311
+ return server
248
312
}
249
313
250
314
extension ClangCompileCommand {
0 commit comments