Skip to content

Commit

Permalink
Merge pull request #599 from erlang-ls/597-customizable-code-lenses
Browse files Browse the repository at this point in the history
[#597] Refactor code lenses, making them customizable
  • Loading branch information
alanz authored Mar 20, 2020
2 parents a3352f2 + efd2a30 commit 30f2f88
Show file tree
Hide file tree
Showing 15 changed files with 254 additions and 103 deletions.
10 changes: 1 addition & 9 deletions include/erlang_ls.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,6 @@
, message := binary()
}.

%%------------------------------------------------------------------------------
%% Command
%%------------------------------------------------------------------------------
-type command() :: #{ title := binary()
, command := binary()
, arguments => [any()]
}.

%%------------------------------------------------------------------------------
%% Insert Text Format
%%------------------------------------------------------------------------------
Expand Down Expand Up @@ -608,7 +600,7 @@
, kind => code_action_kind()
, diagnostics => [diagnostic()]
, edit => workspace_edit()
, command => command()
, command => els_command:command()
}.

%%------------------------------------------------------------------------------
Expand Down
14 changes: 6 additions & 8 deletions src/els_code_action_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,15 @@ replace_lines_action(Uri, Title, Kind, Lines, Range) ->
, <<"end">> := #{ <<"character">> := _EndCol
, <<"line">> := EndLine }
} = Range,
PrefixedCommand
= els_execute_command_provider:add_server_prefix(<<"replace-lines">>),
#{ title => Title
, kind => Kind
, command =>
els_protocol:command( Title
, PrefixedCommand
, [#{ uri => Uri
, lines => Lines
, from => StartLine
, to => EndLine }])
els_command:make_command( Title
, <<"replace-lines">>
, [#{ uri => Uri
, lines => Lines
, from => StartLine
, to => EndLine }])
}.

-spec make_code_action(uri(), diagnostic()) -> [map()].
Expand Down
86 changes: 86 additions & 0 deletions src/els_code_lens.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
%%==============================================================================
%% Code Lens: Behaviour and API
%%==============================================================================

-module(els_code_lens).

%%==============================================================================
%% Callback Functions
%%==============================================================================

-callback command() -> els_command:command_id().
-callback is_default() -> boolean().
-callback lenses(els_dt_document:item()) -> [lens()].

%%==============================================================================
%% API
%%==============================================================================

-export([ available_lenses/0
, default_lenses/0
, enabled_lenses/0
, lenses/2
]).

%%==============================================================================
%% Constructors
%%==============================================================================

-export([ make_lens/3 ]).

%%==============================================================================
%% Includes
%%==============================================================================

-include("erlang_ls.hrl").

%%==============================================================================
%% Type Definitions
%%==============================================================================

-type lens() :: #{ range := range()
, command => els_command:command()
, data => any()
}.
-type lens_id() :: binary().
-export_type([ lens/0
, lens_id/0
]).

%%==============================================================================
%% API
%%==============================================================================

-spec available_lenses() -> [lens_id()].
available_lenses() ->
[<<"server-info">>].

-spec default_lenses() -> [lens_id()].
default_lenses() ->
[Id || Id <- available_lenses(), (cb_module(Id)):is_default()].

-spec enabled_lenses() -> [lens_id()].
enabled_lenses() ->
els_config:get(code_lenses).

-spec lenses(lens_id(), els_dt_document:item()) -> [lens()].
lenses(Id, Document) ->
CbModule = cb_module(Id),
CbModule:lenses(Document).

%%==============================================================================
%% Constructors
%%==============================================================================

-spec make_lens(range(), els_command:command(), any()) -> lens().
make_lens(Range, Command, Data) ->
#{ range => Range
, command => Command
, data => Data
}.

