Skip to content

Commit 21c93f7

Browse files
Simple eval support (#339)
* Simple eval support * Additional tests and unlinking evaluating process from owner * fixup! Additional tests and unlinking evaluating process from owner * fixup! Additional tests and unlinking evaluating process from owner * Access to variables in Watch & Debug Console modes * Reducing count of iterations in all_variables/1
1 parent 396c0c9 commit 21c93f7

File tree

2 files changed

+129
-3
lines changed

2 files changed

+129
-3
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ defmodule ElixirLS.Debugger.Server do
1515
end
1616

1717
alias ElixirLS.Debugger.{Output, Stacktrace, Protocol, Variables}
18+
alias ElixirLS.Debugger.Stacktrace.Frame
1819
use GenServer
1920
use Protocol
2021

@@ -284,9 +285,13 @@ defmodule ElixirLS.Debugger.Server do
284285
{%{"variables" => vars_json}, state}
285286
end
286287

287-
defp handle_request(request(_, "evaluate"), state) do
288-
msg = "(Debugger) Expression evaluation in Elixir debugger is not supported (yet)."
289-
{%{"result" => msg, "variablesReference" => 0}, state}
288+
defp handle_request(request(_cmd, "evaluate", %{"expression" => expr} = _args), state) do
289+
timeout = 1_000
290+
bindings = all_variables(state.paused_processes)
291+
292+
result = evaluate_code_expression(expr, bindings, timeout)
293+
294+
{%{"result" => inspect(result), "variablesReference" => 0}, state}
290295
end
291296

292297
defp handle_request(request(_, "disconnect"), state) do
@@ -376,6 +381,49 @@ defmodule ElixirLS.Debugger.Server do
376381
end)
377382
end
378383

384+
defp evaluate_code_expression(expr, bindings, timeout) do
385+
task =
386+
Task.async(fn ->
387+
try do
388+
{term, _bindings} = Code.eval_string(expr, bindings)
389+
term
390+
catch
391+
error -> error
392+
end
393+
end)
394+
395+
Process.unlink(task.pid)
396+
397+
result = Task.yield(task, timeout) || Task.shutdown(task)
398+
399+
case result do
400+
{:ok, data} -> data
401+
nil -> :elixir_ls_expression_timeout
402+
_otherwise -> result
403+
end
404+
end
405+
406+
defp all_variables(paused_processes) do
407+
paused_processes
408+
|> Enum.flat_map(fn {_pid, paused_process} -> paused_process.frames |> Map.values() end)
409+
|> Enum.filter(&match?(%Frame{bindings: bindings} when is_map(bindings), &1))
410+
|> Enum.flat_map(fn %Frame{bindings: bindings} ->
411+
bindings |> Enum.map(&rename_binding_to_classic_variable/1)
412+
end)
413+
end
414+
415+
defp rename_binding_to_classic_variable({key, value}) do
416+
# binding is present with prefix _ and postfix @
417+
# for example _key@1 and _value@1 are representations of current function variables
418+
new_key =
419+
key
420+
|> Atom.to_string()
421+
|> String.replace(~r/_(.*)@\d/, "\\1")
422+
|> String.to_atom()
423+
424+
{new_key, value}
425+
end
426+
379427
defp find_var(paused_processes, var_id) do
380428
Enum.find_value(paused_processes, fn {pid, paused_process} ->
381429
if Map.has_key?(paused_process.vars, var_id) do

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,82 @@ defmodule ElixirLS.Debugger.ServerTest do
166166
assert(:hello in :int.interpreted())
167167
end)
168168
end
169+
170+
describe "Watch section" do
171+
defp gen_packet(expr) do
172+
%{
173+
"arguments" => %{
174+
"context" => "watch",
175+
"expression" => expr,
176+
"frameId" => 123
177+
},
178+
"command" => "evaluate",
179+
"seq" => 1,
180+
"type" => "request"
181+
}
182+
end
183+
184+
test "Evaluate expression with OK result", %{server: server} do
185+
Server.receive_packet(
186+
server,
187+
gen_packet("1 + 2 + 3 + 4")
188+
)
189+
190+
assert_receive(%{"body" => %{"result" => "10"}}, 1000)
191+
192+
assert Process.alive?(server)
193+
end
194+
195+
test "Evaluate expression with ERROR result", %{server: server} do
196+
Server.receive_packet(
197+
server,
198+
gen_packet("1 = 2")
199+
)
200+
201+
assert_receive(%{"body" => %{"result" => result}}, 1000)
202+
203+
assert result =~ ~r/badmatch/
204+
205+
assert Process.alive?(server)
206+
end
207+
208+
test "Evaluate expression with attempt to exit debugger process", %{server: server} do
209+
Server.receive_packet(
210+
server,
211+
gen_packet("Process.exit(self)")
212+
)
213+
214+
assert_receive(%{"body" => %{"result" => result}}, 1000)
215+
216+
assert result =~ ~r/:exit/
217+
218+
assert Process.alive?(server)
219+
end
220+
221+
test "Evaluate expression with attempt to throw debugger process", %{server: server} do
222+
Server.receive_packet(
223+
server,
224+
gen_packet("throw(:goodmorning_bug)")
225+
)
226+
227+
assert_receive(%{"body" => %{"result" => result}}, 1000)
228+
229+
assert result =~ ~r/:goodmorning_bug/
230+
231+
assert Process.alive?(server)
232+
end
233+
234+
test "Evaluate expression which has long execution", %{server: server} do
235+
Server.receive_packet(
236+
server,
237+
gen_packet(":timer.sleep(10_000)")
238+
)
239+
240+
assert_receive(%{"body" => %{"result" => result}}, 1100)
241+
242+
assert result =~ ~r/:elixir_ls_expression_timeout/
243+
244+
assert Process.alive?(server)
245+
end
246+
end
169247
end

0 commit comments

Comments
 (0)