@@ -133,13 +133,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
133
133
134
134
items =
135
135
ElixirSense . suggestions ( text , line + 1 , character + 1 )
136
+ |> maybe_reject_derived_functions ( context , options )
136
137
|> Enum . map ( & from_completion_item ( & 1 , context , options ) )
137
138
|> Enum . concat ( module_attr_snippets ( context ) )
138
139
139
140
items_json =
140
141
items
141
142
|> Enum . reject ( & is_nil / 1 )
142
- |> Enum . uniq_by ( & & 1 . insert_text )
143
+ |> Enum . uniq_by ( & { & 1 . detail , & 1 . documentation , & 1 . insert_text } )
143
144
|> sort_items ( )
144
145
|> items_to_json ( options )
145
146
@@ -159,6 +160,21 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
159
160
end
160
161
end
161
162
163
+ defp maybe_reject_derived_functions ( suggestions , context , options ) do
164
+ locals_without_parens = Keyword . get ( options , :locals_without_parens )
165
+ signature_help_supported = Keyword . get ( options , :signature_help_supported , false )
166
+ capture_before? = context . capture_before?
167
+
168
+ Enum . reject ( suggestions , fn s ->
169
+ s . type in [ :function , :macro ] &&
170
+ ! capture_before? &&
171
+ s . arity < s . def_arity &&
172
+ signature_help_supported &&
173
+ function_name_with_parens? ( s . name , s . arity , locals_without_parens ) &&
174
+ function_name_with_parens? ( s . name , s . def_arity , locals_without_parens )
175
+ end )
176
+ end
177
+
162
178
defp from_completion_item (
163
179
% { type: :attribute , name: name } ,
164
180
% {
@@ -288,7 +304,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
288
304
289
305
opts = Keyword . put ( options , :with_parens? , true )
290
306
insert_text = def_snippet ( def_str , name , args , arity , opts )
291
- label = "#{ def_str } #{ function_label ( name , args , arity ) } "
307
+ label = "#{ def_str } #{ name } / #{ arity } "
292
308
293
309
filter_text =
294
310
if def_str do
@@ -327,7 +343,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
327
343
def_str = if ( context [ :def_before ] == nil , do: "def " )
328
344
329
345
insert_text = def_snippet ( def_str , name , args , arity , options )
330
- label = "#{ def_str } #{ function_label ( name , args , arity ) } "
346
+ label = "#{ def_str } #{ name } / #{ arity } "
331
347
332
348
% __MODULE__ {
333
349
label: label ,
@@ -443,10 +459,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
443
459
nil
444
460
end
445
461
446
- defp function_label ( name , _args , arity ) do
447
- Enum . join ( [ to_string ( name ) , "/" , arity ] )
448
- end
449
-
450
462
defp def_snippet ( def_str , name , args , arity , opts ) do
451
463
if Keyword . get ( opts , :snippets_supported , false ) do
452
464
"#{ def_str } #{ function_snippet ( name , args , arity , opts ) } do\n \t $0\n end"
@@ -456,56 +468,93 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
456
468
end
457
469
458
470
defp function_snippet ( name , args , arity , opts ) do
459
- cond do
460
- Keyword . get ( opts , :capture_before? ) && arity <= 1 ->
461
- Enum . join ( [ name , "/" , arity ] )
471
+ snippets_supported? = Keyword . get ( opts , :snippets_supported , false )
472
+ trigger_signature? = Keyword . get ( opts , :trigger_signature? , false )
473
+ capture_before? = Keyword . get ( opts , :capture_before? , false )
462
474
463
- not Keyword . get ( opts , :snippets_supported , false ) ->
464
- name
475
+ cond do
476
+ capture_before? ->
477
+ function_snippet_with_capture_before ( name , arity , snippets_supported? )
465
478
466
- Keyword . get ( opts , : trigger_signature?, false ) ->
479
+ trigger_signature? ->
467
480
text_after_cursor = Keyword . get ( opts , :text_after_cursor , "" )
481
+ function_snippet_with_signature ( name , text_after_cursor , snippets_supported? )
468
482
469
- # Don't add the closing parenthesis to the snippet if the cursor is
470
- # immediately before a valid argument (this usually happens when we
471
- # want to wrap an existing variable or literal, e.g. using IO.inspect)
472
- if Regex . match? ( ~r/ ^[a-zA-Z0-9_:"'%<\[ \{ ]/ , text_after_cursor ) do
473
- "#{ name } ("
474
- else
475
- "#{ name } ($1)$0"
476
- end
483
+ has_text_after_cursor? ( opts ) ->
484
+ name
485
+
486
+ snippets_supported? ->
487
+ pipe_before? = Keyword . get ( opts , :pipe_before? , false )
488
+ with_parens? = Keyword . get ( opts , :with_parens? , false )
489
+ function_snippet_with_args ( name , arity , args , pipe_before? , with_parens? )
477
490
478
491
true ->
479
- args_list =
480
- if args && args != "" do
481
- split_args ( args )
482
- else
483
- for i <- Enum . slice ( 0 .. arity , 1 .. - 1 ) , do: "arg#{ i } "
484
- end
492
+ name
493
+ end
494
+ end
485
495
486
- args_list =
487
- if Keyword . get ( opts , :pipe_before? ) do
488
- Enum . slice ( args_list , 1 .. - 1 )
489
- else
490
- args_list
491
- end
496
+ defp function_snippet_with_args ( name , arity , args , pipe_before? , with_parens? ) do
497
+ args_list =
498
+ if args && args != "" do
499
+ split_args_for_snippet ( args , arity )
500
+ else
501
+ for i <- Enum . slice ( 0 .. arity , 1 .. - 1 ) , do: "arg#{ i } "
502
+ end
492
503
493
- tabstops =
494
- args_list
495
- |> Enum . with_index ( )
496
- |> Enum . map ( fn { arg , i } -> "${#{ i + 1 } :#{ arg } }" end )
504
+ args_list =
505
+ if pipe_before? do
506
+ Enum . slice ( args_list , 1 .. - 1 )
507
+ else
508
+ args_list
509
+ end
497
510
498
- { before_args , after_args } =
499
- if Keyword . get ( opts , :with_parens? , false ) do
500
- { "(" , ")" }
501
- else
502
- { " " , "" }
503
- end
511
+ tabstops =
512
+ args_list
513
+ |> Enum . with_index ( )
514
+ |> Enum . map ( fn { arg , i } -> "${#{ i + 1 } :#{ arg } }" end )
504
515
505
- Enum . join ( [ name , before_args , Enum . join ( tabstops , ", " ) , after_args ] )
516
+ { before_args , after_args } =
517
+ if with_parens? do
518
+ { "(" , ")" }
519
+ else
520
+ { " " , "" }
521
+ end
522
+
523
+ Enum . join ( [ name , before_args , Enum . join ( tabstops , ", " ) , after_args ] )
524
+ end
525
+
526
+ defp function_snippet_with_signature ( name , text_after_cursor , snippets_supported? ) do
527
+ # Don't add the closing parenthesis to the snippet if the cursor is
528
+ # immediately before a valid argument. This usually happens when we
529
+ # want to wrap an existing variable or literal, e.g. using IO.inspect/2.
530
+ if ! snippets_supported? || Regex . match? ( ~r/ ^[a-zA-Z0-9_:"'%<@\[ \{ ]/ , text_after_cursor ) do
531
+ "#{ name } ("
532
+ else
533
+ "#{ name } ($1)$0"
506
534
end
507
535
end
508
536
537
+ defp function_snippet_with_capture_before ( name , 0 , _snippets_supported? ) do
538
+ "#{ name } /0"
539
+ end
540
+
541
+ defp function_snippet_with_capture_before ( name , arity , snippets_supported? ) do
542
+ if snippets_supported? do
543
+ "#{ name } ${1:/#{ arity } }$0"
544
+ else
545
+ "#{ name } /#{ arity } "
546
+ end
547
+ end
548
+
549
+ defp has_text_after_cursor? ( opts ) do
550
+ text =
551
+ opts
552
+ |> Keyword . get ( :text_after_cursor , "" )
553
+ |> String . trim ( )
554
+
555
+ text != ""
556
+ end
557
+
509
558
defp completion_kind ( type ) do
510
559
case type do
511
560
:text -> 1
@@ -545,17 +594,34 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
545
594
end
546
595
end
547
596
548
- defp split_args ( args ) do
597
+ defp split_args_for_snippet ( args , arity ) do
549
598
args
550
599
|> String . replace ( "\\ " , "\\ \\ " )
551
600
|> String . replace ( "$" , "\\ $" )
552
601
|> String . replace ( "}" , "\\ }" )
553
602
|> String . split ( "," )
554
- |> Enum . reject ( & is_default_argument? / 1 )
555
- |> Enum . map ( & String . trim / 1 )
603
+ |> remove_unused_default_args ( arity )
556
604
end
557
605
558
- defp is_default_argument? ( s ) , do: String . contains? ( s , "\\ \\ " )
606
+ defp remove_unused_default_args ( args , arity ) do
607
+ reversed_args = Enum . reverse ( args )
608
+ acc = { [ ] , length ( args ) - arity }
609
+
610
+ { result , _ } =
611
+ Enum . reduce ( reversed_args , acc , fn arg , { result , remove_count } ->
612
+ parts = String . split ( arg , "\\ \\ \\ \\ " )
613
+ var = Enum . at ( parts , 0 ) |> String . trim ( )
614
+ default_value = Enum . at ( parts , 1 )
615
+
616
+ if remove_count > 0 && default_value do
617
+ { result , remove_count - 1 }
618
+ else
619
+ { [ var | result ] , remove_count }
620
+ end
621
+ end )
622
+
623
+ result
624
+ end
559
625
560
626
defp module_attr_snippets ( % { prefix: prefix , scope: :module , def_before: nil } ) do
561
627
for { name , snippet , docs } <- @ module_attr_snippets ,
@@ -585,7 +651,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
585
651
defp function_completion ( info , context , options ) do
586
652
% {
587
653
type: type ,
588
- visibility: visibility ,
589
654
args: args ,
590
655
name: name ,
591
656
summary: summary ,
@@ -606,13 +671,11 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
606
671
} = context
607
672
608
673
locals_without_parens = Keyword . get ( options , :locals_without_parens )
674
+ signature_help_supported? = Keyword . get ( options , :signature_help_supported , false )
609
675
with_parens? = function_name_with_parens? ( name , arity , locals_without_parens )
610
676
611
677
trigger_signature? =
612
- Keyword . get ( options , :signature_help_supported , false ) &&
613
- Keyword . get ( options , :snippets_supported , false ) &&
614
- arity > 0 &&
615
- with_parens?
678
+ signature_help_supported? && with_parens? && ( ( arity == 1 && ! pipe_before? ) || arity > 1 )
616
679
617
680
{ label , insert_text } =
618
681
cond do
@@ -625,7 +688,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
625
688
{ name , name }
626
689
627
690
true ->
628
- label = function_label ( name , args , arity )
691
+ label = " #{ name } / #{ arity } "
629
692
630
693
insert_text =
631
694
function_snippet (
@@ -646,22 +709,19 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
646
709
{ label , insert_text }
647
710
end
648
711
649
- detail_header =
712
+ detail_prefix =
650
713
if inspect ( module ) == origin do
651
- "#{ visibility } #{ type } "
714
+ "( #{ type } ) "
652
715
else
653
- "#{ origin } #{ type } "
716
+ "( #{ type } ) #{ origin } . "
654
717
end
655
718
656
- footer =
657
- if String . starts_with? ( type , [ "private" , "public" ] ) do
658
- String . replace ( type , "_" , " " )
659
- else
660
- SourceFile . format_spec ( spec , line_length: 30 )
661
- end
719
+ detail = Enum . join ( [ detail_prefix , name , "(" , args , ")" ] )
720
+
721
+ footer = SourceFile . format_spec ( spec , line_length: 30 )
662
722
663
723
command =
664
- if trigger_signature? do
724
+ if trigger_signature? && ! capture_before? do
665
725
% {
666
726
"title" => "Trigger Parameter Hint" ,
667
727
"command" => "editor.action.triggerParameterHints"
@@ -671,7 +731,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
671
731
% __MODULE__ {
672
732
label: label ,
673
733
kind: :function ,
674
- detail: detail_header <> " \n \n " <> Enum . join ( [ to_string ( name ) , "(" , args , ")" ] ) ,
734
+ detail: detail ,
675
735
documentation: summary <> footer ,
676
736
insert_text: insert_text ,
677
737
priority: 7 ,
0 commit comments