@@ -26,11 +26,12 @@ import SourceKitD
26
26
/// At the sourcekitd level, this uses `codecomplete.open`, `codecomplete.update` and
27
27
/// `codecomplete.close` requests.
28
28
actor CodeCompletionSession {
29
- private unowned let server : SwiftLanguageServer
29
+ private let sourcekitd : any SourceKitD
30
30
private let snapshot : DocumentSnapshot
31
31
let utf8StartOffset : Int
32
32
private let position : Position
33
33
private let compileCommand : SwiftCompileCommand ?
34
+ private let clientSupportsSnippets : Bool
34
35
private var state : State = . closed
35
36
36
37
private enum State {
@@ -39,19 +40,22 @@ actor CodeCompletionSession {
39
40
}
40
41
41
42
nonisolated var uri : DocumentURI { snapshot. uri }
43
+ nonisolated var keys : sourcekitd_keys { return sourcekitd. keys }
42
44
43
45
init (
44
- server : SwiftLanguageServer ,
46
+ sourcekitd : any SourceKitD ,
45
47
snapshot: DocumentSnapshot ,
46
48
utf8Offset: Int ,
47
49
position: Position ,
48
- compileCommand: SwiftCompileCommand ?
50
+ compileCommand: SwiftCompileCommand ? ,
51
+ clientSupportsSnippets: Bool
49
52
) {
50
- self . server = server
53
+ self . sourcekitd = sourcekitd
51
54
self . snapshot = snapshot
52
55
self . utf8StartOffset = utf8Offset
53
56
self . position = position
54
57
self . compileCommand = compileCommand
58
+ self . clientSupportsSnippets = clientSupportsSnippets
55
59
}
56
60
57
61
/// Retrieve completions for the given `filterText`, opening or updating the session.
@@ -89,9 +93,8 @@ actor CodeCompletionSession {
89
93
throw ResponseError ( code: . invalidRequest, message: " open must use the original snapshot " )
90
94
}
91
95
92
- let req = SKDRequestDictionary ( sourcekitd: server. sourcekitd)
93
- let keys = server. sourcekitd. keys
94
- req [ keys. request] = server. sourcekitd. requests. codecomplete_open
96
+ let req = SKDRequestDictionary ( sourcekitd: sourcekitd)
97
+ req [ keys. request] = sourcekitd. requests. codecomplete_open
95
98
req [ keys. offset] = utf8StartOffset
96
99
req [ keys. name] = uri. pseudoPath
97
100
req [ keys. sourcefile] = uri. pseudoPath
@@ -101,7 +104,7 @@ actor CodeCompletionSession {
101
104
req [ keys. compilerargs] = compileCommand. compilerArgs
102
105
}
103
106
104
- let dict = try await server . sourcekitd. send ( req)
107
+ let dict = try await sourcekitd. send ( req)
105
108
106
109
guard let completions: SKDResponseArray = dict [ keys. results] else {
107
110
return CompletionList ( isIncomplete: false , items: [ ] )
@@ -127,14 +130,13 @@ actor CodeCompletionSession {
127
130
// FIXME: Assertion for prefix of snapshot matching what we started with.
128
131
129
132
logger. info ( " Updating code completion session: \( self , privacy: . private) filter= \( filterText) " )
130
- let req = SKDRequestDictionary ( sourcekitd: server. sourcekitd)
131
- let keys = server. sourcekitd. keys
132
- req [ keys. request] = server. sourcekitd. requests. codecomplete_update
133
+ let req = SKDRequestDictionary ( sourcekitd: sourcekitd)
134
+ req [ keys. request] = sourcekitd. requests. codecomplete_update
133
135
req [ keys. offset] = utf8StartOffset
134
136
req [ keys. name] = uri. pseudoPath
135
137
req [ keys. codecomplete_options] = optionsDictionary ( filterText: filterText, options: options)
136
138
137
- let dict = try await server . sourcekitd. send ( req)
139
+ let dict = try await sourcekitd. send ( req)
138
140
guard let completions: SKDResponseArray = dict [ keys. results] else {
139
141
return CompletionList ( isIncomplete: false , items: [ ] )
140
142
}
@@ -152,8 +154,7 @@ actor CodeCompletionSession {
152
154
filterText: String ,
153
155
options: SKCompletionOptions
154
156
) -> SKDRequestDictionary {
155
- let dict = SKDRequestDictionary ( sourcekitd: server. sourcekitd)
156
- let keys = server. sourcekitd. keys
157
+ let dict = SKDRequestDictionary ( sourcekitd: sourcekitd)
157
158
// Sorting and priority options.
158
159
dict [ keys. codecomplete_hideunderscores] = 0
159
160
dict [ keys. codecomplete_hidelowpriority] = 0
@@ -169,26 +170,22 @@ actor CodeCompletionSession {
169
170
return dict
170
171
}
171
172
172
- private func sendClose( _ server: SwiftLanguageServer ) {
173
- let req = SKDRequestDictionary ( sourcekitd: server. sourcekitd)
174
- let keys = server. sourcekitd. keys
175
- req [ keys. request] = server. sourcekitd. requests. codecomplete_close
173
+ private func sendClose( ) {
174
+ let req = SKDRequestDictionary ( sourcekitd: sourcekitd)
175
+ req [ keys. request] = sourcekitd. requests. codecomplete_close
176
176
req [ keys. offset] = self . utf8StartOffset
177
177
req [ keys. name] = self . snapshot. uri. pseudoPath
178
178
logger. info ( " Closing code completion session: \( self , privacy: . private) " )
179
- _ = try ? server . sourcekitd. sendSync ( req)
179
+ _ = try ? sourcekitd. sendSync ( req)
180
180
}
181
181
182
182
func close( ) async {
183
- // Temporary back-reference to server to keep it alive during close().
184
- let server = self . server
185
-
186
183
switch self . state {
187
184
case . closed:
188
185
// Already closed, nothing to do.
189
186
break
190
187
case . open:
191
- self . sendClose ( server )
188
+ self . sendClose ( )
192
189
self . state = . closed
193
190
}
194
191
}
@@ -204,26 +201,24 @@ actor CodeCompletionSession {
204
201
) -> CompletionList {
205
202
var result = CompletionList ( isIncomplete: isIncomplete, items: [ ] )
206
203
207
- completions. forEach { ( i, value) -> Bool in
208
- guard let name: String = value [ server . keys. description] else {
204
+ completions. forEach { ( i: Int , value: SKDResponseDictionary ) -> Bool in
205
+ guard let name: String = value [ keys. description] else {
209
206
return true // continue
210
207
}
211
208
212
- var filterName : String ? = value [ server . keys. name]
213
- let insertText : String ? = value [ server . keys. sourcetext]
214
- let typeName : String ? = value [ server . keys. typename]
215
- let docBrief : String ? = value [ server . keys. doc_brief]
209
+ var filterName : String ? = value [ keys. name]
210
+ let insertText : String ? = value [ keys. sourcetext]
211
+ let typeName : String ? = value [ sourcekitd . keys. typename]
212
+ let docBrief : String ? = value [ sourcekitd . keys. doc_brief]
216
213
217
- let completionCapabilities = server. capabilityRegistry. clientCapabilities. textDocument? . completion
218
- let clientSupportsSnippets = completionCapabilities? . completionItem? . snippetSupport == true
219
214
let text = insertText. map {
220
215
rewriteSourceKitPlaceholders ( inString: $0, clientSupportsSnippets: clientSupportsSnippets)
221
216
}
222
217
let isInsertTextSnippet = clientSupportsSnippets && text != insertText
223
218
224
219
let textEdit : TextEdit ?
225
220
if let text = text {
226
- let utf8CodeUnitsToErase : Int = value [ server . keys. num_bytes_to_erase] ?? 0
221
+ let utf8CodeUnitsToErase : Int = value [ sourcekitd . keys. num_bytes_to_erase] ?? 0
227
222
228
223
textEdit = self . computeCompletionTextEdit (
229
224
completionPos: completionPos,
@@ -254,13 +249,13 @@ actor CodeCompletionSession {
254
249
}
255
250
256
251
// Map SourceKit's not_recommended field to LSP's deprecated
257
- let notRecommended = ( value [ server . keys. not_recommended] as Int ? ) . map ( { $0 != 0 } )
252
+ let notRecommended = ( value [ sourcekitd . keys. not_recommended] as Int ? ) . map ( { $0 != 0 } )
258
253
259
- let kind : sourcekitd_uid_t ? = value [ server . keys. kind]
254
+ let kind : sourcekitd_uid_t ? = value [ sourcekitd . keys. kind]
260
255
result. items. append (
261
256
CompletionItem (
262
257
label: name,
263
- kind: kind? . asCompletionItemKind ( server . values) ?? . value,
258
+ kind: kind? . asCompletionItemKind ( sourcekitd . values) ?? . value,
264
259
detail: typeName,
265
260
documentation: docBrief != nil ? . markupContent( MarkupContent ( kind: . markdown, value: docBrief!) ) : nil ,
266
261
deprecated: notRecommended ?? false ,
0 commit comments