Skip to content

Commit 7535af7

Browse files
authored
Merge pull request #1139 from elixir-lsp/options2
Improvements to keyword param options completions
2 parents eb88093 + e98fbd1 commit 7535af7

File tree

11 files changed

+433
-164
lines changed

11 files changed

+433
-164
lines changed

apps/debug_adapter/lib/debug_adapter/server.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,12 @@ defmodule ElixirLS.DebugAdapter.Server do
642642
{:noreply, state}
643643
end
644644

645+
def handle_info({:ok, [%Frame{} | _]}, state = %__MODULE__{}) do
646+
# timed out response from Stacktrace.get/1
647+
# we already emitted a warning
648+
{:noreply, state}
649+
end
650+
645651
# If we get the disconnect request from the client, we continue with :disconnect so the server will
646652
# die right after responding to the request
647653
@impl GenServer

apps/debug_adapter/test/debugger_test.exs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
12281228
assert_receive(response(_, 2, "launch", _), 3000)
12291229
assert_receive(event(_, "initialized", %{}), 5000)
12301230

1231+
Process.sleep(100)
1232+
12311233
refute :hello in :int.interpreted()
12321234
abs_path = Path.absname("src/hello.erl")
12331235

@@ -1241,6 +1243,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
12411243
5000
12421244
)
12431245

1246+
Process.sleep(100)
1247+
12441248
assert :hello in :int.interpreted()
12451249
assert [{{:hello, 5}, [:active, :enable, :null, _]}] = :int.all_breaks(:hello)
12461250
assert %{^abs_path => [{[:hello], 5}]} = :sys.get_state(server).breakpoints
@@ -1310,6 +1314,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
13101314
assert_receive(response(_, 2, "launch", _), 3000)
13111315
assert_receive(event(_, "initialized", %{}), 5000)
13121316

1317+
Process.sleep(100)
1318+
13131319
refute :hello in :int.interpreted()
13141320
abs_path = Path.absname("src/hello.erl1")
13151321

@@ -1358,6 +1364,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
13581364
assert_receive(response(_, 2, "launch", _), 3000)
13591365
assert_receive(event(_, "initialized", %{}), 5000)
13601366

1367+
Process.sleep(100)
1368+
13611369
refute MixProject in :int.interpreted()
13621370
refute MixProject.Some in :int.interpreted()
13631371
abs_path = Path.absname("lib/mix_project.ex")
@@ -1462,6 +1470,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
14621470
assert_receive(response(_, 2, "launch", _), 3000)
14631471
assert_receive(event(_, "initialized", %{}), 5000)
14641472

1473+
Process.sleep(100)
1474+
14651475
refute MixProject in :int.interpreted()
14661476
refute :hello in :int.interpreted()
14671477

@@ -1498,6 +1508,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
14981508
5000
14991509
)
15001510

1511+
Process.sleep(100)
1512+
15011513
assert :hello in :int.interpreted()
15021514
assert [{{:hello, 5}, _}] = :int.all_breaks(:hello)
15031515

@@ -1554,6 +1566,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
15541566
assert_receive(response(_, 2, "launch", _), 3000)
15551567
assert_receive(event(_, "initialized", %{}), 5000)
15561568

1569+
Process.sleep(100)
1570+
15571571
refute MixProject in :int.interpreted()
15581572

15591573
# set
@@ -1649,6 +1663,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
16491663
assert_receive(response(_, 2, "launch", _), 3000)
16501664
assert_receive(event(_, "initialized", %{}), 5000)
16511665

1666+
Process.sleep(100)
1667+
16521668
refute MixProject in :int.interpreted()
16531669

16541670
# set
@@ -1744,6 +1760,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
17441760
assert_receive(response(_, 2, "launch", _), 3000)
17451761
assert_receive(event(_, "initialized", %{}), 5000)
17461762

1763+
Process.sleep(100)
1764+
17471765
refute MixProject in :int.interpreted()
17481766

17491767
# set
@@ -2066,6 +2084,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
20662084
assert_receive(response(_, 2, "launch", _), 3000)
20672085
assert_receive(event(_, "initialized", %{}), 5000)
20682086

