@@ -10,7 +10,17 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
10
10
alias ElixirLS.LanguageServer.SourceFile
11
11
12
12
@ enforce_keys [ :label , :kind , :insert_text , :priority , :tags ]
13
- defstruct [ :label , :kind , :detail , :documentation , :insert_text , :filter_text , :priority , :tags ]
13
+ defstruct [
14
+ :label ,
15
+ :kind ,
16
+ :detail ,
17
+ :documentation ,
18
+ :insert_text ,
19
+ :filter_text ,
20
+ :priority ,
21
+ :tags ,
22
+ :command
23
+ ]
14
24
15
25
@ module_attr_snippets [
16
26
{ "doc" , "doc \" \" \" \n $0\n \" \" \" " , "Documents a function" } ,
@@ -130,7 +140,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
130
140
def_before: def_before ,
131
141
pipe_before?: Regex . match? ( Regex . recompile! ( ~r/ \| >\s *#{ prefix } $/ ) , text_before_cursor ) ,
132
142
capture_before?: Regex . match? ( Regex . recompile! ( ~r/ &#{ prefix } $/ ) , text_before_cursor ) ,
133
- scope: scope
143
+ scope: scope ,
144
+ module: env . module
134
145
}
135
146
136
147
items =
@@ -278,7 +289,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
278
289
end
279
290
end
280
291
281
- insert_text = def_snippet ( def_str , name , args , arity , options )
292
+ opts = Keyword . put ( options , :with_parens? , true )
293
+ insert_text = def_snippet ( def_str , name , args , arity , opts )
282
294
label = "#{ def_str } #{ function_label ( name , args , arity ) } "
283
295
284
296
filter_text =
@@ -434,12 +446,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
434
446
nil
435
447
end
436
448
437
- defp function_label ( name , args , arity ) do
438
- if args && args != "" do
439
- Enum . join ( [ to_string ( name ) , "(" , args , ")" ] )
440
- else
441
- Enum . join ( [ to_string ( name ) , "/" , arity ] )
442
- end
449
+ defp function_label ( name , _args , arity ) do
450
+ Enum . join ( [ to_string ( name ) , "/" , arity ] )
443
451
end
444
452
445
453
defp def_snippet ( def_str , name , args , arity , opts ) do
@@ -458,6 +466,18 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
458
466
not Keyword . get ( opts , :snippets_supported , false ) ->
459
467
name
460
468
469
+ Keyword . get ( opts , :trigger_signature? , false ) ->
470
+ text_after_cursor = Keyword . get ( opts , :text_after_cursor , "" )
471
+
472
+ # Don't add the closing parenthesis to the snippet if the cursor is
473
+ # immediately before a valid argument (this usually happens when we
474
+ # want to wrap an existing variable or literal, e.g. using IO.inspect)
475
+ if Regex . match? ( ~r/ ^[a-zA-Z0-9_:"'%<\[ \{ ]/ , text_after_cursor ) do
476
+ "#{ name } ("
477
+ else
478
+ "#{ name } ($1)$0"
479
+ end
480
+
461
481
true ->
462
482
args_list =
463
483
if args && args != "" do
@@ -478,7 +498,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
478
498
|> Enum . with_index ( )
479
499
|> Enum . map ( fn { arg , i } -> "${#{ i + 1 } :#{ arg } }" end )
480
500
481
- Enum . join ( [ name , "(" , Enum . join ( tabstops , ", " ) , ")" ] )
501
+ { before_args , after_args } =
502
+ if Keyword . get ( opts , :with_parens? , false ) do
503
+ { "(" , ")" }
504
+ else
505
+ { " " , "" }
506
+ end
507
+
508
+ Enum . join ( [ name , before_args , Enum . join ( tabstops , ", " ) , after_args ] )
482
509
end
483
510
end
484
511
@@ -527,9 +554,12 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
527
554
|> String . replace ( "$" , "\\ $" )
528
555
|> String . replace ( "}" , "\\ }" )
529
556
|> String . split ( "," )
557
+ |> Enum . reject ( & is_default_argument? / 1 )
530
558
|> Enum . map ( & String . trim / 1 )
531
559
end
532
560
561
+ defp is_default_argument? ( s ) , do: String . contains? ( s , "\\ \\ " )
562
+
533
563
defp module_attr_snippets ( % { prefix: prefix , scope: :module , def_before: nil } ) do
534
564
for { name , snippet , docs } <- @ module_attr_snippets ,
535
565
label = "@" <> name ,
@@ -575,6 +605,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
575
605
defp function_completion ( info , context , options ) do
576
606
% {
577
607
type: type ,
608
+ visibility: visibility ,
578
609
args: args ,
579
610
name: name ,
580
611
summary: summary ,
@@ -590,9 +621,19 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
590
621
% {
591
622
pipe_before?: pipe_before? ,
592
623
capture_before?: capture_before? ,
593
- text_after_cursor: text_after_cursor
624
+ text_after_cursor: text_after_cursor ,
625
+ module: module
594
626
} = context
595
627
628
+ locals_without_parens = Keyword . get ( options , :locals_without_parens )
629
+ with_parens? = function_name_with_parens? ( name , arity , locals_without_parens )
630
+
631
+ trigger_signature? =
632
+ Keyword . get ( options , :signature_help_supported , false ) &&
633
+ Keyword . get ( options , :snippets_supported , false ) &&
634
+ arity > 0 &&
635
+ with_parens?
636
+
596
637
{ label , insert_text } =
597
638
cond do
598
639
match? ( "sigil_" <> _ , name ) ->
@@ -614,33 +655,48 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
614
655
Keyword . merge (
615
656
options ,
616
657
pipe_before?: pipe_before? ,
617
- capture_before?: capture_before?
658
+ capture_before?: capture_before? ,
659
+ trigger_signature?: trigger_signature? ,
660
+ locals_without_parens: locals_without_parens ,
661
+ text_after_cursor: text_after_cursor ,
662
+ with_parens?: with_parens?
618
663
)
619
664
)
620
665
621
666
{ label , insert_text }
622
667
end
623
668
624
- detail =
625
- cond do
626
- spec && spec != "" ->
627
- spec
669
+ detail_header =
670
+ if inspect ( module ) == origin do
671
+ "#{ visibility } #{ type } "
672
+ else
673
+ "#{ origin } #{ type } "
674
+ end
628
675
629
- String . starts_with? ( type , [ "private" , "public" ] ) ->
630
- String . replace ( type , "_" , " " )
676
+ footer =
677
+ if String . starts_with? ( type , [ "private" , "public" ] ) do
678
+ String . replace ( type , "_" , " " )
679
+ else
680
+ SourceFile . format_spec ( spec , line_length: 30 )
681
+ end
631
682
632
- true ->
633
- "(#{ origin } ) #{ type } "
683
+ command =
684
+ if trigger_signature? do
685
+ % {
686
+ "title" => "Trigger Parameter Hint" ,
687
+ "command" => "editor.action.triggerParameterHints"
688
+ }
634
689
end
635
690
636
691
% __MODULE__ {
637
692
label: label ,
638
693
kind: :function ,
639
- detail: detail ,
640
- documentation: summary ,
694
+ detail: detail_header <> " \n \n " <> Enum . join ( [ to_string ( name ) , "(" , args , ")" ] ) ,
695
+ documentation: summary <> footer ,
641
696
insert_text: insert_text ,
642
697
priority: 7 ,
643
- tags: metadata_to_tags ( metadata )
698
+ tags: metadata_to_tags ( metadata ) ,
699
+ command: command
644
700
}
645
701
end
646
702
@@ -678,6 +734,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
678
734
"filterText" => item . filter_text ,
679
735
"sortText" => String . pad_leading ( to_string ( idx ) , 8 , "0" ) ,
680
736
"insertText" => item . insert_text ,
737
+ "command" => item . command ,
681
738
"insertTextFormat" =>
682
739
if Keyword . get ( options , :snippets_supported , false ) do
683
740
insert_text_format ( :snippet )
@@ -724,4 +781,10 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
724
781
_ -> [ :deprecated ]
725
782
end
726
783
end
784
+
785
+ defp function_name_with_parens? ( name , arity , locals_without_parens ) do
786
+ ( locals_without_parens || MapSet . new ( ) )
787
+ |> MapSet . member? ( { String . to_atom ( name ) , arity } )
788
+ |> Kernel . not ( )
789
+ end
727
790
end
0 commit comments