Skip to content

Commit 21fc148

Browse files
authored
Go to definition can now handle lines where parsing fails (#1550)
Use tokens to generate POIs on lines where parsing fails. Currently handling call(), module:call(), ?MACRO, #record, atom.
1 parent bfafd78 commit 21fc148

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

apps/els_core/src/els_text.erl

+22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
range/3,
1212
split_at_line/2,
1313
tokens/1,
14+
tokens/2,
1415
apply_edits/2
1516
]).
1617
-export([strip_comments/1]).
@@ -71,6 +72,27 @@ tokens(Text) ->
7172
{error, _, _} -> []
7273
end.
7374

75+
-spec tokens(text(), {integer(), integer()}) -> [any()].
76+
tokens(Text, Pos) ->
77+
case erl_scan:string(els_utils:to_list(Text), Pos) of
78+
{ok, Tokens, _} ->
79+
[unpack_anno(T) || T <- Tokens];
80+
{error, _, _} ->
81+
[]
82+
end.
83+
84+
-spec unpack_anno(erl_scan:token()) ->
85+
{Category :: atom(), Pos :: {integer(), integer()}, Symbol :: any()}
86+
| {Category :: atom(), Pos :: {integer(), integer()}}.
87+
unpack_anno({Category, Anno, Symbol}) ->
88+
Line = erl_anno:line(Anno),
89+
Column = erl_anno:column(Anno),
90+
{Category, {Line, Column}, Symbol};
91+
unpack_anno({Category, Anno}) ->
92+
Line = erl_anno:line(Anno),
93+
Column = erl_anno:column(Anno),
94+
{Category, {Line, Column}}.
95+
7496
%% @doc Extract the last token from the given text.
7597
-spec last_token(text()) -> token() | {error, empty}.
7698
last_token(Text) ->

apps/els_lsp/src/els_code_navigation.erl

+3-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ goto_definition(Uri, #{kind := callback, id := Id}) ->
144144
goto_definition(_Filename, _) ->
145145
{error, not_found}.
146146

147-
-spec is_imported_bif(uri(), atom(), non_neg_integer()) -> boolean().
147+
-spec is_imported_bif(uri(), atom(), non_neg_integer() | any_arity) -> boolean().
148+
is_imported_bif(_Uri, _F, any_arity) ->
149+
false;
148150
is_imported_bif(_Uri, F, A) ->
149151
OldBif = erl_internal:old_bif(F, A),
150152
Bif = erl_internal:bif(F, A),

apps/els_lsp/src/els_definition_provider.erl

+80-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,87 @@ goto_definition(Uri, [POI | Rest]) ->
7373
end.
7474

7575
-spec match_incomplete(binary(), pos()) -> [els_poi:poi()].
76-
match_incomplete(Text, Pos) ->
76+
match_incomplete(Text, {Line, Col} = Pos) ->
7777
%% Try parsing subsets of text to find a matching POI at Pos
78-
match_after(Text, Pos) ++ match_line(Text, Pos).
78+
case match_after(Text, Pos) ++ match_line(Text, Pos) of
79+
[] ->
80+
%% Still found nothing, let's analyze the tokens to kludge a POI
81+
LineText = els_text:line(Text, Line),
82+
Tokens = els_text:tokens(LineText, {Line, 1}),
83+
kludge_match(Tokens, {Line, Col + 1});
84+
POIs ->
85+
POIs
86+
end.
87+
88+
-spec kludge_match([any()], pos()) -> [els_poi:poi()].
89+
kludge_match([], _Pos) ->
90+
[];
91+
kludge_match(
92+
[
93+
{atom, {FromL, FromC}, Module},
94+
{':', _},
95+
{atom, _, Function},
96+
{'(', {ToL, ToC}}
97+
| _
98+
],
99+
{_, C}
100+
) when
101+
FromC =< C, C < ToC
102+
->
103+
%% Match mod:fun(
104+
Range = #{from => {FromL, FromC}, to => {ToL, ToC}},
105+
POI = els_poi:new(Range, application, {Module, Function, any_arity}),
106+
[POI];
107+
kludge_match([{atom, {FromL, FromC}, Function}, {'(', {ToL, ToC}} | _], {_, C}) when
108+
FromC =< C, C < ToC
109+
->
110+
%% Match fun(
111+
Range = #{from => {FromL, FromC}, to => {ToL, ToC}},
112+
POI = els_poi:new(Range, application, {Function, any_arity}),
113+
[POI];
114+
kludge_match([{'#', _}, {atom, {FromL, FromC}, Record} | T], {_, C} = Pos) when
115+
FromC =< C
116+
->
117+
%% Match #record
118+
ToC = FromC + length(atom_to_list(Record)),
119+
case C =< ToC of
120+
true ->
121+
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
122+
POI = els_poi:new(Range, record_expr, Record),
123+
[POI];
124+
false ->
125+
kludge_match(T, Pos)
126+
end;
127+
kludge_match([{'?', _}, {VarOrAtom, {FromL, FromC}, Macro} | T], {_, C} = Pos) when
128+
FromC =< C, (VarOrAtom == var orelse VarOrAtom == atom)
129+
->
130+
%% Match ?MACRO
131+
ToC = FromC + length(atom_to_list(Macro)),
132+
case C =< ToC of
133+
true ->
134+
%% Match fun(
135+
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
136+
POI = els_poi:new(Range, macro, Macro),
137+
[POI];
138+
false ->
139+
kludge_match(T, Pos)
140+
end;
141+
kludge_match([{atom, {FromL, FromC}, Atom} | T], {_, C} = Pos) when
142+
FromC =< C
143+
->
144+
%% Match atom
145+
ToC = FromC + length(atom_to_list(Atom)),
146+
case C =< ToC of
147+
true ->
148+
Range = #{from => {FromL, FromC}, to => {FromL, ToC}},
149+
POI = els_poi:new(Range, atom, Atom),
150+
[POI];
151+
false ->
152+
kludge_match(T, Pos)
153+
end;
154+
kludge_match([_ | T], Pos) ->
155+
%% TODO: Add more kludges here
156+
kludge_match(T, Pos).
79157

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

0 commit comments

Comments
 (0)