Skip to content

Commit

Permalink
Make the operational behavior of the ancestors of structures and modu…
Browse files Browse the repository at this point in the history
…les more consistent (lexical-lsp#408)

For example, find references against the ancestors of a module or structure:

Taking `Mix.Task.Compiler.Diagnostic` as an example, the references are inconsistent when the cursor is on `%Mix.Task.|Compiler.Diagnostic{}` compared to when it is on `Mix.Task.|Compiler.Diagnostic`.

I believe that maintaining consistency between them would provide a better user experience and make it easier to extend for future features, such as renaming.

---------

Co-authored-by: Zach Allaun <[email protected]>
  • Loading branch information
scottming and zachallaun authored Oct 17, 2023
1 parent f093473 commit 557847d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
node_range = {{start_line, start_col + 1}, end_pos}

case resolve_alias(charlist, node_range, document, position) do
{:ok, {_, struct}, range} -> {:ok, {:struct, struct}, range}
{:ok, {struct_or_module, struct}, range} -> {:ok, {struct_or_module, struct}, range}
:error -> {:error, :not_found}
end
end
Expand All @@ -89,11 +89,15 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end

defp resolve_alias(charlist, node_range, document, position) do
with {:ok, path} <- Ast.path_at(document, position),
{{_line, start_column}, _} = node_range

with false <- suffix_contains_module?(charlist, start_column, position),
{:ok, path} <- Ast.path_at(document, position),
:struct <- kind_of_alias(path) do
resolve_struct(charlist, node_range, document, position)
else
_ -> resolve_module(charlist, node_range, document, position)
_ ->
resolve_module(charlist, node_range, document, position)
end
end

Expand Down Expand Up @@ -141,6 +145,31 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end
end

# %TopLevel|.Struct{} -> true
# %TopLevel.Str|uct{} -> false
defp suffix_contains_module?(charlist, start_column, %Position{} = position) do
charlist
|> List.to_string()
|> suffix_contains_module?(position.character - start_column)
end

defp suffix_contains_module?(string, index) when is_binary(string) do
{_, suffix} = String.split_at(string, index)

case String.split(suffix, ".", parts: 2) do
[_before_dot, after_dot] ->
uppercase?(after_dot)

[_before_dot] ->
false
end
end

defp uppercase?(after_dot) when is_binary(after_dot) do
first_char = String.at(after_dot, 0)
String.upcase(first_char) == first_char
end

defp expand_alias({:alias, {:local_or_var, prefix}, charlist}, document, %Position{} = position) do
expand_alias(prefix ++ [?.] ++ charlist, document, position)
end
Expand Down Expand Up @@ -217,6 +246,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
# a `:struct`, otherwise resolve as a `:module`.
defp kind_of_alias(path)

# |%Foo{}
defp kind_of_alias([{:%, _, _}]), do: :struct

# %|Foo{}
# %|Foo.Bar{}
# %__MODULE__.|Foo{}
Expand All @@ -225,6 +257,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
# %|__MODULE__{}
defp kind_of_alias([{:__MODULE__, _, nil}, {:%, _, _} | _]), do: :struct

# %Foo|{}
defp kind_of_alias([{:%{}, _, _}, {:%, _, _} | _]), do: :struct

# %|__MODULE__.Foo{}
defp kind_of_alias([head_of_aliases, {:__aliases__, _, [head_of_aliases | _]}, {:%, _, _} | _]) do
:struct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
Lines{}
]

assert {:ok, {:struct, On.Multiple.Lines}, resolved_range} = resolve(code)
assert {:ok, {:module, On.Multiple.Lines}, resolved_range} = resolve(code)

assert resolved_range =~ """
%«On.
Expand All @@ -236,13 +236,13 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
"""
end

test "includes trailing module segments" do
test "shouldn't include trailing module segments" do
code = ~q[
%My|Struct.Nested{}
]

assert {:ok, {:struct, MyStruct.Nested}, resolved_range} = resolve(code)
assert resolved_range =~ ~S[%«MyStruct.Nested»{}]
assert {:ok, {:module, MyStruct}, resolved_range} = resolve(code)
assert resolved_range =~ ~S[%«MyStruct».Nested{}]
end

test "expands current module" do
Expand Down

0 comments on commit 557847d

Please sign in to comment.