Skip to content

Commit

Permalink
More completions (#1545)
Browse files Browse the repository at this point in the history
* Add better support for completing keywords

* Add support for completing map and list comprehensions

* Add support for completing type after ::

* Add test for completion of comprehensions
  • Loading branch information
plux authored Sep 25, 2024
1 parent 9c0d48e commit 89a2a4c
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 38 deletions.
9 changes: 9 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/completion_more.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-module(completion_more).

lc() ->
[
[].

mc() ->
#{
#{}.
281 changes: 244 additions & 37 deletions apps/els_lsp/src/els_completion_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ find_completions(
%% Check for "[...] #"
[{'#', _} | _] ->
definitions(Document, record);
%% Check for "#{"
[{'{', _}, {'#', _} | _] ->
[map_comprehension_completion_item(Document, Line, Column)];
%% Check for "[...] #anything"
[_, {'#', _} | _] ->
definitions(Document, record);
Expand Down Expand Up @@ -336,6 +339,9 @@ find_completions(
Attribute =:= behaviour; Attribute =:= behavior
->
[item_kind_module(Module) || Module <- behaviour_modules("")];
%% Check for "["
[{'[', _} | _] ->
[list_comprehension_completion_item(Document, Line, Column)];
%% Check for "[...] fun atom"
[{atom, _, _}, {'fun', _} | _] ->
bifs(function, ItemFormat = arity_only) ++
Expand All @@ -345,42 +351,26 @@ find_completions(
{ItemFormat, _POIKind} =
completion_context(Document, Line, Column, Tokens),
complete_type_definition(Document, Name, ItemFormat);
%% Check for "::"
[{'::', _} | _] = Tokens ->
{ItemFormat, _POIKind} =
completion_context(Document, Line, Column, Tokens),
complete_type_definition(Document, '', ItemFormat);
%% Check for ":: atom"
[{atom, _, Name}, {'::', _} | _] = Tokens ->
{ItemFormat, _POIKind} =
completion_context(Document, Line, Column, Tokens),
complete_type_definition(Document, Name, ItemFormat);
%% Check for "[...] atom"
[{atom, _, Name} | _] = Tokens ->
NameBinary = atom_to_binary(Name, utf8),
{ItemFormat, POIKind} = completion_context(Document, Line, Column, Tokens),
case ItemFormat of
arity_only ->
#{text := Text} = Document,
case
is_in(Document, Line, Column, [nifs]) orelse
is_in_heuristic(Text, <<"nifs">>, Line - 1)
of
true ->
definitions(Document, POIKind, ItemFormat, false);
_ ->
%% Only complete unexported definitions when in
%% export
unexported_definitions(Document, POIKind)
end;
_ ->
case complete_record_field(Opts, Tokens) of
[] ->
keywords(POIKind, ItemFormat) ++
bifs(POIKind, ItemFormat) ++
atoms(Document, NameBinary) ++
all_record_fields(Document, NameBinary) ++
modules(NameBinary) ++
definitions(Document, POIKind, ItemFormat) ++
snippets(POIKind, ItemFormat);
RecordFields ->
RecordFields
end
complete_atom(Name, Tokens, Opts);
%% Treat keywords as atom completion
[{Name, _} | _] = Tokens ->
case lists:member(Name, keywords()) of
true ->
complete_atom(Name, Tokens, Opts);
false ->
[]
end;
Tokens ->
?LOG_DEBUG(
Expand All @@ -392,6 +382,100 @@ find_completions(
find_completions(_Prefix, _TriggerKind, _Opts) ->
[].

-spec list_comprehension_completion_item(els_dt_document:item(), line(), column()) ->
completion_item().
list_comprehension_completion_item(#{text := Text}, Line, Column) ->
Suffix =
try els_text:get_char(Text, Line, Column + 1) of
{ok, $]} ->
%% Don't include ']' if next character is a ']'
%% I.e if cursor is at []
%% ^
<<"">>;
_ ->
<<"]">>
catch
_:_:_ ->
<<"]">>
end,
InsertText =
case snippet_support() of
true ->
<<"${3:Expr} || ${2:Elem} <- ${1:List}", Suffix/binary>>;
false ->
<<"Expr || Elem <- List", Suffix/binary>>
end,
#{
label => <<"[Expr || Elem <- List]">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText => InsertText
}.

-spec map_comprehension_completion_item(els_dt_document:item(), line(), column()) ->
completion_item().
map_comprehension_completion_item(#{text := Text}, Line, Column) ->
Suffix =
try els_text:get_char(Text, Line, Column + 1) of
{ok, $}} ->
%% Don't include '}' if next character is a '}'
%% I.e if cursor is at #{}
%% ^
<<"">>;
_ ->
<<"}">>
catch
_:_:_ ->
<<"}">>
end,
InsertText =
case snippet_support() of
true ->
<<"${4:K} => ${5:V} || ${2:K} => ${3:V} <- ${1:Map}", Suffix/binary>>;
false ->
<<"K => V || K := V <- Map", Suffix/binary>>
end,
#{
label => <<"#{K => V || K := V <- Map}">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText => InsertText
}.

-spec complete_atom(atom(), [any()], map()) -> [completion_item()].
complete_atom(Name, Tokens, Opts) ->
#{document := Document, line := Line, column := Column} = Opts,
NameBinary = atom_to_binary(Name, utf8),
{ItemFormat, POIKind} = completion_context(Document, Line, Column, Tokens),
case ItemFormat of
arity_only ->
#{text := Text} = Document,
case
is_in(Document, Line, Column, [nifs]) orelse
is_in_heuristic(Text, <<"nifs">>, Line - 1)
of
true ->
definitions(Document, POIKind, ItemFormat, false);
_ ->
%% Only complete unexported definitions when in
%% export
unexported_definitions(Document, POIKind)
end;
_ ->
case complete_record_field(Opts, Tokens) of
[] ->
keywords(POIKind, ItemFormat) ++
bifs(POIKind, ItemFormat) ++
atoms(Document, NameBinary) ++
all_record_fields(Document, NameBinary) ++
modules(NameBinary) ++
definitions(Document, POIKind, ItemFormat) ++
snippets(POIKind, ItemFormat);
RecordFields ->
RecordFields
end
end.

-spec complete_record_field(map(), list()) -> items().
complete_record_field(_Opts, [{atom, _, _}, {'=', _} | _]) ->
[];
Expand Down Expand Up @@ -999,7 +1083,15 @@ keywords(type_definition, _ItemFormat) ->
keywords(_POIKind, arity_only) ->
[];
keywords(_POIKind, _ItemFormat) ->
Keywords = [
Keywords = keywords(),
[
keyword_completion_item(K, snippet_support())
|| K <- Keywords
].

-spec keywords() -> [atom()].
keywords() ->
[
'after',
'and',
'andalso',
Expand All @@ -1015,9 +1107,11 @@ keywords(_POIKind, _ItemFormat) ->
'cond',
'div',
'end',
'else',
'fun',
'if',
'let',
'maybe',
'not',
'of',
'or',
Expand All @@ -1027,15 +1121,128 @@ keywords(_POIKind, _ItemFormat) ->
'try',
'when',
'xor'
],
[
#{
label => atom_to_binary(K, utf8),
kind => ?COMPLETION_ITEM_KIND_KEYWORD
}
|| K <- Keywords
].

-spec keyword_completion_item(_, _) -> _.
keyword_completion_item('case', true) ->
#{
label => <<"case">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"case ${1:Exprs} of\n"
" ${2:Pattern} ->\n"
" ${3:Body}\n"
"end"
>>
};
keyword_completion_item('try', true) ->
#{
label => <<"try">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"try ${1:Exprs}\n"
"catch\n"
" ${2:Class}:${3:ExceptionPattern}:${4:Stacktrace} ->\n"
" ${5:ExceptionBody}\n"
"end"
>>
};
keyword_completion_item('catch', true) ->
#{
label => <<"catch">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"catch\n"
" ${1:Class}:${2:ExceptionPattern}:${3:Stacktrace} ->\n"
" ${4:ExceptionBody}\n"
"end"
>>
};
keyword_completion_item('begin', true) ->
#{
label => <<"begin">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"begin\n"
" ${1:Body}\n"
"end"
>>
};
keyword_completion_item('maybe', true) ->
#{
label => <<"maybe">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"maybe\n"
" ${1:Body}\n"
"end"
>>
};
keyword_completion_item('after', true) ->
#{
label => <<"after">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"after\n"
" ${1:Duration} ->\n"
" ${2:Body}"
>>
};
keyword_completion_item('else', true) ->
#{
label => <<"else">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"else\n"
" ${1:Pattern} ->\n"
" ${2:Body}"
>>
};
keyword_completion_item('of', true) ->
#{
label => <<"of">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"of\n"
" ${1:Pattern} ->\n"
" ${2:Body}"
>>
};
keyword_completion_item('receive', true) ->
#{
label => <<"receive">>,
kind => ?COMPLETION_ITEM_KIND_KEYWORD,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
insertText =>
<<
"receive\n"
" ${1:Pattern} ->\n"
" ${2:Body}\n"
"end"
>>
};
keyword_completion_item(K, _SnippetSupport) ->
#{
label => atom_to_binary(K, utf8),
kind => ?COMPLETION_ITEM_KIND_KEYWORD
}.

%%==============================================================================
%% Built-in functions
%%==============================================================================
Expand Down
Loading

0 comments on commit 89a2a4c

Please sign in to comment.