diff --git a/apps/els_core/src/els_dodger.erl b/apps/els_core/src/els_dodger.erl index 3277ecdc6..a9cd3fded 100644 --- a/apps/els_core/src/els_dodger.erl +++ b/apps/els_core/src/els_dodger.erl @@ -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); @@ -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) -> diff --git a/apps/els_lsp/src/els_code_navigation.erl b/apps/els_lsp/src/els_code_navigation.erl index 86e8630c4..65e3a5442 100644 --- a/apps/els_lsp/src/els_code_navigation.erl +++ b/apps/els_lsp/src/els_code_navigation.erl @@ -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} @@ -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}. diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl index 2a1855c2c..060cc582e 100644 --- a/apps/els_lsp/src/els_definition_provider.erl +++ b/apps/els_lsp/src/els_definition_provider.erl @@ -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. @@ -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() -> diff --git a/apps/els_lsp/test/els_definition_SUITE.erl b/apps/els_lsp/test/els_definition_SUITE.erl index dcc14f568..7ee5e95a7 100644 --- a/apps/els_lsp/test/els_definition_SUITE.erl +++ b/apps/els_lsp/test/els_definition_SUITE.erl @@ -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, @@ -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),