Skip to content
6 changes: 6 additions & 0 deletions apps/debug_adapter/lib/debug_adapter/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,12 @@ defmodule ElixirLS.DebugAdapter.Server do
{:noreply, state}
end

def handle_info({:ok, [%Frame{} | _]}, state = %__MODULE__{}) do
# timed out response from Stacktrace.get/1
# we already emitted a warning
{:noreply, state}
end

# If we get the disconnect request from the client, we continue with :disconnect so the server will
# die right after responding to the request
@impl GenServer
Expand Down
32 changes: 32 additions & 0 deletions apps/debug_adapter/test/debugger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
assert_receive(response(_, 2, "launch", _), 3000)
assert_receive(event(_, "initialized", %{}), 5000)

Process.sleep(100)

refute :hello in :int.interpreted()
abs_path = Path.absname("src/hello.erl")

Expand All @@ -1241,6 +1243,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
5000
)

Process.sleep(100)

assert :hello in :int.interpreted()
assert [{{:hello, 5}, [:active, :enable, :null, _]}] = :int.all_breaks(:hello)
assert %{^abs_path => [{[:hello], 5}]} = :sys.get_state(server).breakpoints
Expand Down Expand Up @@ -1310,6 +1314,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
assert_receive(response(_, 2, "launch", _), 3000)
assert_receive(event(_, "initialized", %{}), 5000)

Process.sleep(100)

refute :hello in :int.interpreted()
abs_path = Path.absname("src/hello.erl1")

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

Process.sleep(100)

refute MixProject in :int.interpreted()
refute MixProject.Some in :int.interpreted()
abs_path = Path.absname("lib/mix_project.ex")
Expand Down Expand Up @@ -1462,6 +1470,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
assert_receive(response(_, 2, "launch", _), 3000)
assert_receive(event(_, "initialized", %{}), 5000)

Process.sleep(100)

refute MixProject in :int.interpreted()
refute :hello in :int.interpreted()

Expand Down Expand Up @@ -1498,6 +1508,8 @@ defmodule ElixirLS.DebugAdapter.ServerTest do
5000
)

Process.sleep(100)

assert :hello in :int.interpreted()
assert [{{:hello, 5}, _}] = :int.all_breaks(:hello)

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

Process.sleep(100)

refute MixProject in :int.interpreted()

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

Process.sleep(100)

refute MixProject in :int.interpreted()

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

Process.sleep(100)

refute MixProject in :int.interpreted()

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

Process.sleep(100)

refute :hello in :int.interpreted()

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

Process.sleep(100)

refute :hello in :int.interpreted()

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

Process.sleep(100)

refute :hello in :int.interpreted()

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

Process.sleep(100)

refute :hello in :int.interpreted()

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

Process.sleep(100)

assert MixProject.Dbg in :int.interpreted()

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

Process.sleep(100)

assert MixProject.Dbg in :int.interpreted()

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

Process.sleep(100)

refute MixProject.Dbg in :int.interpreted()

Server.receive_packet(server, request(5, "configurationDone", %{}))
Expand Down
1 change: 0 additions & 1 deletion apps/elixir_ls_utils/test/complete_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2208,7 +2208,6 @@ defmodule ElixirLS.Utils.CompletionEngineTest do
] = expand(~c"unquote", %Env{requires: []})
end


test "macros from the same module should not add needed_require" do
macro_info = %ElixirSense.Core.State.ModFunInfo{
type: :defmacro,
Expand Down
45 changes: 38 additions & 7 deletions apps/language_server/lib/language_server/providers/completion.ex
Original file line number Diff line number Diff line change
Expand Up @@ -744,22 +744,53 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
}
end

defp from_completion_item(%{type: :param_option} = suggestion, _context, _options) do
%{name: name, origin: _origin, doc: doc, type_spec: type_spec, expanded_spec: expanded_spec} =
defp from_completion_item(%{type: :param_option} = suggestion, context, _options) do
%{
name: name,
type_spec: type_spec,
origin: origin,
subtype: subtype
} =
suggestion

