Skip to content

Commit

Permalink
Add support for inlay hints (#1504)
Browse files Browse the repository at this point in the history
Argument names in function calls will now show up as inlay hints.

Inlay hints are disabled by default, enable by adding this to config:

    inlay_hints_enabled: true

Additional changes:
* Add functional API to submit job results to els_server
* Improve completions by extracting argument names from spec.
* Improve completions by extracting argument names from all function clauses.
* Gracefully handle parsing of missing header file
  • Loading branch information
plux authored Apr 22, 2024
1 parent a0bf4fe commit 0543c32
Show file tree
Hide file tree
Showing 23 changed files with 659 additions and 110 deletions.
29 changes: 29 additions & 0 deletions apps/els_core/include/els_core.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,35 @@
command => els_command:command()
}.

%%------------------------------------------------------------------------------
%% Inlay hint
%%------------------------------------------------------------------------------
-define(INLAY_HINT_KIND_TYPE, 1).
-define(INLAY_HINT_KIND_PARAMETER, 2).

-type inlay_hint_kind() ::
?INLAY_HINT_KIND_TYPE
| ?INLAY_HINT_KIND_PARAMETER.

-type inlay_hint_label_part() ::
#{
value := binary(),
tooltip => binary() | markup_content(),
location => location(),
command => els_command:command()
}.

-type inlay_hint() :: #{
position := position(),
label := binary() | [inlay_hint_label_part()],
kind => inlay_hint_kind(),
textEdits => [text_edit()],
tooltip => binary() | markup_content(),
paddingLeft => boolean(),
paddingRight => boolean(),
data => map()
}.

%%------------------------------------------------------------------------------
%% Workspace
%%------------------------------------------------------------------------------
Expand Down
13 changes: 12 additions & 1 deletion apps/els_core/src/els_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
preparecallhierarchy/3,
callhierarchy_incomingcalls/1,
callhierarchy_outgoingcalls/1,
get_notifications/0
get_notifications/0,
inlay_hint/2
]).

-export([handle_responses/1]).
Expand Down Expand Up @@ -276,6 +277,10 @@ get_notifications() ->
handle_responses(Responses) ->
gen_server:cast(?SERVER, {handle_responses, Responses}).

-spec inlay_hint(uri(), range()) -> ok.
inlay_hint(Uri, Range) ->
gen_server:call(?SERVER, {inlay_hint, {Uri, Range}}).

