Skip to content

Commit

Permalink
Add goto definition for implemented behaviour callbacks (#1463)
Browse files Browse the repository at this point in the history
* Add goto definition for implemented behaviour callbacks
* Format els_dodger with erlfmt 1.3.0
  • Loading branch information
fridayy authored Dec 20, 2023
1 parent 9aeb824 commit b2b9945
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 12 deletions.
6 changes: 3 additions & 3 deletions apps/els_core/src/els_dodger.erl
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ quickscan_form([{'-', _L}, {'if', La} | _Ts]) ->
kill_form(La);
quickscan_form([{'-', _L}, {atom, La, elif} | _Ts]) ->
kill_form(La);
quickscan_form([{'-', _L}, {atom, La, else} | _Ts]) ->
quickscan_form([{'-', _L}, {atom, La, 'else'} | _Ts]) ->
kill_form(La);
quickscan_form([{'-', _L}, {atom, La, endif} | _Ts]) ->
kill_form(La);
Expand Down Expand Up @@ -791,13 +791,13 @@ scan_form([{'-', _L}, {atom, La, elif} | Ts], Opt) ->
{atom, La, 'elif'}
| scan_macros(Ts, Opt)
];
scan_form([{'-', _L}, {atom, La, else} | Ts], Opt) ->
scan_form([{'-', _L}, {atom, La, 'else'} | Ts], Opt) ->
[
{atom, La, ?pp_form},
{'(', La},
{')', La},
{'->', La},
{atom, La, else}
{atom, La, 'else'}
| scan_macros(Ts, Opt)
];
scan_form([{'-', _L}, {atom, La, endif} | Ts], Opt) ->
Expand Down
11 changes: 9 additions & 2 deletions apps/els_lsp/src/els_code_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").

%%==============================================================================
%% Type definitions
%%==============================================================================
-type goto_definition() :: [{uri(), els_poi:poi()}].
-export_type([goto_definition/0]).

%%==============================================================================
%% API
%%==============================================================================

-spec goto_definition(uri(), els_poi:poi()) ->
{ok, [{uri(), els_poi:poi()}]} | {error, any()}.
{ok, goto_definition()} | {error, any()}.
goto_definition(
Uri,
Var = #{kind := variable}
Expand Down Expand Up @@ -133,6 +138,8 @@ goto_definition(_Uri, #{kind := parse_transform, id := Module}) ->
{ok, Uri} -> defs_to_res(find(Uri, module, Module));
{error, Error} -> {error, Error}
end;
goto_definition(Uri, #{kind := callback, id := Id}) ->
defs_to_res(find(Uri, callback, Id));
goto_definition(_Filename, _) ->
{error, not_found}.

Expand Down
66 changes: 59 additions & 7 deletions apps/els_lsp/src/els_definition_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,35 @@ handle_request({definition, Params}) ->
-spec goto_definition(uri(), [els_poi:poi()]) -> [map()] | null.
goto_definition(_Uri, []) ->
null;
goto_definition(Uri, [#{id := FunId, kind := function} = POI | Rest]) ->
{ok, Document} = els_utils:lookup_document(Uri),
BehaviourPOIs = els_dt_document:pois(Document, [behaviour]),
case BehaviourPOIs of
[] ->
%% cursor is not over a function - continue
case els_code_navigation:goto_definition(Uri, POI) of
{ok, Definitions} ->
goto_definitions_to_goto(Definitions);
_ ->
goto_definition(Uri, Rest)
end;
Behaviours ->
case does_implement_behaviour(FunId, Behaviours) of
false ->
%% no matching callback for this behaviour so proceed
goto_definition(Uri, Rest);
{true, BehaviourModuleUri, MatchingCallback} ->
{ok, Definitions} = els_code_navigation:goto_definition(
BehaviourModuleUri,
MatchingCallback
),
goto_definitions_to_goto(Definitions)
end
end;
goto_definition(Uri, [POI | Rest]) ->
case els_code_navigation:goto_definition(Uri, POI) of
{ok, Definitions} ->
lists:map(
fun({DefUri, DefPOI}) ->
#{range := Range} = DefPOI,
#{uri => DefUri, range => els_protocol:range(Range)}
end,
Definitions
);
goto_definitions_to_goto(Definitions);
_ ->
goto_definition(Uri, Rest)
end.
Expand Down Expand Up @@ -98,6 +117,39 @@ fix_line_offset(
}
}.

-spec goto_definitions_to_goto(Definitions) -> Result when
Definitions :: els_code_navigation:goto_definition(),
Result :: [map()].
goto_definitions_to_goto(Definitions) ->
lists:map(
fun({DefUri, DefPOI}) ->
#{range := Range} = DefPOI,
#{uri => DefUri, range => els_protocol:range(Range)}
end,
Definitions
).

-spec does_implement_behaviour(any(), list()) -> {true, uri(), els_poi:poi()} | false.
does_implement_behaviour(_, []) ->
false;
does_implement_behaviour(FunId, [#{id := ModuleId, kind := behaviour} | Rest]) ->
{ok, BehaviourModuleUri} = els_utils:find_module(ModuleId),
{ok, BehaviourModuleDocument} = els_utils:lookup_document(BehaviourModuleUri),
DefinedCallbacks = els_dt_document:pois(
BehaviourModuleDocument,
[callback]
),
MaybeMatchingCallback = lists:filter(
fun(#{id := CallbackId}) ->
CallbackId =:= FunId
end,
DefinedCallbacks
),
case MaybeMatchingCallback of
[] -> does_implement_behaviour(FunId, Rest);
[H | _] -> {true, BehaviourModuleUri, H}
end.

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
fix_line_offset_test() ->
Expand Down
13 changes: 13 additions & 0 deletions apps/els_lsp/test/els_definition_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
application_remote/1,
atom/1,
behaviour/1,
behaviour_callback_definition/1,
definition_after_closing/1,
duplicate_definition/1,
export_entry/1,
Expand Down Expand Up @@ -179,6 +180,18 @@ behaviour(Config) ->
),
ok.

-spec behaviour_callback_definition(config()) -> ok.
behaviour_callback_definition(Config) ->
Uri = ?config(code_navigation_uri, Config),
Def = els_client:definition(Uri, 28, 5),
#{result := [#{range := Range, uri := DefUri}]} = Def,
?assertEqual(?config(behaviour_a_uri, Config), DefUri),
?assertEqual(
els_protocol:range(#{from => {3, 1}, to => {3, 30}}),
Range
),
ok.

-spec testcase(config()) -> ok.
testcase(Config) ->
Uri = ?config(sample_SUITE_uri, Config),
Expand Down

0 comments on commit b2b9945

Please sign in to comment.