formatted_spec =
if expanded_spec != "" do
"\n\n```elixir\n#{expanded_spec}\n```\n"
if type_spec != "" do
"\n\n```elixir\n#{type_spec}\n```\n"
else
""
end

{insert_text, text_edit} =
cond do
subtype == :keyword and not String.ends_with?(context.prefix, ":") ->
{"#{name}: ", nil}

subtype == :keyword ->
{"",
%{
"range" => %{
"start" => %{
"line" => context.line,
"character" => context.character - String.length(context.prefix)
},
"end" => %{"line" => context.line, "character" => context.character}
},
"newText" => "#{name}: "
}}

match?(":" <> _, context.prefix) ->
{name, nil}

true ->
{":#{name}", nil}
end

%__MODULE__{
label: to_string(name),
detail: "#{type_spec}",
documentation: "#{doc}#{formatted_spec}",
insert_text: "#{name}: ",
detail: "#{origin} option",
documentation: formatted_spec,
insert_text: insert_text,
text_edit: text_edit,
priority: 10,
kind: :field,
tags: []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
alias ElixirSense.Core.Introspection
alias ElixirSense.Core.Metadata
alias ElixirSense.Core.Source
alias ElixirSense.Core.TypeInfo
alias ElixirLS.Utils.Matcher

@type param_option :: %{
type: :param_option,
subtype: :keyword | :atom,
name: String.t(),
origin: String.t(),
type_spec: String.t(),
doc: String.t(),
expanded_spec: String.t()
type_spec: String.t()
}

@doc """
Expand All @@ -36,6 +34,8 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
with %{
candidate: {mod, fun},
elixir_prefix: elixir_prefix,
options_so_far: options_so_far,
cursor_at_option: cursor_at_option,
npar: npar
} <-
Source.which_func(prefix, binding_env),
Expand All @@ -45,42 +45,47 @@ defmodule ElixirLS.LanguageServer.Providers.Completion.Reducers.Params do
env,
mods_funs,
metadata_types,
{1, 1},
cursor_context.cursor_position,
not elixir_prefix
) do
list =
if Code.ensure_loaded?(mod) do
TypeInfo.extract_param_options(mod, fun, npar)
|> Kernel.++(TypeInfo.extract_param_options(mod, :"MACRO-#{fun}", npar + 1))
|> options_to_suggestions(mod)
|> Enum.filter(&Matcher.match?(&1.name, hint))
else
# TODO metadata?
[]
for opt <-
ElixirSense.Core.Options.get_param_options(mod, fun, npar + 1, env, buffer_metadata) do
case opt do
{name, type} ->
# match on atom:
if Matcher.match?(to_string(name) <> ":", hint) do
expanded_spec = Introspection.to_string_with_parens(type)

%{
name: name |> Atom.to_string(),
origin: "#{inspect(mod)}.#{fun}",
type: :param_option,
subtype: :keyword,
type_spec: expanded_spec
}
end

name ->
# match on :atom
if options_so_far == [] and cursor_at_option == true and
Matcher.match?(inspect(name), hint) do
%{
name: name |> Atom.to_string(),
origin: "#{inspect(mod)}.#{fun}",
type: :param_option,
subtype: :atom,
type_spec: ""
}
end
end
end
|> Enum.reject(&is_nil/1)

{:cont, %{acc | result: acc.result ++ list}}
else
_ ->
{:cont, acc}
end
end

defp options_to_suggestions(options, original_module) do
Enum.map(options, fn
{mod, name, type} ->
TypeInfo.get_type_info(mod, type, original_module)
|> Map.merge(%{type: :param_option, name: name |> Atom.to_string()})

{mod, name} ->
%{
doc: "",
expanded_spec: "",
name: name |> Atom.to_string(),
origin: inspect(mod),
type: :param_option,
type_spec: ""
}
end)
end
end
Loading
Loading