Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On demand indexing #1260

Merged
merged 38 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2ff5acf
Store POIs on-demand
robertoaloi Mar 30, 2022
a9882c5
Store references on demand
robertoaloi Apr 1, 2022
7dd5582
Store signatures on demand
robertoaloi Apr 1, 2022
fffe202
Patches
robertoaloi Apr 1, 2022
75d30f2
Add missing module
robertoaloi Apr 1, 2022
6c20c94
Lower severity for missing config message
robertoaloi Apr 1, 2022
92c7e62
Fix end-per-testcase
robertoaloi Apr 1, 2022
055a922
Lower verbosity
robertoaloi Apr 1, 2022
8934fad
Only index specs when needed
robertoaloi Apr 4, 2022
0af544f
Prune signatures and references during re-indexing
robertoaloi Apr 4, 2022
9a478cc
Only register references when necessary
robertoaloi Apr 4, 2022
03579f9
Refactor text search
robertoaloi Apr 4, 2022
9c73c66
Handle whitespaces, actually store references
robertoaloi Apr 4, 2022
1a59fd8
Fixes
robertoaloi Apr 4, 2022
afecb31
Fix Dialyzer specs
robertoaloi Apr 5, 2022
056aa61
In memory grep
robertoaloi Apr 5, 2022
5ba84b8
Do not rely on order
robertoaloi Apr 5, 2022
ff720ce
Refactoring and fixes
robertoaloi Apr 5, 2022
de7f47a
One buffer per document
robertoaloi Apr 6, 2022
99ac925
Resolve race in index buffer
robertoaloi Apr 6, 2022
b9c83d2
Lower verbosity of logs in case of shutdown
robertoaloi Apr 6, 2022
fc9c456
Set reference to undefined
robertoaloi Apr 6, 2022
cec3040
Show edits in logs
robertoaloi Apr 7, 2022
d136370
Make incremental indexing configurable
robertoaloi Apr 7, 2022
a003d31
Clarify indexing logs
robertoaloi Apr 7, 2022
dc94b07
Show background job title on error
robertoaloi Apr 7, 2022
41ca00b
Revert configuration of Incremental
robertoaloi Apr 7, 2022
413b5dd
Basic words scanner
robertoaloi Apr 7, 2022
5416d7a
Lower verbosity
robertoaloi Apr 7, 2022
d98eb18
Real devs write match specs by hand. Not.
robertoaloi Apr 7, 2022
d59306d
Explicitly store buffers
robertoaloi Apr 7, 2022
8d34dfa
Fixes
robertoaloi Apr 7, 2022
3971114
Debug failing testcase in OTP 22
robertoaloi Apr 7, 2022
bbcb689
Do not delete specs for header files, to avoid conflict
robertoaloi Apr 8, 2022
f054f92
Remove dbg expressions
robertoaloi Apr 8, 2022
8a91a23
Fix Dialyzer issues
robertoaloi Apr 8, 2022
161dc2c
Remove obsolete module
robertoaloi Apr 8, 2022
c505eff
Fix indexing of include attributes
robertoaloi Apr 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ consult_config([Path | Paths], ReportMissingConfig) ->
[Config] -> {Path, Config}
catch
Class:Error ->
?LOG_WARNING( "Could not read config file: path=~p class=~p error=~p"
, [Path, Class, Error]),
?LOG_DEBUG( "Could not read config file: path=~p class=~p error=~p"
, [Path, Class, Error]),
consult_config(Paths, ReportMissingConfig)
end.

Expand Down
6 changes: 3 additions & 3 deletions apps/els_core/src/els_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ lookup_document(Uri) ->
case els_dt_document:lookup(Uri) of
{ok, [Document]} ->
{ok, Document};
Error ->
?LOG_INFO("Document lookup failed [error=~p] [uri=~p]", [Error, Uri]),
{error, Error}
{ok, []} ->
?LOG_INFO("Document lookup failed [uri=~p]", [Uri]),
{error, document_lookup_failed}
end
end.

Expand Down
5 changes: 5 additions & 0 deletions apps/els_lsp/priv/code_navigation/src/purge_references.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-module(purge_references).

