Skip to content

Commit

Permalink
Implement rename across multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
timgent committed Oct 21, 2022
1 parent 1daf1c8 commit 987456e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 12 deletions.
31 changes: 21 additions & 10 deletions apps/language_server/lib/language_server/providers/rename.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ 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
length_old = length(char_ident)

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}],
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
52 changes: 51 additions & 1 deletion apps/language_server/test/providers/rename_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule ElixirLS.Test.RenameExampleB do
def ten, do: 10
end

0 comments on commit 987456e

Please sign in to comment.