From 987456e9db68280103840d31588ad04897fd2f4c Mon Sep 17 00:00:00 2001 From: Tim Gent Date: Fri, 21 Oct 2022 12:22:22 +0100 Subject: [PATCH] Implement rename across multiple files --- .../lib/language_server/providers/rename.ex | 31 +++++++---- .../test/providers/rename_test.exs | 52 ++++++++++++++++++- .../test/support/fixtures/rename_example.ex | 2 +- .../test/support/fixtures/rename_example_b.ex | 3 ++ 4 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 apps/language_server/test/support/fixtures/rename_example_b.ex diff --git a/apps/language_server/lib/language_server/providers/rename.ex b/apps/language_server/lib/language_server/providers/rename.ex index 5f524fe12..2897e9d9b 100644 --- a/apps/language_server/lib/language_server/providers/rename.ex +++ b/apps/language_server/lib/language_server/providers/rename.ex @@ -9,8 +9,8 @@ defmodule ElixirLS.LanguageServer.Providers.Rename do def rename(%SourceFile{} = source_file, start_uri, line, character, new_name) do edits = - with %{context: {context, char_ident}} when context in [:local_or_var, :local_call] <- - Code.Fragment.surround_context(source_file.text, {line, character}), + with char_ident when not is_nil(char_ident) <- + get_char_ident(source_file.text, line, character), %ElixirSense.Location{} = definition <- ElixirSense.definition(source_file.text, line, character), references <- ElixirSense.references(source_file.text, line, character) do @@ -18,11 +18,16 @@ defmodule ElixirLS.LanguageServer.Providers.Rename do definition_references = case definition do - %{type: :function} -> - parse_definition_source_code(definition, source_file.text) + %{file: nil, type: :function} -> + parse_definition_source_code(source_file.text) |> get_all_fn_header_positions(char_ident) |> positions_to_references(start_uri, length_old) + %{file: separate_file_path, type: :function} -> + parse_definition_source_code(definition) + |> get_all_fn_header_positions(char_ident) + |> positions_to_references(SourceFile.path_to_uri(separate_file_path), length_old) + _ -> positions_to_references( [{definition.line, definition.column}], @@ -93,14 +98,12 @@ defmodule ElixirLS.LanguageServer.Providers.Rename do end end - defp parse_definition_source_code(definition, source_text) - - defp parse_definition_source_code(%{file: nil}, source_text) do - ElixirSense.Core.Parser.parse_string(source_text, true, true, 0) + defp parse_definition_source_code(%{file: file}) do + ElixirSense.Core.Parser.parse_file(file, true, true, 0) end - defp parse_definition_source_code(%{file: file}, _) do - ElixirSense.Core.Parser.parse_file(file, true, true, 0) + defp parse_definition_source_code(source_text) when is_binary(source_text) do + ElixirSense.Core.Parser.parse_string(source_text, true, true, 0) end defp get_all_fn_header_positions(parsed_source, char_ident) do @@ -129,4 +132,12 @@ defmodule ElixirLS.LanguageServer.Providers.Rename do end: %{line: end_line - 1, character: end_character - 1} } end + + defp get_char_ident(text, line, character) do + case Code.Fragment.surround_context(text, {line, character}) do + %{context: {context, char_ident}} when context in [:local_or_var, :local_call] -> char_ident + %{context: {:dot, _, char_ident}} -> char_ident + _ -> nil + end + end end diff --git a/apps/language_server/test/providers/rename_test.exs b/apps/language_server/test/providers/rename_test.exs index c20a9b563..934ac38a3 100644 --- a/apps/language_server/test/providers/rename_test.exs +++ b/apps/language_server/test/providers/rename_test.exs @@ -112,7 +112,7 @@ defmodule ElixirLS.LanguageServer.Providers.RenameTest do assert sort_edit_by_start_line(edits) == expected_edits end - test "rename function with multiple heads: handle_error -> handle_errors" do + test "rename function with multiple heads: add -> new_add" do file_path = FixtureHelpers.get_path("rename_example.ex") text = File.read!(file_path) uri = SourceFile.path_to_uri(file_path) @@ -142,6 +142,56 @@ defmodule ElixirLS.LanguageServer.Providers.RenameTest do assert sort_edit_by_start_line(edits) == expected_edits end + + test "rename function defined in a different file ten -> new_ten" do + file_path = FixtureHelpers.get_path("rename_example.ex") + text = File.read!(file_path) + uri = SourceFile.path_to_uri(file_path) + + fn_definition_file_uri = + FixtureHelpers.get_path("rename_example_b.ex") |> SourceFile.path_to_uri() + + # b = ElixirLS.Test.RenameExampleB.ten() + {line, char} = {4, 38} + + assert {:ok, + %{ + "documentChanges" => [ + %{ + "textDocument" => %{ + "uri" => ^uri, + "version" => 1 + }, + "edits" => file_edits + }, + %{ + "textDocument" => %{ + "uri" => ^fn_definition_file_uri, + "version" => 1 + }, + "edits" => fn_definition_file_edits + } + ] + }} = + Rename.rename( + %SourceFile{text: text, version: 0}, + uri, + line, + char, + "new_ten" + ) + + expected_edits_fn_definition_file = + [%{line: 1, start_char: 6, end_char: 9}] |> get_expected_edits("new_ten") + + assert sort_edit_by_start_line(fn_definition_file_edits) == + expected_edits_fn_definition_file + + expected_edits = [%{line: 3, start_char: 37, end_char: 40}] |> get_expected_edits("new_ten") + + assert sort_edit_by_start_line(file_edits) == + expected_edits + end end describe "not yet (fully) supported/working renaming cases" do diff --git a/apps/language_server/test/support/fixtures/rename_example.ex b/apps/language_server/test/support/fixtures/rename_example.ex index df86abea5..2110e22a3 100644 --- a/apps/language_server/test/support/fixtures/rename_example.ex +++ b/apps/language_server/test/support/fixtures/rename_example.ex @@ -1,7 +1,7 @@ defmodule ElixirLS.Test.RenameExample do def main do a = 5 - b = 10 + b = ElixirLS.Test.RenameExampleB.ten() c = add(a, b) d = subtract(a, b) add(c, d) diff --git a/apps/language_server/test/support/fixtures/rename_example_b.ex b/apps/language_server/test/support/fixtures/rename_example_b.ex new file mode 100644 index 000000000..06839db1d --- /dev/null +++ b/apps/language_server/test/support/fixtures/rename_example_b.ex @@ -0,0 +1,3 @@ +defmodule ElixirLS.Test.RenameExampleB do + def ten, do: 10 +end