@@ -129,9 +129,36 @@ pub(crate) fn apply_document_changes(
129
129
}
130
130
}
131
131
132
+ /// Checks that the edits inside the completion and the additional edits are disjoint.
133
+ pub ( crate ) fn all_edits_are_disjoint (
134
+ completion : & lsp_types:: CompletionItem ,
135
+ additional_edits : & [ lsp_types:: TextEdit ] ,
136
+ ) -> bool {
137
+ let mut edit_ranges = Vec :: new ( ) ;
138
+ match completion. text_edit . as_ref ( ) {
139
+ Some ( lsp_types:: CompletionTextEdit :: Edit ( edit) ) => {
140
+ edit_ranges. push ( edit. range ) ;
141
+ }
142
+ Some ( lsp_types:: CompletionTextEdit :: InsertAndReplace ( edit) ) => {
143
+ edit_ranges. push ( edit. insert ) ;
144
+ edit_ranges. push ( edit. replace ) ;
145
+ }
146
+ None => { }
147
+ }
148
+ if let Some ( additional_changes) = completion. additional_text_edits . as_ref ( ) {
149
+ edit_ranges. extend ( additional_changes. iter ( ) . map ( |edit| edit. range ) ) ;
150
+ } ;
151
+ edit_ranges. extend ( additional_edits. iter ( ) . map ( |edit| edit. range ) ) ;
152
+ edit_ranges. sort_by_key ( |range| ( range. start , range. end ) ) ;
153
+ edit_ranges. iter ( ) . zip ( edit_ranges. iter ( ) . skip ( 1 ) ) . all ( |( l, r) | l. end <= r. start )
154
+ }
155
+
132
156
#[ cfg( test) ]
133
157
mod tests {
134
- use lsp_types:: { Position , Range , TextDocumentContentChangeEvent } ;
158
+ use lsp_types:: {
159
+ CompletionItem , CompletionTextEdit , InsertReplaceEdit , Position , Range ,
160
+ TextDocumentContentChangeEvent ,
161
+ } ;
135
162
136
163
use super :: * ;
137
164
@@ -197,4 +224,135 @@ mod tests {
197
224
apply_document_changes ( & mut text, c ! [ 0 , 1 ; 1 , 0 => "ț\n c" , 0 , 2 ; 0 , 2 => "c" ] ) ;
198
225
assert_eq ! ( text, "ațc\n cb" ) ;
199
226
}
227
+
228
+ #[ test]
229
+ fn empty_completion_disjoint_tests ( ) {
230
+ let empty_completion =
231
+ CompletionItem :: new_simple ( "label" . to_string ( ) , "detail" . to_string ( ) ) ;
232
+
233
+ let disjoint_edit_1 = lsp_types:: TextEdit :: new (
234
+ Range :: new ( Position :: new ( 2 , 2 ) , Position :: new ( 3 , 3 ) ) ,
235
+ "new_text" . to_string ( ) ,
236
+ ) ;
237
+ let disjoint_edit_2 = lsp_types:: TextEdit :: new (
238
+ Range :: new ( Position :: new ( 3 , 3 ) , Position :: new ( 4 , 4 ) ) ,
239
+ "new_text" . to_string ( ) ,
240
+ ) ;
241
+
242
+ let joint_edit = lsp_types:: TextEdit :: new (
243
+ Range :: new ( Position :: new ( 1 , 1 ) , Position :: new ( 5 , 5 ) ) ,
244
+ "new_text" . to_string ( ) ,
245
+ ) ;
246
+
247
+ assert ! (
248
+ all_edits_are_disjoint( & empty_completion, & [ ] ) ,
249
+ "Empty completion has all its edits disjoint"
250
+ ) ;
251
+ assert ! (
252
+ all_edits_are_disjoint(
253
+ & empty_completion,
254
+ & [ disjoint_edit_1. clone( ) , disjoint_edit_2. clone( ) ]
255
+ ) ,
256
+ "Empty completion is disjoint to whatever disjoint extra edits added"
257
+ ) ;
258
+
259
+ assert ! (
260
+ !all_edits_are_disjoint(
261
+ & empty_completion,
262
+ & [ disjoint_edit_1, disjoint_edit_2, joint_edit]
263
+ ) ,
264
+ "Empty completion does not prevent joint extra edits from failing the validation"
265
+ ) ;
266
+ }
267
+
268
+ #[ test]
269
+ fn completion_with_joint_edits_disjoint_tests ( ) {
270
+ let disjoint_edit = lsp_types:: TextEdit :: new (
271
+ Range :: new ( Position :: new ( 1 , 1 ) , Position :: new ( 2 , 2 ) ) ,
272
+ "new_text" . to_string ( ) ,
273
+ ) ;
274
+ let disjoint_edit_2 = lsp_types:: TextEdit :: new (
275
+ Range :: new ( Position :: new ( 2 , 2 ) , Position :: new ( 3 , 3 ) ) ,
276
+ "new_text" . to_string ( ) ,
277
+ ) ;
278
+ let joint_edit = lsp_types:: TextEdit :: new (
279
+ Range :: new ( Position :: new ( 1 , 1 ) , Position :: new ( 5 , 5 ) ) ,
280
+ "new_text" . to_string ( ) ,
281
+ ) ;
282
+
283
+ let mut completion_with_joint_edits =
284
+ CompletionItem :: new_simple ( "label" . to_string ( ) , "detail" . to_string ( ) ) ;
285
+ completion_with_joint_edits. additional_text_edits =
286
+ Some ( vec ! [ disjoint_edit. clone( ) , joint_edit. clone( ) ] ) ;
287
+ assert ! (
288
+ !all_edits_are_disjoint( & completion_with_joint_edits, & [ ] ) ,
289
+ "Completion with disjoint edits fails the validaton even with empty extra edits"
290
+ ) ;
291
+
292
+ completion_with_joint_edits. text_edit =
293
+ Some ( CompletionTextEdit :: Edit ( disjoint_edit. clone ( ) ) ) ;
294
+ completion_with_joint_edits. additional_text_edits = Some ( vec ! [ joint_edit. clone( ) ] ) ;
295
+ assert ! (
296
+ !all_edits_are_disjoint( & completion_with_joint_edits, & [ ] ) ,
297
+ "Completion with disjoint edits fails the validaton even with empty extra edits"
298
+ ) ;
299
+
300
+ completion_with_joint_edits. text_edit =
301
+ Some ( CompletionTextEdit :: InsertAndReplace ( InsertReplaceEdit {
302
+ new_text : "new_text" . to_string ( ) ,
303
+ insert : disjoint_edit. range ,
304
+ replace : joint_edit. range ,
305
+ } ) ) ;
306
+ completion_with_joint_edits. additional_text_edits = None ;
307
+ assert ! (
308
+ !all_edits_are_disjoint( & completion_with_joint_edits, & [ ] ) ,
309
+ "Completion with disjoint edits fails the validaton even with empty extra edits"
310
+ ) ;
311
+
312
+ completion_with_joint_edits. text_edit =
313
+ Some ( CompletionTextEdit :: InsertAndReplace ( InsertReplaceEdit {
314
+ new_text : "new_text" . to_string ( ) ,
315
+ insert : disjoint_edit. range ,
316
+ replace : disjoint_edit_2. range ,
317
+ } ) ) ;
318
+ completion_with_joint_edits. additional_text_edits = Some ( vec ! [ joint_edit] ) ;
319
+ assert ! (
320
+ !all_edits_are_disjoint( & completion_with_joint_edits, & [ ] ) ,
321
+ "Completion with disjoint edits fails the validaton even with empty extra edits"
322
+ ) ;
323
+ }
324
+
325
+ #[ test]
326
+ fn completion_with_disjoint_edits_disjoint_tests ( ) {
327
+ let disjoint_edit = lsp_types:: TextEdit :: new (
328
+ Range :: new ( Position :: new ( 1 , 1 ) , Position :: new ( 2 , 2 ) ) ,
329
+ "new_text" . to_string ( ) ,
330
+ ) ;
331
+ let disjoint_edit_2 = lsp_types:: TextEdit :: new (
332
+ Range :: new ( Position :: new ( 2 , 2 ) , Position :: new ( 3 , 3 ) ) ,
333
+ "new_text" . to_string ( ) ,
334
+ ) ;
335
+ let joint_edit = lsp_types:: TextEdit :: new (
336
+ Range :: new ( Position :: new ( 1 , 1 ) , Position :: new ( 5 , 5 ) ) ,
337
+ "new_text" . to_string ( ) ,
338
+ ) ;
339
+
340
+ let mut completion_with_disjoint_edits =
341
+ CompletionItem :: new_simple ( "label" . to_string ( ) , "detail" . to_string ( ) ) ;
342
+ completion_with_disjoint_edits. text_edit = Some ( CompletionTextEdit :: Edit ( disjoint_edit) ) ;
343
+ let completion_with_disjoint_edits = completion_with_disjoint_edits;
344
+
345
+ assert ! (
346
+ all_edits_are_disjoint( & completion_with_disjoint_edits, & [ ] ) ,
347
+ "Completion with disjoint edits is valid"
348
+ ) ;
349
+ assert ! (
350
+ !all_edits_are_disjoint( & completion_with_disjoint_edits, & [ joint_edit. clone( ) ] ) ,
351
+ "Completion with disjoint edits and joint extra edit is invalid"
352
+ ) ;
353
+ assert ! (
354
+ all_edits_are_disjoint( & completion_with_disjoint_edits, & [ disjoint_edit_2. clone( ) ] ) ,
355
+ "Completion with disjoint edits and joint extra edit is valid"
356
+ ) ;
357
+ }
200
358
}
0 commit comments