Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Renaming module #636

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file's contents are auto-generated. Do not edit.
defmodule Lexical.Protocol.Types.PrepareRename.Params do
alias Lexical.Proto
alias Lexical.Protocol.Types
use Proto

deftype position: Types.Position,
text_document: Types.TextDocument.Identifier,
work_done_token: optional(Types.Progress.Token)
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# This file's contents are auto-generated. Do not edit.
defmodule Lexical.Protocol.Types.PrepareRenameResult do
alias Lexical.Proto
alias Lexical.Protocol.Types

defmodule PrepareRenameResult do
use Proto
deftype placeholder: string(), range: Types.Range
end

defmodule PrepareRenameResult1 do
use Proto
deftype default_behavior: boolean()
end

use Proto

defalias one_of([
Types.Range,
Lexical.Protocol.Types.PrepareRenameResult.PrepareRenameResult,
Lexical.Protocol.Types.PrepareRenameResult.PrepareRenameResult1
])
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file's contents are auto-generated. Do not edit.
defmodule Lexical.Protocol.Types.Rename.Params do
alias Lexical.Proto
alias Lexical.Protocol.Types
use Proto

deftype new_name: string(),
position: Types.Position,
text_document: Types.TextDocument.Identifier,
work_done_token: optional(Types.Progress.Token)
end
12 changes: 12 additions & 0 deletions apps/protocol/lib/lexical/protocol/requests.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ defmodule Lexical.Protocol.Requests do
defrequest "workspace/symbol", Types.Workspace.Symbol.Params
end

defmodule PrepareRename do
use Proto

defrequest "textDocument/prepareRename", Types.PrepareRename.Params
end

defmodule Rename do
use Proto

defrequest "textDocument/rename", Types.Rename.Params
end

# Server -> Client requests

defmodule RegisterCapability do
Expand Down
12 changes: 12 additions & 0 deletions apps/protocol/lib/lexical/protocol/responses.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,17 @@ defmodule Lexical.Protocol.Responses do
defresponse optional(Types.Message.ActionItem)
end

defmodule PrepareRename do
use Proto

defresponse Types.PrepareRenameResult
end

defmodule Rename do
use Proto

defresponse optional(Types.Workspace.Edit)
end

use Typespecs, for: :responses
end
4 changes: 4 additions & 0 deletions apps/remote_control/lib/lexical/remote_control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ defmodule Lexical.RemoteControl do

defdelegate workspace_symbols(query), to: CodeIntelligence.Symbols, as: :for_workspace

defdelegate maybe_update_rename_progress(triggered_message),
to: RemoteControl.Commands.Rename,
as: :update_progress

def start_link(%Project{} = project) do
:ok = ensure_epmd_started()
start_net_kernel(project)
Expand Down
28 changes: 28 additions & 0 deletions apps/remote_control/lib/lexical/remote_control/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Lexical.RemoteControl.Api do
alias Lexical.Project
alias Lexical.RemoteControl
alias Lexical.RemoteControl.CodeIntelligence
alias Lexical.RemoteControl.CodeMod

require Logger

Expand Down Expand Up @@ -54,6 +55,33 @@ defmodule Lexical.RemoteControl.Api do
])
end

def prepare_rename(
%Project{} = project,
%Analysis{} = analysis,
%Position{} = position
) do
RemoteControl.call(project, CodeMod.Rename, :prepare, [analysis, position])
end

def rename(
%Project{} = project,
%Analysis{} = analysis,
%Position{} = position,
new_name,
client_name
) do
RemoteControl.call(project, CodeMod.Rename, :rename, [
analysis,
position,
new_name,
client_name
])
end

def maybe_update_rename_progress(project, updated_message) do
RemoteControl.call(project, RemoteControl, :maybe_update_rename_progress, [updated_message])
end

def complete(%Project{} = project, %Env{} = env) do
Logger.info("Completion for #{inspect(env.position)}")
RemoteControl.call(project, RemoteControl, :complete, [env])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ defmodule Lexical.RemoteControl.Api.Messages do
diagnostics: [],
elapsed_ms: 0

defrecord :file_saved, uri: nil

defrecord :file_deleted, project: nil, uri: nil

defrecord :module_updated, file: nil, name: nil, functions: [], macros: [], struct: nil
Expand Down Expand Up @@ -95,6 +97,8 @@ defmodule Lexical.RemoteControl.Api.Messages do
elapsed_ms: non_neg_integer
)

@type file_saved :: record(:file_saved, uri: Lexical.uri())

