diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl index 7c7facf5f..4007f3971 100644 --- a/apps/els_core/src/els_config.erl +++ b/apps/els_core/src/els_config.erl @@ -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. diff --git a/apps/els_core/src/els_utils.erl b/apps/els_core/src/els_utils.erl index ded6b42b1..80b02013b 100644 --- a/apps/els_core/src/els_utils.erl +++ b/apps/els_core/src/els_utils.erl @@ -110,7 +110,7 @@ find_header(Id) -> {ok, Uri}; [] -> FileName = atom_to_list(Id) ++ ".hrl", - els_indexing:find_and_index_file(FileName) + els_indexing:find_and_deeply_index_file(FileName) end. %% @doc Look for a module in the DB @@ -128,14 +128,14 @@ find_module(Id) -> find_modules(Id) -> {ok, Candidates} = els_dt_document_index:lookup(Id), case [Uri || #{kind := module, uri := Uri} <- Candidates] of - [] -> - FileName = atom_to_list(Id) ++ ".erl", - case els_indexing:find_and_index_file(FileName) of - {ok, Uri} -> {ok, [Uri]}; - Error -> Error - end; - Uris -> - {ok, prioritize_uris(Uris)} + [] -> + FileName = atom_to_list(Id) ++ ".erl", + case els_indexing:find_and_deeply_index_file(FileName) of + {ok, Uri} -> {ok, [Uri]}; + Error -> Error + end; + Uris -> + {ok, prioritize_uris(Uris)} end. %% @doc Look for a document in the DB. @@ -150,13 +150,13 @@ lookup_document(Uri) -> {ok, Document}; {ok, []} -> Path = els_uri:path(Uri), - {ok, Uri} = els_indexing:index_file(Path), + {ok, Uri} = els_indexing:shallow_index(Path, app), 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. diff --git a/apps/els_lsp/src/els_background_job.erl b/apps/els_lsp/src/els_background_job.erl index d58b71908..a5687d8c3 100644 --- a/apps/els_lsp/src/els_background_job.erl +++ b/apps/els_lsp/src/els_background_job.erl @@ -187,13 +187,21 @@ terminate(normal, #{ config := #{on_complete := OnComplete} ?LOG_DEBUG("Background job completed.", []), OnComplete(InternalState), ok; -terminate(Reason, #{ config := #{on_error := OnError} +terminate(Reason, #{ config := #{ on_error := OnError + , title := Title + } , internal_state := InternalState , token := Token , total := Total , progress_enabled := ProgressEnabled }) -> - ?LOG_WARNING( "Background job aborted. [reason=~p]", [Reason]), + case Reason of + shutdown -> + ?LOG_DEBUG("Background job terminated.", []); + _ -> + ?LOG_ERROR( "Background job aborted. [reason=~p] [title=~p", + [Reason, Title]) + end, notify_end(Token, Total, ProgressEnabled), OnError(InternalState), ok. diff --git a/apps/els_lsp/src/els_buffer_server.erl b/apps/els_lsp/src/els_buffer_server.erl new file mode 100644 index 000000000..293037323 --- /dev/null +++ b/apps/els_lsp/src/els_buffer_server.erl @@ -0,0 +1,126 @@ +%%%============================================================================= +%%% @doc Buffer edits to an open buffer to avoid re-indexing too often. +%%% @end +%%%============================================================================= +-module(els_buffer_server). + +%%============================================================================== +%% API +%%============================================================================== +-export([ new/2 + , stop/1 + , apply_edits/2 + , flush/1 + ]). + +-export([ start_link/2 ]). + +%%============================================================================== +%% Callbacks for the gen_server behaviour +%%============================================================================== +-behaviour(gen_server). +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + ]). + +%%============================================================================== +%% Macro Definitions +%%============================================================================== +-define(FLUSH_DELAY, 200). %% ms + +%%============================================================================== +%% Type Definitions +%%============================================================================== +-type text() :: binary(). +-type state() :: #{ uri := uri() + , text := text() + , ref := undefined | reference() + , pending := [{pid(), any()}] + }. +-type buffer() :: pid(). + +%%============================================================================== +%% Includes +%%============================================================================== +-include_lib("kernel/include/logger.hrl"). +-include("els_lsp.hrl"). + +%%============================================================================== +%% API +%%============================================================================== +-spec new(uri(), text()) -> {ok, pid()}. +new(Uri, Text) -> + supervisor:start_child(els_buffer_sup, [Uri, Text]). + +-spec stop(buffer()) -> ok. +stop(Buffer) -> + supervisor:terminate_child(els_buffer_sup, Buffer). + +-spec apply_edits(buffer(), [els_text:edit()]) -> ok. +apply_edits(Buffer, Edits) -> + gen_server:cast(Buffer, {apply_edits, Edits}). + +-spec flush(buffer()) -> text(). +flush(Buffer) -> + gen_server:call(Buffer, {flush}). + +-spec start_link(uri(), text()) -> {ok, buffer()}. +start_link(Uri, Text) -> + gen_server:start_link(?MODULE, {Uri, Text}, []). + +%%============================================================================== +%% Callbacks for the gen_server behaviour +%%============================================================================== +-spec init({uri(), text()}) -> {ok, state()}. +init({Uri, Text}) -> + {ok, #{ uri => Uri, text => Text, ref => undefined, pending => [] }}. + +-spec handle_call(any(), {pid(), any()}, state()) -> {reply, any(), state()}. +handle_call({flush}, From, State) -> + #{uri := Uri, ref := Ref0, pending := Pending0} = State, + ?LOG_DEBUG("[~p] Flushing request [uri=~p]", [?MODULE, Uri]), + cancel_flush(Ref0), + Ref = schedule_flush(), + {noreply, State#{ref => Ref, pending => [From|Pending0]}}; +handle_call(Request, _From, State) -> + {reply, {not_implemented, Request}, State}. + +-spec handle_cast(any(), state()) -> {noreply, state()}. +handle_cast({apply_edits, Edits}, #{uri := Uri} = State) -> + ?LOG_DEBUG("[~p] Applying edits [uri=~p] [edits=~p]", [?MODULE, Uri, Edits]), + #{text := Text0, ref := Ref0} = State, + cancel_flush(Ref0), + Text = els_text:apply_edits(Text0, Edits), + Ref = schedule_flush(), + {noreply, State#{text => Text, ref => Ref}}. + +-spec handle_info(any(), state()) -> {noreply, state()}. +handle_info(flush, #{uri := Uri, text := Text, pending := Pending0} = State) -> + ?LOG_DEBUG("[~p] Flushing [uri=~p]", [?MODULE, Uri]), + do_flush(Uri, Text), + [gen_server:reply(From, Text) || From <- Pending0], + {noreply, State#{pending => [], ref => undefined}}; +handle_info(_Request, State) -> + {noreply, State}. + +%%============================================================================== +%% Internal Functions +%%============================================================================== + +-spec schedule_flush() -> reference(). +schedule_flush() -> + erlang:send_after(?FLUSH_DELAY, self(), flush). + +-spec cancel_flush(undefined | reference()) -> ok. +cancel_flush(undefined) -> + ok; +cancel_flush(Ref) -> + erlang:cancel_timer(Ref), + ok. + +-spec do_flush(uri(), text()) -> ok. +do_flush(Uri, Text) -> + {ok, Document} = els_utils:lookup_document(Uri), + els_indexing:deep_index(Document#{text => Text}). diff --git a/apps/els_lsp/src/els_buffer_sup.erl b/apps/els_lsp/src/els_buffer_sup.erl new file mode 100644 index 000000000..c122983ea --- /dev/null +++ b/apps/els_lsp/src/els_buffer_sup.erl @@ -0,0 +1,47 @@ +%%============================================================================== +%% Supervisor for Buffers +%%============================================================================== +-module(els_buffer_sup). + +%%============================================================================== +%% Behaviours +%%============================================================================== +-behaviour(supervisor). + +%%============================================================================== +%% Exports +%%============================================================================== + +%% API +-export([ start_link/0 ]). + +%% Supervisor Callbacks +-export([ init/1 ]). + +%%============================================================================== +%% Defines +%%============================================================================== +-define(SERVER, ?MODULE). + +%%============================================================================== +%% API +%%============================================================================== +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%============================================================================== +%% Supervisor callbacks +%%============================================================================== +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init([]) -> + SupFlags = #{ strategy => simple_one_for_one + , intensity => 5 + , period => 60 + }, + ChildSpecs = [#{ id => els_buffer_sup + , start => {els_buffer_server, start_link, []} + , restart => temporary + , shutdown => 5000 + }], + {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl index a8d5c2650..e8d8fec9b 100644 --- a/apps/els_lsp/src/els_completion_provider.erl +++ b/apps/els_lsp/src/els_completion_provider.erl @@ -44,8 +44,18 @@ handle_request({completion, Params}, State) -> } , <<"textDocument">> := #{<<"uri">> := Uri} } = Params, - ok = els_index_buffer:flush(Uri), - {ok, #{text := Text} = Document} = els_utils:lookup_document(Uri), + %% Ensure there are no pending changes. + {ok, Document} = els_utils:lookup_document(Uri), + #{buffer := Buffer, text := Text0} = Document, + Text = case Buffer of + undefined -> + %% This clause is only kept due to the current test suites, + %% where LSP clients can trigger a completion request + %% before a did_open + Text0; + _ -> + els_buffer_server:flush(Buffer) + end, Context = maps:get( <<"context">> , Params , #{ <<"triggerKind">> => ?COMPLETION_TRIGGER_KIND_INVOKED } diff --git a/apps/els_lsp/src/els_dt_document.erl b/apps/els_lsp/src/els_dt_document.erl index 6cc4e0376..1c37386ca 100644 --- a/apps/els_lsp/src/els_dt_document.erl +++ b/apps/els_lsp/src/els_dt_document.erl @@ -22,7 +22,7 @@ , delete/1 ]). --export([ new/2 +-export([ new/3 , pois/1 , pois/2 , get_element_at_pos/3 @@ -31,29 +31,37 @@ , applications_at_pos/3 , wrapping_functions/2 , wrapping_functions/3 + , find_candidates/1 ]). %%============================================================================== %% Includes %%============================================================================== -include("els_lsp.hrl"). +-include_lib("kernel/include/logger.hrl"). %%============================================================================== %% Type Definitions %%============================================================================== -type id() :: atom(). -type kind() :: module | header | other. +-type source() :: otp | app | dep. +-type buffer() :: pid(). +-export_type([source/0]). %%============================================================================== %% Item Definition %%============================================================================== --record(els_dt_document, { uri :: uri() | '_' +-record(els_dt_document, { uri :: uri() | '_' | '$1' , id :: id() | '_' , kind :: kind() | '_' , text :: binary() | '_' , md5 :: binary() | '_' - , pois :: [poi()] | '_' + , pois :: [poi()] | '_' | ondemand + , source :: source() | '$2' + , buffer :: buffer() | '_' | undefined + , words :: sets:set() | '_' | '$3' }). -type els_dt_document() :: #els_dt_document{}. @@ -62,7 +70,10 @@ , kind := kind() , text := binary() , md5 => binary() - , pois => [poi()] + , pois => [poi()] | ondemand + , source => source() + , buffer => buffer() | undefined + , words => sets:set() }. -export_type([ id/0 , item/0 @@ -91,6 +102,9 @@ from_item(#{ uri := Uri , text := Text , md5 := MD5 , pois := POIs + , source := Source + , buffer := Buffer + , words := Words }) -> #els_dt_document{ uri = Uri , id = Id @@ -98,6 +112,9 @@ from_item(#{ uri := Uri , text = Text , md5 = MD5 , pois = POIs + , source = Source + , buffer = Buffer + , words = Words }. -spec to_item(els_dt_document()) -> item(). @@ -107,6 +124,9 @@ to_item(#els_dt_document{ uri = Uri , text = Text , md5 = MD5 , pois = POIs + , source = Source + , buffer = Buffer + , words = Words }) -> #{ uri => Uri , id => Id @@ -114,6 +134,9 @@ to_item(#els_dt_document{ uri = Uri , text => Text , md5 => MD5 , pois => POIs + , source => Source + , buffer => Buffer + , words => Words }. -spec insert(item()) -> ok | {error, any()}. @@ -130,33 +153,39 @@ lookup(Uri) -> delete(Uri) -> els_db:delete(name(), Uri). --spec new(uri(), binary()) -> item(). -new(Uri, Text) -> +-spec new(uri(), binary(), source()) -> item(). +new(Uri, Text, Source) -> Extension = filename:extension(Uri), Id = binary_to_atom(filename:basename(Uri, Extension), utf8), case Extension of <<".erl">> -> - new(Uri, Text, Id, module); + new(Uri, Text, Id, module, Source); <<".hrl">> -> - new(Uri, Text, Id, header); + new(Uri, Text, Id, header, Source); _ -> - new(Uri, Text, Id, other) + new(Uri, Text, Id, other, Source) end. --spec new(uri(), binary(), atom(), kind()) -> item(). -new(Uri, Text, Id, Kind) -> - {ok, POIs} = els_parser:parse(Text), - MD5 = erlang:md5(Text), +-spec new(uri(), binary(), atom(), kind(), source()) -> item(). +new(Uri, Text, Id, Kind, Source) -> + MD5 = erlang:md5(Text), #{ uri => Uri , id => Id , kind => Kind , text => Text , md5 => MD5 - , pois => POIs + , pois => ondemand + , source => Source + , buffer => undefined + , words => get_words(Text) }. %% @doc Returns the list of POIs for the current document -spec pois(item()) -> [poi()]. +pois(#{ uri := Uri, pois := ondemand }) -> + els_indexing:ensure_deeply_indexed(Uri), + {ok, #{pois := POIs}} = els_utils:lookup_document(Uri), + POIs; pois(#{ pois := POIs }) -> POIs. @@ -201,3 +230,49 @@ wrapping_functions(Document, Line, Column) -> wrapping_functions(Document, Range) -> #{start := #{character := Character, line := Line}} = Range, wrapping_functions(Document, Line, Character). + +-spec find_candidates(atom() | string()) -> [uri()]. +find_candidates(Pattern) -> + %% ets:fun2ms(fun(#els_dt_document{source = Source, uri = Uri, words = Words}) + %% when Source =/= otp -> {Uri, Words} end). + MS = [{#els_dt_document{ uri = '$1' + , id = '_' + , kind = '_' + , text = '_' + , md5 = '_' + , pois = '_' + , source = '$2' + , buffer = '_' + , words = '$3'}, + [{'=/=', '$2', otp}], + [{{'$1', '$3'}}]}], + All = ets:select(name(), MS), + Fun = fun({Uri, Words}) -> + case sets:is_element(Pattern, Words) of + true -> {true, Uri}; + false -> false + end + end, + lists:filtermap(Fun, All). + +-spec get_words(binary()) -> sets:set(). +get_words(Text) -> + case erl_scan:string(els_utils:to_list(Text)) of + {ok, Tokens, _EndLocation} -> + Fun = fun({atom, _Location, Atom}, Words) -> + sets:add_element(Atom, Words); + ({string, _Location, String}, Words) -> + case filename:extension(String) of + ".hrl" -> + Id = filename:rootname(filename:basename(String)), + sets:add_element(Id, Words); + _ -> + Words + end; + (_, Words) -> + Words + end, + lists:foldl(Fun, sets:new(), Tokens); + {error, ErrorInfo, _ErrorLocation} -> + ?LOG_DEBUG("Errors while get_words ~p", [ErrorInfo]) + end. diff --git a/apps/els_lsp/src/els_dt_references.erl b/apps/els_lsp/src/els_dt_references.erl index 0926f1790..52bd98882 100644 --- a/apps/els_lsp/src/els_dt_references.erl +++ b/apps/els_lsp/src/els_dt_references.erl @@ -18,7 +18,6 @@ %%============================================================================== -export([ delete_by_uri/1 - , find_all/0 , find_by/1 , find_by_id/2 , insert/2 @@ -45,6 +44,15 @@ }. -export_type([ item/0 ]). +-type poi_category() :: function + | type + | macro + | record + | include + | include_lib + | behaviour. +-export_type([ poi_category/0 ]). + %%============================================================================== %% Callbacks for the els_db_table Behaviour %%============================================================================== @@ -82,12 +90,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) -> @@ -96,11 +98,13 @@ find_by_id(Kind, Id) -> find_by(Pattern). -spec find_by(tuple()) -> {ok, [item()]}. -find_by(Pattern) -> +find_by(#els_dt_references{id = Id} = Pattern) -> + Uris = els_text_search:find_candidate_uris(Id), + [els_indexing:ensure_deeply_indexed(Uri) || 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. +-spec kind_to_category(poi_kind()) -> poi_category(). kind_to_category(Kind) when Kind =:= application; Kind =:= export_entry; Kind =:= function; diff --git a/apps/els_lsp/src/els_dt_signatures.erl b/apps/els_lsp/src/els_dt_signatures.erl index f2a2b8694..1a5b20318 100644 --- a/apps/els_lsp/src/els_dt_signatures.erl +++ b/apps/els_lsp/src/els_dt_signatures.erl @@ -19,13 +19,14 @@ -export([ insert/1 , lookup/1 - , delete_by_module/1 + , delete_by_uri/1 ]). %%============================================================================== %% Includes %%============================================================================== -include("els_lsp.hrl"). +-include_lib("kernel/include/logger.hrl"). %%============================================================================== %% Item Definition @@ -72,11 +73,18 @@ insert(Map) when is_map(Map) -> els_db:write(name(), Record). -spec lookup(mfa()) -> {ok, [item()]}. -lookup(MFA) -> +lookup({M, _F, _A} = MFA) -> + {ok, _Uris} = els_utils:find_modules(M), {ok, Items} = els_db:lookup(name(), MFA), {ok, [to_item(Item) || Item <- Items]}. --spec delete_by_module(atom()) -> ok. -delete_by_module(Module) -> - Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'}, - ok = els_db:match_delete(name(), Pattern). +-spec delete_by_uri(uri()) -> ok. +delete_by_uri(Uri) -> + case filename:extension(Uri) of + <<".erl">> -> + Module = els_uri:module(Uri), + Pattern = #els_dt_signatures{mfa = {Module, '_', '_'}, _ = '_'}, + ok = els_db:match_delete(name(), Pattern); + _ -> + ok + end. diff --git a/apps/els_lsp/src/els_index_buffer.erl b/apps/els_lsp/src/els_index_buffer.erl deleted file mode 100644 index 7687cb12d..000000000 --- a/apps/els_lsp/src/els_index_buffer.erl +++ /dev/null @@ -1,140 +0,0 @@ -%% @doc Buffer textedits to avoid spamming indexing for every keystroke -%% -%% FIXME: Currently implemented as a simple process. -%% Might be nicer to rewrite this as a gen_server -%% FIXME: Edits are bottlenecked by this single process, so this could slow -%% down indexing when making changes in multiple files, this could be -%% mitigated by using a worker pool or spawning a process per Uri. --module(els_index_buffer). - --export([ start/0 - , stop/0 - , apply_edits_async/2 - , flush/1 - , load/2 - ]). - --include_lib("kernel/include/logger.hrl"). --include("els_lsp.hrl"). - --define(SERVER, ?MODULE). - --spec start() -> {ok, pid()} | {error, _}. -start() -> - case whereis(?SERVER) of - undefined -> - Pid = proc_lib:spawn_link(fun loop/0), - true = erlang:register(?SERVER, Pid), - ?LOG_INFO("[~p] Started.", [?SERVER]), - {ok, Pid}; - Pid -> - {error, {already_started, Pid}} - end. - --spec stop() -> ok. -stop() -> - ?SERVER ! stop, - erlang:unregister(?SERVER), - ?LOG_INFO("[~p] Stopped.", [?SERVER]), - ok. - --spec apply_edits_async(uri(), [els_text:edit()]) -> ok. -apply_edits_async(Uri, Edits) -> - ?SERVER ! {apply_edits, Uri, Edits}, - ok. - --spec load(uri(), binary()) -> ok. -load(Uri, Text) -> - Ref = make_ref(), - ?SERVER ! {load, self(), Ref, Uri, Text}, - receive - {Ref, done} -> - ok - end. - --spec flush(uri()) -> ok. -flush(Uri) -> - Ref = make_ref(), - ?SERVER ! {flush, self(), Ref, Uri}, - receive - {Ref, done} -> - ok - end. - --spec loop() -> ok. -loop() -> - receive - stop -> ok; - {apply_edits, Uri, Edits} -> - try - do_apply_edits(Uri, Edits) - catch E:R:St -> - ?LOG_ERROR("[~p] Crashed while applying edits ~p: ~p", - [?SERVER, Uri, {E, R, St}]) - end, - loop(); - {flush, Pid, Ref, Uri} -> - try - do_flush(Uri) - catch E:R:St -> - ?LOG_ERROR("[~p] Crashed while flushing ~p: ~p", - [?SERVER, Uri, {E, R, St}]) - end, - Pid ! {Ref, done}, - loop(); - {load, Pid, Ref, Uri, Text} -> - try - do_load(Uri, Text) - catch E:R:St -> - ?LOG_ERROR("[~p] Crashed while loading ~p: ~p", - [?SERVER, Uri, {E, R, St}]) - end, - Pid ! {Ref, done}, - loop() - end. - --spec do_apply_edits(uri(), [els_text:edit()]) -> ok. -do_apply_edits(Uri, Edits0) -> - ?LOG_DEBUG("[~p] Processing index request for ~p", [?SERVER, Uri]), - {ok, [#{text := Text0}]} = els_dt_document:lookup(Uri), - ?LOG_DEBUG("[~p] Apply edits: ~p", [?SERVER, Edits0]), - Text1 = els_text:apply_edits(Text0, Edits0), - Text = receive_all(Uri, Text1), - ?LOG_DEBUG("[~p] Started indexing ~p", [?SERVER, Uri]), - {Duration, ok} = timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end), - ?LOG_DEBUG("[~p] Done indexing ~p [duration: ~pms]", - [?SERVER, Uri, Duration div 1000]), - ok. - --spec do_flush(uri()) -> ok. -do_flush(Uri) -> - ?LOG_DEBUG("[~p] Flushing ~p", [?SERVER, Uri]), - {ok, [#{text := Text0}]} = els_dt_document:lookup(Uri), - Text = receive_all(Uri, Text0), - {Duration, ok} = timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end), - ?LOG_DEBUG("[~p] Done flushing ~p [duration: ~pms]", - [?SERVER, Uri, Duration div 1000]), - ok. - --spec do_load(uri(), binary()) -> ok. -do_load(Uri, Text) -> - ?LOG_DEBUG("[~p] Loading ~p", [?SERVER, Uri]), - {Duration, ok} = - timer:tc(fun() -> - els_indexing:index(Uri, Text, 'deep') - end), - ?LOG_DEBUG("[~p] Done load ~p [duration: ~pms]", - [?SERVER, Uri, Duration div 1000]), - ok. - --spec receive_all(uri(), binary()) -> binary(). -receive_all(Uri, Text0) -> - receive - {apply_edits, Uri, Edits} -> - ?LOG_DEBUG("[~p] Received more edits ~p.", [?SERVER, Uri]), - ?LOG_DEBUG("[~p] Apply edits: ~p", [?SERVER, Edits]), - Text = els_text:apply_edits(Text0, Edits), - receive_all(Uri, Text) - after - 0 -> Text0 - end. diff --git a/apps/els_lsp/src/els_indexing.erl b/apps/els_lsp/src/els_indexing.erl index f103698d6..dd300ef8a 100644 --- a/apps/els_lsp/src/els_indexing.erl +++ b/apps/els_lsp/src/els_indexing.erl @@ -3,12 +3,14 @@ -callback index(els_dt_document:item()) -> ok. %% API --export([ find_and_index_file/1 - , index_file/1 - , index/3 +-export([ find_and_deeply_index_file/1 , index_dir/2 , start/0 , maybe_start/0 + , ensure_deeply_indexed/1 + , shallow_index/2 + , deep_index/1 + , remove/1 ]). %%============================================================================== @@ -20,48 +22,30 @@ %%============================================================================== %% Types %%============================================================================== --type mode() :: 'deep' | 'shallow'. %%============================================================================== %% Exported functions %%============================================================================== --spec find_and_index_file(string()) -> +-spec find_and_deeply_index_file(string()) -> {ok, uri()} | {error, any()}. -find_and_index_file(FileName) -> +find_and_deeply_index_file(FileName) -> SearchPaths = els_config:get(search_paths), case file:path_open( SearchPaths , els_utils:to_binary(FileName) , [read] ) of - {ok, IoDevice, FullName} -> + {ok, IoDevice, Path} -> %% TODO: Avoid opening file twice file:close(IoDevice), - index_file(FullName); + Uri = els_uri:uri(Path), + ensure_deeply_indexed(Uri), + {ok, Uri}; {error, Error} -> {error, Error} end. --spec index_file(binary()) -> {ok, uri()}. -index_file(Path) -> - GeneratedFilesTag = els_config_indexing:get_generated_files_tag(), - try_index_file(Path, 'deep', false, GeneratedFilesTag), - {ok, els_uri:uri(Path)}. - --spec index_if_not_generated(uri(), binary(), mode(), boolean(), string()) -> - ok | skipped. -index_if_not_generated(Uri, Text, Mode, false, _GeneratedFilesTag) -> - index(Uri, Text, Mode); -index_if_not_generated(Uri, Text, Mode, true, GeneratedFilesTag) -> - case is_generated_file(Text, GeneratedFilesTag) of - true -> - ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]), - skipped; - false -> - ok = index(Uri, Text, Mode) - end. - -spec is_generated_file(binary(), string()) -> boolean(). is_generated_file(Text, Tag) -> [Line|_] = string:split(Text, "\n", leading), @@ -72,66 +56,84 @@ is_generated_file(Text, Tag) -> false end. --spec index(uri(), binary(), mode()) -> ok. -index(Uri, Text, Mode) -> - MD5 = erlang:md5(Text), - case els_dt_document:lookup(Uri) of - {ok, [#{md5 := MD5}]} -> - ok; - {ok, LookupResult} -> - Document = els_dt_document:new(Uri, Text), - do_index(Document, Mode, LookupResult =/= []) +-spec ensure_deeply_indexed(uri()) -> ok. +ensure_deeply_indexed(Uri) -> + {ok, #{pois := POIs} = Document} = els_utils:lookup_document(Uri), + case POIs of + ondemand -> + deep_index(Document); + _ -> + ok 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 - 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 deep_index(els_dt_document:item()) -> ok. +deep_index(Document) -> + #{id := Id, uri := Uri, text := Text, source := Source} = Document, + {ok, POIs} = els_parser:parse(Text), + ok = els_dt_document:insert(Document#{pois => POIs}), + index_signatures(Id, Uri, Text, POIs), + case Source of + otp -> + ok; + S when S =:= app orelse S =:= dep -> + index_references(Id, Uri, POIs) + end. --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 - ], +-spec index_signatures(atom(), uri(), binary(), [poi()]) -> ok. +index_signatures(Id, Uri, Text, POIs) -> + ok = els_dt_signatures:delete_by_uri(Uri), + [index_signature(Id, Text, POI) || #{kind := spec} = POI <- POIs], 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], +-spec index_signature(atom(), binary(), poi()) -> ok. +index_signature(_M, _Text, #{id := undefined}) -> ok; -index_references(_Document, 'shallow', _) -> +index_signature(M, Text, #{id := {F, A}, range := Range}) -> + #{from := From, to := To} = Range, + Spec = els_text:range(Text, From, To), + els_dt_signatures:insert(#{ mfa => {M, F, A}, spec => Spec}). + +-spec index_references(atom(), uri(), [poi()]) -> ok. +index_references(Id, Uri, POIs) -> + ok = els_dt_references:delete_by_uri(Uri), + ReferenceKinds = [ %% Function + application + , implicit_fun + , import_entry + %% Include + , include + , include_lib + %% Behaviour + , behaviour + %% Type + , type_application + ], + [index_reference(Id, Uri, POI) + || #{kind := Kind} = POI <- POIs, + lists:member(Kind, ReferenceKinds)], ok. +-spec index_reference(atom(), uri(), poi()) -> ok. +index_reference(M, Uri, #{id := {F, A}} = POI) -> + index_reference(M, Uri, POI#{id => {M, F, A}}); +index_reference(_M, Uri, #{kind := Kind, id := Id, range := Range}) -> + els_dt_references:insert(Kind, #{id => Id, uri => Uri, range => Range}). + +-spec shallow_index(binary(), els_dt_document:source()) -> {ok, uri()}. +shallow_index(Path, Source) -> + Uri = els_uri:uri(Path), + {ok, Text} = file:read_file(Path), + shallow_index(Uri, Text, Source), + {ok, Uri}. + +-spec shallow_index(uri(), binary(), els_dt_document:source()) -> ok. +shallow_index(Uri, Text, Source) -> + Document = els_dt_document:new(Uri, Text, Source), + ok = els_dt_document:insert(Document), + #{id := Id, kind := Kind} = Document, + ModuleItem = els_dt_document_index:new(Id, Uri, Kind), + ok = els_dt_document_index:insert(ModuleItem). + -spec maybe_start() -> true | false. maybe_start() -> IndexingEnabled = els_config:get(indexing_enabled), @@ -145,17 +147,18 @@ maybe_start() -> -spec start() -> ok. start() -> - start(<<"OTP">>, entries_otp()), - start(<<"Applications">>, entries_apps()), - start(<<"Dependencies">>, entries_deps()). - --spec start(binary(), [{string(), 'deep' | 'shallow'}]) -> ok. -start(Group, Entries) -> - SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(), - GeneratedFilesTag = els_config_indexing:get_generated_files_tag(), - Task = fun({Dir, Mode}, {Succeeded0, Skipped0, Failed0}) -> - {Su, Sk, Fa} = index_dir(Dir, Mode, - SkipGeneratedFiles, GeneratedFilesTag), + Skip = els_config_indexing:get_skip_generated_files(), + SkipTag = els_config_indexing:get_generated_files_tag(), + ?LOG_INFO("Start indexing. [skip=~p] [skip_tag=~p]", [Skip, SkipTag]), + start(<<"OTP">>, Skip, SkipTag, els_config:get(otp_paths), otp), + start(<<"Applications">>, Skip, SkipTag, els_config:get(apps_paths), app), + start(<<"Dependencies">>, Skip, SkipTag, els_config:get(deps_paths), dep). + +-spec start(binary(), boolean(), string(), [string()], + els_dt_document:source()) -> ok. +start(Group, Skip, SkipTag, Entries, Source) -> + Task = fun(Dir, {Succeeded0, Skipped0, Failed0}) -> + {Su, Sk, Fa} = index_dir(Dir, Skip, SkipTag, Source), {Succeeded0 + Su, Skipped0 + Sk, Failed0 + Fa} end, Config = #{ task => Task @@ -172,65 +175,48 @@ start(Group, Entries) -> {ok, _Pid} = els_background_job:new(Config), ok. +-spec remove(uri()) -> ok. +remove(Uri) -> + ok = els_dt_document:delete(Uri), + ok = els_dt_document_index:delete_by_uri(Uri), + ok = els_dt_references:delete_by_uri(Uri), + ok = els_dt_signatures:delete_by_uri(Uri). + %%============================================================================== %% Internal functions %%============================================================================== - -%% @doc Try indexing a file. --spec try_index_file(binary(), mode(), boolean(), string()) -> - ok | skipped | {error, any()}. -try_index_file(FullName, Mode, SkipGeneratedFiles, GeneratedFilesTag) -> +-spec shallow_index(binary(), boolean(), string(), els_dt_document:source()) -> + ok | skipped. +shallow_index(FullName, SkipGeneratedFiles, GeneratedFilesTag, Source) -> Uri = els_uri:uri(FullName), - try - ?LOG_DEBUG("Indexing file. [filename=~s, uri=~s]", [FullName, Uri]), - {ok, Text} = file:read_file(FullName), - index_if_not_generated(Uri, Text, Mode, - SkipGeneratedFiles, GeneratedFilesTag) - catch Type:Reason:St -> - ?LOG_ERROR("Error indexing file " - "[filename=~s, uri=~s] " - "~p:~p:~p", [FullName, Uri, Type, Reason, St]), - {error, {Type, Reason}} + ?LOG_DEBUG("Shallow indexing file. [filename=~s] [uri=~s]", + [FullName, Uri]), + {ok, Text} = file:read_file(FullName), + case SkipGeneratedFiles andalso is_generated_file(Text, GeneratedFilesTag) of + true -> + ?LOG_DEBUG("Skip indexing for generated file ~p", [Uri]), + skipped; + false -> + shallow_index(Uri, Text, Source) 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()) -> +-spec index_dir(string(), els_dt_document:source()) -> {non_neg_integer(), non_neg_integer(), non_neg_integer()}. -index_dir(Dir, Mode) -> - SkipGeneratedFiles = els_config_indexing:get_skip_generated_files(), - GeneratedFilesTag = els_config_indexing:get_generated_files_tag(), - index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag). +index_dir(Dir, Source) -> + Skip = els_config_indexing:get_skip_generated_files(), + SkipTag = els_config_indexing:get_generated_files_tag(), + index_dir(Dir, Skip, SkipTag, Source). --spec index_dir(string(), mode(), boolean(), string()) -> +-spec index_dir(string(), boolean(), string(), els_dt_document:source()) -> {non_neg_integer(), non_neg_integer(), non_neg_integer()}. -index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag) -> - ?LOG_DEBUG("Indexing directory. [dir=~s] [mode=~s]", [Dir, Mode]), +index_dir(Dir, Skip, SkipTag, Source) -> + ?LOG_DEBUG("Indexing directory. [dir=~s]", [Dir]), F = fun(FileName, {Succeeded, Skipped, Failed}) -> - case try_index_file(els_utils:to_binary(FileName), Mode, - SkipGeneratedFiles, GeneratedFilesTag) of + BinaryName = els_utils:to_binary(FileName), + case shallow_index(BinaryName, Skip, SkipTag, Source) of ok -> {Succeeded + 1, Skipped, Failed}; - skipped -> {Succeeded, Skipped + 1, Failed}; - {error, _Error} -> {Succeeded, Skipped, Failed + 1} + skipped -> {Succeeded, Skipped + 1, Failed} end end, Filter = fun(Path) -> @@ -246,19 +232,7 @@ index_dir(Dir, Mode, SkipGeneratedFiles, GeneratedFilesTag) -> , {0, 0, 0} ] ), - ?LOG_DEBUG("Finished indexing directory. [dir=~s] [mode=~s] [time=~p] " + ?LOG_DEBUG("Finished indexing directory. [dir=~s] [time=~p] " "[succeeded=~p] [skipped=~p] [failed=~p]", - [Dir, Mode, Time/1000/1000, Succeeded, Skipped, Failed]), + [Dir, Time/1000/1000, Succeeded, Skipped, Failed]), {Succeeded, Skipped, Failed}. - --spec entries_apps() -> [{string(), 'deep' | 'shallow'}]. -entries_apps() -> - [{Dir, 'deep'} || Dir <- els_config:get(apps_paths)]. - --spec entries_deps() -> [{string(), 'deep' | 'shallow'}]. -entries_deps() -> - [{Dir, 'deep'} || Dir <- els_config:get(deps_paths)]. - --spec entries_otp() -> [{string(), 'deep' | 'shallow'}]. -entries_otp() -> - [{Dir, 'shallow'} || Dir <- els_config:get(otp_paths)]. diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl index 1a6d9efda..8fbe5bec2 100644 --- a/apps/els_lsp/src/els_sup.erl +++ b/apps/els_lsp/src/els_sup.erl @@ -67,14 +67,14 @@ init([]) -> , start => {els_distribution_sup, start_link, []} , type => supervisor } - , #{ id => els_snippets_server - , start => {els_snippets_server, start_link, []} + , #{ id => els_snippets_server + , start => {els_snippets_server, start_link, []} } - , #{ id => els_bsp_client + , #{ id => els_bsp_client , start => {els_bsp_client, start_link, []} } - , #{ id => els_index_buffer - , start => {els_index_buffer, start, []} + , #{ id => els_buffer_sup + , start => {els_buffer_sup, start_link, []} } , #{ id => els_server , start => {els_server, start_link, []} diff --git a/apps/els_lsp/src/els_text_search.erl b/apps/els_lsp/src/els_text_search.erl new file mode 100644 index 000000000..c55bdd37f --- /dev/null +++ b/apps/els_lsp/src/els_text_search.erl @@ -0,0 +1,46 @@ +%%============================================================================== +%% Text-based search +%%============================================================================== +-module(els_text_search). + +%%============================================================================== +%% API +%%============================================================================== +-export([ find_candidate_uris/1 ]). + +%%============================================================================== +%% Includes +%%============================================================================== +-include("els_lsp.hrl"). + +%%============================================================================== +%% API +%%============================================================================== +-spec find_candidate_uris({els_dt_references:poi_category(), any()}) -> [uri()]. +find_candidate_uris(Id) -> + Pattern = extract_pattern(Id), + els_dt_document:find_candidates(Pattern). + +%%============================================================================== +%% Internal Functions +%%============================================================================== +-spec extract_pattern({els_dt_references:poi_category(), any()}) -> + atom() | binary(). +extract_pattern({function, {_M, F, _A}}) -> + F; +extract_pattern({type, {_M, F, _A}}) -> + F; +extract_pattern({macro, {Name, _Arity}}) -> + Name; +extract_pattern({macro, Name}) -> + Name; +extract_pattern({include, Id}) -> + include_id(Id); +extract_pattern({include_lib, Id}) -> + include_id(Id); +extract_pattern({behaviour, Name}) -> + Name. + +-spec include_id(string()) -> string(). +include_id(Id) -> + filename:rootname(filename:basename(Id)). diff --git a/apps/els_lsp/src/els_text_synchronization.erl b/apps/els_lsp/src/els_text_synchronization.erl index abca45edf..d7a2aea86 100644 --- a/apps/els_lsp/src/els_text_synchronization.erl +++ b/apps/els_lsp/src/els_text_synchronization.erl @@ -30,7 +30,10 @@ did_change(Params) -> %% Full text sync #{<<"text">> := Text} = Change, {Duration, ok} = - timer:tc(fun() -> els_indexing:index(Uri, Text, 'deep') end), + timer:tc(fun() -> + {ok, Document} = els_utils:lookup_document(Uri), + els_indexing:deep_index(Document) + end), ?LOG_DEBUG("didChange FULLSYNC [size: ~p] [duration: ~pms]\n", [size(Text), Duration div 1000]), ok; @@ -39,7 +42,10 @@ did_change(Params) -> ?LOG_DEBUG("didChange INCREMENTAL [changes: ~p]", [ContentChanges]), Edits = [to_edit(Change) || Change <- ContentChanges], {Duration, ok} = - timer:tc(fun() -> els_index_buffer:apply_edits_async(Uri, Edits) end), + timer:tc(fun() -> + {ok, #{buffer := Buffer}} = els_utils:lookup_document(Uri), + els_buffer_server:apply_edits(Buffer, Edits) + end), ?LOG_DEBUG("didChange INCREMENTAL [duration: ~pms]\n", [Duration div 1000]), ok @@ -49,8 +55,9 @@ did_change(Params) -> did_open(Params) -> #{<<"textDocument">> := #{ <<"uri">> := Uri , <<"text">> := Text}} = Params, - ok = els_index_buffer:load(Uri, Text), - ok = els_index_buffer:flush(Uri), + {ok, Document} = els_utils:lookup_document(Uri), + {ok, Buffer} = els_buffer_server:new(Uri, Text), + els_dt_document:insert(Document#{buffer => Buffer}), Provider = els_diagnostics_provider, els_provider:handle_request(Provider, {run_diagnostics, Params}), ok. @@ -58,9 +65,7 @@ did_open(Params) -> -spec did_save(map()) -> ok. did_save(Params) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, - {ok, Text} = file:read_file(els_uri:path(Uri)), - ok = els_index_buffer:load(Uri, Text), - ok = els_index_buffer:flush(Uri), + reload_from_disk(Uri), Provider = els_diagnostics_provider, els_provider:handle_request(Provider, {run_diagnostics, Params}), ok. @@ -87,11 +92,20 @@ to_edit(#{<<"text">> := Text, <<"range">> := Range}) -> -spec handle_file_change(uri(), file_change_type()) -> ok. handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_CREATED; Type =:= ?FILE_CHANGE_TYPE_CHANGED -> - {ok, Text} = file:read_file(els_uri:path(Uri)), - ok = els_index_buffer:load(Uri, Text), - ok = els_index_buffer:flush(Uri); + reload_from_disk(Uri); handle_file_change(Uri, Type) when Type =:= ?FILE_CHANGE_TYPE_DELETED -> - ok = els_dt_document:delete(Uri), - ok = els_dt_document_index:delete_by_uri(Uri), - ok = els_dt_references:delete_by_uri(Uri), - ok = els_dt_signatures:delete_by_module(els_uri:module(Uri)). + els_indexing:remove(Uri). + +-spec reload_from_disk(uri()) -> ok. +reload_from_disk(Uri) -> + {ok, Text} = file:read_file(els_uri:path(Uri)), + {ok, #{buffer := OldBuffer} = Document} = els_utils:lookup_document(Uri), + case OldBuffer of + undefined -> + els_indexing:deep_index(Document#{text => Text}); + _ -> + els_buffer_server:stop(OldBuffer), + {ok, B} = els_buffer_server:new(Uri, Text), + els_indexing:deep_index(Document#{text => Text, buffer => B}) + end, + ok. diff --git a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl index 2242ff508..b5d5330d3 100644 --- a/apps/els_lsp/test/els_call_hierarchy_SUITE.erl +++ b/apps/els_lsp/test/els_call_hierarchy_SUITE.erl @@ -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 => @@ -134,37 +163,9 @@ 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). + [?assert(lists:member(Call, Result)) || Call <- Calls], + ?assertEqual(length(Calls), length(Result)). -spec outgoing_calls(config()) -> ok. outgoing_calls(Config) -> diff --git a/apps/els_lsp/test/els_hover_SUITE.erl b/apps/els_lsp/test/els_hover_SUITE.erl index 312c87184..c05887d84 100644 --- a/apps/els_lsp/test/els_hover_SUITE.erl +++ b/apps/els_lsp/test/els_hover_SUITE.erl @@ -375,4 +375,4 @@ has_eep48(Module) -> case catch code:get_doc(Module) of {ok, _} -> true; _ -> false - end. \ No newline at end of file + end. diff --git a/apps/els_lsp/test/els_indexer_SUITE.erl b/apps/els_lsp/test/els_indexer_SUITE.erl index ee0adccb9..dd97c4787 100644 --- a/apps/els_lsp/test/els_indexer_SUITE.erl +++ b/apps/els_lsp/test/els_indexer_SUITE.erl @@ -92,7 +92,7 @@ index_dir_not_dir(Config) -> index_erl_file(Config) -> DataDir = ?config(data_dir, Config), Path = filename:join(els_utils:to_binary(DataDir), "test.erl"), - {ok, Uri} = els_indexing:index_file(Path), + {ok, Uri} = els_indexing:shallow_index(Path, app), {ok, [#{id := test, kind := module}]} = els_dt_document:lookup(Uri), ok. @@ -100,7 +100,7 @@ index_erl_file(Config) -> index_hrl_file(Config) -> DataDir = ?config(data_dir, Config), Path = filename:join(els_utils:to_binary(DataDir), "test.hrl"), - {ok, Uri} = els_indexing:index_file(Path), + {ok, Uri} = els_indexing:shallow_index(Path, app), {ok, [#{id := test, kind := header}]} = els_dt_document:lookup(Uri), ok. @@ -108,7 +108,7 @@ index_hrl_file(Config) -> index_unkown_extension(Config) -> DataDir = ?config(data_dir, Config), Path = filename:join(els_utils:to_binary(DataDir), "test.foo"), - {ok, Uri} = els_indexing:index_file(Path), + {ok, Uri} = els_indexing:shallow_index(Path, app), {ok, [#{kind := other}]} = els_dt_document:lookup(Uri), ok. @@ -117,7 +117,7 @@ do_not_skip_generated_file_by_tag_by_default(Config) -> DataDir = data_dir(Config), GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"), GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"), - ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, 'deep')), + ?assertEqual({4, 0, 0}, els_indexing:index_dir(DataDir, app)), {ok, [#{ id := generated_file_by_tag , kind := module } @@ -133,7 +133,7 @@ skip_generated_file_by_tag(Config) -> DataDir = data_dir(Config), GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"), GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"), - ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')), + ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)), {ok, []} = els_dt_document:lookup(GeneratedByTagUri), {ok, [#{ id := generated_file_by_custom_tag , kind := module @@ -146,7 +146,7 @@ skip_generated_file_by_custom_tag(Config) -> DataDir = data_dir(Config), GeneratedByTagUri = uri(DataDir, "generated_file_by_tag.erl"), GeneratedByCustomTagUri = uri(DataDir, "generated_file_by_custom_tag.erl"), - ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, 'deep')), + ?assertEqual({3, 1, 0}, els_indexing:index_dir(DataDir, app)), {ok, [#{ id := generated_file_by_tag , kind := module } diff --git a/apps/els_lsp/test/els_indexing_SUITE.erl b/apps/els_lsp/test/els_indexing_SUITE.erl index 212024754..40673f9d9 100644 --- a/apps/els_lsp/test/els_indexing_SUITE.erl +++ b/apps/els_lsp/test/els_indexing_SUITE.erl @@ -68,7 +68,7 @@ reindex_otp(_Config) -> -spec do_index_otp() -> ok. do_index_otp() -> - [els_indexing:index_dir(Dir, 'shallow') || Dir <- els_config:get(otp_paths)], + [els_indexing:index_dir(Dir, otp) || Dir <- els_config:get(otp_paths)], ok. -spec otp_apps_exclude() -> [string()]. diff --git a/apps/els_lsp/test/els_rebar3_release_SUITE.erl b/apps/els_lsp/test/els_rebar3_release_SUITE.erl index fce638d8d..4872e2f8c 100644 --- a/apps/els_lsp/test/els_rebar3_release_SUITE.erl +++ b/apps/els_lsp/test/els_rebar3_release_SUITE.erl @@ -65,8 +65,8 @@ init_per_testcase(_TestCase, Config) -> els_client:initialize(RootUri), {ok, AppText} = file:read_file(els_uri:path(AppUri)), els_client:did_open(AppUri, erlang, 1, AppText), - els_indexing:find_and_index_file("rebar3_release_app.erl"), - els_indexing:find_and_index_file("rebar3_release_sup.erl"), + els_indexing:find_and_deeply_index_file("rebar3_release_app.erl"), + els_indexing:find_and_deeply_index_file("rebar3_release_sup.erl"), [{started, Started}|Config]. -spec end_per_testcase(atom(), config()) -> ok. diff --git a/apps/els_lsp/test/els_references_SUITE.erl b/apps/els_lsp/test/els_references_SUITE.erl index eae89560c..0e5e72c09 100644 --- a/apps/els_lsp/test/els_references_SUITE.erl +++ b/apps/els_lsp/test/els_references_SUITE.erl @@ -27,7 +27,6 @@ , included_record_field/1 , undefined_record/1 , undefined_record_field/1 - , purge_references/1 , type_local/1 , type_remote/1 , type_included/1 @@ -69,7 +68,8 @@ end_per_suite(Config) -> -spec init_per_testcase(atom(), config()) -> config(). init_per_testcase(TestCase, Config0) - when TestCase =:= refresh_after_watched_file_changed -> + when TestCase =:= refresh_after_watched_file_changed; + TestCase =:= refresh_after_watched_file_deleted -> Config = els_test_utils:init_per_testcase(TestCase, Config0), PathB = ?config(watched_file_b_path, Config), {ok, OldContent} = file:read_file(PathB), @@ -79,10 +79,17 @@ init_per_testcase(TestCase, Config) -> -spec end_per_testcase(atom(), config()) -> ok. end_per_testcase(TestCase, Config) - when TestCase =:= refresh_after_watched_file_changed -> + when TestCase =:= refresh_after_watched_file_changed; + TestCase =:= refresh_after_watched_file_deleted -> PathB = ?config(watched_file_b_path, Config), ok = file:write_file(PathB, ?config(old_content, Config)), els_test_utils:end_per_testcase(TestCase, Config); +end_per_testcase(TestCase, Config) + when TestCase =:= refresh_after_watched_file_added -> + PathB = ?config(watched_file_b_path, Config), + ok = file:delete(filename:join(filename:dirname(PathB), + "watched_file_c.erl")), + els_test_utils:end_per_testcase(TestCase, Config); end_per_testcase(TestCase, Config) -> els_test_utils:end_per_testcase(TestCase, Config). @@ -413,34 +420,6 @@ undefined_record_field(Config) -> assert_locations(Locations1, ExpectedLocations), ok. -%% Issue #245 --spec purge_references(config()) -> ok. -purge_references(_Config) -> - els_db:clear_tables(), - Uri = <<"file:///tmp/foo.erl">>, - Text0 = <<"-spec foo() -> ok.\nfoo(_X) -> ok.\nbar() -> foo().">>, - Text1 = <<"\n-spec foo() -> ok.\nfoo(_X)-> ok.\nbar() -> foo().">>, - Doc0 = els_dt_document:new(Uri, Text0), - Doc1 = els_dt_document:new(Uri, Text1), - - ok = els_indexing:index(Uri, Text0, 'deep'), - ?assertEqual({ok, [Doc0]}, els_dt_document:lookup(Uri)), - ?assertEqual({ok, [#{ id => {foo, foo, 0} - , range => #{from => {3, 10}, to => {3, 13}} - , uri => <<"file:///tmp/foo.erl">> - }]} - , els_dt_references:find_all() - ), - - ok = els_indexing:index(Uri, Text1, 'deep'), - ?assertEqual({ok, [Doc1]}, els_dt_document:lookup(Uri)), - ?assertEqual({ok, [#{ id => {foo, foo, 0} - , range => #{from => {4, 10}, to => {4, 13}} - , uri => <<"file:///tmp/foo.erl">> - }]} - , els_dt_references:find_all() - ), - ok. -spec type_local(config()) -> ok. type_local(Config) -> @@ -507,6 +486,7 @@ refresh_after_watched_file_deleted(Config) -> %% Before UriA = ?config(watched_file_a_uri, Config), UriB = ?config(watched_file_b_uri, Config), + PathB = ?config(watched_file_b_path, Config), ExpectedLocationsBefore = [ #{ uri => UriB , range => #{from => {6, 3}, to => {6, 22}} } @@ -514,6 +494,7 @@ refresh_after_watched_file_deleted(Config) -> #{result := LocationsBefore} = els_client:references(UriA, 5, 2), assert_locations(LocationsBefore, ExpectedLocationsBefore), %% Delete (Simulate a checkout, rebase or similar) + ok = file:delete(PathB), els_client:did_change_watched_files([{UriB, ?FILE_CHANGE_TYPE_DELETED}]), %% After #{result := null} = els_client:references(UriA, 5, 2), @@ -554,6 +535,7 @@ refresh_after_watched_file_added(Config) -> %% Before UriA = ?config(watched_file_a_uri, Config), UriB = ?config(watched_file_b_uri, Config), + PathB = ?config(watched_file_b_path, Config), ExpectedLocationsBefore = [ #{ uri => UriB , range => #{from => {6, 3}, to => {6, 22}} } @@ -563,10 +545,12 @@ refresh_after_watched_file_added(Config) -> %% Add (Simulate a checkout, rebase or similar) DataDir = ?config(data_dir, Config), PathC = filename:join([DataDir, "watched_file_c.erl"]), - UriC = els_uri:uri(els_utils:to_binary(PathC)), - els_client:did_change_watched_files([{UriC, ?FILE_CHANGE_TYPE_CREATED}]), + NewPathC = filename:join(filename:dirname(PathB), "watched_file_c.erl"), + NewUriC = els_uri:uri(NewPathC), + {ok, _} = file:copy(PathC, NewPathC), + els_client:did_change_watched_files([{NewUriC, ?FILE_CHANGE_TYPE_CREATED}]), %% After - ExpectedLocationsAfter = [ #{ uri => UriC + ExpectedLocationsAfter = [ #{ uri => NewUriC , range => #{from => {6, 3}, to => {6, 22}} } , #{ uri => UriB diff --git a/apps/els_lsp/test/els_test_utils.erl b/apps/els_lsp/test/els_test_utils.erl index 2d96aa3a1..a2fe3d11e 100644 --- a/apps/els_lsp/test/els_test_utils.erl +++ b/apps/els_lsp/test/els_test_utils.erl @@ -130,7 +130,8 @@ includes() -> %% accessing this information from test cases. -spec index_file(binary()) -> [{atom(), any()}]. index_file(Path) -> - {ok, Uri} = els_indexing:index_file(Path), + Uri = els_uri:uri(Path), + ok = els_indexing:ensure_deeply_indexed(Uri), {ok, Text} = file:read_file(Path), ConfigId = config_id(Path), [ {atoms_append(ConfigId, '_path'), Path}