Skip to content

Commit

Permalink
nifs attribute completion
Browse files Browse the repository at this point in the history
  • Loading branch information
plux committed Sep 23, 2024
1 parent 6410bf6 commit dc59a83
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 30 deletions.
2 changes: 2 additions & 0 deletions apps/els_core/src/els_poi.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
| keyword_expr
| macro
| module
| nifs
| nifs_entry
| parse_transform
| record
| record_def_field
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_code_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ goto_definition(
) when
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= export_entry
Kind =:= export_entry;
Kind =:= nifs_entry
->
%% try to find local function first
%% fall back to bif search if unsuccessful
Expand Down
4 changes: 4 additions & 0 deletions apps/els_lsp/src/els_compiler_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ make_code(erl_lint, {bad_dialyzer_option, _Term}) ->
<<"L1316">>;
make_code(erl_lint, {format_error, {_Fmt, _Args}}) ->
<<"L1317">>;
make_code(erl_lint, {undefined_nif, {_F, _A}}) ->
<<"L1318">>;
make_code(erl_link, no_load_nif) ->
<<"L1319">>;
make_code(erl_lint, _Other) ->
<<"L1399">>;
%% stdlib-3.15.2/src/erl_scan.erl
Expand Down
66 changes: 45 additions & 21 deletions apps/els_lsp/src/els_completion_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ find_completions(
%% Check for "-export(["
[{'[', _}, {'(', _}, {atom, _, export}, {'-', _}] ->
unexported_definitions(Document, function);
%% Check for "-nifs(["
[{'[', _}, {'(', _}, {atom, _, nifs}, {'-', _}] ->
definitions(Document, function, arity_only, false);
%% Check for "-export_type(["
[{'[', _}, {'(', _}, {atom, _, export_type}, {'-', _}] ->
unexported_definitions(Document, type_definition);
Expand Down Expand Up @@ -353,8 +356,18 @@ find_completions(
{ItemFormat, POIKind} = completion_context(Document, Line, Column, Tokens),
case ItemFormat of
arity_only ->
%% Only complete unexported definitions when in export
unexported_definitions(Document, POIKind);
#{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
[] ->
Expand Down Expand Up @@ -495,6 +508,7 @@ attributes(Document, Line) ->
snippet(attribute_include),
snippet(attribute_include_lib),
snippet(attribute_on_load),
snippet(attribute_nifs),
snippet(attribute_opaque),
snippet(attribute_record),
snippet(attribute_type),
Expand Down Expand Up @@ -579,6 +593,11 @@ snippet(attribute_on_load) ->
<<"-on_load().">>,
<<"on_load(${1:Function}).">>
);
snippet(attribute_nifs) ->
snippet(
<<"-nifs().">>,
<<"nifs([${1:}]).">>
);
snippet(attribute_export_type) ->
snippet(<<"-export_type().">>, <<"export_type([${1:}]).">>);
snippet(attribute_feature) ->
Expand Down Expand Up @@ -787,7 +806,7 @@ definitions(Document, POIKind, ItemFormat, ExportedOnly) ->
{item_format(), els_poi:poi_kind() | any}.
completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
ItemFormat =
case is_in_export(Document, Line, Column) of
case is_in_mfa_list_attr(Document, Line, Column) of
true ->
arity_only;
false ->
Expand All @@ -811,7 +830,7 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
true ->
type_definition;
false ->
case is_in(Document, Line, Column, [export, function]) of
case is_in(Document, Line, Column, [export, nifs, function]) of
true ->
function;
false ->
Expand All @@ -820,25 +839,30 @@ completion_context(#{text := Text} = Document, Line, Column, Tokens) ->
end,
{ItemFormat, POIKind}.

-spec is_in_export(els_dt_document:item(), line(), column()) -> boolean().
is_in_export(#{text := Text} = Document, Line, Column) ->
%% Sometimes is_in will be confused because -export() failed to be parsed.
-spec is_in_mfa_list_attr(els_dt_document:item(), line(), column()) -> boolean().
is_in_mfa_list_attr(#{text := Text} = Document, Line, Column) ->
%% Sometimes is_in will be confused because e.g. -export() failed to be parsed.
%% In such case we can use a heuristic to determine if we are inside
%% an export.
is_in(Document, Line, Column, [export, export_type]) orelse
is_in_export_heuristic(Text, Line - 1).
is_in(Document, Line, Column, [export, export_type, nifs]) orelse
is_in_mfa_list_attr_heuristic(Text, Line - 1).

-spec is_in_mfa_list_attr_heuristic(binary(), line()) -> boolean().
is_in_mfa_list_attr_heuristic(Text, Line) ->
is_in_heuristic(Text, <<"export">>, Line) orelse
is_in_heuristic(Text, <<"nifs">>, Line).

-spec is_in_export_heuristic(binary(), line()) -> boolean().
is_in_export_heuristic(Text, Line) ->
-spec is_in_heuristic(binary(), binary(), line()) -> boolean().
is_in_heuristic(Text, Attr, Line) ->
Len = byte_size(Attr),
case els_text:line(Text, Line) of
<<"-export", _/binary>> ->
%% In export
<<"-", Attr:Len/binary, _/binary>> ->
%% In Attr
true;
<<" ", _/binary>> when Line > 1 ->
%% Indented line, continue to search previous line
is_in_export_heuristic(Text, Line - 1);
is_in_heuristic(Text, Attr, Line - 1);
_ ->
%% Not in export
false
end.

Expand Down Expand Up @@ -1409,12 +1433,12 @@ is_exported_heuristic_test_() ->
"-define(FOO, foo).\n"
>>,
[
?_assertEqual(false, is_in_export_heuristic(Text, 0)),
?_assertEqual(true, is_in_export_heuristic(Text, 1)),
?_assertEqual(true, is_in_export_heuristic(Text, 2)),
?_assertEqual(true, is_in_export_heuristic(Text, 3)),
?_assertEqual(true, is_in_export_heuristic(Text, 4)),
?_assertEqual(false, is_in_export_heuristic(Text, 5))
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 0)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 1)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 2)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 3)),
?_assertEqual(true, is_in_mfa_list_attr_heuristic(Text, 4)),
?_assertEqual(false, is_in_mfa_list_attr_heuristic(Text, 5))
].

