@@ -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