@@ -37,32 +37,6 @@ extension SwiftLanguageServer {
37
37
38
38
let options = req. sourcekitlspOptions ?? serverOptions. completionOptions
39
39
40
- if options. serverSideFiltering {
41
- return try await completionWithServerFiltering (
42
- offset: offset,
43
- completionPos: completionPos,
44
- snapshot: snapshot,
45
- request: req,
46
- options: options
47
- )
48
- } else {
49
- return try await completionWithClientFiltering (
50
- offset: offset,
51
- completionPos: completionPos,
52
- snapshot: snapshot,
53
- request: req,
54
- options: options
55
- )
56
- }
57
- }
58
-
59
- private func completionWithServerFiltering(
60
- offset: Int ,
61
- completionPos: Position ,
62
- snapshot: DocumentSnapshot ,
63
- request req: CompletionRequest ,
64
- options: SKCompletionOptions
65
- ) async throws -> CompletionList {
66
40
guard let start = snapshot. indexOf ( utf8Offset: offset) ,
67
41
let end = snapshot. index ( of: req. position)
68
42
else {
@@ -72,164 +46,23 @@ extension SwiftLanguageServer {
72
46
73
47
let filterText = String ( snapshot. text [ start..< end] )
74
48
75
- let session : CodeCompletionSession
76
- if req. context? . triggerKind == . triggerFromIncompleteCompletions {
77
- guard let currentSession = currentCompletionSession else {
78
- logger. error ( " triggerFromIncompleteCompletions with no existing completion session " )
79
- throw ResponseError . serverCancelled
80
- }
81
- guard currentSession. uri == snapshot. uri, currentSession. utf8StartOffset == offset else {
82
- logger. error (
83
- """
84
- triggerFromIncompleteCompletions with incompatible completion session; expected \
85
- \( currentSession. uri. forLogging) @ \( currentSession. utf8StartOffset) , \
86
- but got \( snapshot. uri. forLogging) @ \( offset)
87
- """
88
- )
89
- throw ResponseError . serverCancelled
90
- }
91
- session = currentSession
92
- } else {
93
- // FIXME: even if trigger kind is not from incomplete, we could to detect a compatible
94
- // location if we also check that the rest of the snapshot has not changed.
95
- session = CodeCompletionSession (
96
- server: self ,
97
- snapshot: snapshot,
98
- utf8Offset: offset,
99
- position: completionPos,
100
- compileCommand: await buildSettings ( for: snapshot. uri)
101
- )
102
-
103
- await currentCompletionSession? . close ( )
104
- currentCompletionSession = session
105
- }
106
-
107
- return try await session. update ( filterText: filterText, position: req. position, in: snapshot, options: options)
108
- }
109
-
110
- private func completionWithClientFiltering(
111
- offset: Int ,
112
- completionPos: Position ,
113
- snapshot: DocumentSnapshot ,
114
- request req: CompletionRequest ,
115
- options: SKCompletionOptions
116
- ) async throws -> CompletionList {
117
- let skreq = SKDRequestDictionary ( sourcekitd: sourcekitd)
118
- skreq [ keys. request] = requests. codecomplete
119
- skreq [ keys. offset] = offset
120
- skreq [ keys. sourcefile] = snapshot. uri. pseudoPath
121
- skreq [ keys. sourcetext] = snapshot. text
122
-
123
- let skreqOptions = SKDRequestDictionary ( sourcekitd: sourcekitd)
124
- skreqOptions [ keys. codecomplete_sort_byname] = 1
125
- skreq [ keys. codecomplete_options] = skreqOptions
126
-
127
- // FIXME: SourceKit should probably cache this for us.
128
- if let compileCommand = await buildSettings ( for: snapshot. uri) {
129
- skreq [ keys. compilerargs] = compileCommand. compilerArgs
130
- }
131
-
132
- let dict = try await sourcekitd. send ( skreq)
133
-
134
- guard let completions: SKDResponseArray = dict [ self . keys. results] else {
135
- return CompletionList ( isIncomplete: false , items: [ ] )
136
- }
137
-
138
- try Task . checkCancellation ( )
139
-
140
- return self . completionsFromSKDResponse (
141
- completions,
142
- in: snapshot,
143
- completionPos: completionPos,
144
- requestPosition: req. position,
145
- isIncomplete: false
49
+ let clientSupportsSnippets = capabilityRegistry. clientCapabilities. textDocument? . completion? . completionItem? . snippetSupport ?? false
50
+ let buildSettings = await buildSettings ( for: snapshot. uri)
51
+ return try await CodeCompletionSession . completionList (
52
+ sourcekitd: sourcekitd,
53
+ snapshot: snapshot,
54
+ completionPosition: completionPos,
55
+ completionUtf8Offset: offset,
56
+ cursorPosition: req. position,
57
+ compileCommand: buildSettings,
58
+ options: options,
59
+ clientSupportsSnippets: clientSupportsSnippets,
60
+ filterText: filterText,
61
+ mustReuse: req. context? . triggerKind == . triggerFromIncompleteCompletions
146
62
)
147
63
}
148
64
149
- nonisolated func completionsFromSKDResponse(
150
- _ completions: SKDResponseArray ,
151
- in snapshot: DocumentSnapshot ,
152
- completionPos: Position ,
153
- requestPosition: Position ,
154
- isIncomplete: Bool
155
- ) -> CompletionList {
156
- var result = CompletionList ( isIncomplete: isIncomplete, items: [ ] )
157
-
158
- completions. forEach { ( i, value) -> Bool in
159
- guard let name: String = value [ self . keys. description] else {
160
- return true // continue
161
- }
162
-
163
- var filterName : String ? = value [ self . keys. name]
164
- let insertText : String ? = value [ self . keys. sourcetext]
165
- let typeName : String ? = value [ self . keys. typename]
166
- let docBrief : String ? = value [ self . keys. doc_brief]
167
-
168
- let completionCapabilities = self . capabilityRegistry. clientCapabilities. textDocument? . completion
169
- let clientSupportsSnippets = completionCapabilities? . completionItem? . snippetSupport == true
170
- let text = insertText. map {
171
- rewriteSourceKitPlaceholders ( inString: $0, clientSupportsSnippets: clientSupportsSnippets)
172
- }
173
- let isInsertTextSnippet = clientSupportsSnippets && text != insertText
174
-
175
- let textEdit : TextEdit ?
176
- if let text = text {
177
- let utf8CodeUnitsToErase : Int = value [ self . keys. num_bytes_to_erase] ?? 0
178
-
179
- textEdit = self . computeCompletionTextEdit (
180
- completionPos: completionPos,
181
- requestPosition: requestPosition,
182
- utf8CodeUnitsToErase: utf8CodeUnitsToErase,
183
- newText: text,
184
- snapshot: snapshot
185
- )
186
-
187
- if utf8CodeUnitsToErase != 0 , filterName != nil , let textEdit = textEdit {
188
- // To support the case where the client is doing prefix matching on the TextEdit range,
189
- // we need to prepend the deleted text to filterText.
190
- // This also works around a behaviour in VS Code that causes completions to not show up
191
- // if a '.' is being replaced for Optional completion.
192
- let startIndex = snapshot. lineTable. stringIndexOf (
193
- line: textEdit. range. lowerBound. line,
194
- utf16Column: textEdit. range. lowerBound. utf16index
195
- ) !
196
- let endIndex = snapshot. lineTable. stringIndexOf (
197
- line: completionPos. line,
198
- utf16Column: completionPos. utf16index
199
- ) !
200
- let filterPrefix = snapshot. text [ startIndex..< endIndex]
201
- filterName = filterPrefix + filterName!
202
- }
203
- } else {
204
- textEdit = nil
205
- }
206
-
207
- // Map SourceKit's not_recommended field to LSP's deprecated
208
- let notRecommended = ( value [ self . keys. not_recommended] as Int ? ) . map ( { $0 != 0 } )
209
-
210
- let kind : sourcekitd_uid_t ? = value [ self . keys. kind]
211
- result. items. append (
212
- CompletionItem (
213
- label: name,
214
- kind: kind? . asCompletionItemKind ( self . values) ?? . value,
215
- detail: typeName,
216
- documentation: docBrief != nil ? . markupContent( MarkupContent ( kind: . markdown, value: docBrief!) ) : nil ,
217
- deprecated: notRecommended ?? false ,
218
- sortText: nil ,
219
- filterText: filterName,
220
- insertText: text,
221
- insertTextFormat: isInsertTextSnippet ? . snippet : . plain,
222
- textEdit: textEdit. map ( CompletionItemEdit . textEdit)
223
- )
224
- )
225
-
226
- return true
227
- }
228
-
229
- return result
230
- }
231
-
232
- /// Adjust completion position to the start of identifier characters.
65
+ /// Adjust completion position to the start of identifier characters.
233
66
private func adjustCompletionLocation( _ pos: Position , in snapshot: DocumentSnapshot ) -> Position ? {
234
67
guard pos. line < snapshot. lineTable. count else {
235
68
// Line out of range.
@@ -259,57 +92,4 @@ extension SwiftLanguageServer {
259
92
let adjustedOffset = lineSlice. utf16. distance ( from: startIndex, to: loc)
260
93
return Position ( line: pos. line, utf16index: adjustedOffset)
261
94
}
262
-
263
- private nonisolated func computeCompletionTextEdit(
264
- completionPos: Position ,
265
- requestPosition: Position ,
266
- utf8CodeUnitsToErase: Int ,
267
- newText: String ,
268
- snapshot: DocumentSnapshot
269
- ) -> TextEdit {
270
- let textEditRangeStart : Position
271
-
272
- // Compute the TextEdit
273
- if utf8CodeUnitsToErase == 0 {
274
- // Nothing to delete. Fast path and avoid UTF-8/UTF-16 conversions
275
- textEditRangeStart = completionPos
276
- } else if utf8CodeUnitsToErase == 1 {
277
- // Fast path: Erasing a single UTF-8 byte code unit means we are also need to erase exactly one UTF-16 code unit, meaning we don't need to process the file contents
278
- if completionPos. utf16index >= 1 {
279
- // We can delete the character.
280
- textEditRangeStart = Position ( line: completionPos. line, utf16index: completionPos. utf16index - 1 )
281
- } else {
282
- // Deleting the character would cross line boundaries. This is not supported by LSP.
283
- // Fall back to ignoring utf8CodeUnitsToErase.
284
- // If we discover that multi-lines replacements are often needed, we can add an LSP extension to support multi-line edits.
285
- textEditRangeStart = completionPos
286
- }
287
- } else {
288
- // We need to delete more than one text character. Do the UTF-8/UTF-16 dance.
289
- assert ( completionPos. line == requestPosition. line)
290
- // Construct a string index for the edit range start by subtracting the UTF-8 code units to erase from the completion position.
291
- let line = snapshot. lineTable [ completionPos. line]
292
- let completionPosStringIndex = snapshot. lineTable. stringIndexOf (
293
- line: completionPos. line,
294
- utf16Column: completionPos. utf16index
295
- ) !
296
- let deletionStartStringIndex = line. utf8. index ( completionPosStringIndex, offsetBy: - utf8CodeUnitsToErase)
297
-
298
- // Compute the UTF-16 offset of the deletion start range. If the start lies in a previous line, this will be negative
299
- let deletionStartUtf16Offset = line. utf16. distance ( from: line. startIndex, to: deletionStartStringIndex)
300
-
301
- // Check if we are only deleting on one line. LSP does not support deleting over multiple lines.
302
- if deletionStartUtf16Offset >= 0 {
303
- // We are only deleting characters on the same line. Construct the corresponding text edit.
304
- textEditRangeStart = Position ( line: completionPos. line, utf16index: deletionStartUtf16Offset)
305
- } else {
306
- // Deleting the character would cross line boundaries. This is not supported by LSP.
307
- // Fall back to ignoring utf8CodeUnitsToErase.
308
- // If we discover that multi-lines replacements are often needed, we can add an LSP extension to support multi-line edits.
309
- textEditRangeStart = completionPos
310
- }
311
- }
312
-
313
- return TextEdit ( range: textEditRangeStart..< requestPosition, newText: newText)
314
- }
315
95
}
0 commit comments