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

Debugger value format hex #1109

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
127 changes: 86 additions & 41 deletions apps/elixir_ls_debugger/lib/debugger/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ defmodule ElixirLS.Debugger.Server do
task_ref: nil,
threads: %{},
threads_inverse: %{},
paused_processes: %{},
paused_processes: %{
evaluator: %{
vars: %{},
vars_inverse: %{}
}
},
next_id: 1,
output: Output,
breakpoints: %{},
Expand Down Expand Up @@ -479,7 +484,7 @@ defmodule ElixirLS.Debugger.Server do
{state, vars_json} =
case find_var(state.paused_processes, var_id) do
{pid, var} ->
variables(state, pid, var, args["start"], args["count"], args["filter"])
variables(state, pid, var, args["start"], args["count"], args["filter"], args["format"])

nil ->
raise ServerError,
Expand All @@ -494,15 +499,26 @@ defmodule ElixirLS.Debugger.Server do
end

defp handle_request(
request(_cmd, "evaluate", %{"expression" => expr} = _args),
request(_cmd, "evaluate", %{"expression" => expr} = args),
state = %__MODULE__{}
) do
timeout = Map.get(state.config, "debugExpressionTimeoutMs", 10_000)
bindings = all_variables(state.paused_processes)
bindings = all_variables(state.paused_processes, args["frameId"])

value = evaluate_code_expression(expr, bindings, timeout)

result = evaluate_code_expression(expr, bindings, timeout)
child_type = Variables.child_type(value)
{state, var_id} = get_variable_reference(child_type, state, :evaluator, value)

json =
%{
"result" => format_variable(value, args["format"]),
"variablesReference" => var_id
}
|> maybe_append_children_number(state.client_info, child_type, value)
|> maybe_append_variable_type(state.client_info, value)

{%{"result" => inspect(result), "variablesReference" => 0}, state}
{json, state}
end

defp handle_request(continue_req(_, thread_id), state = %__MODULE__{}) do
Expand Down Expand Up @@ -607,7 +623,7 @@ defmodule ElixirLS.Debugger.Server do
%__MODULE__{state | paused_processes: paused_processes}
end

defp variables(state = %__MODULE__{}, pid, var, start, count, filter) do
defp variables(state = %__MODULE__{}, pid, var, start, count, filter, format) do
var_child_type = Variables.child_type(var)

children =
Expand All @@ -618,34 +634,44 @@ defmodule ElixirLS.Debugger.Server do
end

Enum.reduce(children, {state, []}, fn {name, value}, {state = %__MODULE__{}, result} ->
num_children = Variables.num_children(value)
child_type = Variables.child_type(value)

{state, var_id} =
if child_type do
ensure_var_id(state, pid, value)
else
{state, 0}
end

json = %{
"name" => to_string(name),
"value" => inspect(value),
"variablesReference" => var_id,
"type" => Variables.type(value)
}
{state, var_id} = get_variable_reference(child_type, state, pid, value)

json =
case child_type do
:indexed -> Map.put(json, "indexedVariables", num_children)
:named -> Map.put(json, "namedVariables", num_children)
nil -> json
end
%{
"name" => to_string(name),
"value" => format_variable(value, format),
"variablesReference" => var_id
}
|> maybe_append_children_number(state.client_info, child_type, value)
|> maybe_append_variable_type(state.client_info, value)

{state, result ++ [json]}
end)
end

defp get_variable_reference(nil, state, _pid, _value), do: {state, 0}

defp get_variable_reference(_child_type, state, pid, value),
do: ensure_var_id(state, pid, value)

defp maybe_append_children_number(map, %{"supportsVariablePaging" => true}, atom, value)
when atom in [:indexed, :named],
do: Map.put(map, Atom.to_string(atom) <> "Variables", Variables.num_children(value))

defp maybe_append_children_number(map, _, _, _value), do: map

defp maybe_append_variable_type(map, %{"supportsVariableType" => true}, value) do
Map.put(map, "type", Variables.type(value))
end

defp maybe_append_variable_type(map, _, _value), do: map

defp format_variable(value, %{"hex" => true}) when is_binary(value) do
Base.encode16(value)
end
defp format_variable(value, _), do: inspect(value)

