Skip to content

Commit

Permalink
add a configuration option for advertised LSP providers
Browse files Browse the repository at this point in the history
  • Loading branch information
the-mikedavis committed Jun 17, 2022
1 parent 637022b commit c955e9e
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 15 deletions.
8 changes: 6 additions & 2 deletions apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
| indexing_enabled
| compiler_telemetry_enabled
| refactorerl
| edoc_custom_tags.
| edoc_custom_tags
| providers.

-type path() :: file:filename().
-type state() :: #{
Expand All @@ -79,7 +80,8 @@
code_reload => map() | 'disabled',
indexing_enabled => boolean(),
compiler_telemetry_enabled => boolean(),
refactorerl => map() | 'notconfigured'
refactorerl => map() | 'notconfigured',
providers => map()
}.

%%==============================================================================
Expand Down Expand Up @@ -147,6 +149,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true),

RefactorErl = maps:get("refactorerl", Config, notconfigured),
Providers = maps:get("providers", Config, #{}),

%% Passed by the LSP client
ok = set(root_uri, RootUri),
Expand All @@ -160,6 +163,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) ->
ok = set(macros, Macros),
ok = set(plt_path, DialyzerPltPath),
ok = set(code_reload, CodeReload),
ok = set(providers, Providers),
?LOG_INFO("Config=~p", [Config]),
ok = set(
runtime,
Expand Down
121 changes: 109 additions & 12 deletions apps/els_lsp/src/els_general_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
-behaviour(els_provider).
-export([
is_enabled/0,
default_providers/0,
enabled_providers/0,
handle_request/1
]).

Expand Down Expand Up @@ -44,6 +46,7 @@
-type exit_request() :: {exit, exit_params()}.
-type exit_params() :: #{status => atom()}.
-type exit_result() :: null.
-type provider_id() :: string().

%%==============================================================================
%% els_provider functions
Expand Down Expand Up @@ -111,10 +114,60 @@ handle_request({exit, #{status := Status}}) ->
%% API
%%==============================================================================

%% @doc Give all available providers
-spec available_providers() -> [provider_id()].
available_providers() ->
[
"text-document-sync",
"hover",
"completion",
"signature-help",
"definition",
"references",
"document-highlight",
"document-symbol",
"workspace-symbol",
"code-action",
"document-formatting",
"document-range-formatting",
"document-on-type-formatting",
"folding-range",
"implementation",
"execute-command",
"code-lens",
"rename",
"call-hierarchy"
].

%% @doc Give the list of all providers enabled by default.
-spec default_providers() -> [provider_id()].
default_providers() ->
available_providers() --
[
"document-range-formatting",
%% NOTE: because erlang_ls does not send incremental document changes
%% via `textDocument/didChange', this kind of formatting does not
%% make sense.
"document-on-type-formatting",
%% Signature help is experimental.
"signature-help"
].

%% @doc Give the list of all providers enabled by the current configuration.
-spec enabled_providers() -> [provider_id()].
enabled_providers() ->
Config = els_config:get(providers),
Default = default_providers(),
Enabled = maps:get("enabled", Config, []),
Disabled = maps:get("disabled", Config, []),
lists:usort((Default ++ valid(Enabled)) -- valid(Disabled)).

%% @doc Give the LSP server capabilities map for all capabilities enabled by
%% the current configuration.
-spec server_capabilities() -> server_capabilities().
server_capabilities() ->
{ok, Version} = application:get_key(?APP, vsn),
Capabilities =
AvailableCapabilities =
#{
textDocumentSync =>
els_text_synchronization_provider:options(),
Expand Down Expand Up @@ -159,18 +212,16 @@ server_capabilities() ->
callHierarchyProvider =>
els_call_hierarchy_provider:is_enabled()
},
ActiveCapabilities =
case els_signature_help_provider:is_enabled() of
%% This pattern can never match because is_enabled/0 is currently
%% hard-coded to `false'. When enabling signature help manually,
%% uncomment this branch.
%% true ->
%% Capabilities;
false ->
maps:remove(signatureHelpProvider, Capabilities)
end,
EnabledProviders = enabled_providers(),
ConfiguredCapabilities =
maps:filter(
fun(Provider, _Config) ->
lists:member(provider_id(Provider), EnabledProviders)
end,
AvailableCapabilities
),
#{
capabilities => ActiveCapabilities,
capabilities => ConfiguredCapabilities,
serverInfo =>
#{
name => <<"Erlang LS">>,
Expand Down Expand Up @@ -213,3 +264,49 @@ dynamic_registration_options(<<"didChangeWatchedFiles">>) ->
watchers => [#{globPattern => GlobPattern}]
}
}.

-spec valid([any()]) -> [provider_id()].
valid(ProviderIds) ->
{Valid, Invalid} = lists:partition(fun is_valid_provider_id/1, ProviderIds),
case Invalid of
[] ->
ok;
_ ->
Fmt = "Discarding invalid providers in config file: ~p",
Args = [Invalid],
Msg = lists:flatten(io_lib:format(Fmt, Args)),
?LOG_WARNING(Msg),
els_server:send_notification(
<<"window/showMessage">>,
#{
type => ?MESSAGE_TYPE_WARNING,
message => els_utils:to_binary(Msg)
}
)
end,
Valid.

-spec is_valid_provider_id(any()) -> boolean().
is_valid_provider_id(ProviderId) ->
lists:member(ProviderId, available_providers()).

-spec provider_id(atom()) -> provider_id().
provider_id(textDocumentSync) -> "text-document-sync";
provider_id(completionProvider) -> "completion";
provider_id(hoverProvider) -> "hover";
provider_id(signatureHelpProvider) -> "signature-help";
provider_id(definitionProvider) -> "definition";
provider_id(referencesProvider) -> "references";
provider_id(documentHighlightProvider) -> "document-highlight";
provider_id(documentSymbolProvider) -> "document-symbol";
provider_id(workspaceSymbolProvider) -> "workspace-symbol";
provider_id(codeActionProvider) -> "code-action";
provider_id(documentFormattingProvider) -> "document-formatting";
provider_id(documentRangeFormattingProvider) -> "document-range-formatting";
provider_id(documentOnTypeFormattingProvider) -> "document-on-type-formatting";
provider_id(foldingRangeProvider) -> "folding-range";
provider_id(implementationProvider) -> "implementation";
provider_id(executeCommandProvider) -> "execute-command";
provider_id(codeLensProvider) -> "code-lens";
provider_id(renameProvider) -> "rename";
provider_id(callHierarchyProvider) -> "call-hierarchy".
47 changes: 46 additions & 1 deletion apps/els_lsp/test/els_initialization_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
initialize_diagnostics_invalid/1,
initialize_lenses_default/1,
initialize_lenses_custom/1,
initialize_lenses_invalid/1
initialize_lenses_invalid/1,
initialize_providers_default/1,
initialize_providers_custom/1,
initialize_providers_invalid/1
]).

