Skip to content

Commit

Permalink
Use info from -spec for inlay hints if available. (#1519)
Browse files Browse the repository at this point in the history
  • Loading branch information
plux authored May 6, 2024
1 parent 46b80b5 commit 4b497a4
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 97 deletions.
27 changes: 21 additions & 6 deletions apps/els_lsp/priv/code_navigation/src/inlay_hint.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,34 @@ test() ->
b(1, 2),
c(1),
d(1, 2),
e(1, 2),
f(1, 2),
g(1, 2),
lists:append([], []).

a(Hej, Hoj) ->
Hej + Hoj.
a(A1, A2) ->
A1 + A2.

b(x, y) ->
0;
b(A, _B) ->
A.
b(B1, _B2) ->
B1.

c(#foo{}) ->
ok.

d([1,2,3] = Foo,
Bar = #{hej := 123}) ->
d([1,2,3] = D1,
D2 = #{hej := 123}) ->
ok.

-spec e(E1 :: any(), E2 :: any()) -> ok.
e(_, _) ->
ok.

-spec f(F1, F2) -> ok when F1 :: any(), F2 :: any().
f(_, _) ->
ok.

-spec g(G1, any()) -> ok when G1 :: any().
g(_, G2) ->
ok.
31 changes: 30 additions & 1 deletion apps/els_lsp/src/els_arg.erl
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
-module(els_arg).
-export([new/2]).
-export([name/1]).
-export([name/2]).
-export([index/1]).
-export([merge_args/2]).

-export_type([arg/0]).
-export_type([args/0]).

-type args() :: [arg()].
-type arg() :: #{
index := pos_integer(),
name := string() | undefined,
name := string() | undefined | {type, string() | undefined},
range => els_poi:poi_range()
}.

-spec new(pos_integer(), string()) -> arg().
new(Index, Name) ->
#{index => Index, name => Name}.

-spec name(arg()) -> string().
name(Arg) ->
name("Arg", Arg).

-spec name(string(), arg()) -> string().
name(Prefix, #{index := N, name := undefined}) ->
Prefix ++ integer_to_list(N);
name(_Prefix, #{name := {type, Name}}) ->
Name;
name(_Prefix, #{name := Name}) ->
Name.

-spec index(arg()) -> string().
index(#{index := Index}) ->
integer_to_list(Index).

-spec merge_args(args(), args()) -> args().
merge_args([], []) ->
[];
merge_args([#{name := undefined} | T1], [Arg | T2]) ->
[Arg | merge_args(T1, T2)];
merge_args(
[#{name := {type, Name}} = Arg | T1],
[#{name := undefined} | T2]
) ->
[Arg#{name := Name} | merge_args(T1, T2)];
merge_args(
[#{name := {type, _}} | T1],
[Arg | T2]
) ->
[Arg | merge_args(T1, T2)];
merge_args([Arg | T1], [_ | T2]) ->
[Arg | merge_args(T1, T2)].
22 changes: 7 additions & 15 deletions apps/els_lsp/src/els_completion_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1039,10 +1039,10 @@ bif_pois(define) ->
|| {Id, Args} <- Macros
].

-spec generate_arguments(string(), integer()) -> els_parser:args().
-spec generate_arguments(string(), integer()) -> els_arg:args().
generate_arguments(Prefix, Arity) ->
[
#{index => N, name => Prefix ++ integer_to_list(N)}
els_arg:new(N, Prefix ++ integer_to_list(N))
|| N <- lists:seq(1, Arity)
].

Expand Down Expand Up @@ -1134,7 +1134,7 @@ completion_item(#{kind := Kind = define, id := Name, data := Info}, Data, _, _Ur
data => Data
}.

-spec args(els_poi:poi(), uri()) -> els_parser:args().
-spec args(els_poi:poi(), uri()) -> els_arg:args().
args(#{kind := type_definition, data := POIData}, _Uri) ->
maps:get(args, POIData);
args(#{kind := _Kind, data := POIData}, _Uri = undefined) ->
Expand All @@ -1145,19 +1145,11 @@ args(#{kind := function, data := POIData, id := Id}, Uri) ->
POIs = els_dt_document:pois(Document, [spec]),
case [P || #{id := SpecId} = P <- POIs, SpecId == Id] of
[#{data := #{args := SpecArgs}} | _] when SpecArgs /= [] ->
merge_args(SpecArgs, maps:get(args, POIData));
els_arg:merge_args(SpecArgs, maps:get(args, POIData));
_ ->
maps:get(args, POIData)
end.

-spec merge_args(els_parser:args(), els_parser:args()) -> els_parser:args().
merge_args([], []) ->
[];
merge_args([#{name := undefined} | T1], [Arg | T2]) ->
[Arg | merge_args(T1, T2)];
merge_args([Arg | T1], [_ | T2]) ->
[Arg | merge_args(T1, T2)].

-spec features() -> items().
features() ->
%% Hardcoded for now. Could use erl_features:all() in the future.
Expand All @@ -1179,13 +1171,13 @@ macro_label({Name, Arity}) ->
macro_label(Name) ->
atom_to_binary(Name, utf8).

-spec format_function(atom(), els_parser:args(), boolean(), els_poi:poi_kind()) -> binary().
-spec format_function(atom(), els_arg:args(), boolean(), els_poi:poi_kind()) -> binary().
format_function(Name, Args, SnippetSupport, Kind) ->
format_args(atom_to_label(Name), Args, SnippetSupport, Kind).

-spec format_macro(
atom() | {atom(), non_neg_integer()},
els_parser:args(),
els_arg:args(),
boolean()
) -> binary().
format_macro({Name0, _Arity}, Args, SnippetSupport) ->
Expand All @@ -1196,7 +1188,7 @@ format_macro(Name, none, _SnippetSupport) ->

-spec format_args(
binary(),
els_parser:args(),
els_arg:args(),
boolean(),
els_poi:poi_kind()
) -> binary().
Expand Down
2 changes: 1 addition & 1 deletion apps/els_lsp/src/els_docs.erl
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ format_edoc(Desc) when is_map(Desc) ->
FormattedDoc = els_utils:to_list(docsh_edoc:format_edoc(Doc, #{})),
[{text, FormattedDoc}].

-spec macro_signature(els_poi:poi_id(), els_parser:args()) -> unicode:charlist().
-spec macro_signature(els_poi:poi_id(), els_arg:args()) -> unicode:charlist().
macro_signature({Name, _Arity}, Args) ->
[atom_to_list(Name), "(", lists:join(", ", [els_arg:name(A) || A <- Args]), ")"];
macro_signature(Name, none) ->
Expand Down
90 changes: 65 additions & 25 deletions apps/els_lsp/src/els_inlay_hint_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,37 @@ get_inlay_hints({Uri, Range}, _) ->
%% Wait for indexing job to finish, so that we have the updated document
wait_for_indexing_job(Uri),
%% Read the document to get the latest version
TS = erlang:timestamp(),
{ok, [Document]} = els_dt_document:lookup(Uri),
%% Fetch all application POIs that are in the given range
AppPOIs = els_dt_document:pois_in_range(Document, [application], Range),
[
arg_hint(ArgRange, ArgName)
|| #{data := #{args := CallArgs}} = POI <- AppPOIs,
#{index := N, name := Name, range := ArgRange} <- CallArgs,
#{data := #{args := DefArgs}} <- [definition(Uri, POI)],
ArgName <- [arg_name(N, DefArgs)],
should_show_arg_hint(Name, ArgName)
].
Res = lists:flatmap(fun(POI) -> arg_hints(Uri, POI) end, AppPOIs),
?LOG_DEBUG(
"Inlay hints took ~p ms",
[timer:now_diff(erlang:timestamp(), TS) div 1000]
),
Res.

-spec arg_hints(uri(), els_poi:poi()) -> [inlay_hint()].
arg_hints(Uri, #{kind := application, data := #{args := CallArgs}} = POI) ->
lists:flatmap(
fun(#{index := N, range := ArgRange, name := Name}) ->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, [{DefUri, DefPOI} | _]} ->
DefArgs = get_args(DefUri, DefPOI),
DefArgName = arg_name(N, DefArgs),
case should_show_arg_hint(Name, DefArgName) of
true ->
[arg_hint(ArgRange, DefArgName)];
false ->
[]
end;
{error, _} ->
[]
end
end,
CallArgs
).

-spec arg_hint(els_poi:poi_range(), string()) -> inlay_hint().
arg_hint(#{from := {FromL, FromC}}, ArgName) ->
Expand All @@ -75,18 +95,27 @@ arg_hint(#{from := {FromL, FromC}}, ArgName) ->
kind => ?INLAY_HINT_KIND_PARAMETER
}.

-spec should_show_arg_hint(string() | undefined, string() | undefined) ->
-spec should_show_arg_hint(
string() | undefined,
string() | undefined
) ->
boolean().
should_show_arg_hint(Name, Name) ->
false;
should_show_arg_hint(_Name, undefined) ->
false;
should_show_arg_hint(_Name, _DefArgName) ->
true.
should_show_arg_hint(undefined, _Name) ->
true;
should_show_arg_hint(Name, DefArgName) ->
strip_trailing_digits(Name) /= strip_trailing_digits(DefArgName).

-spec strip_trailing_digits(string()) -> string().
strip_trailing_digits(String) ->
string:trim(String, trailing, "0123456789").

-spec wait_for_indexing_job(uri()) -> ok.
wait_for_indexing_job(Uri) ->
%% Add delay to allowing indexing job to finish
%% Add delay to allowing indexing job to start
timer:sleep(10),
JobTitles = els_background_job:list_titles(),
case lists:member(<<"Indexing ", Uri/binary>>, JobTitles) of
Expand All @@ -98,22 +127,33 @@ wait_for_indexing_job(Uri) ->
wait_for_indexing_job(Uri)
end.

-spec arg_name(non_neg_integer(), els_parser:args()) -> string() | undefined.
-spec arg_name(non_neg_integer(), els_arg:args()) -> string() | undefined.
arg_name(_N, []) ->
undefined;
arg_name(N, Args) ->
#{name := Name0} = lists:nth(N, Args),
case Name0 of
"_" ++ Name ->
case lists:nth(N, Args) of
#{name := "_" ++ Name} ->
Name;
Name ->
#{name := Name} ->
Name
end.

-spec definition(uri(), els_poi:poi()) -> els_poi:poi() | error.
definition(Uri, POI) ->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, [{_Uri, DefPOI} | _]} ->
DefPOI;
Err ->
?LOG_INFO("Error: ~p ~p", [Err, POI]),
error
-spec get_args(uri(), els_poi:poi()) -> els_arg:args().
get_args(Uri, #{
id := {F, A},
data := #{args := Args}
}) ->
{ok, Document} = els_utils:lookup_document(Uri),
SpecPOIs = els_dt_document:pois(Document, [spec]),
SpecMatches = [
SpecArgs
|| #{id := Id, data := #{args := SpecArgs}} <- SpecPOIs,
Id == {F, A},
SpecArgs /= []
],
case SpecMatches of
[] ->
Args;
[SpecArgs | _] ->
els_arg:merge_args(SpecArgs, Args)
end.
Loading

0 comments on commit 4b497a4

Please sign in to comment.