Skip to content

Commit

Permalink
Go to definition can now handle lines where parsing fails (#1550)
Browse files Browse the repository at this point in the history
Use tokens to generate POIs on lines where parsing fails.
Currently handling call(), module:call(), ?MACRO, #record, atom.
  • Loading branch information
plux authored Sep 26, 2024
1 parent bfafd78 commit 21fc148
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 3 deletions.
22 changes: 22 additions & 0 deletions apps/els_core/src/els_text.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
range/3,
split_at_line/2,
tokens/1,
tokens/2,
apply_edits/2
]).
-export([strip_comments/1]).
Expand Down Expand Up @@ -71,6 +72,27 @@ tokens(Text) ->
{error, _, _} -> []
end.

-spec tokens(text(), {integer(), integer()}) -> [any()].
tokens(Text, Pos) ->
case erl_scan:string(els_utils:to_list(Text), Pos) of
{ok, Tokens, _} ->
[unpack_anno(T) || T <- Tokens];
{error, _, _} ->
[]
end.

-spec unpack_anno(erl_scan:token()) ->
{Category :: atom(), Pos :: {integer(), integer()}, Symbol :: any()}
| {Category :: atom(), Pos :: {integer(), integer()}}.
unpack_anno({Category, Anno, Symbol}) ->
Line = erl_anno:line(Anno),
Column = erl_anno:column(Anno),
{Category, {Line, Column}, Symbol};
unpack_anno({Category, Anno}) ->
Line = erl_anno:line(Anno),
Column = erl_anno:column(Anno),
{Category, {Line, Column}}.

%% @doc Extract the last token from the given text.
-spec last_token(text()) -> token() | {error, empty}.
last_token(Text) ->
Expand Down
4 changes: 3 additions & 1 deletion apps/els_lsp/src/els_code_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ goto_definition(Uri, #{kind := callback, id := Id}) ->
goto_definition(_Filename, _) ->
{error, not_found}.

-spec is_imported_bif(uri(), atom(), non_neg_integer()) -> boolean().
-spec is_imported_bif(uri(), atom(), non_neg_integer() | any_arity) -> boolean().
is_imported_bif(_Uri, _F, any_arity) ->
false;
is_imported_bif(_Uri, F, A) ->
OldBif = erl_internal:old_bif(F, A),
Bif = erl_internal:bif(F, A),
Expand Down
82 changes: 80 additions & 2 deletions apps/els_lsp/src/els_definition_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,87 @@ goto_definition(Uri, [POI | Rest]) ->
end.

-spec match_incomplete(binary(), pos()) -> [els_poi:poi()].
match_incomplete(Text, Pos) ->
match_incomplete(Text, {Line, Col} = Pos) ->
%% Try parsing subsets of text to find a matching POI at Pos
match_after(Text, Pos) ++ match_line(Text, Pos).
case match_after(Text, Pos) ++ match_line(Text, Pos) of
[] ->
%% Still found nothing, let's analyze the tokens to kludge a POI
LineText = els_text:line(Text, Line),
Tokens = els_text:tokens(LineText, {Line, 1}),
kludge_match(Tokens, {Line, Col + 1});
POIs ->
POIs
end.

-spec kludge_match([any()], pos()) -> [els_poi:poi()].
kludge_match([], _Pos) ->
[];
kludge_match(
[
{atom, {FromL, FromC}, Module},
{':', _},
{atom, _, Function},
{'(', {ToL, ToC}}
| _
],
{_, C}
) when
FromC =< C, C < ToC
->
%% Match mod:fun(
Range = #{from => {FromL, FromC}, to => {ToL, ToC}},
POI = els_poi:new(Range, application, {Module, Function, any_arity}),
[POI];
kludge_match([{atom, {FromL, FromC}, Function}, {'(', {ToL, ToC}} | _], {_, C}) when
FromC =< C, C < ToC
->
%% Match fun(
Range = #{from => {FromL, FromC}, to => {ToL, ToC}},
POI = els_poi:new(Range, application, {Function, any_arity}),
[POI];
kludge_match([{'#', _}, {atom, {FromL, FromC}, Record} | T], {_, C} = Pos) when
FromC =< C
->
%% Match #record
ToC = FromC + length(atom_to_list(Record)),
case C =< ToC of
true ->
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
POI = els_poi:new(Range, record_expr, Record),
[POI];
false ->
kludge_match(T, Pos)
end;
kludge_match([{'?', _}, {VarOrAtom, {FromL, FromC}, Macro} | T], {_, C} = Pos) when
FromC =< C, (VarOrAtom == var orelse VarOrAtom == atom)
->
%% Match ?MACRO
ToC = FromC + length(atom_to_list(Macro)),
case C =< ToC of
true ->
%% Match fun(
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
POI = els_poi:new(Range, macro, Macro),
[POI];
false ->
kludge_match(T, Pos)
end;
kludge_match([{atom, {FromL, FromC}, Atom} | T], {_, C} = Pos) when
FromC =< C
->
%% Match atom
ToC = FromC + length(atom_to_list(Atom)),
case C =< ToC of
true ->
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
POI = els_poi:new(Range, atom, Atom),
[POI];
false ->
kludge_match(T, Pos)
end;
kludge_match([_ | T], Pos) ->
%% TODO: Add more kludges here
kludge_match(T, Pos).

-spec match_after(binary(), pos()) -> [els_poi:poi()].
match_after(Text, {Line, Character}) ->
Expand Down

0 comments on commit 21fc148

Please sign in to comment.