%%==============================================================================
Expand Down Expand Up @@ -210,3 +213,45 @@ initialize_lenses_invalid(Config) ->
],
?assertEqual(Expected, Result),
ok.

-spec initialize_providers_default(config()) -> ok.
initialize_providers_default(Config) ->
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "providers_default.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),
Result = els_general_provider:enabled_providers(),
Expected = lists:usort(els_general_provider:default_providers()),
?assertEqual(Expected, Result),
#{capabilities := Capabilities} = els_general_provider:server_capabilities(),
?assertEqual(true, maps:is_key(hoverProvider, Capabilities)),
ok.

-spec initialize_providers_custom(config()) -> ok.
initialize_providers_custom(Config) ->
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "providers_custom.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),
EnabledProviders = els_general_provider:enabled_providers(),
?assertEqual(false, lists:member("hover", EnabledProviders)),
?assertEqual(true, lists:member("document-on-type-formatting", EnabledProviders)),
#{capabilities := Capabilities} = els_general_provider:server_capabilities(),
?assertEqual(false, maps:is_key(hoverProvider, Capabilities)),
ok.

-spec initialize_providers_invalid(config()) -> ok.
initialize_providers_invalid(Config) ->
RootUri = els_test_utils:root_uri(),
DataDir = ?config(data_dir, Config),
ConfigPath = filename:join(DataDir, "providers_invalid.config"),
InitOpts = #{<<"erlang">> => #{<<"config_path">> => ConfigPath}},
els_client:initialize(RootUri, InitOpts),
Result = els_general_provider:enabled_providers(),
Expected = lists:usort(els_general_provider:default_providers()),
?assertEqual(Expected, Result),
#{capabilities := Capabilities} = els_general_provider:server_capabilities(),
?assertEqual(true, maps:is_key(hoverProvider, Capabilities)),
ok.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
providers:
enabled:
- document-on-type-formatting
disabled:
- hover
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
providers:
enabled:
- hover
disabled:
- ssignaturee-hhelpp # Typos intentional

0 comments on commit c955e9e

Please sign in to comment.