-spec foo(any()) -> ok.
foo(_X) -> ok.
bar() -> foo(42).
robertoaloi marked this conversation as resolved.
Show resolved Hide resolved
13 changes: 8 additions & 5 deletions apps/els_lsp/src/els_dt_document.erl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
, kind :: kind() | '_'
, text :: binary() | '_'
, md5 :: binary() | '_'
, pois :: [poi()] | '_'
, pois :: [poi()] | '_' | ondemand
}).
-type els_dt_document() :: #els_dt_document{}.

Expand All @@ -62,7 +62,7 @@
, kind := kind()
, text := binary()
, md5 => binary()
, pois => [poi()]
, pois => [poi()] | ondemand
}.
-export_type([ id/0
, item/0
Expand Down Expand Up @@ -145,18 +145,21 @@ new(Uri, Text) ->

-spec new(uri(), binary(), atom(), kind()) -> item().
new(Uri, Text, Id, Kind) ->
{ok, POIs} = els_parser:parse(Text),
MD5 = erlang:md5(Text),
MD5 = erlang:md5(Text),
#{ uri => Uri
, id => Id
, kind => Kind
, text => Text
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it necessary to store the Text if the POIs are not (yet) stored? if Text is still stored it could be used to grep in the in-memory binary. If it's not stored it can spare some memory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, it would be good to perform the in-memory grep, but it may require some time before we get there. We should check how the Text is used and where. There's quite some complex logic around the apply-edit and the index buffer which we may want to simplify...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to an in-memory search (that can definitely be optimized) instead of the grep.

, md5 => MD5
, pois => POIs
, pois => ondemand
}.

%% @doc Returns the list of POIs for the current document
-spec pois(item()) -> [poi()].
pois(#{ text := Text, pois := ondemand } = Document) ->
{ok, POIs} = els_parser:parse(Text),
ok = els_dt_document:insert(Document#{pois => POIs}),
POIs;
pois(#{ pois := POIs }) ->
POIs.

Expand Down
93 changes: 85 additions & 8 deletions apps/els_lsp/src/els_dt_references.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,23 @@
%%==============================================================================

-export([ delete_by_uri/1
, find_all/0
, find_by/1
, find_by_id/2
, insert/2
]).

%%==============================================================================
%% Test API
%%==============================================================================

-export([ find_candidate_uris/1
]).

%%==============================================================================
%% Includes
%%==============================================================================
-include("els_lsp.hrl").
-include_lib("kernel/include/logger.hrl").

%%==============================================================================
%% Item Definition
Expand All @@ -45,6 +52,14 @@
}.
-export_type([ item/0 ]).

-type poi_category() :: function
| type
| macro
| record
| include
| include_lib
| behaviour.

%%==============================================================================
%% Callbacks for the els_db_table Behaviour
%%==============================================================================
Expand Down Expand Up @@ -82,12 +97,6 @@ insert(Kind, Map) when is_map(Map) ->
Record = from_item(Kind, Map),
els_db:write(name(), Record).

%% @doc Find all
-spec find_all() -> {ok, [item()]} | {error, any()}.
find_all() ->
Pattern = #els_dt_references{_ = '_'},
find_by(Pattern).

%% @doc Find by id
-spec find_by_id(poi_kind(), any()) -> {ok, [item()]} | {error, any()}.
find_by_id(Kind, Id) ->
Expand All @@ -97,10 +106,58 @@ find_by_id(Kind, Id) ->

-spec find_by(tuple()) -> {ok, [item()]}.
find_by(Pattern) ->
#els_dt_references{id = Id} = Pattern,
Uris = find_candidate_uris(Id),
[begin
{ok, Document} = els_utils:lookup_document(Uri),
POIs = els_dt_document:pois(Document, [ application
, behaviour
, implicit_fun
, include
, include_lib
, type_application
, import_entry
]),
%% TODO: Only re-register references if necessary
ok = els_dt_references:delete_by_uri(Uri),
[register_reference(Uri, POI) || POI <- POIs]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do I understand correctly that every time I look up references of an entity, it will call find_by and try to insert a lot of references in the ref_db (ets entries might be there already, so some of them might be noop) Why do we store the references in ets then? can't we run the matching on the POIs list in place?
Alternatively can we mark Uris, which already registered their references, so we can skip lookup_pois+register_references for them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my latest version (soon to be pushed), we only do this when expanding the POIs and enforcing a full indexing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My major concern here is that, depending on the pattern, we may end up indexing a lot of files. Need to find a good strategy to reduce the number of candidates significantly.

end || Uri <- Uris],
{ok, Items} = els_db:match(name(), Pattern),
{ok, [to_item(Item) || Item <- Items]}.

-spec kind_to_category(poi_kind()) -> function | type | macro | record.
%% TODO: What about references from header files?
-spec greppable_string({poi_category(), any()}) ->
{ok, string()} | {error, {any(), not_supported}}.
greppable_string({function, {_M, F, _A}}) ->
io_lib:format("~p", [F]);
greppable_string({type, {_M, F, _A}}) ->
io_lib:format("~p", [F]);
greppable_string({macro, {Name, _Arity}}) ->
io_lib:format("~p", [Name]);
greppable_string({macro, Name}) ->
io_lib:format("~p", [Name]);
greppable_string({include, String}) ->
io_lib:format("~s", [String]);
%% TODO: This could be tricky
greppable_string({include_lib, String}) ->
io_lib:format("~s", [String]);
greppable_string({behaviour, Name}) ->
io_lib:format("~p", [Name]).

-spec find_candidate_uris(any()) -> [uri()].
find_candidate_uris(Id) ->
IdString = greppable_string(Id),
Paths = els_config:get(apps_paths) ++ els_config:get(deps_paths),
PathsString = string:join(Paths, " "),
Cmd = "grep -l -r \"" ++ IdString ++ "\" " ++ PathsString,
?LOG_DEBUG("Command: ~p", [Cmd]),
Result = string:trim(os:cmd(Cmd), trailing),
?LOG_DEBUG("Result: ~p", [Result]),
Candidates = string:split(Result, "\n", all),
[els_uri:uri(els_utils:to_binary(Candidate)) || Candidate <- Candidates,
Candidate =/= []].

-spec kind_to_category(poi_kind()) -> poi_category().
kind_to_category(Kind) when Kind =:= application;
Kind =:= export_entry;
Kind =:= function;
Expand All @@ -124,3 +181,23 @@ kind_to_category(Kind) when Kind =:= include_lib ->
include_lib;
kind_to_category(Kind) when Kind =:= behaviour ->
behaviour.

-spec register_reference(uri(), poi()) -> ok.
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 %% Include
Kind =:= include;
Kind =:= include_lib;
%% Function
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= import_entry;
%% Type
Kind =:= type_application;
%% Behaviour
Kind =:= behaviour ->
insert( Kind
, #{id => Id, uri => Uri, range => Range}
).
21 changes: 18 additions & 3 deletions apps/els_lsp/src/els_dt_signatures.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,24 @@ insert(Map) when is_map(Map) ->
els_db:write(name(), Record).

-spec lookup(mfa()) -> {ok, [item()]}.
lookup(MFA) ->
{ok, Items} = els_db:lookup(name(), MFA),
{ok, [to_item(Item) || Item <- Items]}.
lookup({M, _F, _A} = MFA) ->
%% TODO: Only do it when necessary
case els_utils:find_module(M) of
{ok, Uri} ->
{ok, #{text := Text} = Document} = els_utils:lookup_document(Uri),
Specs = els_dt_document:pois(Document, [spec]),
[ begin
#{from := From, to := To} = Range,
Spec = els_text:range(Text, From, To),
els_dt_signatures:insert(#{ mfa => {M, F, A} , spec => Spec})
end
|| #{id := {F, A}, range := Range} <- Specs
],
{ok, Items} = els_db:lookup(name(), MFA),
{ok, [to_item(Item) || Item <- Items]};
{error, _} ->
{ok, []}
end.

-spec delete_by_module(atom()) -> ok.
delete_by_module(Module) ->
Expand Down
69 changes: 3 additions & 66 deletions apps/els_lsp/src/els_indexing.erl
Original file line number Diff line number Diff line change
Expand Up @@ -80,57 +80,14 @@ index(Uri, Text, Mode) ->
ok;
{ok, LookupResult} ->
Document = els_dt_document:new(Uri, Text),
ok = els_dt_document:insert(Document),
do_index(Document, Mode, LookupResult =/= [])
end.

-spec do_index(els_dt_document:item(), mode(), boolean()) -> ok.
do_index(#{uri := Uri, id := Id, kind := Kind} = Document, Mode, Reset) ->
case Mode of
'deep' ->
ok = els_dt_document:insert(Document);
'shallow' ->
%% Don't store detailed POIs when "shallow" indexing.
%% They will be reloaded and inserted when needed
%% by calling els_utils:lookup_document/1
ok
end,
%% Mapping from document id to uri
do_index(#{uri := Uri, id := Id, kind := Kind}, _Mode, _Reset) ->
ModuleItem = els_dt_document_index:new(Id, Uri, Kind),
ok = els_dt_document_index:insert(ModuleItem),
index_signatures(Document),
index_references(Document, Mode, Reset).

-spec index_signatures(els_dt_document:item()) -> ok.
index_signatures(#{id := Id, text := Text} = Document) ->
Specs = els_dt_document:pois(Document, [spec]),
[ begin
#{from := From, to := To} = Range,
Spec = els_text:range(Text, From, To),
els_dt_signatures:insert(#{ mfa => {Id, F, A} , spec => Spec})
end
|| #{id := {F, A}, range := Range} <- Specs
],
ok.

-spec index_references(els_dt_document:item(), mode(), boolean()) -> ok.
index_references(#{uri := Uri} = Document, 'deep', true) ->
%% Optimization to only do (non-optimized) match_delete when necessary
ok = els_dt_references:delete_by_uri(Uri),
index_references(Document, 'deep', false);
index_references(#{uri := Uri} = Document, 'deep', false) ->
%% References
POIs = els_dt_document:pois(Document, [ application
, behaviour
, implicit_fun
, include
, include_lib
, type_application
, import_entry
]),
[register_reference(Uri, POI) || POI <- POIs],
ok;
index_references(_Document, 'shallow', _) ->
ok.
ok = els_dt_document_index:insert(ModuleItem).

-spec maybe_start() -> true | false.
maybe_start() ->
Expand Down Expand Up @@ -194,26 +151,6 @@ try_index_file(FullName, Mode, SkipGeneratedFiles, GeneratedFilesTag) ->
{error, {Type, Reason}}
end.

-spec register_reference(uri(), poi()) -> ok.
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 %% Include
Kind =:= include;
Kind =:= include_lib;
%% Function
Kind =:= application;
Kind =:= implicit_fun;
Kind =:= import_entry;
%% Type
Kind =:= type_application;
%% Behaviour
Kind =:= behaviour ->
els_dt_references:insert( Kind
, #{id => Id, uri => Uri, range => Range}
).

-spec index_dir(string(), mode()) ->
{non_neg_integer(), non_neg_integer(), non_neg_integer()}.
index_dir(Dir, Mode) ->
Expand Down
60 changes: 30 additions & 30 deletions apps/els_lsp/test/els_call_hierarchy_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,36 @@ incoming_calls(Config) ->
, uri => UriA},
?assertEqual([Item], PrepareResult),
#{result := Result} = els_client:callhierarchy_incomingcalls(Item),
Calls = [#{ from =>
Calls = [ #{ from =>
#{ data =>
els_utils:base64_encode_term(
#{ poi =>
#{ data =>
#{ args => [{1, "Arg1"}]
, wrapping_range =>
#{ from => {7, 1}
, to => {14, 0}}}
, id => {function_a, 1}
, kind => function
, range => #{from => {7, 1}, to => {7, 11}}}})
, detail => <<"call_hierarchy_b [L11]">>
, kind => 12
, name => <<"function_a/1">>
, range =>
#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}
, selectionRange =>
#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}
, uri => UriB}
, fromRanges =>
[#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}]
}
, #{ from =>
#{ data =>
els_utils:base64_encode_term(
#{ poi =>
Expand Down Expand Up @@ -134,35 +163,6 @@ incoming_calls(Config) ->
[#{ 'end' => #{character => 12, line => 15}
, start => #{character => 2, line => 15}
}]}
, #{ from =>
#{ data =>
els_utils:base64_encode_term(
#{ poi =>
#{ data =>
#{ args => [{1, "Arg1"}]
, wrapping_range =>
#{ from => {7, 1}
, to => {14, 0}}}
, id => {function_a, 1}
, kind => function
, range => #{from => {7, 1}, to => {7, 11}}}})
, detail => <<"call_hierarchy_b [L11]">>
, kind => 12
, name => <<"function_a/1">>
, range =>
#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}
, selectionRange =>
#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}
, uri => UriB}
, fromRanges =>
[#{ 'end' => #{character => 29, line => 10}
, start => #{character => 2, line => 10}
}]
}
],
?assertEqual(Calls, Result).

Expand Down
Loading