2087+
Process.sleep(100)
2088+
20692089
refute :hello in :int.interpreted()
20702090

20712091
Server.receive_packet(
@@ -2140,6 +2160,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
21402160
assert_receive(response(_, 2, "launch", _), 3000)
21412161
assert_receive(event(_, "initialized", %{}), 5000)
21422162

2163+
Process.sleep(100)
2164+
21432165
refute :hello in :int.interpreted()
21442166

21452167
Server.receive_packet(
@@ -2184,6 +2206,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
21842206
assert_receive(response(_, 2, "launch", _), 3000)
21852207
assert_receive(event(_, "initialized", %{}), 5000)
21862208

2209+
Process.sleep(100)
2210+
21872211
refute :hello in :int.interpreted()
21882212

21892213
# set
@@ -2277,6 +2301,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
22772301
assert_receive(response(_, 2, "launch", _), 3000)
22782302
assert_receive(event(_, "initialized", %{}), 5000)
22792303

2304+
Process.sleep(100)
2305+
22802306
refute :hello in :int.interpreted()
22812307

22822308
# set
@@ -2708,6 +2734,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
27082734
assert_receive(response(_, 2, "launch", _), 3000)
27092735
assert_receive(event(_, "initialized", %{}), 5000)
27102736

2737+
Process.sleep(100)
2738+
27112739
assert MixProject.Dbg in :int.interpreted()
27122740

27132741
Server.receive_packet(server, request(5, "configurationDone", %{}))
@@ -2894,6 +2922,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
28942922
assert_receive(response(_, 2, "launch", _), 3000)
28952923
assert_receive(event(_, "initialized", %{}), 5000)
28962924

2925+
Process.sleep(100)
2926+
28972927
assert MixProject.Dbg in :int.interpreted()
28982928

28992929
Server.receive_packet(server, request(5, "configurationDone", %{}))
@@ -3062,6 +3092,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
30623092
assert_receive(response(_, 2, "launch", _), 3000)
30633093
assert_receive(event(_, "initialized", %{}), 5000)
30643094

3095+
Process.sleep(100)
3096+
30653097
refute MixProject.Dbg in :int.interpreted()
30663098

30673099
Server.receive_packet(server, request(5, "configurationDone", %{}))

apps/elixir_ls_utils/test/complete_test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2208,7 +2208,6 @@ defmodule ElixirLS.Utils.CompletionEngineTest do
22082208
] = expand(~c"unquote", %Env{requires: []})
22092209
end
22102210

2211-
22122211
test "macros from the same module should not add needed_require" do
22132212
macro_info = %ElixirSense.Core.State.ModFunInfo{
22142213
type: :defmacro,

apps/language_server/lib/language_server/providers/completion.ex

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -752,22 +752,53 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
752752
}
753753
end
754754

755-
defp from_completion_item(%{type: :param_option} = suggestion, _context, _options) do
756-
%{name: name, origin: _origin, doc: doc, type_spec: type_spec, expanded_spec: expanded_spec} =
755+
defp from_completion_item(%{type: :param_option} = suggestion, context, _options) do
756+
%{
757+
name: name,
758+
type_spec: type_spec,
759+
origin: origin,
760+
subtype: subtype
761+
} =
757762
suggestion
758763

759764
formatted_spec =
760-
if expanded_spec != "" do
761-
"\n\n```elixir\n#{expanded_spec}\n```\n"
765+
if type_spec != "" do
766+
"\n\n```elixir\n#{type_spec}\n```\n"
762767
else
763768
""
764769
end
765770

771+
{insert_text, text_edit} =
772+
cond do
773+
subtype == :keyword and not String.ends_with?(context.prefix, ":") ->
774+
{"#{name}: ", nil}
775+
776+
subtype == :keyword ->
777+
{"",
778+
%{
779+
"range" => %{
780+
"start" => %{
781+
"line" => context.line,
782+
"character" => context.character - String.length(context.prefix)
783+
},
784+
"end" => %{"line" => context.line, "character" => context.character}
785+
},
786+
"newText" => "#{name}: "
787+
}}
788+
789+
match?(":" <> _, context.prefix) ->
790+
{name, nil}
791+
792+
true ->
793+
{":#{name}", nil}
794+
end
795+
766796
%__MODULE__{
767797
label: to_string(name),
768-
detail: "#{type_spec}",
769-
documentation: "#{doc}#{formatted_spec}",
770-
insert_text: "#{name}: ",
798+
detail: "#{origin} option",
799+
documentation: formatted_spec,
800+
insert_text: insert_text,
801+
text_edit: text_edit,
771802
priority: 10,
772803
kind: :field,
773804
tags: []

apps/language_server/lib/language_server/providers/completion/reducers/params.ex

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
1111
alias ElixirSense.Core.Introspection
1212
alias ElixirSense.Core.Metadata
1313
alias ElixirSense.Core.Source
14-
alias ElixirSense.Core.TypeInfo
1514
alias ElixirLS.Utils.Matcher
1615

1716
@type param_option :: %{
1817
type: :param_option,
18+
subtype: :keyword | :atom,
1919
name: String.t(),
2020
origin: String.t(),
21-
type_spec: String.t(),
22-
doc: String.t(),
23-
expanded_spec: String.t()
21+
type_spec: String.t()
2422
}
2523

2624
@doc """
@@ -36,6 +34,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
3634
with %{
3735
candidate: {mod, fun},
3836
elixir_prefix: elixir_prefix,
37+
options_so_far: options_so_far,
38+
cursor_at_option: cursor_at_option,
3939
npar: npar
4040
} <-
4141
Source.which_func(prefix, binding_env),
@@ -45,42 +45,47 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
4545
env,
4646
mods_funs,
4747
metadata_types,
48-
{1, 1},
48+
cursor_context.cursor_position,
4949
not elixir_prefix
5050
) do
5151
list =
52-
if Code.ensure_loaded?(mod) do
53-
TypeInfo.extract_param_options(mod, fun, npar)
54-
|> Kernel.++(TypeInfo.extract_param_options(mod, :"MACRO-#{fun}", npar + 1))
55-
|> options_to_suggestions(mod)
56-
|> Enum.filter(&Matcher.match?(&1.name, hint))
57-
else
58-
# TODO metadata?
59-
[]
52+
for opt <-
53+
ElixirSense.Core.Options.get_param_options(mod, fun, npar + 1, env, buffer_metadata) do
54+
case opt do
55+
{name, type} ->
56+
# match on atom:
57+
if Matcher.match?(to_string(name) <> ":", hint) do
58+
expanded_spec = Introspection.to_string_with_parens(type)
59+
60+
%{
61+
name: name |> Atom.to_string(),
62+
origin: "#{inspect(mod)}.#{fun}",
63+
type: :param_option,
64+
subtype: :keyword,
65+
type_spec: expanded_spec
66+
}
67+
end
68+
69+
name ->
70+
# match on :atom
71+
if options_so_far == [] and cursor_at_option == true and
72+
Matcher.match?(inspect(name), hint) do
73+
%{
74+
name: name |> Atom.to_string(),
75+
origin: "#{inspect(mod)}.#{fun}",
76+
type: :param_option,
77+
subtype: :atom,
78+
type_spec: ""
79+
}
80+
end
81+
end
6082
end
83+
|> Enum.reject(&is_nil/1)
6184

6285
{:cont, %{acc | result: acc.result ++ list}}
6386
else
6487
_ ->
6588
{:cont, acc}
6689
end
6790
end
68-
69-
defp options_to_suggestions(options, original_module) do
70-
Enum.map(options, fn
71-
{mod, name, type} ->
72-
TypeInfo.get_type_info(mod, type, original_module)
73-
|> Map.merge(%{type: :param_option, name: name |> Atom.to_string()})
74-
75-
{mod, name} ->
76-
%{
77-
doc: "",
78-
expanded_spec: "",
79-
name: name |> Atom.to_string(),
80-
origin: inspect(mod),
81-
type: :param_option,
82-
type_spec: ""
83-
}
84-
end)
85-
end
8691
end

0 commit comments

Comments
 (0)