Skip to content

Commit

Permalink
Support record fields in reference provider
Browse files Browse the repository at this point in the history
References are only searched in the document containing the definition and
documents that (maybe recursively) include the defining document.

The scoping for macro and record referencing is also changed to the same.

As a sideeffect macros and records are not stored in the els_dt_references table
any more.
  • Loading branch information
gomoripeti committed May 16, 2021
1 parent 3b1f128 commit dde6762
Show file tree
Hide file tree
Showing 16 changed files with 367 additions and 99 deletions.
22 changes: 21 additions & 1 deletion apps/els_core/src/els_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
, fold_files/4
, halt/1
, lookup_document/1
, include_id/1
, include_lib_id/1
, macro_string_to_term/1
, project_relative/1
, resolve_paths/3
Expand Down Expand Up @@ -88,7 +90,8 @@ cmd_path(Cmd) ->
Epmd
end.

-spec filename_to_atom(els_dt_document:id()) -> atom().
%% @doc Convert an 'include'/'include_lib' POI ID to a document index ID
-spec filename_to_atom(string()) -> atom().
filename_to_atom(FileName) ->
list_to_atom(filename:basename(FileName, filename:extension(FileName))).

Expand Down Expand Up @@ -141,6 +144,23 @@ lookup_document(Uri) ->
end
end.

%% @doc Convert path to an 'include' POI id
-spec include_id(file:filename_all()) -> string().
include_id(Path) ->
els_utils:to_list(filename:basename(Path)).

%% @doc Convert path to an 'include_lib' POI id
-spec include_lib_id(file:filename_all()) -> string().
include_lib_id(Path) ->
Components = filename:split(Path),
Length = length(Components),
End = Length - 1,
Beginning = max(1, Length - 2),
[H|T] = lists:sublist(Components, Beginning, End),
%% Strip the app version number from the path
Id = filename:join([re:replace(H, "-.*", "", [{return, list}]) | T]),
els_utils:to_list(Id).

-spec macro_string_to_term(list()) -> any().
macro_string_to_term(Value) ->
try
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-module(code_navigation_undefined).

-export([f0/0, f1/1]).

f0() ->
#undef_rec{undef_field = ?UNDEF_MACRO}.