%% @doc Return the callback module for a given Code Lens Identifier
-spec cb_module(els_code_lens:lens_id()) -> module().
cb_module(Id0) ->
Id = re:replace(Id0, "-", "_", [global, {return, binary}]),
binary_to_atom(<<"els_code_lens_", Id/binary>>, utf8).
28 changes: 5 additions & 23 deletions src/els_code_lens_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,9 @@ handle_request({document_codelens, Params}, State) ->
%%==============================================================================
%% Internal Functions
%%==============================================================================
-spec lenses(uri()) -> [map()].
-spec lenses(uri()) -> [els_code_lens:lens()].
lenses(Uri) ->
{ok, _Document} = els_utils:lookup_document(Uri),
Command = els_execute_command_provider:add_server_prefix(<<"info">>),
Root = filename:basename(els_uri:path(els_config:get(root_uri))),
Lenses = [#{ range => one_line_range(1)
, command =>
make_command( <<"Erlang LS (in ", Root/binary, ") info">>
, Command
, [#{ uri => Uri }])
}],
Lenses.

-spec one_line_range(non_neg_integer()) -> range().
one_line_range(Line) ->
Range = #{from => {Line, 1}, to => {Line + 1, 1}},
els_protocol:range(Range).

-spec make_command(binary(), binary(), [any()]) -> command().
make_command(Title, Command, Args) ->
#{ title => Title
, command => Command
, arguments => Args
}.
{ok, Document} = els_utils:lookup_document(Uri),
lists:flatten(
[els_code_lens:lenses(Id, Document) ||
Id <- els_code_lens:enabled_lenses()]).
29 changes: 29 additions & 0 deletions src/els_code_lens_server_info.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
%%==============================================================================
%% Code Lens: server_info
%%==============================================================================

-module(els_code_lens_server_info).

-export([ command/0
, is_default/0
, lenses/1
]).

-behaviour(els_code_lens).

-spec command() -> els_command:command_id().
command() ->
<<"server-info">>.

-spec is_default() -> boolean().
is_default() ->
false.

%% @doc Given a Document, returns the available lenses
-spec lenses(els_dt_document:item()) -> [els_code_lens:lens()].
lenses(_Document) ->
Root = filename:basename(els_uri:path(els_config:get(root_uri))),
Title = <<"Erlang LS (in ", Root/binary, ") info">>,
Range = els_range:line(1),
Command = els_command:make_command(Title, command(), []),
[ els_code_lens:make_lens(Range, Command, []) ].
72 changes: 72 additions & 0 deletions src/els_command.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
%%==============================================================================
%% Command
%%==============================================================================
-module(els_command).

%%==============================================================================
%% API
%%==============================================================================
-export([ with_prefix/1
, without_prefix/1
]).

%%==============================================================================
%% Constructors
%%==============================================================================

-export([ make_command/3 ]).

%%==============================================================================
%% Type Definitions
%%==============================================================================

-type command() :: #{ title := binary()
, command := command_id()
, arguments => [any()]
}.
-type command_id() :: binary().
-export_type([ command/0
, command_id/0
]).

%%==============================================================================
%% API
%%==============================================================================

%% @doc Add a server-unique prefix to a command.
-spec with_prefix(command_id()) -> command_id().
with_prefix(Id) ->
Prefix = server_prefix(),
<<Prefix/binary, ":", Id/binary>>.

%% @doc Strip a server-unique prefix from a command.
-spec without_prefix(command_id()) -> command_id().
without_prefix(Id0) ->
case binary:split(Id0, <<":">>) of
[_, Id] -> Id;
[Id] -> Id
end.

%%==============================================================================
%% Constructors
%%==============================================================================

-spec make_command(binary(), command_id(), [any()]) -> command().
make_command(Title, CommandId, Args) ->
#{ title => Title
, command => with_prefix(CommandId)
, arguments => Args
}.

%%==============================================================================
%% Internal Functions
%%==============================================================================

