From 2c539fd167e6cef229d9e710f4c4a136e36dae1d Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Tue, 14 Jun 2022 14:54:40 +0200 Subject: [PATCH] Merge server and provider processes. (#1329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge server and provider processes. The els_provide process is a remain from the times where providers had their own dedicated processes and it does not currently provide additional value. It actually makes provider errors not bubble up to the client. In fact, in case of a provider error the provider process would crash, but the server would indefinitely wait for a response. The error would not be returned via LSP. This change simplifies the architecture of Erlang LS by merging the els_server and els_provider processes: * Get rid of els_provider process and internal_state * Remove obsolete available_providers function * Do not pass server state to providers As a follow up, it should be possible to: * Pass requests as-they-are (as opposed to only the parameters) to providers, avoiding an un-necessary translation layer * Generalize the code in els_method * Update apps/els_core/src/els_provider.erl Co-authored-by: Michał Muskała * Fix dialyzer spec Co-authored-by: Michał Muskała --- apps/els_core/src/els_provider.erl | 245 +----------------- .../src/els_call_hierarchy_provider.erl | 19 +- apps/els_lsp/src/els_code_action_provider.erl | 8 +- apps/els_lsp/src/els_code_lens_provider.erl | 8 +- apps/els_lsp/src/els_completion_provider.erl | 8 +- apps/els_lsp/src/els_definition_provider.erl | 10 +- apps/els_lsp/src/els_diagnostics_provider.erl | 8 +- .../src/els_document_highlight_provider.erl | 11 +- .../src/els_document_symbol_provider.erl | 8 +- .../src/els_execute_command_provider.erl | 8 +- .../src/els_folding_range_provider.erl | 6 +- apps/els_lsp/src/els_formatting_provider.erl | 22 +- apps/els_lsp/src/els_general_provider.erl | 14 +- apps/els_lsp/src/els_hover_provider.erl | 8 +- .../src/els_implementation_provider.erl | 6 +- apps/els_lsp/src/els_methods.erl | 144 ++++++---- apps/els_lsp/src/els_references_provider.erl | 6 +- apps/els_lsp/src/els_rename_provider.erl | 6 +- apps/els_lsp/src/els_server.erl | 195 ++++++++++---- .../src/els_signature_help_provider.erl | 7 +- apps/els_lsp/src/els_sup.erl | 4 - .../src/els_text_synchronization_provider.erl | 14 +- .../src/els_workspace_symbol_provider.erl | 8 +- apps/els_lsp/test/els_server_SUITE.erl | 2 +- apps/els_lsp/test/prop_statem.erl | 2 +- 25 files changed, 323 insertions(+), 454 deletions(-) diff --git a/apps/els_core/src/els_provider.erl b/apps/els_core/src/els_provider.erl index 714200df3..a95050e1e 100644 --- a/apps/els_core/src/els_provider.erl +++ b/apps/els_core/src/els_provider.erl @@ -2,254 +2,33 @@ %% API -export([ - handle_request/2, - start_link/0, - available_providers/0, - cancel_request/1, - cancel_request_by_uri/1 -]). - --behaviour(gen_server). --export([ - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2 + handle_request/2 ]). %%============================================================================== %% Includes %%============================================================================== --include_lib("kernel/include/logger.hrl"). +-include("els_core.hrl"). + +-callback handle_request(provider_request()) -> provider_result(). --callback handle_request(request(), any()) -> +-type provider() :: module(). +-type provider_request() :: {atom(), map()}. +-type provider_result() :: {async, uri(), pid()} | {response, any()} | {diagnostics, uri(), [pid()]} | noresponse. --callback handle_info(any(), any()) -> any(). --optional_callbacks([handle_info/2]). --type config() :: any(). --type provider() :: - els_completion_provider - | els_definition_provider - | els_document_symbol_provider - | els_hover_provider - | els_references_provider - | els_formatting_provider - | els_document_highlight_provider - | els_workspace_symbol_provider - | els_folding_range_provider - | els_implementation_provider - | els_code_action_provider - | els_general_provider - | els_code_lens_provider - | els_execute_command_provider - | els_rename_provider - | els_text_synchronization_provider - | els_signature_help_provider. --type request() :: {atom() | binary(), map()}. --type state() :: #{ - in_progress := [progress_entry()], - in_progress_diagnostics := [diagnostic_entry()], - open_buffers := sets:set(buffer()) -}. --type buffer() :: uri(). --type progress_entry() :: {uri(), job()}. --type diagnostic_entry() :: #{ - uri := uri(), - pending := [job()], - diagnostics := [els_diagnostics:diagnostic()] -}. --type job() :: pid(). -%% TODO: Redefining uri() due to a type conflict with request() --type uri() :: binary(). -export_type([ - config/0, provider/0, - request/0, - state/0 + provider_request/0, + provider_result/0 ]). %%============================================================================== -%% Macro Definitions -%%============================================================================== --define(SERVER, ?MODULE). - -%%============================================================================== -%% External functions +%% API %%============================================================================== - --spec start_link() -> {ok, pid()}. -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, unused, []). - --spec handle_request(provider(), request()) -> any(). +-spec handle_request(provider(), provider_request()) -> provider_result(). handle_request(Provider, Request) -> - gen_server:call(?SERVER, {handle_request, Provider, Request}, infinity). - --spec cancel_request(pid()) -> any(). -cancel_request(Job) -> - gen_server:cast(?SERVER, {cancel_request, Job}). - --spec cancel_request_by_uri(uri()) -> any(). -cancel_request_by_uri(Uri) -> - gen_server:cast(?SERVER, {cancel_request_by_uri, Uri}). - -%%============================================================================== -%% gen_server callbacks -%%============================================================================== - --spec init(unused) -> {ok, state()}. -init(unused) -> - %% Ensure the terminate function is called on shutdown, allowing the - %% job to clean up. - process_flag(trap_exit, true), - {ok, #{ - in_progress => [], - in_progress_diagnostics => [], - open_buffers => sets:new() - }}. - --spec handle_call(any(), {pid(), any()}, state()) -> - {reply, any(), state()}. -handle_call({handle_request, Provider, Request}, _From, State) -> - #{in_progress := InProgress, in_progress_diagnostics := InProgressDiagnostics} = - State, - case Provider:handle_request(Request, State) of - {async, Uri, Job} -> - {reply, {async, Job}, State#{in_progress => [{Uri, Job} | InProgress]}}; - {response, Response} -> - {reply, {response, Response}, State}; - {diagnostics, Uri, Jobs} -> - Entry = #{uri => Uri, pending => Jobs, diagnostics => []}, - NewState = - State#{in_progress_diagnostics => [Entry | InProgressDiagnostics]}, - {reply, noresponse, NewState}; - noresponse -> - {reply, noresponse, State} - end. - --spec handle_cast(any(), state()) -> {noreply, state()}. -handle_cast({cancel_request, Job}, State) -> - ?LOG_DEBUG("Cancelling request [job=~p]", [Job]), - els_background_job:stop(Job), - #{in_progress := InProgress} = State, - NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)}, - {noreply, NewState}; -handle_cast({cancel_request_by_uri, Uri}, State) -> - #{in_progress := InProgress0} = State, - Fun = fun({U, Job}) -> - case U =:= Uri of - true -> - els_background_job:stop(Job), - false; - false -> - true - end - end, - InProgress = lists:filtermap(Fun, InProgress0), - ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]), - NewState = State#{in_progress => InProgress}, - {noreply, NewState}. - --spec handle_info(any(), state()) -> {noreply, state()}. -handle_info({result, Result, Job}, State) -> - ?LOG_DEBUG("Received result [job=~p]", [Job]), - #{in_progress := InProgress} = State, - els_server:send_response(Job, Result), - NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)}, - {noreply, NewState}; -%% LSP 3.15 introduce versioning for diagnostics. Until all clients -%% support it, we need to keep track of old diagnostics and re-publish -%% them every time we get a new chunk. -handle_info({diagnostics, Diagnostics, Job}, State) -> - #{in_progress_diagnostics := InProgress} = State, - ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]), - case find_entry(Job, InProgress) of - {ok, { - #{ - pending := Jobs, - diagnostics := OldDiagnostics, - uri := Uri - }, - Rest - }} -> - NewDiagnostics = Diagnostics ++ OldDiagnostics, - els_diagnostics_provider:publish(Uri, NewDiagnostics), - NewState = - case lists:delete(Job, Jobs) of - [] -> - State#{in_progress_diagnostics => Rest}; - Remaining -> - State#{ - in_progress_diagnostics => - [ - #{ - pending => Remaining, - diagnostics => NewDiagnostics, - uri => Uri - } - | Rest - ] - } - end, - {noreply, NewState}; - {error, not_found} -> - {noreply, State} - end; -handle_info(_Request, State) -> - {noreply, State}. - --spec terminate(any(), state()) -> ok. -terminate(_Reason, #{in_progress := InProgress}) -> - [els_background_job:stop(Job) || {_Uri, Job} <- InProgress], - ok. - --spec available_providers() -> [provider()]. -available_providers() -> - [ - els_completion_provider, - els_definition_provider, - els_document_symbol_provider, - els_hover_provider, - els_references_provider, - els_formatting_provider, - els_document_highlight_provider, - els_workspace_symbol_provider, - els_folding_range_provider, - els_implementation_provider, - els_code_action_provider, - els_general_provider, - els_code_lens_provider, - els_execute_command_provider, - els_diagnostics_provider, - els_rename_provider, - els_call_hierarchy_provider, - els_text_synchronization_provider, - els_signature_help_provider - ]. - -%%============================================================================== -%% Internal Functions -%%============================================================================== --spec find_entry(job(), [diagnostic_entry()]) -> - {ok, {diagnostic_entry(), [diagnostic_entry()]}} - | {error, not_found}. -find_entry(Job, InProgress) -> - find_entry(Job, InProgress, []). - --spec find_entry(job(), [diagnostic_entry()], [diagnostic_entry()]) -> - {ok, {diagnostic_entry(), [diagnostic_entry()]}} - | {error, not_found}. -find_entry(_Job, [], []) -> - {error, not_found}; -find_entry(Job, [#{pending := Pending} = Entry | Rest], Acc) -> - case lists:member(Job, Pending) of - true -> - {ok, {Entry, Rest ++ Acc}}; - false -> - find_entry(Job, Rest, [Entry | Acc]) - end. + Provider:handle_request(Request). diff --git a/apps/els_lsp/src/els_call_hierarchy_provider.erl b/apps/els_lsp/src/els_call_hierarchy_provider.erl index 8084d80ee..d374e97ce 100644 --- a/apps/els_lsp/src/els_call_hierarchy_provider.erl +++ b/apps/els_lsp/src/els_call_hierarchy_provider.erl @@ -4,7 +4,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). %%============================================================================== @@ -13,36 +13,27 @@ -include("els_lsp.hrl"). -include_lib("kernel/include/logger.hrl"). -%%============================================================================== -%% Defines -%%============================================================================== - -%%============================================================================== -%% Types -%%============================================================================== --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({prepare, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({prepare, Params}) -> {Uri, Line, Char} = els_text_document_position_params:uri_line_character(Params), {ok, Document} = els_utils:lookup_document(Uri), Functions = els_dt_document:wrapping_functions(Document, Line + 1, Char + 1), Items = [function_to_item(Uri, F) || F <- Functions], {response, Items}; -handle_request({incoming_calls, Params}, _State) -> +handle_request({incoming_calls, Params}) -> #{<<"item">> := #{<<"uri">> := Uri} = Item} = Params, POI = els_call_hierarchy_item:poi(Item), References = els_references_provider:find_references(Uri, POI), Items = [reference_to_item(Reference) || Reference <- References], {response, incoming_calls(Items)}; -handle_request({outgoing_calls, Params}, _State) -> +handle_request({outgoing_calls, Params}) -> #{<<"item">> := Item} = Params, #{<<"uri">> := Uri} = Item, POI = els_call_hierarchy_item:poi(Item), diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl index 213e82418..f42534c86 100644 --- a/apps/els_lsp/src/els_code_action_provider.erl +++ b/apps/els_lsp/src/els_code_action_provider.erl @@ -4,21 +4,19 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({document_codeaction, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({document_codeaction, Params}) -> #{ <<"textDocument">> := #{<<"uri">> := Uri}, <<"range">> := RangeLSP, diff --git a/apps/els_lsp/src/els_code_lens_provider.erl b/apps/els_lsp/src/els_code_lens_provider.erl index 08feb56de..9cb2b468f 100644 --- a/apps/els_lsp/src/els_code_lens_provider.erl +++ b/apps/els_lsp/src/els_code_lens_provider.erl @@ -4,7 +4,7 @@ -export([ is_enabled/0, options/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). @@ -20,8 +20,8 @@ is_enabled() -> true. options() -> #{resolveProvider => false}. --spec handle_request(any(), any()) -> {async, uri(), pid()}. -handle_request({document_codelens, Params}, _State) -> +-spec handle_request(any()) -> {async, uri(), pid()}. +handle_request({document_codelens, Params}) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, ?LOG_DEBUG("Starting lenses job [uri=~p]", [Uri]), Job = run_lenses_job(Uri), @@ -47,7 +47,7 @@ run_lenses_job(Uri) -> title => <<"Lenses">>, on_complete => fun(Lenses) -> - els_provider ! {result, Lenses, self()}, + els_server ! {result, Lenses, self()}, ok end }, diff --git a/apps/els_lsp/src/els_completion_provider.erl b/apps/els_lsp/src/els_completion_provider.erl index cda22bc53..3f32a1e43 100644 --- a/apps/els_lsp/src/els_completion_provider.erl +++ b/apps/els_lsp/src/els_completion_provider.erl @@ -6,7 +6,7 @@ -include_lib("kernel/include/logger.hrl"). -export([ - handle_request/2, + handle_request/1, trigger_characters/0 ]). @@ -33,8 +33,8 @@ trigger_characters() -> [<<":">>, <<"#">>, <<"?">>, <<".">>, <<"-">>, <<"\"">>]. --spec handle_request(els_provider:request(), any()) -> {response, any()}. -handle_request({completion, Params}, _State) -> +-spec handle_request(els_provider:provider_request()) -> {response, any()}. +handle_request({completion, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, @@ -74,7 +74,7 @@ handle_request({completion, Params}, _State) -> }, Completions = find_completions(Prefix, TriggerKind, Opts), {response, Completions}; -handle_request({resolve, CompletionItem}, _State) -> +handle_request({resolve, CompletionItem}) -> {response, resolve(CompletionItem)}. %%============================================================================== diff --git a/apps/els_lsp/src/els_definition_provider.erl b/apps/els_lsp/src/els_definition_provider.erl index f7726ed61..d9fa53737 100644 --- a/apps/els_lsp/src/els_definition_provider.erl +++ b/apps/els_lsp/src/els_definition_provider.erl @@ -4,21 +4,19 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({definition, Params}, State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({definition, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, @@ -34,7 +32,7 @@ handle_request({definition, Params}, State) -> IncompletePOIs = match_incomplete(Text, {Line, Character}), case goto_definition(Uri, IncompletePOIs) of null -> - els_references_provider:handle_request({references, Params}, State); + els_references_provider:handle_request({references, Params}); GoTo -> {response, GoTo} end; diff --git a/apps/els_lsp/src/els_diagnostics_provider.erl b/apps/els_lsp/src/els_diagnostics_provider.erl index 90902b1a4..69cf75884 100644 --- a/apps/els_lsp/src/els_diagnostics_provider.erl +++ b/apps/els_lsp/src/els_diagnostics_provider.erl @@ -5,7 +5,7 @@ -export([ is_enabled/0, options/0, - handle_request/2 + handle_request/1 ]). -export([ @@ -29,8 +29,8 @@ is_enabled() -> true. options() -> #{}. --spec handle_request(any(), any()) -> {diagnostics, uri(), [pid()]}. -handle_request({run_diagnostics, Params}, _State) -> +-spec handle_request(any()) -> {diagnostics, uri(), [pid()]}. +handle_request({run_diagnostics, Params}) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, ?LOG_DEBUG("Starting diagnostics jobs [uri=~p]", [Uri]), Jobs = els_diagnostics:run_diagnostics(Uri), @@ -41,7 +41,7 @@ handle_request({run_diagnostics, Params}, _State) -> %%============================================================================== -spec notify([els_diagnostics:diagnostic()], pid()) -> ok. notify(Diagnostics, Job) -> - els_provider ! {diagnostics, Diagnostics, Job}, + els_server ! {diagnostics, Diagnostics, Job}, ok. -spec publish(uri(), [els_diagnostics:diagnostic()]) -> ok. diff --git a/apps/els_lsp/src/els_document_highlight_provider.erl b/apps/els_lsp/src/els_document_highlight_provider.erl index cccda4b69..51f2477f2 100644 --- a/apps/els_lsp/src/els_document_highlight_provider.erl +++ b/apps/els_lsp/src/els_document_highlight_provider.erl @@ -4,7 +4,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). %%============================================================================== @@ -12,19 +12,14 @@ %%============================================================================== -include("els_lsp.hrl"). -%%============================================================================== -%% Types -%%============================================================================== --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({document_highlight, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({document_highlight, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, diff --git a/apps/els_lsp/src/els_document_symbol_provider.erl b/apps/els_lsp/src/els_document_symbol_provider.erl index 12e917c17..7a6e2db21 100644 --- a/apps/els_lsp/src/els_document_symbol_provider.erl +++ b/apps/els_lsp/src/els_document_symbol_provider.erl @@ -4,21 +4,19 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({document_symbol, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({document_symbol, Params}) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, Symbols = symbols(Uri), case Symbols of diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl index 79d801f2a..16857e5b2 100644 --- a/apps/els_lsp/src/els_execute_command_provider.erl +++ b/apps/els_lsp/src/els_execute_command_provider.erl @@ -5,7 +5,7 @@ -export([ is_enabled/0, options/0, - handle_request/2 + handle_request/1 ]). %%============================================================================== @@ -14,8 +14,6 @@ -include("els_lsp.hrl"). -include_lib("kernel/include/logger.hrl"). --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== @@ -34,8 +32,8 @@ options() -> ] }. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({workspace_executecommand, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({workspace_executecommand, Params}) -> #{<<"command">> := PrefixedCommand} = Params, Arguments = maps:get(<<"arguments">>, Params, []), Result = execute_command( diff --git a/apps/els_lsp/src/els_folding_range_provider.erl b/apps/els_lsp/src/els_folding_range_provider.erl index 5d15c9832..c441bebab 100644 --- a/apps/els_lsp/src/els_folding_range_provider.erl +++ b/apps/els_lsp/src/els_folding_range_provider.erl @@ -6,7 +6,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). %%============================================================================== @@ -20,8 +20,8 @@ -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(tuple(), any()) -> {response, folding_range_result()}. -handle_request({document_foldingrange, Params}, _State) -> +-spec handle_request(tuple()) -> {response, folding_range_result()}. +handle_request({document_foldingrange, Params}) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, {ok, Document} = els_utils:lookup_document(Uri), POIs = els_dt_document:pois(Document, [function, record]), diff --git a/apps/els_lsp/src/els_formatting_provider.erl b/apps/els_lsp/src/els_formatting_provider.erl index 8901c1680..a52e4958d 100644 --- a/apps/els_lsp/src/els_formatting_provider.erl +++ b/apps/els_lsp/src/els_formatting_provider.erl @@ -3,8 +3,7 @@ -behaviour(els_provider). -export([ - init/0, - handle_request/2, + handle_request/1, is_enabled/0, is_enabled_document/0, is_enabled_range/0, @@ -15,13 +14,6 @@ %% Includes %%============================================================================== -include("els_lsp.hrl"). --include_lib("kernel/include/logger.hrl"). - -%%============================================================================== -%% Types -%%============================================================================== --type formatter() :: fun((string(), string(), formatting_options()) -> boolean()). --type state() :: [formatter()]. %%============================================================================== %% Macro Definitions @@ -31,10 +23,6 @@ %%============================================================================== %% els_provider functions %%============================================================================== --spec init() -> state(). -init() -> - [fun format_document_local/3]. - %% Keep the behaviour happy -spec is_enabled() -> boolean(). is_enabled() -> is_enabled_document(). @@ -52,8 +40,8 @@ is_enabled_range() -> -spec is_enabled_on_type() -> document_ontypeformatting_options(). is_enabled_on_type() -> false. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({document_formatting, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({document_formatting, Params}) -> #{ <<"options">> := Options, <<"textDocument">> := #{<<"uri">> := Uri} @@ -65,7 +53,7 @@ handle_request({document_formatting, Params}, _State) -> RelativePath -> format_document(Path, RelativePath, Options) end; -handle_request({document_rangeformatting, Params}, _State) -> +handle_request({document_rangeformatting, Params}) -> #{ <<"range">> := #{ <<"start">> := StartPos, @@ -78,7 +66,7 @@ handle_request({document_rangeformatting, Params}, _State) -> {ok, Document} = els_utils:lookup_document(Uri), {ok, TextEdit} = rangeformat_document(Uri, Document, Range, Options), {response, TextEdit}; -handle_request({document_ontypeformatting, Params}, _State) -> +handle_request({document_ontypeformatting, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, diff --git a/apps/els_lsp/src/els_general_provider.erl b/apps/els_lsp/src/els_general_provider.erl index 9a067f0a9..8cda414e3 100644 --- a/apps/els_lsp/src/els_general_provider.erl +++ b/apps/els_lsp/src/els_general_provider.erl @@ -3,7 +3,7 @@ -behaviour(els_provider). -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -export([server_capabilities/0]). @@ -44,7 +44,6 @@ -type exit_request() :: {exit, exit_params()}. -type exit_params() :: #{status => atom()}. -type exit_result() :: null. --type state() :: any(). %%============================================================================== %% els_provider functions @@ -56,15 +55,14 @@ is_enabled() -> true. initialize_request() | initialized_request() | shutdown_request() - | exit_request(), - state() + | exit_request() ) -> {response, initialize_result() | initialized_result() | shutdown_result() | exit_result()}. -handle_request({initialize, Params}, _State) -> +handle_request({initialize, Params}) -> #{ <<"rootUri">> := RootUri0, <<"capabilities">> := Capabilities @@ -86,7 +84,7 @@ handle_request({initialize, Params}, _State) -> end, ok = els_config:initialize(RootUri, Capabilities, InitOptions, true), {response, server_capabilities()}; -handle_request({initialized, _Params}, _State) -> +handle_request({initialized, _Params}) -> RootUri = els_config:get(root_uri), NodeName = els_distribution_server:node_name( <<"erlang_ls">>, @@ -96,9 +94,9 @@ handle_request({initialized, _Params}, _State) -> ?LOG_INFO("Started distribution for: [~p]", [NodeName]), els_indexing:maybe_start(), {response, null}; -handle_request({shutdown, _Params}, _State) -> +handle_request({shutdown, _Params}) -> {response, null}; -handle_request({exit, #{status := Status}}, _State) -> +handle_request({exit, #{status := Status}}) -> ?LOG_INFO("Language server stopping..."), ExitCode = case Status of diff --git a/apps/els_lsp/src/els_hover_provider.erl b/apps/els_lsp/src/els_hover_provider.erl index e084e2af4..e49aa0803 100644 --- a/apps/els_lsp/src/els_hover_provider.erl +++ b/apps/els_lsp/src/els_hover_provider.erl @@ -7,7 +7,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). @@ -24,8 +24,8 @@ is_enabled() -> true. --spec handle_request(any(), any()) -> {async, uri(), pid()}. -handle_request({hover, Params}, _State) -> +-spec handle_request(any()) -> {async, uri(), pid()}. +handle_request({hover, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, @@ -52,7 +52,7 @@ run_hover_job(Uri, Line, Character) -> title => <<"Hover">>, on_complete => fun(HoverResp) -> - els_provider ! {result, HoverResp, self()}, + els_server ! {result, HoverResp, self()}, ok end }, diff --git a/apps/els_lsp/src/els_implementation_provider.erl b/apps/els_lsp/src/els_implementation_provider.erl index 21cbc4e82..34cffaea2 100644 --- a/apps/els_lsp/src/els_implementation_provider.erl +++ b/apps/els_lsp/src/els_implementation_provider.erl @@ -6,7 +6,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). %%============================================================================== @@ -15,8 +15,8 @@ -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(tuple(), els_provider:state()) -> {response, [location()]}. -handle_request({implementation, Params}, _State) -> +-spec handle_request(tuple()) -> {response, [location()]}. +handle_request({implementation, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, diff --git a/apps/els_lsp/src/els_methods.erl b/apps/els_lsp/src/els_methods.erl index d8a43d9fa..ba88f90b0 100644 --- a/apps/els_lsp/src/els_methods.erl +++ b/apps/els_lsp/src/els_methods.erl @@ -43,20 +43,21 @@ -include_lib("kernel/include/logger.hrl"). -type method_name() :: binary(). --type state() :: map(). -type params() :: map(). -type result() :: - {response, params() | null, state()} - | {error, params(), state()} - | {noresponse, state()} - | {noresponse, pid(), state()} - | {notification, binary(), params(), state()}. + {response, params() | null, els_server:state()} + | {error, params(), els_server:state()} + | {noresponse, els_server:state()} + | {noresponse, uri(), pid(), els_server:state()} + | {notification, binary(), params(), els_server:state()} + | {diagnostics, uri(), [pid()], els_server:state()} + | {async, uri(), pid(), els_server:state()}. -type request_type() :: notification | request. %%============================================================================== %% @doc Dispatch the handling of the method to els_method %%============================================================================== --spec dispatch(method_name(), params(), request_type(), state()) -> result(). +-spec dispatch(method_name(), params(), request_type(), els_server:state()) -> result(). dispatch(<<"$/", Method/binary>>, Params, notification, State) -> Msg = "Ignoring $/ notification [method=~p] [params=~p]", Fmt = [Method, Params], @@ -81,17 +82,25 @@ dispatch(Method, Params, _Type, State) -> not_implemented_method(Method, State); Type:Reason:Stack -> ?LOG_ERROR( - "Unexpected error [type=~p] [error=~p] [stack=~p]", + "Internal [type=~p] [error=~p] [stack=~p]", [Type, Reason, Stack] ), Error = #{ - code => ?ERR_UNKNOWN_ERROR_CODE, - message => <<"Unexpected error while ", Method/binary>> + type => Type, + reason => Reason, + stack => Stack, + method => Method, + params => Params }, - {error, Error, State} + ErrorMsg = els_utils:to_binary(lists:flatten(io_lib:format("~p", [Error]))), + ErrorResponse = #{ + code => ?ERR_INTERNAL_ERROR, + message => <<"Internal Error: ", ErrorMsg/binary>> + }, + {error, ErrorResponse, State} end. --spec do_dispatch(atom(), params(), state()) -> result(). +-spec do_dispatch(atom(), params(), els_server:state()) -> result(). do_dispatch(exit, Params, State) -> els_methods:exit(Params, State); do_dispatch(_Function, _Params, #{status := shutdown} = State) -> @@ -113,7 +122,7 @@ do_dispatch(_Function, _Params, State) -> }, {error, Result, State}. --spec not_implemented_method(method_name(), state()) -> result(). +-spec not_implemented_method(method_name(), els_server:state()) -> result(). not_implemented_method(Method, State) -> ?LOG_WARNING("[Method not implemented] [method=~s]", [Method]), Message = <<"Method not implemented: ", Method/binary>>, @@ -137,7 +146,7 @@ method_to_function_name(Method) -> %% Initialize %%============================================================================== --spec initialize(params(), state()) -> result(). +-spec initialize(params(), els_server:state()) -> result(). initialize(Params, State) -> Provider = els_general_provider, Request = {initialize, Params}, @@ -148,7 +157,7 @@ initialize(Params, State) -> %% Initialized %%============================================================================== --spec initialized(params(), state()) -> result(). +-spec initialized(params(), els_server:state()) -> result(). initialized(Params, State) -> Provider = els_general_provider, Request = {initialized, Params}, @@ -173,7 +182,7 @@ initialized(Params, State) -> %% shutdown %%============================================================================== --spec shutdown(params(), state()) -> result(). +-spec shutdown(params(), els_server:state()) -> result(). shutdown(Params, State) -> Provider = els_general_provider, Request = {shutdown, Params}, @@ -184,54 +193,56 @@ shutdown(Params, State) -> %% exit %%============================================================================== --spec exit(params(), state()) -> no_return(). +-spec exit(params(), els_server:state()) -> no_return(). exit(_Params, State) -> Provider = els_general_provider, - Request = {exit, #{status => maps:get(status, State, undefined)}}, + Request = {exit, #{status => maps:get(status, State)}}, {response, _Response} = els_provider:handle_request(Provider, Request), - {noresponse, #{}}. + %% Only reached by property-based test (where halt/1 is mocked for + %% faster iteration + {noresponse, State#{status => exiting}}. %%============================================================================== %% textDocument/didopen %%============================================================================== --spec textdocument_didopen(params(), state()) -> result(). +-spec textdocument_didopen(params(), els_server:state()) -> result(). textdocument_didopen(Params, #{open_buffers := OpenBuffers} = State) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, Provider = els_text_synchronization_provider, Request = {did_open, Params}, - noresponse = els_provider:handle_request(Provider, Request), - {noresponse, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}. + {diagnostics, Uri, Jobs} = els_provider:handle_request(Provider, Request), + {diagnostics, Uri, Jobs, State#{open_buffers => sets:add_element(Uri, OpenBuffers)}}. %%============================================================================== %% textDocument/didchange %%============================================================================== --spec textdocument_didchange(params(), state()) -> result(). -textdocument_didchange(Params, State) -> +-spec textdocument_didchange(params(), els_server:state()) -> result(). +textdocument_didchange(Params, State0) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, - els_provider:cancel_request_by_uri(Uri), + State = cancel_request_by_uri(Uri, State0), Provider = els_text_synchronization_provider, Request = {did_change, Params}, - els_provider:handle_request(Provider, Request), + _ = els_provider:handle_request(Provider, Request), {noresponse, State}. %%============================================================================== %% textDocument/didsave %%============================================================================== --spec textdocument_didsave(params(), state()) -> result(). +-spec textdocument_didsave(params(), els_server:state()) -> result(). textdocument_didsave(Params, State) -> Provider = els_text_synchronization_provider, Request = {did_save, Params}, - noresponse = els_provider:handle_request(Provider, Request), - {noresponse, State}. + {diagnostics, Uri, Jobs} = els_provider:handle_request(Provider, Request), + {diagnostics, Uri, Jobs, State}. %%============================================================================== %% textDocument/didclose %%============================================================================== --spec textdocument_didclose(params(), state()) -> result(). +-spec textdocument_didclose(params(), els_server:state()) -> result(). textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, Provider = els_text_synchronization_provider, @@ -243,7 +254,7 @@ textdocument_didclose(Params, #{open_buffers := OpenBuffers} = State) -> %% textdocument/documentSymbol %%============================================================================== --spec textdocument_documentsymbol(params(), state()) -> result(). +-spec textdocument_documentsymbol(params(), els_server:state()) -> result(). textdocument_documentsymbol(Params, State) -> Provider = els_document_symbol_provider, Request = {document_symbol, Params}, @@ -254,17 +265,17 @@ textdocument_documentsymbol(Params, State) -> %% textDocument/hover %%============================================================================== --spec textdocument_hover(params(), state()) -> result(). +-spec textdocument_hover(params(), els_server:state()) -> result(). textdocument_hover(Params, State) -> Provider = els_hover_provider, - {async, Job} = els_provider:handle_request(Provider, {hover, Params}), - {noresponse, Job, State}. + {async, Uri, Job} = els_provider:handle_request(Provider, {hover, Params}), + {async, Uri, Job, State}. %%============================================================================== %% textDocument/completion %%============================================================================== --spec textdocument_completion(params(), state()) -> result(). +-spec textdocument_completion(params(), els_server:state()) -> result(). textdocument_completion(Params, State) -> Provider = els_completion_provider, {response, Response} = @@ -275,7 +286,7 @@ textdocument_completion(Params, State) -> %% completionItem/resolve %%============================================================================== --spec completionitem_resolve(params(), state()) -> result(). +-spec completionitem_resolve(params(), els_server:state()) -> result(). completionitem_resolve(Params, State) -> Provider = els_completion_provider, {response, Response} = @@ -286,7 +297,7 @@ completionitem_resolve(Params, State) -> %% textDocument/definition %%============================================================================== --spec textdocument_definition(params(), state()) -> result(). +-spec textdocument_definition(params(), els_server:state()) -> result(). textdocument_definition(Params, State) -> Provider = els_definition_provider, {response, Response} = @@ -297,7 +308,7 @@ textdocument_definition(Params, State) -> %% textDocument/references %%============================================================================== --spec textdocument_references(params(), state()) -> result(). +-spec textdocument_references(params(), els_server:state()) -> result(). textdocument_references(Params, State) -> Provider = els_references_provider, {response, Response} = @@ -308,7 +319,7 @@ textdocument_references(Params, State) -> %% textDocument/documentHightlight %%============================================================================== --spec textdocument_documenthighlight(params(), state()) -> result(). +-spec textdocument_documenthighlight(params(), els_server:state()) -> result(). textdocument_documenthighlight(Params, State) -> Provider = els_document_highlight_provider, {response, Response} = @@ -319,7 +330,7 @@ textdocument_documenthighlight(Params, State) -> %% textDocument/formatting %%============================================================================== --spec textdocument_formatting(params(), state()) -> result(). +-spec textdocument_formatting(params(), els_server:state()) -> result(). textdocument_formatting(Params, State) -> Provider = els_formatting_provider, {response, Response} = @@ -330,7 +341,7 @@ textdocument_formatting(Params, State) -> %% textDocument/rangeFormatting %%============================================================================== --spec textdocument_rangeformatting(params(), state()) -> result(). +-spec textdocument_rangeformatting(params(), els_server:state()) -> result(). textdocument_rangeformatting(Params, State) -> Provider = els_formatting_provider, {response, Response} = @@ -341,7 +352,7 @@ textdocument_rangeformatting(Params, State) -> %% textDocument/onTypeFormatting %%============================================================================== --spec textdocument_ontypeformatting(params(), state()) -> result(). +-spec textdocument_ontypeformatting(params(), els_server:state()) -> result(). textdocument_ontypeformatting(Params, State) -> Provider = els_formatting_provider, {response, Response} = @@ -352,7 +363,7 @@ textdocument_ontypeformatting(Params, State) -> %% textDocument/foldingRange %%============================================================================== --spec textdocument_foldingrange(params(), state()) -> result(). +-spec textdocument_foldingrange(params(), els_server:state()) -> result(). textdocument_foldingrange(Params, State) -> Provider = els_folding_range_provider, {response, Response} = @@ -363,7 +374,7 @@ textdocument_foldingrange(Params, State) -> %% textDocument/implementation %%============================================================================== --spec textdocument_implementation(params(), state()) -> result(). +-spec textdocument_implementation(params(), els_server:state()) -> result(). textdocument_implementation(Params, State) -> Provider = els_implementation_provider, {response, Response} = @@ -374,7 +385,7 @@ textdocument_implementation(Params, State) -> %% workspace/didChangeConfiguration %%============================================================================== --spec workspace_didchangeconfiguration(params(), state()) -> result(). +-spec workspace_didchangeconfiguration(params(), els_server:state()) -> result(). workspace_didchangeconfiguration(_Params, State) -> %% Some clients send this notification on startup, even though we %% have no server-side config. So swallow it without complaining. @@ -384,7 +395,7 @@ workspace_didchangeconfiguration(_Params, State) -> %% textDocument/codeAction %%============================================================================== --spec textdocument_codeaction(params(), state()) -> result(). +-spec textdocument_codeaction(params(), els_server:state()) -> result(). textdocument_codeaction(Params, State) -> Provider = els_code_action_provider, {response, Response} = @@ -395,18 +406,18 @@ textdocument_codeaction(Params, State) -> %% textDocument/codeLens %%============================================================================== --spec textdocument_codelens(params(), state()) -> result(). +-spec textdocument_codelens(params(), els_server:state()) -> result(). textdocument_codelens(Params, State) -> Provider = els_code_lens_provider, - {async, Job} = + {async, Uri, Job} = els_provider:handle_request(Provider, {document_codelens, Params}), - {noresponse, Job, State}. + {async, Uri, Job, State}. %%============================================================================== %% textDocument/rename %%============================================================================== --spec textdocument_rename(params(), state()) -> result(). +-spec textdocument_rename(params(), els_server:state()) -> result(). textdocument_rename(Params, State) -> Provider = els_rename_provider, {response, Response} = @@ -417,7 +428,7 @@ textdocument_rename(Params, State) -> %% textDocument/preparePreparecallhierarchy %%============================================================================== --spec textdocument_preparecallhierarchy(params(), state()) -> result(). +-spec textdocument_preparecallhierarchy(params(), els_server:state()) -> result(). textdocument_preparecallhierarchy(Params, State) -> Provider = els_call_hierarchy_provider, {response, Response} = @@ -428,7 +439,7 @@ textdocument_preparecallhierarchy(Params, State) -> %% textDocument/signatureHelp %%============================================================================== --spec textdocument_signaturehelp(params(), state()) -> result(). +-spec textdocument_signaturehelp(params(), els_server:state()) -> result(). textdocument_signaturehelp(Params, State) -> Provider = els_signature_help_provider, {response, Response} = @@ -439,7 +450,7 @@ textdocument_signaturehelp(Params, State) -> %% callHierarchy/incomingCalls %%============================================================================== --spec callhierarchy_incomingcalls(params(), state()) -> result(). +-spec callhierarchy_incomingcalls(params(), els_server:state()) -> result(). callhierarchy_incomingcalls(Params, State) -> Provider = els_call_hierarchy_provider, {response, Response} = @@ -450,7 +461,7 @@ callhierarchy_incomingcalls(Params, State) -> %% callHierarchy/outgoingCalls %%============================================================================== --spec callhierarchy_outgoingcalls(params(), state()) -> result(). +-spec callhierarchy_outgoingcalls(params(), els_server:state()) -> result(). callhierarchy_outgoingcalls(Params, State) -> Provider = els_call_hierarchy_provider, {response, Response} = @@ -461,7 +472,7 @@ callhierarchy_outgoingcalls(Params, State) -> %% workspace/executeCommand %%============================================================================== --spec workspace_executecommand(params(), state()) -> result(). +-spec workspace_executecommand(params(), els_server:state()) -> result(). workspace_executecommand(Params, State) -> Provider = els_execute_command_provider, {response, Response} = @@ -472,7 +483,7 @@ workspace_executecommand(Params, State) -> %% workspace/didChangeWatchedFiles %%============================================================================== --spec workspace_didchangewatchedfiles(map(), state()) -> result(). +-spec workspace_didchangewatchedfiles(map(), els_server:state()) -> result(). workspace_didchangewatchedfiles(Params0, State) -> #{open_buffers := OpenBuffers} = State, #{<<"changes">> := Changes0} = Params0, @@ -491,9 +502,28 @@ workspace_didchangewatchedfiles(Params0, State) -> %% workspace/symbol %%============================================================================== --spec workspace_symbol(map(), state()) -> result(). +-spec workspace_symbol(map(), els_server:state()) -> result(). workspace_symbol(Params, State) -> Provider = els_workspace_symbol_provider, {response, Response} = els_provider:handle_request(Provider, {symbol, Params}), {response, Response, State}. + +%%============================================================================== +%% Internal Functions +%%============================================================================== +-spec cancel_request_by_uri(uri(), els_server:state()) -> els_server:state(). +cancel_request_by_uri(Uri, State) -> + #{in_progress := InProgress0} = State, + Fun = fun({U, Job}) -> + case U =:= Uri of + true -> + els_background_job:stop(Job), + false; + false -> + true + end + end, + InProgress = lists:filtermap(Fun, InProgress0), + ?LOG_DEBUG("Cancelling requests by Uri [uri=~p]", [Uri]), + State#{in_progress => InProgress}. diff --git a/apps/els_lsp/src/els_references_provider.erl b/apps/els_lsp/src/els_references_provider.erl index c2c4f35dd..aa67b70b1 100644 --- a/apps/els_lsp/src/els_references_provider.erl +++ b/apps/els_lsp/src/els_references_provider.erl @@ -4,7 +4,7 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). %% For use in other providers @@ -29,8 +29,8 @@ -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), any()) -> {response, [location()] | null}. -handle_request({references, Params}, _State) -> +-spec handle_request(any()) -> {response, [location()] | null}. +handle_request({references, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, diff --git a/apps/els_lsp/src/els_rename_provider.erl b/apps/els_lsp/src/els_rename_provider.erl index 024662bdd..6d33e0ffb 100644 --- a/apps/els_lsp/src/els_rename_provider.erl +++ b/apps/els_lsp/src/els_rename_provider.erl @@ -3,7 +3,7 @@ -behaviour(els_provider). -export([ - handle_request/2, + handle_request/1, is_enabled/0 ]). @@ -27,8 +27,8 @@ -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), any()) -> {response, any()}. -handle_request({rename, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({rename, Params}) -> #{ <<"textDocument">> := #{<<"uri">> := Uri}, <<"position">> := #{ diff --git a/apps/els_lsp/src/els_server.erl b/apps/els_lsp/src/els_server.erl index 7a06ba438..e87ce12d1 100644 --- a/apps/els_lsp/src/els_server.erl +++ b/apps/els_lsp/src/els_server.erl @@ -18,7 +18,9 @@ -export([ init/1, handle_call/3, - handle_cast/2 + handle_cast/2, + handle_info/2, + terminate/2 ]). %% API @@ -31,32 +33,40 @@ ]). %% Testing --export([reset_internal_state/0]). +-export([reset_state/0]). %%============================================================================== %% Includes %%============================================================================== -include_lib("kernel/include/logger.hrl"). +-include_lib("els_core/include/els_core.hrl"). %%============================================================================== %% Macros %%============================================================================== -define(SERVER, ?MODULE). -%%============================================================================== -%% Record Definitions -%%============================================================================== --record(state, { - io_device :: any(), - request_id :: number(), - internal_state :: map(), - pending :: [{number(), pid()}] -}). - %%============================================================================== %% Type Definitions %%============================================================================== --type state() :: #state{}. +-type state() :: + #{ + status := started | initialized | shutdown | exiting, + io_device := pid() | standard_io, + request_id := number(), + pending := [{number(), pid()}], + open_buffers := sets:set(buffer()), + in_progress := [progress_entry()], + in_progress_diagnostics := [diagnostic_entry()] + }. +-type buffer() :: uri(). +-type progress_entry() :: {uri(), pid()}. +-type diagnostic_entry() :: #{ + uri := uri(), + pending := [pid()], + diagnostics := [els_diagnostics:diagnostic()] +}. +-export_type([diagnostic_entry/0, state/0]). %%============================================================================== %% API @@ -93,28 +103,40 @@ send_response(Job, Result) -> %%============================================================================== %% Testing %%============================================================================== --spec reset_internal_state() -> ok. -reset_internal_state() -> - gen_server:call(?MODULE, {reset_internal_state}). +-spec reset_state() -> ok. +reset_state() -> + gen_server:call(?MODULE, {reset_state}). %%============================================================================== %% gen_server callbacks %%============================================================================== -spec init([]) -> {ok, state()}. init([]) -> + %% Ensure the terminate function is called on shutdown, allowing the + %% job to clean up. + process_flag(trap_exit, true), ?LOG_INFO("Starting els_server..."), - State = #state{ - request_id = 0, - internal_state = #{open_buffers => sets:new()}, - pending = [] + State = #{ + status => started, + io_device => standard_io, + request_id => 0, + pending => [], + open_buffers => sets:new(), + in_progress => [], + in_progress_diagnostics => [] }, {ok, State}. -spec handle_call(any(), any(), state()) -> {reply, any(), state()}. handle_call({set_io_device, IoDevice}, _From, State) -> - {reply, ok, State#state{io_device = IoDevice}}; -handle_call({reset_internal_state}, _From, State) -> - {reply, ok, State#state{internal_state = #{}}}. + {reply, ok, State#{io_device := IoDevice}}; +handle_call({reset_state}, _From, State) -> + {reply, ok, State#{ + status => started, + open_buffers => sets:new(), + in_progress => [], + in_progress_diagnostics => [] + }}. -spec handle_cast(any(), state()) -> {noreply, state()}. handle_cast({process_requests, Requests}, State0) -> @@ -132,6 +154,59 @@ handle_cast({response, Job, Result}, State0) -> handle_cast(_, State) -> {noreply, State}. +-spec handle_info(any(), els_server:state()) -> {noreply, els_server:state()}. +handle_info({result, Result, Job}, State0) -> + ?LOG_DEBUG("Received result [job=~p]", [Job]), + #{in_progress := InProgress} = State0, + State = do_send_response(Job, Result, State0), + NewState = State#{in_progress => lists:keydelete(Job, 2, InProgress)}, + {noreply, NewState}; +%% LSP 3.15 introduce versioning for diagnostics. Until all clients +%% support it, we need to keep track of old diagnostics and re-publish +%% them every time we get a new chunk. +handle_info({diagnostics, Diagnostics, Job}, State) -> + #{in_progress_diagnostics := InProgress} = State, + ?LOG_DEBUG("Received diagnostics [job=~p]", [Job]), + case find_entry(Job, InProgress) of + {ok, { + #{ + pending := Jobs, + diagnostics := OldDiagnostics, + uri := Uri + }, + Rest + }} -> + NewDiagnostics = Diagnostics ++ OldDiagnostics, + els_diagnostics_provider:publish(Uri, NewDiagnostics), + NewState = + case lists:delete(Job, Jobs) of + [] -> + State#{in_progress_diagnostics => Rest}; + Remaining -> + State#{ + in_progress_diagnostics => + [ + #{ + pending => Remaining, + diagnostics => NewDiagnostics, + uri => Uri + } + | Rest + ] + } + end, + {noreply, NewState}; + {error, not_found} -> + {noreply, State} + end; +handle_info(_Request, State) -> + {noreply, State}. + +-spec terminate(any(), els_server:state()) -> ok. +terminate(_Reason, #{in_progress := InProgress}) -> + [els_background_job:stop(Job) || {_Uri, Job} <- InProgress], + ok. + %%============================================================================== %% Internal Functions %%============================================================================== @@ -144,7 +219,7 @@ handle_request( State0 ) -> #{<<"id">> := Id} = Params, - #state{pending = Pending} = State0, + #{pending := Pending, in_progress := InProgress} = State0, case lists:keyfind(Id, 1, Pending) of false -> ?LOG_DEBUG( @@ -154,14 +229,18 @@ handle_request( State0; {RequestId, Job} when RequestId =:= Id -> ?LOG_DEBUG("[SERVER] Cancelling request [id=~p] [job=~p]", [Id, Job]), - els_provider:cancel_request(Job), - State0#state{pending = lists:keydelete(Id, 1, Pending)} + els_background_job:stop(Job), + State0#{ + pending => lists:keydelete(Id, 1, Pending), + in_progress => lists:keydelete(Job, 2, InProgress) + } end; handle_request( #{<<"method">> := _ReqMethod} = Request, - #state{ - internal_state = InternalState, - pending = Pending + #{ + pending := Pending, + in_progress := InProgress, + in_progress_diagnostics := InProgressDiagnostics } = State0 ) -> Method = maps:get(<<"method">>, Request), @@ -171,14 +250,14 @@ handle_request( true -> request; false -> notification end, - case els_methods:dispatch(Method, Params, Type, InternalState) of - {response, Result, NewInternalState} -> + case els_methods:dispatch(Method, Params, Type, State0) of + {response, Result, State} -> RequestId = maps:get(<<"id">>, Request), Response = els_protocol:response(RequestId, Result), ?LOG_DEBUG("[SERVER] Sending response [response=~s]", [Response]), send(Response, State0), - State0#state{internal_state = NewInternalState}; - {error, Error, NewInternalState} -> + State; + {error, Error, State} -> RequestId = maps:get(<<"id">>, Request, null), ErrorResponse = els_protocol:error(RequestId, Error), ?LOG_DEBUG( @@ -186,24 +265,27 @@ handle_request( [ErrorResponse] ), send(ErrorResponse, State0), - State0#state{internal_state = NewInternalState}; - {noresponse, NewInternalState} -> + State; + {noresponse, State} -> ?LOG_DEBUG("[SERVER] No response", []), - State0#state{internal_state = NewInternalState}; - {noresponse, BackgroundJob, NewInternalState} -> + State; + {async, Uri, BackgroundJob, State} -> RequestId = maps:get(<<"id">>, Request), ?LOG_DEBUG( "[SERVER] Suspending response [background_job=~p]", [BackgroundJob] ), NewPending = [{RequestId, BackgroundJob} | Pending], - State0#state{ - internal_state = NewInternalState, - pending = NewPending + State#{ + pending => NewPending, + in_progress => [{Uri, BackgroundJob} | InProgress] }; - {notification, M, P, NewInternalState} -> + {diagnostics, Uri, Jobs, State} -> + Entry = #{uri => Uri, pending => Jobs, diagnostics => []}, + State#{in_progress_diagnostics => [Entry | InProgressDiagnostics]}; + {notification, M, P, State} -> do_send_notification(M, P, State0), - State0#state{internal_state = NewInternalState} + State end; handle_request(Response, State0) -> ?LOG_DEBUG( @@ -227,7 +309,7 @@ do_send_notification(Method, Params, State) -> send(Notification, State). -spec do_send_request(binary(), map(), state()) -> state(). -do_send_request(Method, Params, #state{request_id = RequestId0} = State0) -> +do_send_request(Method, Params, #{request_id := RequestId0} = State0) -> RequestId = RequestId0 + 1, Request = els_protocol:request(RequestId, Method, Params), ?LOG_DEBUG( @@ -235,11 +317,11 @@ do_send_request(Method, Params, #state{request_id = RequestId0} = State0) -> [Request] ), send(Request, State0), - State0#state{request_id = RequestId}. + State0#{request_id => RequestId}. -spec do_send_response(pid(), any(), state()) -> state(). do_send_response(Job, Result, State0) -> - #state{pending = Pending0} = State0, + #{pending := Pending0} = State0, case lists:keyfind(Job, 2, Pending0) of false -> ?LOG_DEBUG( @@ -255,9 +337,28 @@ do_send_response(Job, Result, State0) -> ), send(Response, State0), Pending = lists:keydelete(RequestId, 1, Pending0), - State0#state{pending = Pending} + State0#{pending => Pending} end. -spec send(binary(), state()) -> ok. -send(Payload, #state{io_device = IoDevice}) -> +send(Payload, #{io_device := IoDevice}) -> els_stdio:send(IoDevice, Payload). + +-spec find_entry(pid(), [els_server:diagnostic_entry()]) -> + {ok, {els_server:diagnostic_entry(), [els_server:diagnostic_entry()]}} + | {error, not_found}. +find_entry(Job, InProgress) -> + find_entry(Job, InProgress, []). + +-spec find_entry(pid(), [els_server:diagnostic_entry()], [els_server:diagnostic_entry()]) -> + {ok, {els_server:diagnostic_entry(), [els_server:diagnostic_entry()]}} + | {error, not_found}. +find_entry(_Job, [], []) -> + {error, not_found}; +find_entry(Job, [#{pending := Pending} = Entry | Rest], Acc) -> + case lists:member(Job, Pending) of + true -> + {ok, {Entry, Rest ++ Acc}}; + false -> + find_entry(Job, Rest, [Entry | Acc]) + end. diff --git a/apps/els_lsp/src/els_signature_help_provider.erl b/apps/els_lsp/src/els_signature_help_provider.erl index ce9443874..86391a21f 100644 --- a/apps/els_lsp/src/els_signature_help_provider.erl +++ b/apps/els_lsp/src/els_signature_help_provider.erl @@ -7,7 +7,7 @@ -export([ is_enabled/0, - handle_request/2, + handle_request/1, trigger_characters/0 ]). @@ -26,8 +26,9 @@ trigger_characters() -> is_enabled() -> false. --spec handle_request(els_provider:request(), any()) -> {response, signature_help() | null}. -handle_request({signature_help, Params}, _State) -> +-spec handle_request(els_provider:provider_request()) -> + {response, signature_help() | null}. +handle_request({signature_help, Params}) -> #{ <<"position">> := #{ <<"line">> := Line, diff --git a/apps/els_lsp/src/els_sup.erl b/apps/els_lsp/src/els_sup.erl index 567eae62d..cbef27ec0 100644 --- a/apps/els_lsp/src/els_sup.erl +++ b/apps/els_lsp/src/els_sup.erl @@ -76,10 +76,6 @@ init([]) -> #{ id => els_server, start => {els_server, start_link, []} - }, - #{ - id => els_provider, - start => {els_provider, start_link, []} } ], {ok, {SupFlags, ChildSpecs}}. diff --git a/apps/els_lsp/src/els_text_synchronization_provider.erl b/apps/els_lsp/src/els_text_synchronization_provider.erl index 913d5cdbd..2fd80fc25 100644 --- a/apps/els_lsp/src/els_text_synchronization_provider.erl +++ b/apps/els_lsp/src/els_text_synchronization_provider.erl @@ -2,7 +2,7 @@ -behaviour(els_provider). -export([ - handle_request/2, + handle_request/1, options/0 ]). @@ -19,15 +19,15 @@ options() -> save => #{includeText => false} }. --spec handle_request(any(), any()) -> +-spec handle_request(any()) -> {diagnostics, uri(), [pid()]} | noresponse | {async, uri(), pid()}. -handle_request({did_open, Params}, _State) -> +handle_request({did_open, Params}) -> ok = els_text_synchronization:did_open(Params), #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)}; -handle_request({did_change, Params}, _State) -> +handle_request({did_change, Params}) -> #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, case els_text_synchronization:did_change(Params) of ok -> @@ -35,13 +35,13 @@ handle_request({did_change, Params}, _State) -> {ok, Job} -> {async, Uri, Job} end; -handle_request({did_save, Params}, _State) -> +handle_request({did_save, Params}) -> ok = els_text_synchronization:did_save(Params), #{<<"textDocument">> := #{<<"uri">> := Uri}} = Params, {diagnostics, Uri, els_diagnostics:run_diagnostics(Uri)}; -handle_request({did_close, Params}, _State) -> +handle_request({did_close, Params}) -> ok = els_text_synchronization:did_close(Params), noresponse; -handle_request({did_change_watched_files, Params}, _State) -> +handle_request({did_change_watched_files, Params}) -> ok = els_text_synchronization:did_change_watched_files(Params), noresponse. diff --git a/apps/els_lsp/src/els_workspace_symbol_provider.erl b/apps/els_lsp/src/els_workspace_symbol_provider.erl index a88d709fe..c2e57f304 100644 --- a/apps/els_lsp/src/els_workspace_symbol_provider.erl +++ b/apps/els_lsp/src/els_workspace_symbol_provider.erl @@ -4,23 +4,21 @@ -export([ is_enabled/0, - handle_request/2 + handle_request/1 ]). -include("els_lsp.hrl"). -define(LIMIT, 100). --type state() :: any(). - %%============================================================================== %% els_provider functions %%============================================================================== -spec is_enabled() -> boolean(). is_enabled() -> true. --spec handle_request(any(), state()) -> {response, any()}. -handle_request({symbol, Params}, _State) -> +-spec handle_request(any()) -> {response, any()}. +handle_request({symbol, Params}) -> %% TODO: Version 3.15 of the protocol introduces a much nicer way of %% specifying queries, allowing clients to send the symbol kind. #{<<"query">> := Query} = Params, diff --git a/apps/els_lsp/test/els_server_SUITE.erl b/apps/els_lsp/test/els_server_SUITE.erl index e199d43f3..b873f7ee2 100644 --- a/apps/els_lsp/test/els_server_SUITE.erl +++ b/apps/els_lsp/test/els_server_SUITE.erl @@ -108,6 +108,6 @@ wait_until_no_lens_jobs() -> -spec get_current_lens_jobs() -> [pid()]. get_current_lens_jobs() -> - State = sys:get_state(els_provider, 30 * 1000), + State = sys:get_state(els_server, 30 * 1000), #{in_progress := InProgress} = State, [Job || {_Uri, Job} <- InProgress]. diff --git a/apps/els_lsp/test/prop_statem.erl b/apps/els_lsp/test/prop_statem.erl index 74a2a8ec3..91bb1f7c7 100644 --- a/apps/els_lsp/test/prop_statem.erl +++ b/apps/els_lsp/test/prop_statem.erl @@ -396,7 +396,7 @@ cleanup() -> catch disconnect(), %% Restart the server, since though the client disconnects the %% server keeps its state. - els_server:reset_internal_state(), + els_server:reset_state(), ok. %%==============================================================================