@@ -128,14 +128,79 @@ defmodule ElixirLS.DebugAdapter.Server do
128128 end
129129
130130 def dbg(code, options, %Macro.Env{} = caller) do
131+ options = Keyword.put(options, :print_location, false)
132+
131133 quote do
132134 {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
133135 GenServer.call(unquote(__MODULE__), {:dbg, binding(), __ENV__, stacktrace}, :infinity)
134136 unquote(Macro.dbg(code, options, caller))
135137 end
136138 end
137139
138- def __next__(next?, binding, opts_or_env) when is_boolean(next?) do
140+ @doc """
141+ Annotate a quoted expression with line-by-line debugging steps.
142+ """
143+ @spec annotate_quoted(Macro.t(), Macro.t(), Macro.Env.t()) :: Macro.t()
144+ def annotate_quoted(quoted, condition, %Macro.Env{} = caller) do
145+ prelude =
146+ quote do
147+ [
148+ env = unquote(Macro.escape(Macro.Env.prune_compile_info(caller))),
149+ next? = unquote(condition)
150+ ]
151+ end
152+
153+ next_pry =
154+ fn line, _version, _binding ->
155+ quote do
156+ next? = unquote(__MODULE__).__next__(next?, binding(), %{env | line: unquote(line)})
157+ end
158+ end
159+
160+ annotate_quoted(quoted, prelude, caller.line, 0, :ok, fn _, _ -> :ok end, next_pry)
161+ end
162+
163+ defp annotate_quoted(maybe_block, prelude, line, version, binding, next_binding, next_pry)
164+ when is_list(prelude) do
165+ exprs =
166+ maybe_block
167+ |> unwrap_block()
168+ |> annotate_quoted(true, line, version, binding, {next_binding, next_pry})
169+
170+ {:__block__, [], prelude ++ exprs}
171+ end
172+
173+ defp annotate_quoted([expr | exprs], force?, line, version, binding, funs) do
174+ {next_binding, next_pry} = funs
175+ new_binding = next_binding.(expr, binding)
176+ {min_line, max_line} = line_range(expr, line)
177+
178+ if force? or min_line > line do
179+ [
180+ next_pry.(min_line, version, binding),
181+ expr | annotate_quoted(exprs, false, max_line, version + 1, new_binding, funs)
182+ ]
183+ else
184+ [expr | annotate_quoted(exprs, false, max_line, version, new_binding, funs)]
185+ end
186+ end
187+
188+ defp annotate_quoted([], _force?, _line, _version, _binding, _funs) do
189+ []
190+ end
191+
192+ def __next__(next?, binding, opts) when is_boolean(next?) and is_list(opts) do
193+ vars = for {key, _} when is_atom(key) <- binding, do: {key, nil}
194+
195+ env =
196+ opts
197+ |> Code.env_for_eval()
198+ |> :elixir_env.with_vars(vars)
199+
200+ __next__(next?, binding, env)
201+ end
202+
203+ def __next__(next?, binding, %Macro.Env{} = opts_or_env) when is_boolean(next?) do
139204 if next? do
140205 {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
141206
@@ -3179,6 +3244,72 @@ defmodule ElixirLS.DebugAdapter.Server do
31793244 Enum.map(asts, fn {ast, _pipe_index} -> Macro.to_string(ast) end)
31803245 end
31813246
3247+ defp line_range(ast, line) do
3248+ {_, {min, max}} =
3249+ Macro.prewalk(ast, {:infinity, line}, fn
3250+ {_, meta, _} = current_ast, {min_line, max_line} when is_list(meta) ->
3251+ case Keyword.fetch(meta, :line) do
3252+ {:ok, current_line} when current_line > 0 ->
3253+ {current_ast, {min(current_line, min_line), max(current_line, max_line)}}
3254+
3255+ _ ->
3256+ {current_ast, {min_line, max_line}}
3257+ end
3258+
3259+ current_ast, acc ->
3260+ {current_ast, acc}
3261+ end)
3262+
3263+ if min == :infinity, do: {line, max}, else: {min, max}
3264+ end
3265+
3266+ @doc false
3267+ def next_binding(ast, binding) do
3268+ {_, binding} =
3269+ Macro.prewalk(ast, binding, fn
3270+ {:=, _, [left, _right]}, acc ->
3271+ {:ok, match_binding(left, acc)}
3272+
3273+ {:case, _, [arg, _block]}, acc ->
3274+ {arg, acc}
3275+
3276+ {special_form, _, _}, acc
3277+ when special_form in [:cond, :fn, :for, :receive, :try, :with] ->
3278+ {:ok, acc}
3279+
3280+ current_ast, acc ->
3281+ {current_ast, acc}
3282+ end)
3283+
3284+ binding
3285+ end
3286+
3287+ @doc false
3288+ def match_binding(match, binding) do
3289+ {_, binding} =
3290+ Macro.prewalk(match, binding, fn
3291+ {name, _, nil} = var, acc when name != :_ and is_atom(name) ->
3292+ {var, Map.put(acc, name, var)}
3293+
3294+ {special_form, _, _}, acc when special_form in [:^, :"::"] ->
3295+ {:ok, acc}
3296+
3297+ current_ast, acc ->
3298+ {current_ast, acc}
3299+ end)
3300+
3301+ binding
3302+ end
3303+
3304+ @doc false
3305+ def next_var(id) do
3306+ {:next?, [version: -id], __MODULE__}
3307+ end
3308+
3309+ defp unwrap_block(expr), do: expr |> unwrap_block([]) |> Enum.reverse()
3310+ defp unwrap_block({:__block__, _, exprs}, acc), do: Enum.reduce(exprs, acc, &unwrap_block/2)
3311+ defp unwrap_block(expr, acc), do: [expr | acc]
3312+
31823313 defp env_with_line_from_asts(asts) do
31833314 line =
31843315 Enum.find_value(asts, fn
0 commit comments