f1(#undef_rec{undef_field = ?UNDEF_MACRO}) ->
ok.
2 changes: 1 addition & 1 deletion apps/els_lsp/src/els_code_navigation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ include_uris(Document) ->
POIs = els_dt_document:pois(Document, [include, include_lib]),
lists:foldl(fun add_include_uri/2, [], POIs).

-spec add_include_uri(els_dt_document:item(), [uri()]) -> [uri()].
-spec add_include_uri(poi(), [uri()]) -> [uri()].
add_include_uri(#{ id := Id }, Acc) ->
case els_utils:find_header(els_utils:filename_to_atom(Id)) of
{ok, Uri} -> [Uri | Acc];
Expand Down
18 changes: 2 additions & 16 deletions apps/els_lsp/src/els_compiler_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ inclusion_range(IncludePath, Document) ->
-> [poi_range()].
inclusion_range(IncludePath, Document, include) ->
POIs = els_dt_document:pois(Document, [include]),
IncludeId = include_id(IncludePath),
IncludeId = els_utils:include_id(IncludePath),
[Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, include_lib) ->
POIs = els_dt_document:pois(Document, [include_lib]),
IncludeId = include_lib_id(IncludePath),
IncludeId = els_utils:include_lib_id(IncludePath),
[Range || #{id := Id, range := Range} <- POIs, Id =:= IncludeId];
inclusion_range(IncludePath, Document, behaviour) ->
POIs = els_dt_document:pois(Document, [behaviour]),
Expand All @@ -272,20 +272,6 @@ inclusion_range(IncludePath, Document, parse_transform) ->
= els_uri:module(els_uri:uri(els_utils:to_binary(IncludePath))),
[Range || #{id := Id, range := Range} <- POIs, Id =:= ParseTransformId].

-spec include_id(string()) -> string().
include_id(Path) ->
filename:basename(Path).

-spec include_lib_id(string()) -> string().
include_lib_id(Path) ->
Components = filename:split(Path),
Length = length(Components),
End = Length - 1,
Beginning = max(1, Length - 2),
[H|T] = lists:sublist(Components, Beginning, End),
%% Strip the app version number from the path
filename:join([re:replace(H, "-.*", "", [{return, list}])], filename:join(T)).

-spec macro_options() -> [macro_option()].
macro_options() ->
Macros = els_config:get(macros),
Expand Down
43 changes: 6 additions & 37 deletions apps/els_lsp/src/els_completion_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ snippet(Label, InsertText) ->

-spec atoms(els_dt_document:item(), binary()) -> [map()].
atoms(Document, Prefix) ->
POIs = local_and_included_pois(Document, atom),
POIs = els_scope:local_and_included_pois(Document, atom),
Atoms = [Id || #{id := Id} <- POIs],
Unique = lists:usort(Atoms),
filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_atom/1).
Expand Down Expand Up @@ -472,13 +472,13 @@ definitions(Document, POIKind, ExportFormat) ->
-spec definitions(els_dt_document:item(), poi_kind(), boolean(), boolean()) ->
[map()].
definitions(Document, POIKind, ExportFormat, ExportedOnly) ->
POIs = local_and_included_pois(Document, POIKind),
POIs = els_scope:local_and_included_pois(Document, POIKind),
#{uri := Uri} = Document,
%% Find exported entries when there is an export_entry kind available
FAs = case export_entry_kind(POIKind) of
{error, no_export_entry_kind} -> [];
ExportKind ->
Exports = local_and_included_pois(Document, ExportKind),
Exports = els_scope:local_and_included_pois(Document, ExportKind),
[FA || #{id := FA} <- Exports]
end,
Items = resolve_definitions(Uri, POIs, FAs, ExportedOnly, ExportFormat),
Expand Down Expand Up @@ -547,8 +547,8 @@ variables(Document) ->

-spec all_record_fields(els_dt_document:item(), binary()) -> [map()].
all_record_fields(Document, Prefix) ->
POIs = local_and_included_pois(Document, [ record_def_field
, record_field]),
POIs = els_scope:local_and_included_pois(Document, [ record_def_field
, record_field]),
Fields = [Id || #{id := {_Record, Id}} <- POIs],
Unique = lists:usort(Fields),
filter_by_prefix(Prefix, Unique, fun atom_to_label/1, fun item_kind_field/1).
Expand All @@ -566,7 +566,7 @@ record_fields(Document, RecordName) ->

-spec find_record_definition(els_dt_document:item(), atom()) -> [poi()].
find_record_definition(Document, RecordName) ->
POIs = local_and_included_pois(Document, record),
POIs = els_scope:local_and_included_pois(Document, record),
[X || X = #{id := Name} <- POIs, Name =:= RecordName].

-spec item_kind_field(binary()) -> map().
Expand Down Expand Up @@ -725,37 +725,6 @@ export_entry_kind(type_definition) -> export_type_entry;
export_entry_kind(function) -> export_entry;
export_entry_kind(_) -> {error, no_export_entry_kind}.

%% @doc Returns POIs of the provided `Kinds' in the document and included files
-spec local_and_included_pois(els_dt_document:item(), poi_kind() | [poi_kind()])
-> [poi()].
local_and_included_pois(Document, Kind) when is_atom(Kind) ->
local_and_included_pois(Document, [Kind]);
local_and_included_pois(Document, Kinds) ->
lists:flatten([ els_dt_document:pois(Document, Kinds)
, included_pois(Document, Kinds)
]).

%% @doc Returns POIs of the provided `Kinds' in included files from `Document'
-spec included_pois(els_dt_document:item(), [poi_kind()]) -> [[map()]].
included_pois(Document, Kinds) ->
POIs = els_dt_document:pois(Document, [include, include_lib]),
[include_file_pois(Name, Kinds) || #{id := Name} <- POIs].

%% @doc Returns POIs of the provided `Kinds' in the included file
-spec include_file_pois(string(), [poi_kind()]) -> [map()].
include_file_pois(Name, Kinds) ->
Filename = filename:basename(Name, filename:extension(Name)),
H = list_to_atom(Filename),
case els_utils:find_header(H) of
{ok, Uri} ->
{ok, IncludeDocument} = els_utils:lookup_document(Uri),
%% NB: Recursive call to support includes in the include file
IncludedInHeader = lists:flatten(included_pois(IncludeDocument, Kinds)),
els_dt_document:pois(IncludeDocument, Kinds) ++ IncludedInHeader;
{error, _} ->
[]
end.

-spec atom_to_label(atom()) -> binary().
atom_to_label(Atom) when is_atom(Atom) ->
unicode:characters_to_binary(io_lib:write(Atom)).
Expand Down
4 changes: 4 additions & 0 deletions apps/els_lsp/src/els_dt_references.erl
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,9 @@ kind_to_category(Kind) when Kind =:= macro;
kind_to_category(Kind) when Kind =:= record_expr;
Kind =:= record ->
record;
kind_to_category(Kind) when Kind =:= include ->
include;
kind_to_category(Kind) when Kind =:= include_lib ->
include_lib;
kind_to_category(Kind) when Kind =:= behaviour ->
behaviour.
11 changes: 5 additions & 6 deletions apps/els_lsp/src/els_indexing.erl
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ index_references(#{uri := Uri} = Document, 'deep', false) ->
POIs = els_dt_document:pois(Document, [ application
, behaviour
, implicit_fun
, macro
, record_expr
, include
, include_lib
, type_application
, import_entry
]),
Expand Down Expand Up @@ -152,10 +152,9 @@ register_reference(Uri, #{id := {F, A}} = POI) ->
M = els_uri:module(Uri),
register_reference(Uri, POI#{id => {M, F, A}});
register_reference(Uri, #{kind := Kind, id := Id, range := Range})
when %% Record
Kind =:= record_expr;
%% Macro
Kind =:= macro;
when %% Include
Kind =:= include;
Kind =:= include_lib;
%% Function
Kind =:= application;
Kind =:= implicit_fun;
Expand Down
51 changes: 44 additions & 7 deletions apps/els_lsp/src/els_references_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
, is_enabled/0
]).

%% For use in other providers
-export([ find_scoped_references_for_def/2 ]).

%%==============================================================================
%% Includes
%%==============================================================================
Expand Down Expand Up @@ -69,14 +72,25 @@ find_references(Uri, #{ kind := Kind
{F, A, _Index} = Id,
Key = {els_uri:module(Uri), F, A},
find_references_for_id(Kind, Key);
find_references(_Uri, #{kind := Kind, id := Name})
when Kind =:= record_expr;
Kind =:= record ->
find_references_for_id(Kind, Name);
find_references(_Uri, #{kind := Kind, id := Name})
when Kind =:= macro;
find_references(Uri, Poi = #{kind := Kind})
when Kind =:= record;
Kind =:= record_def_field;
Kind =:= define ->
find_references_for_id(Kind, Name);
uri_pois_to_locations(
find_scoped_references_for_def(Uri, Poi));
find_references(Uri, Poi = #{kind := Kind})
when Kind =:= record_expr;
Kind =:= record_field;
Kind =:= macro ->
case els_code_navigation:goto_definition(Uri, Poi) of
{ok, DefUri, DefPoi} ->
uri_pois_to_locations(
find_scoped_references_for_def(DefUri, DefPoi));
{error, _} ->
%% look for references only in the current document
uri_pois_to_locations(
find_scoped_references_for_def(Uri, Poi))
end;
find_references(Uri, #{kind := module}) ->
case els_utils:lookup_document(Uri) of
{ok, Doc} ->
Expand All @@ -94,11 +108,34 @@ find_references(_Uri, #{kind := Kind, id := Name})
find_references(_Uri, _POI) ->
[].

-spec find_scoped_references_for_def(uri(), poi()) -> [{uri(), poi()}].
find_scoped_references_for_def(Uri, #{kind := Kind, id := Name}) ->
Kinds = kind_to_ref_kinds(Kind),
Refs = els_scope:local_and_includer_pois(Uri, Kinds),
[{U, Poi} || {U, Pois} <- Refs,
#{id := N} = Poi <- Pois,
N =:= Name].

-spec kind_to_ref_kinds(poi_kind()) -> [poi_kind()].
kind_to_ref_kinds(define) ->
[macro];
kind_to_ref_kinds(record) ->
[record_expr];
kind_to_ref_kinds(record_def_field) ->
[record_field];
kind_to_ref_kinds(Kind) ->
[Kind].


-spec find_references_for_id(poi_kind(), any()) -> [location()].
find_references_for_id(Kind, Id) ->
{ok, Refs} = els_dt_references:find_by_id(Kind, Id),
[location(U, R) || #{uri := U, range := R} <- Refs].

-spec uri_pois_to_locations([{uri(), poi()}]) -> [location()].
uri_pois_to_locations(Refs) ->
[location(U, R) || {U, #{range := R}} <- Refs].

-spec location(uri(), poi_range()) -> location().
location(Uri, Range) ->
#{ uri => Uri
Expand Down
18 changes: 6 additions & 12 deletions apps/els_lsp/src/els_rename_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,6 @@ editable_range(#{kind := application, id := {M, F, _A}, range := Range}) ->
editable_range(#{kind := _Kind, range := Range}) ->
els_protocol:range(Range).

-spec editable_range(poi_kind(), els_dt_references:item()) -> range().
editable_range(macro, #{range := Range}) ->
#{ from := {FromL, FromC} } = Range,
EditFromC = FromC + string:length("?"),
els_protocol:range(Range#{ from := {FromL, EditFromC} }).

-spec changes(uri(), poi(), binary()) -> #{uri() => [text_edit()]} | null.
changes(Uri, #{kind := variable, id := VarId, range := VarRange}, NewName) ->
%% Rename variable in function clause scope
Expand Down Expand Up @@ -190,13 +184,13 @@ changes(Uri, #{kind := function, id := {F, A}}, NewName) ->
[F, A, NewName, length(lists:flatten(maps:values(Changes))),
length(maps:keys(Changes))]),
Changes;
changes(Uri, #{kind := 'define', id := Id} = POI, NewName) ->
Self = #{range => editable_range(POI), newText => NewName},
{ok, Refs} = els_dt_references:find_by_id(macro, Id),
changes(Uri, #{kind := 'define'} = DefPoi, NewName) ->
Self = #{range => editable_range(DefPoi), newText => NewName},
Refs = els_references_provider:find_scoped_references_for_def(Uri, DefPoi),
lists:foldl(
fun(#{uri := U} = Ref, Acc) ->
Change = #{ range => editable_range(macro, Ref)
, newText => NewName
fun({U, Poi}, Acc) ->
Change = #{ range => editable_range(Poi)
, newText => <<"?", NewName/binary>>
},
maps:update_with(U, fun(V) -> [Change|V] end, [Change], Acc)
end, #{Uri => [Self]}, Refs);
Expand Down
Loading

0 comments on commit dde6762

Please sign in to comment.