From aaef692ae55a198f4172e0471e9cf596775d4fbb Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Fri, 26 Jul 2019 16:23:34 +0200 Subject: [PATCH] [#22] Update navigation to function definitions --- src/erlang_ls_buffer.erl | 26 ++++------ src/erlang_ls_compiler_diagnostics.erl | 4 +- src/erlang_ls_dialyzer_diagnostics.erl | 4 +- src/erlang_ls_indexer.erl | 9 +--- src/erlang_ls_navigation.erl | 67 -------------------------- src/erlang_ls_parser.erl | 14 ++++++ src/erlang_ls_protocol.erl | 16 ++---- src/erlang_ls_server.erl | 55 +++++++++------------ src/erlang_ls_uri.erl | 8 ++- 9 files changed, 67 insertions(+), 136 deletions(-) delete mode 100644 src/erlang_ls_navigation.erl diff --git a/src/erlang_ls_buffer.erl b/src/erlang_ls_buffer.erl index d037c220f..21f64e82b 100644 --- a/src/erlang_ls_buffer.erl +++ b/src/erlang_ls_buffer.erl @@ -37,7 +37,7 @@ %%============================================================================== %% Type Definitions %%============================================================================== --type state() :: #state{}. +-type state() :: #state{}. %%%=================================================================== %%% API @@ -140,22 +140,14 @@ do_get_mfa(Text, Line, _Character) -> {M, F, A}. -spec do_get_element_at_pos(binary(), non_neg_integer(), non_neg_integer()) -> - any(). -do_get_element_at_pos(Text, Line, Character) -> - Ast = get_ast(Text), - erlang_ls_navigation:find_by_pos({Line, Character}, Ast). - --spec get_ast(binary()) -> [any()]. -get_ast(Text) -> - %% TODO: Do not write to file - File = "/tmp/x.erl", - ok = file:write_file(File, Text), - {ok, F} = file:open(File, [read]), - {ok, E} = epp:open(File, F, {1, 1}, [], []), - Epp = epp:parse_file(E), - ok = epp:close(E), - ok = file:close(F), - Epp. + erlang_ls_parser:poi(). +do_get_element_at_pos(Text, Line, Column) -> + %% TODO: Cache tree + {ok, Tree} = erlang_ls_parser:parse(Text), + AnnotatedTree = erlang_ls_parser:annotate(Tree), + %% TODO: Refine API to handle multiple, overlapping, POIs + [POI] = erlang_ls_parser:find_poi_by_pos(AnnotatedTree, {Line, Column}), + POI. -spec get_line_text(binary(), integer()) -> binary(). get_line_text(Text, Line) -> diff --git a/src/erlang_ls_compiler_diagnostics.erl b/src/erlang_ls_compiler_diagnostics.erl index 2ed07e9ac..71ae43e58 100644 --- a/src/erlang_ls_compiler_diagnostics.erl +++ b/src/erlang_ls_compiler_diagnostics.erl @@ -53,7 +53,9 @@ diagnostics(List, Severity) -> -spec diagnostic(integer(), module(), string(), integer()) -> diagnostic(). diagnostic(Line, Module, Desc, Severity) -> - Range = erlang_ls_protocol:range(Line - 1), + Range = erlang_ls_protocol:range(#{ from => {Line - 1, 0} + , to => {Line - 1, 0} + }), Message = list_to_binary(lists:flatten(Module:format_error(Desc))), #{ range => Range , message => Message diff --git a/src/erlang_ls_dialyzer_diagnostics.erl b/src/erlang_ls_dialyzer_diagnostics.erl index 7b25f6f0c..6db0abf32 100644 --- a/src/erlang_ls_dialyzer_diagnostics.erl +++ b/src/erlang_ls_dialyzer_diagnostics.erl @@ -35,7 +35,9 @@ diagnostics(Uri) -> %%============================================================================== -spec diagnostic({any(), {any(), integer()}, any()}) -> diagnostic(). diagnostic({_, {_, Line}, _} = Warning) -> - Range = erlang_ls_protocol:range(Line - 1), + Range = erlang_ls_protocol:range(#{ from => {Line - 1, 0} + , to => {Line - 1, 0} + }), Message = list_to_binary(lists:flatten(dialyzer:format_warning(Warning))), #{ range => Range , message => Message diff --git a/src/erlang_ls_indexer.erl b/src/erlang_ls_indexer.erl index 62b4551c1..cae6eeaa7 100644 --- a/src/erlang_ls_indexer.erl +++ b/src/erlang_ls_indexer.erl @@ -91,14 +91,9 @@ handle_cast(_Msg, State) -> {noreply, State}. %%============================================================================== -spec do_index(uri(), binary()) -> ok. do_index(Uri, Text) -> - %% TODO: Avoid writing - Path = "/tmp/erlang_ls_tmp", - ok = file:write_file(Path, Text), - {ok, IoDevice} = file:open(Path, [read]), - {ok, Forms} = epp_dodger:parse(IoDevice, {1, 1}), + {ok, Tree} = erlang_ls_parser:parse(Text), F = fun(Form) -> index_form(Form, Uri) end, - erl_syntax_lib:map(F, erl_syntax:form_list(Forms)), - ok = file:close(IoDevice), + erl_syntax_lib:map(F, Tree), ok. -spec index_form(erl_syntax:syntaxTree(), uri()) -> erl_syntax:syntaxTree(). diff --git a/src/erlang_ls_navigation.erl b/src/erlang_ls_navigation.erl deleted file mode 100644 index 5b601c955..000000000 --- a/src/erlang_ls_navigation.erl +++ /dev/null @@ -1,67 +0,0 @@ --module(erlang_ls_navigation). - --export([find_by_pos/2]). - --include("erlang_ls.hrl"). - --spec find_by_pos( pos() - , erl_syntax:syntaxTree() | [erl_syntax:syntaxTree()] - ) -> any(). -find_by_pos(Pos, Tree) -> - AnnotatedTree = postorder_update(fun annotate_with_range/1, erl_syntax:form_list(Tree)), - {_Pos, Found} = erl_syntax_lib:fold(fun do_find_by_pos/2, {Pos, undefined}, AnnotatedTree), - Found. - --spec do_find_by_pos(erl_syntax:syntaxTree(), {integer(), boolean()}) -> - {integer(), boolean()}. -do_find_by_pos(Tree, {Pos, Found}) -> - case Found of - undefined -> - case in_range(Pos, Tree) of - true -> {Pos, Tree}; - false -> {Pos, Found} - end; - _ -> - {Pos, Found} - end. - --spec in_range(integer(), erl_syntax:syntaxTree()) -> boolean(). -in_range(Pos, Tree) -> - Ann = erl_syntax:get_ann(Tree), - case lists:keyfind(range, 1, Ann) of - false -> - false; - {range, Start, End} -> - (Start =< Pos) andalso (Pos =< End) - end. - --spec annotate_with_range(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). -annotate_with_range(Node) -> - case erl_syntax:type(Node) of - application -> - Op = erl_syntax:application_operator(Node), - case erl_syntax:type(Op) of - module_qualifier -> - M = erl_syntax:module_qualifier_argument(Op), - F = erl_syntax:module_qualifier_body(Op), - Start = erl_syntax:get_pos(M), - {Line, Column} = erl_syntax:get_pos(F), - End = {Line, Column + length(erl_syntax:atom_name(F))}, - erl_syntax:add_ann({range, Start, End}, Node); - _ -> - Node - end; - _ -> - Node - end. - --spec postorder_update(fun(), erl_syntax:syntaxTree()) -> - erl_syntax:syntaxTree(). -postorder_update(F, Tree) -> - F(case erl_syntax:subtrees(Tree) of - [] -> Tree; - List -> erl_syntax:update_tree(Tree, - [[postorder_update(F, Subtree) - || Subtree <- Group] - || Group <- List]) - end). diff --git a/src/erlang_ls_parser.erl b/src/erlang_ls_parser.erl index ae471d7b6..88e273c3e 100644 --- a/src/erlang_ls_parser.erl +++ b/src/erlang_ls_parser.erl @@ -3,6 +3,7 @@ -export([ annotate/1 , annotate_node/1 + , find_poi_by_info/2 , find_poi_by_pos/2 , list_poi/1 , parse/1 @@ -18,6 +19,8 @@ %% Point of Interest -type poi() :: #{ type := atom(), info => any(), range := range()}. +-export_type([ poi/0 ]). + %% TODO: Generate random filename %% TODO: Ideally avoid writing to file at all (require epp changes) -define(TMP_PATH, "/tmp/erlang_ls_tmp"). @@ -83,6 +86,10 @@ get_range(_Tree, {_Line, _Column}, {exports_entry, {_F, _A}}) -> %% TODO: The location information for the arity qualifiers are lost during %% parsing in `epp_dodger`. This requires fixing. #{ from => {0, 0}, to => {0, 0} }; +get_range(_Tree, {Line, Column}, {function, {F, _A}}) -> + From = {Line - 1, Column - 1}, + To = {Line - 1, Column + length(atom_to_list(F)) - 1}, + #{ from => From, to => To }; get_range(_Tree, {Line, Column}, {include, Include}) -> From = {Line, Column - 1}, To = {Line, Column + length("include") + length(Include)}, @@ -104,6 +111,10 @@ get_range(_Tree, {_Line, _Column}, {spec, _Spec}) -> %% parsing in `epp_dodger`. This requires fixing. #{ from => {0, 0}, to => {0, 0} }. +-spec find_poi_by_info(syntax_tree(), any()) -> poi(). +find_poi_by_info(Tree, Info0) -> + [POI || #{info := Info} = POI <- list_poi(Tree), Info0 =:= Info]. + -spec find_poi_by_pos(syntax_tree(), pos()) -> [poi()]. find_poi_by_pos(Tree, Pos) -> [POI || #{range := Range} = POI <- list_poi(Tree), matches_pos(Pos, Range)]. @@ -192,6 +203,9 @@ analyze(Tree, attribute) -> _ -> [] end; +analyze(Tree, function) -> + {F, A} = erl_syntax_lib:analyze_function(Tree), + [poi(Tree, {function, {F, A}})]; analyze(Tree, macro) -> Macro = erl_syntax:variable_name(erl_syntax:macro_name(Tree)), [poi(Tree, {macro, Macro})]; diff --git a/src/erlang_ls_protocol.erl b/src/erlang_ls_protocol.erl index f8a5e3eca..ca4a64f46 100644 --- a/src/erlang_ls_protocol.erl +++ b/src/erlang_ls_protocol.erl @@ -13,9 +13,7 @@ ]). %% Data Structures --export([ range/1 - , range/2 - ]). +-export([ range/1 ]). %%============================================================================== %% Includes @@ -54,14 +52,10 @@ response(RequestId, Result) -> %%============================================================================== %% Data Structures %%============================================================================== --spec range(integer()) -> range(). -range(FromLine) -> - range(FromLine, FromLine). - --spec range(integer(), integer()) -> range(). -range(FromLine, ToLine) -> - #{ start => #{line => FromLine, character => 0} - , 'end' => #{line => ToLine, character => 0} +-spec range(erlang_ls_parser:range()) -> range(). +range(#{ from := {FromL, FromC}, to := {ToL, ToC} }) -> + #{ start => #{line => FromL, character => FromC} + , 'end' => #{line => ToL, character => ToC} }. %%============================================================================== diff --git a/src/erlang_ls_server.erl b/src/erlang_ls_server.erl index e5dd84fd7..5de941cc3 100644 --- a/src/erlang_ls_server.erl +++ b/src/erlang_ls_server.erl @@ -36,6 +36,8 @@ %%============================================================================== -record(state, {socket, buffer}). +-define(OTP_INCLUDE_PATH, "/usr/local/Cellar/erlang/21.2.4/lib/erlang/lib"). + %%============================================================================== %% Type Definitions %%============================================================================== @@ -175,8 +177,8 @@ handle_method(<<"textDocument/definition">>, Params) -> TextDocument = maps:get(<<"textDocument">>, Params), Uri = maps:get(<<"uri">> , TextDocument), {ok, Buffer} = erlang_ls_buffer_server:get_buffer(Uri), - Element = erlang_ls_buffer:get_element_at_pos(Buffer, Line + 1, Character + 1), - Result = definition(Element), + POI = erlang_ls_buffer:get_element_at_pos(Buffer, Line + 1, Character + 1), + Result = definition(Uri, POI), {response, Result}; handle_method(Method, _Params) -> lager:warning("[Method not implemented] [method=~s]", [Method]), @@ -193,36 +195,27 @@ send_notification(Socket, Method, Params) -> lager:debug("[SERVER] Sending notification [notification=~p]", [Notification]), gen_tcp:send(Socket, Notification). --spec definition(any()) -> null | [map()]. -definition(Element) -> - case erl_syntax:type(Element) of - application -> - Op = erl_syntax:application_operator(Element), - A = length(erl_syntax:application_arguments(Element)), - case erl_syntax:type(Op) of - module_qualifier -> - definition_from_module_qualifier(Op, A); - _ -> +-spec definition(uri(), erlang_ls_parser:poi()) -> null | map(). +definition(_Uri, #{ info := {application, {M, F, A}} }) -> + %% TODO: Check whether URI is already cached + Path = filelib:wildcard(filename:join([?OTP_INCLUDE_PATH, "*/src"])), + %% TODO: Cache syntax tree here? + case file:path_open(Path, atom_to_list(M) ++ ".erl", [read]) of + {ok, IoDevice, FullName} -> + %% TODO: Avoid opening file twice + file:close(IoDevice), + {ok, Tree} = erlang_ls_parser:parse_file(FullName), + AnnotatedTree = erlang_ls_parser:annotate(Tree), + Info = {function, {F, A}}, + case erlang_ls_parser:find_poi_by_info(AnnotatedTree, Info) of + [#{ range := Range }] -> + %% TODO: Use API to create types + #{ uri => erlang_ls_uri:uri(list_to_binary(FullName)) + , range => erlang_ls_protocol:range(Range) + }; + [] -> null end; - _ -> - null - end. - --spec definition_from_module_qualifier(any(), non_neg_integer()) -> null | map(). -definition_from_module_qualifier(Op, A) -> - M = list_to_atom(erl_syntax:atom_name(erl_syntax:module_qualifier_argument(Op))), - F = list_to_atom(erl_syntax:atom_name(erl_syntax:module_qualifier_body(Op))), - Which = code:which(M), - Source = list_to_binary(proplists:get_value( source - , M:module_info(compile))), - DefUri = <<"file://", Source/binary>>, - {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Which, [abstract_code]), - case [ AL || {function, AL, AF, AA, _} <- AC, F =:= AF, A =:= AA] of - [DefLine] -> - #{ uri => DefUri - , range => erlang_ls_protocol:range(erl_anno:line(DefLine) - 1) - }; - [] -> + {error, _Error} -> null end. diff --git a/src/erlang_ls_uri.erl b/src/erlang_ls_uri.erl index d2f89e174..1664f1b90 100644 --- a/src/erlang_ls_uri.erl +++ b/src/erlang_ls_uri.erl @@ -8,7 +8,9 @@ %%============================================================================== %% Exports %%============================================================================== --export([ path/1 ]). +-export([ path/1 + , uri/1 + ]). %%============================================================================== %% Includes @@ -18,3 +20,7 @@ -spec path(uri()) -> uri_path(). path(<<"file://", Path/binary>>) -> Path. + +-spec uri(uri_path()) -> uri(). +uri(Path) -> + <<"file://", Path/binary>>.