%% @doc Generate a prefix unique to this running erlang_ls server.
%%
%% This is needed because some clients have a global namespace for all
%% registered commands, and we need to be able to run multiple
%% erlang_ls instances at the same time against a single client.
-spec server_prefix() -> binary().
server_prefix() ->
els_utils:to_binary(os:getpid()).
4 changes: 4 additions & 0 deletions src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
-type key() :: apps_dirs
| apps_paths
| capabilities
| code_lenses
| deps_dirs
| deps_paths
| include_dirs
Expand All @@ -51,6 +52,7 @@
-type path() :: file:filename().
-type state() :: #{ apps_dirs => [path()]
, apps_paths => [path()]
, code_lenses => [els_code_lens:lens()]
, deps_dirs => [path()]
, deps_paths => [path()]
, include_dirs => [path()]
Expand Down Expand Up @@ -87,6 +89,7 @@ do_initialize(RootUri, Capabilities, {ConfigPath, Config}) ->
, Config
, ?DEFAULT_EXCLUDED_OTP_APPS
),
CodeLenses = maps:get("code_lenses", Config, els_code_lens:default_lenses()),
ExcludePathsSpecs = [[OtpPath, "lib", P ++ "*"] || P <- OtpAppsExclude],
ExcludePaths = els_utils:resolve_paths(ExcludePathsSpecs, RootPath, true),
lager:info("Excluded OTP Applications: ~p", [OtpAppsExclude]),
Expand All @@ -108,6 +111,7 @@ do_initialize(RootUri, Capabilities, {ConfigPath, Config}) ->
ok = set(deps_paths , project_paths(RootPath, DepsDirs, false)),
ok = set(include_paths , include_paths(RootPath, IncludeDirs, false)),
ok = set(otp_paths , otp_paths(OtpPath, false) -- ExcludePaths),
ok = set(code_lenses , CodeLenses),
%% All (including subdirs) paths used to search files with file:path_open/3
ok = set( search_paths
, lists:append([ project_paths(RootPath, AppsDirs, true)
Expand Down
37 changes: 6 additions & 31 deletions src/els_execute_command_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
-export([ handle_request/2
, is_enabled/0
, options/0
, add_server_prefix/1
, strip_server_prefix/1
]).

-include("erlang_ls.hrl").
Expand All @@ -20,22 +18,23 @@ is_enabled() -> true.

-spec options() -> map().
options() ->
#{ commands => [ add_server_prefix(<<"replace-lines">>)
, add_server_prefix(<<"info">>)] }.
#{ commands => [ els_command:with_prefix(<<"replace-lines">>)
, els_command:with_prefix(<<"server-info">>)] }.

-spec handle_request(any(), els_provider:state()) ->
{any(), els_provider:state()}.
handle_request({workspace_executecommand, Params}, State) ->
#{ <<"command">> := PrefixedCommand } = Params,
Arguments = maps:get(<<"arguments">>, Params, []),
Result = execute_command(strip_server_prefix(PrefixedCommand), Arguments),
Result = execute_command( els_command:without_prefix(PrefixedCommand)
, Arguments),
{Result, State}.

%%==============================================================================
%% Internal Functions
%%==============================================================================

-spec execute_command(binary(), [any()]) -> [map()].
-spec execute_command(els_command:command_id(), [any()]) -> [map()].
execute_command(<<"replace-lines">>
, [#{ <<"uri">> := Uri
, <<"lines">> := Lines
Expand All @@ -47,8 +46,7 @@ execute_command(<<"replace-lines">>
},
els_server:send_request(Method, Params),
[];
execute_command(<<"info">>
, [#{ <<"uri">> := _Uri }] = _Arguments) ->
execute_command(<<"server-info">>, _Arguments) ->
{ok, Version} = application:get_key(?APP, vsn),
BinVersion = list_to_binary(Version),
Root = filename:basename(els_uri:path(els_config:get(root_uri))),
Expand All @@ -70,26 +68,3 @@ execute_command(Command, Arguments) ->
lager:info("Unsupported command: [Command=~p] [Arguments=~p]"
, [Command, Arguments]),
[].


%% @doc Strip a server-unique prefix from a command.
-spec strip_server_prefix(binary()) -> binary().
strip_server_prefix(PrefixedCommand) ->
case binary:split(PrefixedCommand, <<":">>) of
[_, Command] -> Command;
[Command] -> Command
end.

%% @doc Add a server-unique prefix to a command.
-spec add_server_prefix(binary()) -> binary().
add_server_prefix(Command) ->
Prefix = server_prefix(),
<<Prefix/binary, ":", Command/binary>>.

%% @doc Generate a prefix unique to this running erlang_ls server. This is
%% needed because some clients have a global namespace for all registered
%% commands, and we need to be able to run multiple erlang_ls instances at the
%% same time against a single client.
-spec server_prefix() -> binary().
server_prefix() ->
els_utils:to_binary(os:getpid()).
4 changes: 2 additions & 2 deletions src/els_methods.erl
Original file line number Diff line number Diff line change
Expand Up @@ -407,14 +407,14 @@ textdocument_codeaction(Params, State) ->
{document_codeaction, Params}),
{response, Response, State}.

%%==============================================================================
%% textDocument/codeLens
%%==============================================================================

-spec textdocument_codelens(params(), state()) -> result().
textdocument_codelens(Params, State) ->
Provider = els_code_lens_provider,
Response = els_provider:handle_request(Provider,
{document_codelens, Params}),
Response = els_provider:handle_request(Provider, {document_codelens, Params}),
{response, Response, State}.

%%==============================================================================
Expand Down
Loading

0 comments on commit 30f2f88

Please sign in to comment.