From 3342c367b3469e6124410f6a18228f94a829cb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kan=20Nilsson?= Date: Mon, 30 Sep 2024 15:08:50 +0200 Subject: [PATCH] Add support for bump variables code action (#1553) --- apps/els_lsp/src/els_code_action_provider.erl | 3 +- apps/els_lsp/src/els_code_actions.erl | 41 ++++++++++- .../src/els_execute_command_provider.erl | 69 ++++++++++++++++++- 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/apps/els_lsp/src/els_code_action_provider.erl b/apps/els_lsp/src/els_code_action_provider.erl index 53e3d868b..3e4dc00c7 100644 --- a/apps/els_lsp/src/els_code_action_provider.erl +++ b/apps/els_lsp/src/els_code_action_provider.erl @@ -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()]. diff --git a/apps/els_lsp/src/els_code_actions.erl b/apps/els_lsp/src/els_code_actions.erl index 6476d976e..9f367d636 100644 --- a/apps/els_lsp/src/els_code_actions.erl +++ b/apps/els_lsp/src/els_code_actions.erl @@ -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"). @@ -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( @@ -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() diff --git a/apps/els_lsp/src/els_execute_command_provider.erl b/apps/els_lsp/src/els_execute_command_provider.erl index f1b39b3e2..014ada2a8 100644 --- a/apps/els_lsp/src/els_execute_command_provider.erl +++ b/apps/els_lsp/src/els_execute_command_provider.erl @@ -25,7 +25,8 @@ options() -> <<"suggest-spec">>, <<"function-references">>, <<"refactor.extract">>, - <<"add-behaviour-callbacks">> + <<"add-behaviour-callbacks">>, + <<"bump-variables">> ], #{ commands => [ @@ -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, @@ -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(<>, 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),