Skip to content

Commit c5fd13f

Browse files
authored
Add support for variable expansion of evaluate results (#672)
compatibility improvement: only return variable type and pagination properties when supported by client run evaluate in a stacktrace frame if specified
1 parent 3e531b9 commit c5fd13f

File tree

2 files changed

+86
-39
lines changed

2 files changed

+86
-39
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ defmodule ElixirLS.Debugger.Server do
3535
task_ref: nil,
3636
threads: %{},
3737
threads_inverse: %{},
38-
paused_processes: %{},
38+
paused_processes: %{
39+
evaluator: %{
40+
vars: %{},
41+
vars_inverse: %{}
42+
}
43+
},
3944
next_id: 1,
4045
output: Output,
4146
breakpoints: %{},
@@ -499,15 +504,26 @@ defmodule ElixirLS.Debugger.Server do
499504
end
500505

501506
defp handle_request(
502-
request(_cmd, "evaluate", %{"expression" => expr} = _args),
507+
request(_cmd, "evaluate", %{"expression" => expr} = args),
503508
state = %__MODULE__{}
504509
) do
505510
timeout = Map.get(state.config, "debugExpressionTimeoutMs", 10_000)
506-
bindings = all_variables(state.paused_processes)
511+
bindings = all_variables(state.paused_processes, args["frameId"])
512+
513+
value = evaluate_code_expression(expr, bindings, timeout)
507514

508-
result = evaluate_code_expression(expr, bindings, timeout)
515+
child_type = Variables.child_type(value)
516+
{state, var_id} = get_variable_reference(child_type, state, :evaluator, value)
517+
518+
json =
519+
%{
520+
"result" => inspect(value),
521+
"variablesReference" => var_id
522+
}
523+
|> maybe_append_children_number(state.client_info, child_type, value)
524+
|> maybe_append_variable_type(state.client_info, value)
509525

510-
{%{"result" => inspect(result), "variablesReference" => 0}, state}
526+
{json, state}
511527
end
512528

513529
defp handle_request(continue_req(_, thread_id), state = %__MODULE__{}) do
@@ -623,34 +639,39 @@ defmodule ElixirLS.Debugger.Server do
623639
end
624640

625641
Enum.reduce(children, {state, []}, fn {name, value}, {state = %__MODULE__{}, result} ->
626-
num_children = Variables.num_children(value)
627642
child_type = Variables.child_type(value)
628-
629-
{state, var_id} =
630-
if child_type do
631-
ensure_var_id(state, pid, value)
632-
else
633-
{state, 0}
634-
end
635-
636-
json = %{
637-
"name" => to_string(name),
638-
"value" => inspect(value),
639-
"variablesReference" => var_id,
640-
"type" => Variables.type(value)
641-
}
643+
{state, var_id} = get_variable_reference(child_type, state, pid, value)
642644

643645
json =
644-
case child_type do
645-
:indexed -> Map.put(json, "indexedVariables", num_children)
646-
:named -> Map.put(json, "namedVariables", num_children)
647-
nil -> json
648-
end
646+
%{
647+
"name" => to_string(name),
648+
"value" => inspect(value),
649+
"variablesReference" => var_id
650+
}
651+
|> maybe_append_children_number(state.client_info, child_type, value)
652+
|> maybe_append_variable_type(state.client_info, value)
649653

650654
{state, result ++ [json]}
651655
end)
652656
end
653657

658+
defp get_variable_reference(nil, state, _pid, _value), do: {state, 0}
659+
660+
defp get_variable_reference(_child_type, state, pid, value),
661+
do: ensure_var_id(state, pid, value)
662+
663+
defp maybe_append_children_number(map, %{"supportsVariablePaging" => true}, atom, value)
664+
when atom in [:indexed, :named],
665+
do: Map.put(map, Atom.to_string(atom) <> "Variables", Variables.num_children(value))
666+
667+
defp maybe_append_children_number(map, _, _, _value), do: map
668+
669+
defp maybe_append_variable_type(map, %{"supportsVariableType" => true}, value) do
670+
Map.put(map, "type", Variables.type(value))
671+
end
672+
673+
defp maybe_append_variable_type(map, _, _value), do: map
674+
654675
defp evaluate_code_expression(expr, bindings, timeout) do
655676
task =
656677
Task.async(fn ->
@@ -678,34 +699,53 @@ defmodule ElixirLS.Debugger.Server do
678699
end
679700
end
680701

681-
defp all_variables(paused_processes) do
702+
defp all_variables(paused_processes, nil) do
682703
paused_processes
683-
|> Enum.flat_map(fn {_pid, %PausedProcess{} = paused_process} ->
684-
paused_process.frames |> Map.values()
704+
|> Enum.flat_map(fn
705+
{:evaluator, _} ->
706+
# TODO setVariable?
707+
[]
708+
709+
{_pid, %PausedProcess{} = paused_process} ->
710+
paused_process.frames |> Map.values()
685711
end)
686712
|> Enum.filter(&match?(%Frame{bindings: bindings} when is_map(bindings), &1))
687713
|> Enum.flat_map(fn %Frame{bindings: bindings} ->
688714
Binding.to_elixir_variable_names(bindings)
689715
end)
690716
end
691717

718+
defp all_variables(paused_processes, frame_id) do
719+
case find_frame(paused_processes, frame_id) do
720+
{_pid, %Frame{bindings: bindings}} when is_map(bindings) ->
721+
Binding.to_elixir_variable_names(bindings)
722+
723+
_ ->
724+
[]
725+
end
726+
end
727+
692728
defp find_var(paused_processes, var_id) do
693-
Enum.find_value(paused_processes, fn {pid, %PausedProcess{} = paused_process} ->
694-
if Map.has_key?(paused_process.vars, var_id) do
695-
{pid, paused_process.vars[var_id]}
729+
Enum.find_value(paused_processes, fn {pid, %{vars: vars}} ->
730+
if Map.has_key?(vars, var_id) do
731+
{pid, vars[var_id]}
696732
end
697733
end)
698734
end
699735

700736
defp find_frame(paused_processes, frame_id) do
701-
Enum.find_value(paused_processes, fn {pid, %PausedProcess{} = paused_process} ->
702-
if Map.has_key?(paused_process.frames, frame_id) do
703-
{pid, paused_process.frames[frame_id]}
704-
end
737+
Enum.find_value(paused_processes, fn
738+
{pid, %{frames: frames}} ->
739+
if Map.has_key?(frames, frame_id) do
740+
{pid, frames[frame_id]}
741+
end
742+
743+
{:evaluator, _} ->
744+
nil
705745
end)
706746
end
707747

708-
defp ensure_thread_id(state = %__MODULE__{}, pid) do
748+
defp ensure_thread_id(state = %__MODULE__{}, pid) when is_pid(pid) do
709749
if Map.has_key?(state.threads_inverse, pid) do
710750
{state, state.threads_inverse[pid]}
711751
else
@@ -724,7 +764,7 @@ defmodule ElixirLS.Debugger.Server do
724764
end)
725765
end
726766

727-
defp ensure_var_id(state = %__MODULE__{}, pid, var) do
767+
defp ensure_var_id(state = %__MODULE__{}, pid, var) when is_pid(pid) or pid == :evaluator do
728768
unless Map.has_key?(state.paused_processes, pid) do
729769
raise ArgumentError, message: "paused process #{inspect(pid)} not found"
730770
end
@@ -747,7 +787,7 @@ defmodule ElixirLS.Debugger.Server do
747787
end)
748788
end
749789

750-
defp ensure_frame_id(state = %__MODULE__{}, pid, %Frame{} = frame) do
790+
defp ensure_frame_id(state = %__MODULE__{}, pid, %Frame{} = frame) when is_pid(pid) do
751791
unless Map.has_key?(state.paused_processes, pid) do
752792
raise ArgumentError, message: "paused process #{inspect(pid)} not found"
753793
end

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,14 @@ defmodule ElixirLS.Debugger.ServerTest do
9393
@tag :fixture
9494
test "basic debugging", %{server: server} do
9595
in_fixture(__DIR__, "mix_project", fn ->
96-
Server.receive_packet(server, initialize_req(1, %{}))
96+
Server.receive_packet(
97+
server,
98+
initialize_req(1, %{
99+
"supportsVariablePaging" => true,
100+
"supportsVariableType" => true
101+
})
102+
)
103+
97104
assert_receive(response(_, 1, "initialize", %{"supportsConfigurationDoneRequest" => true}))
98105

99106
Server.receive_packet(

0 commit comments

Comments
 (0)