@@ -14,7 +14,6 @@ use harp::utils::r_is_function;
14
14
use tower_lsp:: lsp_types:: CompletionItem ;
15
15
use tree_sitter:: Node ;
16
16
17
- use super :: pipe:: PipeRoot ;
18
17
use crate :: lsp:: completions:: completion_context:: CompletionContext ;
19
18
use crate :: lsp:: completions:: completion_item:: completion_item_from_parameter;
20
19
use crate :: lsp:: completions:: sources:: utils:: call_node_position_type;
@@ -24,7 +23,6 @@ use crate::lsp::completions::sources::CompletionSource;
24
23
use crate :: lsp:: document_context:: DocumentContext ;
25
24
use crate :: lsp:: indexer;
26
25
use crate :: lsp:: traits:: rope:: RopeExt ;
27
- use crate :: treesitter:: NodeTypeExt ;
28
26
29
27
pub ( super ) struct CallSource ;
30
28
@@ -37,45 +35,20 @@ impl CompletionSource for CallSource {
37
35
& self ,
38
36
completion_context : & CompletionContext ,
39
37
) -> anyhow:: Result < Option < Vec < CompletionItem > > > {
40
- completions_from_call (
41
- completion_context. document_context ,
42
- completion_context. pipe_root ( ) ,
43
- )
38
+ completions_from_call ( completion_context)
44
39
}
45
40
}
46
41
47
42
fn completions_from_call (
48
- context : & DocumentContext ,
49
- root : Option < PipeRoot > ,
43
+ context : & CompletionContext ,
50
44
) -> anyhow:: Result < Option < Vec < CompletionItem > > > {
51
- let mut node = context. node ;
52
- let mut has_call = false ;
53
-
54
- loop {
55
- // If we landed on a 'call', then we should provide parameter completions
56
- // for the associated callee if possible.
57
- if node. is_call ( ) {
58
- has_call = true ;
59
- break ;
60
- }
61
-
62
- // If we reach a brace list, bail.
63
- if node. is_braced_expression ( ) {
64
- break ;
65
- }
66
-
67
- // Update the node.
68
- node = match node. parent ( ) {
69
- Some ( node) => node,
70
- None => break ,
71
- } ;
72
- }
73
-
74
- if !has_call {
75
- // Didn't detect anything worth completing in this context,
76
- // let other sources add their own candidates instead
45
+ let Some ( node) = context. containing_call_node ( ) else {
46
+ // Not in a call, let other sources add their own candidates
77
47
return Ok ( None ) ;
78
- }
48
+ } ;
49
+
50
+ let document_context = context. document_context ;
51
+ let point = document_context. point ;
79
52
80
53
// Now that we know we are in a call, detect if we are in a location where
81
54
// we should provide argument completions, i.e. if we are in the `name`
@@ -84,7 +57,7 @@ fn completions_from_call(
84
57
// fn(name = value)
85
58
// ~~~~
86
59
//
87
- match call_node_position_type ( & context . node , context . point ) {
60
+ match call_node_position_type ( & document_context . node , point) {
88
61
// We should provide argument completions. Ambiguous states like
89
62
// `fn(arg<tab>)` or `fn(x, arg<tab>)` should still get argument
90
63
// completions.
@@ -102,23 +75,28 @@ fn completions_from_call(
102
75
return Ok ( None ) ;
103
76
} ;
104
77
105
- let callee = context. document . contents . node_slice ( & callee) ?. to_string ( ) ;
78
+ let callee = document_context
79
+ . document
80
+ . contents
81
+ . node_slice ( & callee) ?
82
+ . to_string ( ) ;
106
83
107
84
// - Prefer `root` as the first argument if it exists
108
85
// - Then fall back to looking it up, if possible
109
86
// - Otherwise use `NULL` to signal that we can't figure it out
87
+ let root = context. pipe_root ( ) ;
110
88
let object = match root {
111
89
Some ( root) => match root. object {
112
90
Some ( object) => object,
113
91
None => RObject :: null ( ) ,
114
92
} ,
115
- None => match get_first_argument ( context , & node) ? {
93
+ None => match get_first_argument ( document_context , & node) ? {
116
94
Some ( object) => object,
117
95
None => RObject :: null ( ) ,
118
96
} ,
119
97
} ;
120
98
121
- completions_from_arguments ( context , & callee, object)
99
+ completions_from_arguments ( document_context , & callee, object)
122
100
}
123
101
124
102
fn get_first_argument ( context : & DocumentContext , node : & Node ) -> anyhow:: Result < Option < RObject > > {
@@ -302,32 +280,38 @@ fn completions_from_workspace_arguments(
302
280
#[ cfg( test) ]
303
281
mod tests {
304
282
use harp:: eval:: RParseEvalOptions ;
305
- use tree_sitter:: Point ;
306
283
284
+ use crate :: fixtures:: point_from_cursor;
285
+ use crate :: lsp:: completions:: completion_context:: CompletionContext ;
307
286
use crate :: lsp:: completions:: sources:: composite:: call:: completions_from_call;
308
287
use crate :: lsp:: document_context:: DocumentContext ;
309
288
use crate :: lsp:: documents:: Document ;
289
+ use crate :: lsp:: state:: WorldState ;
310
290
use crate :: r_task;
311
291
312
292
#[ test]
313
293
fn test_completions_after_user_types_part_of_an_argument_name ( ) {
314
294
r_task ( || {
315
295
// Right after `tab`
316
- let point = Point { row : 0 , column : 9 } ;
317
- let document = Document :: new ( "match(tab)" , None ) ;
318
- let context = DocumentContext :: new ( & document, point, None ) ;
319
- let completions = completions_from_call ( & context, None ) . unwrap ( ) . unwrap ( ) ;
296
+ let ( text, point) = point_from_cursor ( "match(tab@)" ) ;
297
+ let document = Document :: new ( text. as_str ( ) , None ) ;
298
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
299
+ let state = WorldState :: default ( ) ;
300
+ let context = CompletionContext :: new ( & document_context, & state) ;
301
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
320
302
321
303
// We detect this as a `name` position and return all possible completions
322
304
assert_eq ! ( completions. len( ) , 4 ) ;
323
305
assert_eq ! ( completions. get( 0 ) . unwrap( ) . label, "x = " ) ;
324
306
assert_eq ! ( completions. get( 1 ) . unwrap( ) . label, "table = " ) ;
325
307
326
308
// Right after `tab`
327
- let point = Point { row : 0 , column : 12 } ;
328
- let document = Document :: new ( "match(1, tab)" , None ) ;
329
- let context = DocumentContext :: new ( & document, point, None ) ;
330
- let completions = completions_from_call ( & context, None ) . unwrap ( ) . unwrap ( ) ;
309
+ let ( text, point) = point_from_cursor ( "match(1, tab@)" ) ;
310
+ let document = Document :: new ( text. as_str ( ) , None ) ;
311
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
312
+ let state = WorldState :: default ( ) ;
313
+ let context = CompletionContext :: new ( & document_context, & state) ;
314
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
331
315
332
316
// We detect this as a `name` position and return all possible completions
333
317
// (TODO: Should not return `x` as a possible completion)
@@ -342,10 +326,12 @@ mod tests {
342
326
// Can't find the function
343
327
r_task ( || {
344
328
// Place cursor between `()`
345
- let point = Point { row : 0 , column : 21 } ;
346
- let document = Document :: new ( "not_a_known_function()" , None ) ;
347
- let context = DocumentContext :: new ( & document, point, None ) ;
348
- let completions = completions_from_call ( & context, None ) . unwrap ( ) ;
329
+ let ( text, point) = point_from_cursor ( "not_a_known_function(@)" ) ;
330
+ let document = Document :: new ( text. as_str ( ) , None ) ;
331
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
332
+ let state = WorldState :: default ( ) ;
333
+ let context = CompletionContext :: new ( & document_context, & state) ;
334
+ let completions = completions_from_call ( & context) . unwrap ( ) ;
349
335
assert ! ( completions. is_none( ) ) ;
350
336
} ) ;
351
337
@@ -360,10 +346,12 @@ mod tests {
360
346
harp:: parse_eval ( "my_fun <- function(y, x) x + y" , options. clone ( ) ) . unwrap ( ) ;
361
347
362
348
// Place cursor between `()`
363
- let point = Point { row : 0 , column : 7 } ;
364
- let document = Document :: new ( "my_fun()" , None ) ;
365
- let context = DocumentContext :: new ( & document, point, None ) ;
366
- let completions = completions_from_call ( & context, None ) . unwrap ( ) . unwrap ( ) ;
349
+ let ( text, point) = point_from_cursor ( "my_fun(@)" ) ;
350
+ let document = Document :: new ( text. as_str ( ) , None ) ;
351
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
352
+ let state = WorldState :: default ( ) ;
353
+ let context = CompletionContext :: new ( & document_context, & state) ;
354
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
367
355
368
356
assert_eq ! ( completions. len( ) , 2 ) ;
369
357
@@ -375,17 +363,21 @@ mod tests {
375
363
assert_eq ! ( completion. label, "x = " ) ;
376
364
377
365
// Place just before the `()`
378
- let point = Point { row : 0 , column : 6 } ;
379
- let document = Document :: new ( "my_fun()" , None ) ;
380
- let context = DocumentContext :: new ( & document, point, None ) ;
381
- let completions = completions_from_call ( & context, None ) . unwrap ( ) ;
366
+ let ( text, point) = point_from_cursor ( "my_fun@()" ) ;
367
+ let document = Document :: new ( text. as_str ( ) , None ) ;
368
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
369
+ let state = WorldState :: default ( ) ;
370
+ let context = CompletionContext :: new ( & document_context, & state) ;
371
+ let completions = completions_from_call ( & context) . unwrap ( ) ;
382
372
assert ! ( completions. is_none( ) ) ;
383
373
384
374
// Place just after the `()`
385
- let point = Point { row : 0 , column : 8 } ;
386
- let document = Document :: new ( "my_fun()" , None ) ;
387
- let context = DocumentContext :: new ( & document, point, None ) ;
388
- let completions = completions_from_call ( & context, None ) . unwrap ( ) ;
375
+ let ( text, point) = point_from_cursor ( "my_fun()@" ) ;
376
+ let document = Document :: new ( text. as_str ( ) , None ) ;
377
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
378
+ let state = WorldState :: default ( ) ;
379
+ let context = CompletionContext :: new ( & document_context, & state) ;
380
+ let completions = completions_from_call ( & context) . unwrap ( ) ;
389
381
assert ! ( completions. is_none( ) ) ;
390
382
391
383
// Clean up
@@ -403,14 +395,86 @@ mod tests {
403
395
harp:: parse_eval ( "my_fun <- 1" , options. clone ( ) ) . unwrap ( ) ;
404
396
405
397
// Place cursor between `()`
406
- let point = Point { row : 0 , column : 7 } ;
407
- let document = Document :: new ( "my_fun()" , None ) ;
408
- let context = DocumentContext :: new ( & document, point, None ) ;
409
- let completions = completions_from_call ( & context, None ) . unwrap ( ) . unwrap ( ) ;
398
+ let ( text, point) = point_from_cursor ( "my_fun(@)" ) ;
399
+ let document = Document :: new ( text. as_str ( ) , None ) ;
400
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
401
+ let state = WorldState :: default ( ) ;
402
+ let context = CompletionContext :: new ( & document_context, & state) ;
403
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
410
404
assert_eq ! ( completions. len( ) , 0 ) ;
411
405
412
406
// Clean up
413
407
harp:: parse_eval ( "remove(my_fun)" , options. clone ( ) ) . unwrap ( ) ;
414
408
} )
415
409
}
410
+
411
+ #[ test]
412
+ fn test_completions_multiline_call ( ) {
413
+ r_task ( || {
414
+ // No arguments typed yet
415
+ let ( text, point) = point_from_cursor ( "match(\n @\n )" ) ;
416
+ let document = Document :: new ( text. as_str ( ) , None ) ;
417
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
418
+ let state = WorldState :: default ( ) ;
419
+ let context = CompletionContext :: new ( & document_context, & state) ;
420
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
421
+
422
+ assert_eq ! ( completions. len( ) , 4 ) ;
423
+ assert_eq ! ( completions. get( 0 ) . unwrap( ) . label, "x = " ) ;
424
+ assert_eq ! ( completions. get( 1 ) . unwrap( ) . label, "table = " ) ;
425
+
426
+ // Partially typed argument
427
+ let ( text, point) = point_from_cursor ( "match(\n tab@\n )" ) ;
428
+ let document = Document :: new ( text. as_str ( ) , None ) ;
429
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
430
+ let state = WorldState :: default ( ) ;
431
+ let context = CompletionContext :: new ( & document_context, & state) ;
432
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
433
+
434
+ assert_eq ! ( completions. len( ) , 4 ) ;
435
+ assert_eq ! ( completions. get( 0 ) . unwrap( ) . label, "x = " ) ;
436
+ assert_eq ! ( completions. get( 1 ) . unwrap( ) . label, "table = " ) ;
437
+
438
+ // Partially typed second argument
439
+ let ( text, point) = point_from_cursor ( "match(\n 1,\n tab@\n )" ) ;
440
+ let document = Document :: new ( text. as_str ( ) , None ) ;
441
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
442
+ let state = WorldState :: default ( ) ;
443
+ let context = CompletionContext :: new ( & document_context, & state) ;
444
+ let completions = completions_from_call ( & context) . unwrap ( ) . unwrap ( ) ;
445
+
446
+ assert_eq ! ( completions. len( ) , 4 ) ;
447
+ assert_eq ! ( completions. get( 0 ) . unwrap( ) . label, "x = " ) ;
448
+ assert_eq ! ( completions. get( 1 ) . unwrap( ) . label, "table = " ) ;
449
+ } )
450
+ }
451
+
452
+ #[ test]
453
+ fn test_completions_in_value_position ( ) {
454
+ r_task ( || {
455
+ fn assert_no_call_completions ( code_with_cursor : & str ) {
456
+ let ( text, point) = point_from_cursor ( code_with_cursor) ;
457
+ let document = Document :: new ( text. as_str ( ) , None ) ;
458
+ let document_context = DocumentContext :: new ( & document, point, None ) ;
459
+ let state = WorldState :: default ( ) ;
460
+ let context = CompletionContext :: new ( & document_context, & state) ;
461
+ let completions = completions_from_call ( & context) . unwrap ( ) ;
462
+
463
+ // We shouldn't get completions in value position
464
+ assert ! ( completions. is_none( ) ) ;
465
+ }
466
+
467
+ // Single line, with space
468
+ assert_no_call_completions ( "match(x = @)" ) ;
469
+
470
+ // Single line, no space
471
+ assert_no_call_completions ( "match(x =@)" ) ;
472
+
473
+ // Multiline case, with space
474
+ assert_no_call_completions ( "match(\n x = @\n )" ) ;
475
+
476
+ // Multiline case, no space
477
+ assert_no_call_completions ( "match(\n x =@\n )" ) ;
478
+ } ) ;
479
+ }
416
480
}
0 commit comments