-endif.
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_crossref_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ run(Uri) ->
application,
implicit_fun,
import_entry,
export_entry
export_entry,
nifs_entry
]),
[make_diagnostic(POI) || POI <- POIs, not has_definition(POI, Document)]
end.
Expand Down
1 change: 1 addition & 0 deletions apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ docs(Uri, #{kind := Kind, id := {F, A}}) when
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= export_entry;
Kind =:= nifs_entry;
Kind =:= spec
->
M = els_uri:module(Uri),
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_document_highlight_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ kind_groups() ->
application,
implicit_fun,
function,
export_entry
export_entry,
nifs_entry
],
%% record
[
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_dt_references.erl
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ kind_to_category(Kind) when
Kind =:= function;
Kind =:= function_clause;
Kind =:= import_entry;
Kind =:= implicit_fun
Kind =:= implicit_fun;
Kind =:= nifs_entry
->
function;
kind_to_category(Kind) when
Expand Down
31 changes: 30 additions & 1 deletion apps/els_lsp/src/els_parser.erl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ ensure_dot(Tokens) ->
-spec find_attribute_tokens([erlfmt_scan:token()]) -> [els_poi:poi()].
find_attribute_tokens([{'-', Anno}, {atom, _, Name} | [_ | _] = Rest]) when
Name =:= export;
Name =:= export_type
Name =:= export_type;
Name =:= nifs
->
From = erlfmt_scan:get_anno(location, Anno),
To = erlfmt_scan:get_anno(end_location, lists:last(Rest)),
Expand Down Expand Up @@ -451,6 +452,13 @@ attribute(Tree) ->
AttrName =:= export_type
->
find_export_pois(Tree, AttrName, Arg);
{nifs, [Arg]} ->
Nifs = erl_syntax:list_elements(Arg),
NifsEntries = find_nifs_entry_pois(Nifs),
[
poi(erl_syntax:get_pos(Tree), nifs, get_start_location(Tree))
| NifsEntries
];
{import, [ModTree, ImportList]} ->
case is_atom_node(ModTree) of
{true, _} ->
Expand Down Expand Up @@ -660,6 +668,27 @@ find_export_entry_pois(EntryPoiKind, Exports) ->
]
).

-spec find_nifs_entry_pois([tree()]) ->
[els_poi:poi()].
find_nifs_entry_pois(Nifs) ->
lists:flatten(
[
case get_name_arity(FATree) of
{F, A} ->
FTree = erl_syntax:arity_qualifier_body(FATree),
poi(
erl_syntax:get_pos(FATree),
nifs_entry,
{F, A},
#{name_range => els_range:range(erl_syntax:get_pos(FTree))}
);
false ->
[]
end
|| FATree <- Nifs
]
).

-spec find_import_entry_pois(tree(), [tree()]) -> [els_poi:poi()].
find_import_entry_pois(ModTree, Imports) ->
M = erl_syntax:atom_value(ModTree),
Expand Down
1 change: 1 addition & 0 deletions apps/els_lsp/src/els_range.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ in(#{from := FromA, to := ToA}, #{from := FromB, to := ToB}) ->
els_poi:poi_range().
range({{_Line, _Column} = From, {_ToLine, _ToColumn} = To}, Name, _, _Data) when
Name =:= export;
Name =:= nifs;
Name =:= export_type;
Name =:= spec
->
Expand Down
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_references_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ find_references(Uri, #{
Kind =:= implicit_fun;
Kind =:= function;
Kind =:= export_entry;
Kind =:= export_type_entry
Kind =:= export_type_entry;
Kind =:= nifs_entry
->
Key =
case Id of
Expand Down
9 changes: 6 additions & 3 deletions apps/els_lsp/src/els_rename_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
Kind =:= export_entry;
Kind =:= import_entry;
Kind =:= export_type_entry;
Kind =:= type_application
Kind =:= type_application;
Kind =:= nifs_entry
->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, [{DefUri, DefPOI}]} ->
Expand Down Expand Up @@ -205,7 +206,8 @@ editable_range(#{kind := Kind, data := #{name_range := Range}}, function) when
Kind =:= export_type_entry;
Kind =:= import_entry;
Kind =:= type_application;
Kind =:= type_definition
Kind =:= type_definition;
Kind =:= nifs_entry
->
%% application POI of a local call and
%% type_application POI of a built-in type don't have name_range data
Expand Down Expand Up @@ -267,7 +269,8 @@ changes(Uri, #{kind := function, id := {F, A}}, NewName) ->
|| P <- els_dt_document:pois(Doc, [
export_entry,
function_clause,
spec
spec,
nifs_entry
]),
IsMatch(P)
],
Expand Down
6 changes: 6 additions & 0 deletions apps/els_lsp/test/els_completion_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ attributes(Config) ->
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
label => <<"-spec">>
},
#{
insertText => <<"nifs([${1:}]).">>,
insertTextFormat => ?INSERT_TEXT_FORMAT_SNIPPET,
kind => ?COMPLETION_ITEM_KIND_SNIPPET,
label => <<"-nifs().">>
}
] ++ docs_attributes(),
#{result := Completions} =
Expand Down

0 comments on commit dc59a83

Please sign in to comment.