Skip to content

Commit

Permalink
Add support for bump variables code action (#1553)
Browse files Browse the repository at this point in the history
  • Loading branch information
plux authored Sep 30, 2024
1 parent 0a15a75 commit 3342c36
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 3 deletions.
3 changes: 2 additions & 1 deletion apps/els_lsp/src/els_code_action_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ code_actions(Uri, Range, #{<<"diagnostics">> := Diagnostics}) ->
lists:usort(
lists:flatten([make_code_actions(Uri, D) || D <- Diagnostics]) ++
wrangler_handler:get_code_actions(Uri, Range) ++
els_code_actions:extract_function(Uri, Range)
els_code_actions:extract_function(Uri, Range) ++
els_code_actions:bump_variables(Uri, Range)
).

-spec make_code_actions(uri(), map()) -> [map()].
Expand Down
41 changes: 40 additions & 1 deletion apps/els_lsp/src/els_code_actions.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
add_include_lib_record/4,
suggest_macro/4,
suggest_record/4,
suggest_record_field/4
suggest_record_field/4,
bump_variables/2
]).

-include("els_lsp.hrl").
Expand Down Expand Up @@ -440,6 +441,36 @@ extract_function(Uri, Range) ->
[]
end.

-spec bump_variables(uri(), range()) -> [map()].
bump_variables(Uri, Range) ->
{ok, Document} = els_utils:lookup_document(Uri),
#{from := {Line, Column}} = els_range:to_poi_range(Range),
POIs = els_dt_document:get_element_at_pos(Document, Line, Column),
case [POI || #{kind := variable} = POI <- POIs] of
[] ->
[];
[#{id := Id, range := PoiRange} = _POI | _] ->
Name = atom_to_binary(Id),
case ends_with_digit(Name) of
false ->
[];
true ->
VarRange = els_protocol:range(PoiRange),
[
#{
title => <<"Bump variables: ", Name/binary>>,
kind => ?CODE_ACTION_KIND_QUICKFIX,
command => make_bump_variables_command(VarRange, Uri, Name)
}
]
end
end.

-spec ends_with_digit(binary()) -> boolean().
ends_with_digit(Bin) ->
N = binary:last(Bin),
$0 =< N andalso N =< $9.

-spec make_extract_function_command(range(), uri()) -> map().
make_extract_function_command(Range, Uri) ->
els_command:make_command(
Expand All @@ -448,6 +479,14 @@ make_extract_function_command(Range, Uri) ->
[#{uri => Uri, range => Range}]
).

-spec make_bump_variables_command(range(), uri(), binary()) -> map().
make_bump_variables_command(Range, Uri, Name) ->
els_command:make_command(
<<"Bump variables">>,
<<"bump-variables">>,
[#{uri => Uri, range => Range, name => Name}]
).

-spec contains_function_clause(
els_dt_document:item(),
non_neg_integer()
Expand Down
69 changes: 68 additions & 1 deletion apps/els_lsp/src/els_execute_command_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ options() ->
<<"suggest-spec">>,
<<"function-references">>,
<<"refactor.extract">>,
<<"add-behaviour-callbacks">>
<<"add-behaviour-callbacks">>,
<<"bump-variables">>
],
#{
commands => [
Expand Down Expand Up @@ -115,6 +116,15 @@ execute_command(<<"refactor.extract">>, [
]) ->
ok = extract_function(Uri, Range),
[];
execute_command(<<"bump-variables">>, [
#{
<<"uri">> := Uri,
<<"range">> := Range,
<<"name">> := Name
}
]) ->
ok = bump_variables(Uri, Range, Name),
[];
execute_command(<<"add-behaviour-callbacks">>, [
#{
<<"uri">> := Uri,
Expand Down Expand Up @@ -206,6 +216,63 @@ execute_command(Command, Arguments) ->
end,
[].

-spec bump_variables(uri(), range(), binary()) -> ok.
bump_variables(Uri, Range, VarName) ->
{Name, Number} = split_variable(VarName),
{ok, Document} = els_utils:lookup_document(Uri),
VarPOIs = els_poi:sort(els_dt_document:pois(Document, [variable])),
VarRange = els_range:to_poi_range(Range),
ScopeRange = els_scope:variable_scope_range(VarRange, Document),
Changes =
[
bump_variable_change(POI)
|| POI <- pois_in(VarPOIs, ScopeRange),
should_bump_variable(POI, Name, Number)
],
Method = <<"workspace/applyEdit">>,
Params = #{edit => #{changes => #{Uri => Changes}}},
els_server:send_request(Method, Params).

-spec should_bump_variable(els_poi:poi(), binary(), binary()) -> boolean().
should_bump_variable(#{id := Id}, Name, Number) ->
case split_variable(Id) of
{PName, PNumber} when PName == Name ->
binary_to_integer(PNumber) >= binary_to_integer(Number);
_ ->
false
end.

-spec bump_variable_change(els_poi:poi()) -> map().
bump_variable_change(#{id := Id, range := PoiRange}) ->
{Name, Number} = split_variable(Id),
NewNumber = integer_to_binary(binary_to_integer(Number) + 1),
NewId = binary_to_atom(<<Name/binary, NewNumber/binary>>, utf8),
#{
newText => NewId,
range => els_protocol:range(PoiRange)
}.

-spec pois_in([els_poi:poi()], els_poi:poi_range()) ->
[els_poi:poi()].
pois_in(POIs, Range) ->
[POI || #{range := R} = POI <- POIs, els_range:in(R, Range)].

-spec split_variable(atom() | binary() | list()) -> {binary(), binary()} | error.
split_variable(Name) when is_atom(Name) ->
split_variable(atom_to_list(Name));
split_variable(Name) when is_binary(Name) ->
split_variable(unicode:characters_to_list(Name));
split_variable(Name) when is_list(Name) ->
split_variable(lists:reverse(Name), []).

-spec split_variable(string(), string()) -> {binary(), binary()} | error.
split_variable([H | T], Acc) when $0 =< H, H =< $9 ->
split_variable(T, [H | Acc]);
split_variable(_Name, []) ->
error;
split_variable(Name, Acc) ->
{list_to_binary(lists:reverse(Name)), list_to_binary(Acc)}.

-spec extract_function(uri(), range()) -> ok.
extract_function(Uri, Range) ->
{ok, [#{text := Text} = Document]} = els_dt_document:lookup(Uri),
Expand Down

0 comments on commit 3342c36

Please sign in to comment.