defp evaluate_code_expression(expr, bindings, timeout) do
task =
Task.async(fn ->
Expand Down Expand Up @@ -673,34 +699,53 @@ defmodule ElixirLS.Debugger.Server do
end
end

defp all_variables(paused_processes) do
defp all_variables(paused_processes, nil) do
paused_processes
|> Enum.flat_map(fn {_pid, %PausedProcess{} = paused_process} ->
paused_process.frames |> Map.values()
|> Enum.flat_map(fn
{:evaluator, _} ->
# TODO setVariable?
[]

{_pid, %PausedProcess{} = paused_process} ->
paused_process.frames |> Map.values()
end)
|> Enum.filter(&match?(%Frame{bindings: bindings} when is_map(bindings), &1))
|> Enum.flat_map(fn %Frame{bindings: bindings} ->
Binding.to_elixir_variable_names(bindings)
end)
end

defp all_variables(paused_processes, frame_id) do
case find_frame(paused_processes, frame_id) do
{_pid, %Frame{bindings: bindings}} when is_map(bindings) ->
Binding.to_elixir_variable_names(bindings)

_ ->
[]
end
end

defp find_var(paused_processes, var_id) do
Enum.find_value(paused_processes, fn {pid, %PausedProcess{} = paused_process} ->
if Map.has_key?(paused_process.vars, var_id) do
{pid, paused_process.vars[var_id]}
Enum.find_value(paused_processes, fn {pid, %{vars: vars}} ->
if Map.has_key?(vars, var_id) do
{pid, vars[var_id]}
end
end)
end

defp find_frame(paused_processes, frame_id) do
Enum.find_value(paused_processes, fn {pid, %PausedProcess{} = paused_process} ->
if Map.has_key?(paused_process.frames, frame_id) do
{pid, paused_process.frames[frame_id]}
end
Enum.find_value(paused_processes, fn
{pid, %{frames: frames}} ->
if Map.has_key?(frames, frame_id) do
{pid, frames[frame_id]}
end

{:evaluator, _} ->
nil
end)
end

defp ensure_thread_id(state = %__MODULE__{}, pid) do
defp ensure_thread_id(state = %__MODULE__{}, pid) when is_pid(pid) do
if Map.has_key?(state.threads_inverse, pid) do
{state, state.threads_inverse[pid]}
else
Expand All @@ -719,7 +764,7 @@ defmodule ElixirLS.Debugger.Server do
end)
end

defp ensure_var_id(state = %__MODULE__{}, pid, var) do
defp ensure_var_id(state = %__MODULE__{}, pid, var) when is_pid(pid) or pid == :evaluator do
unless Map.has_key?(state.paused_processes, pid) do
raise ArgumentError, message: "paused process #{inspect(pid)} not found"
end
Expand All @@ -742,7 +787,7 @@ defmodule ElixirLS.Debugger.Server do
end)
end

defp ensure_frame_id(state = %__MODULE__{}, pid, %Frame{} = frame) do
defp ensure_frame_id(state = %__MODULE__{}, pid, %Frame{} = frame) when is_pid(pid) do
unless Map.has_key?(state.paused_processes, pid) do
raise ArgumentError, message: "paused process #{inspect(pid)} not found"
end
Expand Down Expand Up @@ -856,7 +901,7 @@ defmodule ElixirLS.Debugger.Server do
"supportedChecksumAlgorithms" => [],
"supportsRestartRequest" => false,
"supportsExceptionOptions" => false,
"supportsValueFormattingOptions" => false,
"supportsValueFormattingOptions" => true,
"supportsExceptionInfoRequest" => false,
"supportTerminateDebuggee" => false
}
Expand Down
9 changes: 8 additions & 1 deletion apps/elixir_ls_debugger/test/debugger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ defmodule ElixirLS.Debugger.ServerTest do
@tag :fixture
test "basic debugging", %{server: server} do
in_fixture(__DIR__, "mix_project", fn ->
Server.receive_packet(server, initialize_req(1, %{}))
Server.receive_packet(
server,
initialize_req(1, %{
"supportsVariablePaging" => true,
"supportsVariableType" => true
})
)

assert_receive(response(_, 1, "initialize", %{"supportsConfigurationDoneRequest" => true}))

Server.receive_packet(
Expand Down