Skip to content

Commit

Permalink
Go to definition can now handle lines where parsing fails
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 committed Sep 26, 2024
1 parent eeec8ef commit b183d54
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 b183d54

Please sign in to comment.