%%==============================================================================
%% gen_server Callback Functions
%%==============================================================================
Expand Down Expand Up @@ -462,6 +467,7 @@ method_lookup(implementation) -> <<"textDocument/implementation">>;
method_lookup(folding_range) -> <<"textDocument/foldingRange">>;
method_lookup(semantic_tokens) -> <<"textDocument/semanticTokens/full">>;
method_lookup(preparecallhierarchy) -> <<"textDocument/prepareCallHierarchy">>;
method_lookup(inlay_hint) -> <<"textDocument/inlayHint">>;
method_lookup(callhierarchy_incomingcalls) -> <<"callHierarchy/incomingCalls">>;
method_lookup(callhierarchy_outgoingcalls) -> <<"callHierarchy/outgoingCalls">>;
method_lookup(workspace_symbol) -> <<"workspace/symbol">>;
Expand All @@ -476,6 +482,11 @@ request_params({document_symbol, {Uri}}) ->
#{textDocument => TextDocument};
request_params({workspace_symbol, {Query}}) ->
#{query => Query};
request_params({inlay_hint, {Uri, Range}}) ->
#{
textDocument => #{uri => Uri},
range => Range
};
request_params({workspace_executecommand, {Command, Args}}) ->
#{
command => Command,
Expand Down
2 changes: 2 additions & 0 deletions apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
RefactorErl = maps:get("refactorerl", Config, notconfigured),
Providers = maps:get("providers", Config, #{}),
EdocParseEnabled = maps:get("edoc_parse_enabled", Config, true),
InlayHintsEnabled = maps:get("inlay_hints_enabled", Config, false),
Formatting = maps:get("formatting", Config, #{}),
DocsMemo = maps:get("docs_memo", Config, false),

Expand Down Expand Up @@ -248,6 +249,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(edoc_custom_tags, EDocCustomTags),
ok = set(edoc_parse_enabled, EdocParseEnabled),
ok = set(incremental_sync, IncrementalSync),
ok = set(inlay_hints_enabled, InlayHintsEnabled),
ok = set(
indexing,
maps:merge(
Expand Down
26 changes: 26 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/inlay_hint.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-module(inlay_hint).
-export([test/0]).

-record(foo, {}).

test() ->
a(1, 2),
b(1, 2),
c(1),
d(1, 2),
lists:append([], []).

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

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

c(#foo{}) ->
ok.

d([1,2,3] = Foo,
Bar = #{hej := 123}) ->
ok.
25 changes: 25 additions & 0 deletions apps/els_lsp/src/els_arg.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-module(els_arg).
-export([name/1]).
-export([name/2]).
-export([index/1]).
-export_type([arg/0]).

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

-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 := Name}) ->
Name.

-spec index(arg()) -> string().
index(#{index := Index}) ->
integer_to_list(Index).
23 changes: 23 additions & 0 deletions apps/els_lsp/src/els_background_job.erl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
-export([
new/1,
list/0,
list_titles/0,
stop/1,
stop_all/0
]).
Expand Down Expand Up @@ -82,6 +83,22 @@ list() ->
supervisor:which_children(els_background_job_sup)
].

%% @doc Return the list of running background jobs
-spec list_titles() -> [any()].
list_titles() ->
Children = supervisor:which_children(els_background_job_sup),
lists:flatmap(
fun({_Id, Pid, _Type, _Modules}) ->
case catch gen_server:call(Pid, get_title) of
{ok, Title} ->
[Title];
_ ->
[]
end
end,
Children
).

%% @doc Terminate a background job
-spec stop(pid()) -> ok.
stop(Pid) ->
Expand Down Expand Up @@ -145,6 +162,12 @@ init(#{entries := Entries, title := Title} = Config) ->

-spec handle_call(any(), {pid(), any()}, state()) ->
{noreply, state()}.
handle_call(
get_title,
_From,
#{config := #{title := Title}} = State
) ->
{reply, {ok, Title}, State};
handle_call(_Request, _From, State) ->
{noreply, State}.

Expand Down
2 changes: 1 addition & 1 deletion apps/els_lsp/src/els_code_actions.erl
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ format_args(Document, Arity, Range) ->
],
case Matches of
[#{data := #{args := Args0}} | _] ->
string:join([A || {_N, A} <- Args0], ", ");
string:join([els_arg:name(A) || A <- Args0], ", ");
[] ->
string:join(lists:duplicate(Arity, "_"), ", ")
end.
Expand Down
6 changes: 1 addition & 5 deletions apps/els_lsp/src/els_code_lens_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@ run_lenses_job(Uri) ->
end,
entries => [Document],
title => <<"Lenses">>,
on_complete =>
fun(Lenses) ->
els_server ! {result, Lenses, self()},
ok
end
on_complete => fun els_server:register_result/1
},
{ok, Pid} = els_background_job:new(Config),
Pid.
24 changes: 17 additions & 7 deletions apps/els_lsp/src/els_compiler_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,30 @@ parse(Uri) ->
_ ->
undefined
end,
{ok, Epp} = epp:open([
Options = [
{name, FileName},
{includes, els_config:get(include_paths)},
{macros, [
{'MODULE', dummy_module, redefine},
{'MODULE_STRING', "dummy_module", redefine}
]}
]),
Res = [
epp_diagnostic(Document, Anno, Module, Desc)
|| {error, {Anno, Module, Desc}} <- epp:parse_file(Epp)
],
epp:close(Epp),
Res.
case epp:open(Options) of
{ok, Epp} ->
Res = [
epp_diagnostic(Document, Anno, Module, Desc)
|| {error, {Anno, Module, Desc}} <- epp:parse_file(Epp)
],
epp:close(Epp),
Res;
{error, Reason} ->
?LOG_ERROR(
"Failed to open: ~s\n"
"Reason: ~p",
[FileName, Reason]
),
[]
end.

%% Possible cases to handle
%% ,{error,{19,erl_parse,["syntax error before: ","'-'"]}}
Expand Down
Loading

0 comments on commit 0543c32

Please sign in to comment.