@type module_updated ::
record(:module_updated,
name: module(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Lexical.RemoteControl.Application do
[
RemoteControl.Api.Proxy,
RemoteControl.Commands.Reindex,
RemoteControl.Commands.RenameSupervisor,
RemoteControl.Module.Loader,
{RemoteControl.Dispatch, progress: true},
RemoteControl.ModuleMappings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
)
end

@spec phoenix_controller_module?(module()) :: boolean()
def phoenix_controller_module?(module) do
function_exists?(module, :call, 2) and function_exists?(module, :action, 2)
end

@spec phoenix_liveview_module?(module()) :: boolean()
def phoenix_liveview_module?(module) do
function_exists?(module, :mount, 3) and function_exists?(module, :render, 1)
end

@spec phoenix_component_module?(module()) :: boolean()
def phoenix_component_module?(module) do
function_exists?(module, :__components__, 0) and
function_exists?(module, :__phoenix_component_verify__, 1)
end

scohen marked this conversation as resolved.
Show resolved Hide resolved
defp check_commented(%Analysis{} = analysis, %Position{} = position) do
if Analysis.commented?(analysis, position) do
:error
Expand Down Expand Up @@ -294,14 +310,6 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end
end

defp phoenix_controller_module?(module) do
function_exists?(module, :call, 2) and function_exists?(module, :action, 2)
end

defp phoenix_liveview_module?(module) do
function_exists?(module, :mount, 3) and function_exists?(module, :render, 1)
end

# Take only the segments at and before the cursor, e.g.
# Foo|.Bar.Baz -> Foo
# Foo.|Bar.Baz -> Foo.Bar
Expand Down
91 changes: 91 additions & 0 deletions apps/remote_control/lib/lexical/remote_control/code_mod/rename.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
defmodule Lexical.RemoteControl.CodeMod.Rename do
alias Lexical.Ast.Analysis
alias Lexical.Document
alias Lexical.Document.Position
alias Lexical.Document.Range
alias Lexical.RemoteControl.CodeMod.Rename
alias Lexical.RemoteControl.Commands
alias Lexical.RemoteControl.Progress

import Lexical.RemoteControl.Api.Messages

@spec prepare(Analysis.t(), Position.t()) ::
{:ok, {atom(), String.t()}, Range.t()} | {:error, term()}
defdelegate prepare(analysis, position), to: Rename.Prepare

@rename_mappings %{module: Rename.Module}

@spec rename(Analysis.t(), Position.t(), String.t(), String.t() | nil) ::
{:ok, [Document.Changes.t()]} | {:error, term()}
def rename(%Analysis{} = analysis, %Position{} = position, new_name, client_name) do
with {:ok, {renamable, entity}, range} <- Rename.Prepare.resolve(analysis, position) do
rename_module = Map.fetch!(@rename_mappings, renamable)
results = rename_module.rename(range, new_name, entity)
set_rename_progress(results, client_name)
{:ok, results}
end
end

defp set_rename_progress(document_changes_list, client_name) do
uri_with_expected_operation =
uri_with_expected_operation(client_name, document_changes_list)

{paths_to_delete, paths_to_remind} =
for %Document.Changes{rename_file: rename_file, document: document} <- document_changes_list do
if rename_file do
{rename_file.old_uri, rename_file.new_uri}
else
{nil, document.uri}
end
end
|> Enum.unzip()

paths_to_delete = Enum.reject(paths_to_delete, &is_nil/1)
renaming_operation_count = Enum.count(uri_with_expected_operation)

total_operation_count =
renaming_operation_count + length(paths_to_delete) + length(paths_to_remind)

{report_progress_func, complete_func} =
Progress.begin_percent("Renaming", total_operation_count)

Commands.RenameSupervisor.start_renaming(
uri_with_expected_operation,
paths_to_remind,
paths_to_delete,
report_progress_func,
complete_func
)
end

defp uri_with_expected_operation(client_name, document_changes_list)
when client_name in ["Visual Studio Code"] do
document_changes_list
|> Enum.flat_map(fn %Document.Changes{document: document, rename_file: rename_file} ->
if rename_file do
# when the file is renamed, we won't receive `DidSave` for the old file
[
{rename_file.old_uri, file_changed(uri: rename_file.old_uri)},
{rename_file.new_uri, file_saved(uri: rename_file.new_uri)}
]
else
[{document.uri, file_saved(uri: document.uri)}]
end
end)
|> Map.new()
end

defp uri_with_expected_operation(_, document_changes_list) do
document_changes_list
|> Enum.flat_map(fn %Document.Changes{document: document, rename_file: rename_file} ->
if rename_file do
[{rename_file.new_uri, file_saved(uri: rename_file.new_uri)}]
else
# Some editors do not directly save the file after renaming, such as *neovim*.
# when the file is not renamed, we'll only received `DidChange` for the old file
[{document.uri, file_changed(uri: document.uri)}]
end
end)
|> Map.new()
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
defmodule Lexical.RemoteControl.CodeMod.Rename.Entry do
@moduledoc """
"""
alias Lexical.RemoteControl.Search.Indexer, as: Indexer

# When renaming, we rely on the `Indexer.Entry`,
# and we also need some other fields used exclusively for renaming, such as `edit_range`.
@type t :: %__MODULE__{
id: Indexer.Entry.entry_id(),
path: Lexical.path(),
subject: Indexer.Entry.subject(),
block_range: Lexical.Document.Range.t() | nil,
range: Lexical.Document.Range.t(),
edit_range: Lexical.Document.Range.t(),
subtype: Indexer.Entry.entry_subtype()
}
defstruct [
:id,
:path,
:subject,
:block_range,
:range,
:edit_range,
:subtype
]

def new(%Indexer.Entry{} = indexer_entry) do
%__MODULE__{
id: indexer_entry.id,
path: indexer_entry.path,
subject: indexer_entry.subject,
subtype: indexer_entry.subtype,
block_range: indexer_entry.block_range,
range: indexer_entry.range,
edit_range: indexer_entry.range
}
end
end
Loading
Loading