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

Improve UX when completing struct #190

Merged
Merged
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
17 changes: 17 additions & 0 deletions apps/remote_control/test/fixtures/project/lib/structs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ defmodule Project.Structs do
}
end

defmodule Order do
@type t :: %__MODULE__{
id: integer(),
lines: [Line.t()]
}
defstruct [:id, :lines]

defmodule Line do
@type t :: %__MODULE__{
id: integer(),
product_id: integer(),
quantity: integer()
}
defstruct [:id, :product_id, :quantity]
end
end

defmodule NotAStruct do
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,28 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehavi

defines_struct? = Intelligence.defines_struct?(env.project, module.full_name)

immediate_descendent_structs =
immediate_descendent_struct_modules(env.project, module.full_name)

defines_struct_in_descendents? =
immediate_descendent_defines_struct?(env.project, module.full_name) and
length(immediate_descendent_structs) > 1

cond do
struct_reference? and defines_struct_in_descendents? and defines_struct? ->
more = length(immediate_descendent_structs) - 1

[
Translations.Struct.completion(env, builder, module.name, module.full_name, more),
Translations.Struct.completion(env, builder, module.name, module.full_name)
]

struct_reference? and defines_struct? ->
Translations.Struct.completion(env, builder, module.name, module.full_name)

struct_reference? and
immediate_descentent_defines_struct?(env.project, module.full_name) ->
env.project
|> immediate_descendent_struct_modules(module.full_name)
|> Enum.map(fn child_module_name ->
immediate_descendent_defines_struct?(env.project, module.full_name) ->
Enum.map(immediate_descendent_structs, fn child_module_name ->
local_name = local_module_name(module.full_name, child_module_name)
Translations.Struct.completion(env, builder, local_name, child_module_name)
end)
Expand Down Expand Up @@ -67,7 +80,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehavi
defp strip_leading_period(<<".", rest::binary>>), do: rest
defp strip_leading_period(string_without_period), do: string_without_period

defp immediate_descentent_defines_struct?(%Lexical.Project{} = project, module_name) do
defp immediate_descendent_defines_struct?(%Lexical.Project{} = project, module_name) do
Intelligence.defines_struct?(project, module_name, to: :grandchild)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Struct do
alias Lexical.Server.CodeIntelligence.Completion.Translations

use Translatable.Impl, for: Result.Struct
require Logger

def translate(%Result.Struct{} = struct, builder, %Env{} = env) do
if Env.in_context?(env, :struct_reference) do
Expand All @@ -20,6 +19,19 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Struct do
end
end

def completion(%Env{} = env, builder, module_name, full_name, more) when is_integer(more) do
builder_opts = [
kind: :module,
label: "#{module_name}...(#{more} more structs)",
detail: "#{full_name}."
]

insert_text = "#{module_name}."
range = edit_range(env)

builder.text_edit_snippet(env, insert_text, range, builder_opts)
end

def completion(%Env{} = env, builder, struct_name, full_name) do
builder_opts = [
kind: :struct,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehavi
assert apply_completion(completion) == "%MapSet{$1}\n"
end

test "should work for aliased struct", %{project: project} do
source = ~q[
alias Project.Structs.Account, as: MyAccount
%My|
]

expected = ~q[
alias Project.Structs.Account, as: MyAccount
%MyAccount{$1}
]

assert {:ok, completion} =
project
|> complete(source)
|> fetch_completion(kind: :struct)

assert apply_completion(completion) == expected
end

test "modules that define a struct should emit curlies if in a struct reference", %{
project: project
} do
Expand Down Expand Up @@ -182,9 +201,31 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehavi

test "should offer no other types of completions", %{project: project} do
assert [] = complete(project, "%MapSet.|")
assert [account, user] = complete(project, "%Project.|")
assert [account, order, order_line, user] = complete(project, "%Project.|")

assert account.label == "Structs.Account"
assert order.label == "Structs.Order"
assert order_line.label == "Structs.Order.Line"
assert user.label == "Structs.User"
end

test "should offer two completions when there are struct and its descendants", %{
project: project
} do
source = ~q[
alias Project.Structs.Order
%O|
]

[order_line, order] = complete(project, source)

assert order_line.label == "Order...(1 more structs)"
assert order_line.kind == :module
assert apply_completion(order_line) =~ "%Order."

assert order.label == "Order"
assert order.kind == :struct
assert apply_completion(order) =~ "%Order{$1}"
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do

describe "structs" do
test "should complete after %", %{project: project} do
assert {:ok, [_, _] = account_and_user} =
assert {:ok, [_, _, _] = account_and_user_and_order} =
project
|> complete("%Project.Structs.|")
|> fetch_completion(kind: :struct)

assert Enum.find(account_and_user, &(&1.label == "User"))
account = Enum.find(account_and_user, &(&1.label == "Account"))
assert Enum.find(account_and_user_and_order, &(&1.label == "User"))
account = Enum.find(account_and_user_and_order, &(&1.label == "Account"))
assert account
assert account.detail == "Project.Structs.Account"

Expand Down Expand Up @@ -129,12 +129,18 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do
end

test "when using %, child structs are returned", %{project: project} do
assert [account, user] = complete(project, "%Project.|", "%")
assert [account, order, order_line, user] = complete(project, "%Project.|", "%")
assert account.label == "Structs.Account"
assert account.detail == "Project.Structs.Account"

assert user.label == "Structs.User"
assert user.detail == "Project.Structs.User"

assert order.label == "Structs.Order"
assert order.detail == "Project.Structs.Order"

assert order_line.label == "Structs.Order.Line"
assert order_line.detail == "Project.Structs.Order.Line"
end

test "it should complete struct fields", %{project: project} do
Expand Down