@@ -37,7 +37,9 @@ use tower_lsp::lsp_types::Range;
37
37
use tower_lsp:: lsp_types:: TextEdit ;
38
38
use tree_sitter:: Node ;
39
39
40
- use crate :: lsp:: completions:: parameter_hints:: ParameterHints ;
40
+ use crate :: lsp:: completions:: function_context:: ArgumentsStatus ;
41
+ use crate :: lsp:: completions:: function_context:: FunctionContext ;
42
+ use crate :: lsp:: completions:: function_context:: FunctionRefUsage ;
41
43
use crate :: lsp:: completions:: types:: CompletionData ;
42
44
use crate :: lsp:: completions:: types:: PromiseStrategy ;
43
45
use crate :: lsp:: document_context:: DocumentContext ;
@@ -119,6 +121,8 @@ pub(super) fn completion_item_from_assignment(
119
121
item. documentation = Some ( Documentation :: MarkupContent ( markup) ) ;
120
122
item. kind = Some ( CompletionItemKind :: VARIABLE ) ;
121
123
124
+ // TODO: This ad hoc completion item construction for a function does not
125
+ // benefit from the logic in completion_item_from_function() :(
122
126
if rhs. node_type ( ) == NodeType :: FunctionDefinition {
123
127
if let Some ( parameters) = rhs. child_by_field_name ( "parameters" ) {
124
128
let parameters = context
@@ -167,36 +171,125 @@ pub(super) unsafe fn completion_item_from_package(
167
171
pub ( super ) fn completion_item_from_function (
168
172
name : & str ,
169
173
package : Option < & str > ,
170
- parameter_hints : & ParameterHints ,
174
+ function_context : & FunctionContext ,
171
175
) -> anyhow:: Result < CompletionItem > {
172
- let label = format ! ( "{}" , name) ;
176
+ let label = name. to_string ( ) ;
173
177
let mut item = completion_item ( label, CompletionData :: Function {
174
178
name : name. to_string ( ) ,
175
179
package : package. map ( |s| s. to_string ( ) ) ,
176
180
} ) ?;
177
181
178
182
item. kind = Some ( CompletionItemKind :: FUNCTION ) ;
179
183
184
+ let insert_text = sym_quote_invalid ( name) ;
185
+
180
186
let label_details = item_details ( package) ;
181
187
item. label_details = Some ( label_details) ;
182
188
183
- let insert_text = sym_quote_invalid ( name) ;
189
+ // Are we forming a completion item that's an exact match for an existing
190
+ // function name that is already in the document?
191
+ // This identifies scenarios where we need to edit text, not just insert it.
192
+ let item_is_an_edit = name == function_context. name && !function_context. cursor_is_at_end ;
193
+
194
+ // These settings are part of the trick we use to make it easy to accept
195
+ // this matching completion item, which will feel like we're just moving
196
+ // the cursor past existing text.
197
+ if item_is_an_edit {
198
+ item. sort_text = Some ( format ! ( "0-{name}" ) ) ;
199
+ item. preselect = Some ( true ) ;
200
+ }
184
201
185
- if parameter_hints. is_enabled ( ) {
186
- item. insert_text_format = Some ( InsertTextFormat :: SNIPPET ) ;
187
- item. insert_text = Some ( format ! ( "{insert_text}($0)" ) ) ;
202
+ if function_context. arguments_status == ArgumentsStatus :: Absent {
203
+ if item_is_an_edit {
204
+ // This is a case like `dplyr::@across` or
205
+ // `debug(dplyr::@across`), i.e. adding the `dplyr::`
206
+ // namespace qualification after-the-fact.
207
+ // We need to consume the existing function name (e.g. `across`) and
208
+ // move the cursor to its end. We don't add parentheses, both
209
+ // because it feels presumptuous and because we don't have a
210
+ // practical way of doing so, in any case. We leave the arguments
211
+ // just as we found them: absent.
212
+ let text_edit = TextEdit {
213
+ range : function_context. range ,
214
+ new_text : insert_text,
215
+ } ;
216
+ item. text_edit = Some ( CompletionTextEdit :: Edit ( text_edit) ) ;
217
+
218
+ return Ok ( item) ;
219
+ }
188
220
189
- // provide parameter completions after completing function
190
- item. command = Some ( Command {
191
- title : "Trigger Parameter Hints" . to_string ( ) ,
192
- command : "editor.action.triggerParameterHints" . to_string ( ) ,
193
- ..Default :: default ( )
194
- } ) ;
195
- } else {
196
- item. insert_text_format = Some ( InsertTextFormat :: PLAIN_TEXT ) ;
197
- item. insert_text = Some ( insert_text) ;
221
+ // These are the two most common, plain vanilla function completion
222
+ // scenarios: typing out a known call or reference for the first time.
223
+ match function_context. usage {
224
+ FunctionRefUsage :: Call => {
225
+ item. insert_text_format = Some ( InsertTextFormat :: SNIPPET ) ;
226
+ item. insert_text = Some ( format ! ( "{insert_text}($0)" ) ) ;
227
+ item. command = Some ( Command {
228
+ title : "Trigger Parameter Hints" . to_string ( ) ,
229
+ command : "editor.action.triggerParameterHints" . to_string ( ) ,
230
+ ..Default :: default ( )
231
+ } ) ;
232
+ } ,
233
+ FunctionRefUsage :: Value => {
234
+ item. insert_text_format = Some ( InsertTextFormat :: PLAIN_TEXT ) ;
235
+ item. insert_text = Some ( insert_text) ;
236
+ } ,
237
+ }
238
+
239
+ return Ok ( item) ;
240
+ }
241
+
242
+ // Addresses a sequence that starts like this:
243
+ // some_thing()
244
+ // User realizes they want a different function and backspaces.
245
+ // some_@()
246
+ // User accepts one of the offered completions.
247
+ // some_other_thing(@)
248
+ // User is back on the Happy Path.
249
+ // Also handles the case of editing `some_thing(foo = "bar")`, i.e. where
250
+ // something already exists inside the parentheses.
251
+ // Also handles the case of adding `pkg::` after the fact to an existing
252
+ // call like `pkg::@fcn()` or `pkg::@fcn(foo = "bar)`.
253
+ if function_context. usage == FunctionRefUsage :: Call {
254
+ // Tweak the range to cover the opening parenthesis "(" and
255
+ // include the opening parenthesis in the new text.
256
+ // The effect is to move the cursor inside the parentheses.
257
+ let text_edit = TextEdit {
258
+ range : {
259
+ let mut range = function_context. range ;
260
+ range. end . character += 1 ;
261
+ range
262
+ } ,
263
+ new_text : format ! ( "{}(" , insert_text) ,
264
+ } ;
265
+ item. text_edit = Some ( CompletionTextEdit :: Edit ( text_edit) ) ;
266
+ if function_context. arguments_status == ArgumentsStatus :: Empty {
267
+ item. command = Some ( Command {
268
+ title : "Trigger Parameter Hints" . to_string ( ) ,
269
+ command : "editor.action.triggerParameterHints" . to_string ( ) ,
270
+ ..Default :: default ( )
271
+ } ) ;
272
+ }
273
+
274
+ return Ok ( item) ;
198
275
}
199
276
277
+ // Fallback case which should really never happen, but let's be safe
278
+ log:: trace!(
279
+ "completion_item_from_function() - Unexpected case:
280
+ usage={usage:?},
281
+ arguments_status={arguments_status:?},
282
+ name='{name}',
283
+ package={package:?},
284
+ cursor_at_end={cursor_is_at_end}" ,
285
+ usage = function_context. usage,
286
+ arguments_status = function_context. arguments_status,
287
+ cursor_is_at_end = function_context. cursor_is_at_end
288
+ ) ;
289
+
290
+ item. insert_text_format = Some ( InsertTextFormat :: PLAIN_TEXT ) ;
291
+ item. insert_text = Some ( insert_text) ;
292
+
200
293
Ok ( item)
201
294
}
202
295
@@ -250,7 +343,7 @@ pub(super) unsafe fn completion_item_from_object(
250
343
envir : SEXP ,
251
344
package : Option < & str > ,
252
345
promise_strategy : PromiseStrategy ,
253
- parameter_hints : & ParameterHints ,
346
+ function_context : & FunctionContext ,
254
347
) -> anyhow:: Result < CompletionItem > {
255
348
if r_typeof ( object) == PROMSXP {
256
349
return completion_item_from_promise (
@@ -259,7 +352,7 @@ pub(super) unsafe fn completion_item_from_object(
259
352
envir,
260
353
package,
261
354
promise_strategy,
262
- parameter_hints ,
355
+ function_context ,
263
356
) ;
264
357
}
265
358
@@ -269,7 +362,7 @@ pub(super) unsafe fn completion_item_from_object(
269
362
// In other words, when creating a completion item for these functions,
270
363
// we should also figure out where we can receive the help from.
271
364
if Rf_isFunction ( object) != 0 {
272
- return completion_item_from_function ( name, package, parameter_hints ) ;
365
+ return completion_item_from_function ( name, package, function_context ) ;
273
366
}
274
367
275
368
let mut item = completion_item ( name, CompletionData :: Object {
@@ -300,7 +393,7 @@ pub(super) unsafe fn completion_item_from_promise(
300
393
envir : SEXP ,
301
394
package : Option < & str > ,
302
395
promise_strategy : PromiseStrategy ,
303
- parameter_hints : & ParameterHints ,
396
+ function_context : & FunctionContext ,
304
397
) -> anyhow:: Result < CompletionItem > {
305
398
if r_promise_is_forced ( object) {
306
399
// Promise has already been evaluated before.
@@ -312,7 +405,7 @@ pub(super) unsafe fn completion_item_from_promise(
312
405
envir,
313
406
package,
314
407
promise_strategy,
315
- parameter_hints ,
408
+ function_context ,
316
409
) ;
317
410
}
318
411
@@ -329,7 +422,7 @@ pub(super) unsafe fn completion_item_from_promise(
329
422
envir,
330
423
package,
331
424
promise_strategy,
332
- parameter_hints ,
425
+ function_context ,
333
426
) ;
334
427
}
335
428
@@ -370,7 +463,7 @@ pub(super) unsafe fn completion_item_from_namespace(
370
463
name : & str ,
371
464
namespace : SEXP ,
372
465
package : & str ,
373
- parameter_hints : & ParameterHints ,
466
+ function_context : & FunctionContext ,
374
467
) -> anyhow:: Result < CompletionItem > {
375
468
// We perform two passes to locate the object. It is normal for the first pass to
376
469
// error when the `namespace` doesn't have a binding for `name` because the associated
@@ -385,7 +478,7 @@ pub(super) unsafe fn completion_item_from_namespace(
385
478
namespace,
386
479
Some ( package) ,
387
480
PromiseStrategy :: Force ,
388
- parameter_hints ,
481
+ function_context ,
389
482
) {
390
483
Ok ( item) => return Ok ( item) ,
391
484
Err ( error) => error,
@@ -398,7 +491,7 @@ pub(super) unsafe fn completion_item_from_namespace(
398
491
imports,
399
492
Some ( package) ,
400
493
PromiseStrategy :: Force ,
401
- parameter_hints ,
494
+ function_context ,
402
495
) {
403
496
Ok ( item) => return Ok ( item) ,
404
497
Err ( error) => error,
@@ -423,10 +516,21 @@ pub(super) unsafe fn completion_item_from_lazydata(
423
516
let promise_strategy = PromiseStrategy :: Simple ;
424
517
425
518
// Lazydata objects are never functions, so this doesn't really matter
426
- let parameter_hints = ParameterHints :: Disabled ;
519
+ let dummy_function_context = FunctionContext {
520
+ name : String :: new ( ) ,
521
+ range : Default :: default ( ) ,
522
+ usage : FunctionRefUsage :: Call ,
523
+ arguments_status : ArgumentsStatus :: Absent ,
524
+ cursor_is_at_end : true ,
525
+ } ;
427
526
428
- match completion_item_from_symbol ( name, env, Some ( package) , promise_strategy, & parameter_hints)
429
- {
527
+ match completion_item_from_symbol (
528
+ name,
529
+ env,
530
+ Some ( package) ,
531
+ promise_strategy,
532
+ & dummy_function_context,
533
+ ) {
430
534
Ok ( item) => Ok ( item) ,
431
535
Err ( err) => {
432
536
// Should be impossible, but we'll be extra safe
@@ -440,7 +544,7 @@ pub(super) unsafe fn completion_item_from_symbol(
440
544
envir : SEXP ,
441
545
package : Option < & str > ,
442
546
promise_strategy : PromiseStrategy ,
443
- parameter_hints : & ParameterHints ,
547
+ function_context : & FunctionContext ,
444
548
) -> anyhow:: Result < CompletionItem > {
445
549
let symbol = r_symbol ! ( name) ;
446
550
@@ -477,7 +581,7 @@ pub(super) unsafe fn completion_item_from_symbol(
477
581
envir,
478
582
package,
479
583
promise_strategy,
480
- parameter_hints ,
584
+ function_context ,
481
585
)
482
586
}
483
587
@@ -553,12 +657,11 @@ fn completion_item_from_dot_dot_dot(
553
657
start : position,
554
658
end : position,
555
659
} ;
556
- let textedit = TextEdit {
660
+ let text_edit = TextEdit {
557
661
range,
558
662
new_text : "" . to_string ( ) ,
559
663
} ;
560
- let textedit = CompletionTextEdit :: Edit ( textedit) ;
561
- item. text_edit = Some ( textedit) ;
664
+ item. text_edit = Some ( CompletionTextEdit :: Edit ( text_edit) ) ;
562
665
563
666
Ok ( item)
564
667
}
0 commit comments