@@ -16,27 +16,27 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
16
16
@ spec register_condition (
17
17
module ,
18
18
module ,
19
- [ non_neg_integer ] ,
19
+ non_neg_integer ,
20
20
String . t ( ) ,
21
21
String . t ( ) | nil ,
22
- non_neg_integer
22
+ String . t ( )
23
23
) ::
24
24
{ :ok , { module , atom } } | { :error , :limit_reached }
25
- def register_condition ( name \\ __MODULE__ , module , lines , condition , log_message , hit_count ) do
25
+ def register_condition ( name \\ __MODULE__ , module , line , env , condition , log_message , hit_count ) do
26
26
GenServer . call (
27
27
name ,
28
- { :register_condition , { module , lines } , condition , log_message , hit_count }
28
+ { :register_condition , { module , line } , env , condition , log_message , hit_count }
29
29
)
30
30
end
31
31
32
- @ spec unregister_condition ( module , module , [ non_neg_integer ] ) :: :ok
33
- def unregister_condition ( name \\ __MODULE__ , module , lines ) do
34
- GenServer . cast ( name , { :unregister_condition , { module , lines } } )
32
+ @ spec unregister_condition ( module , module , non_neg_integer ) :: :ok
33
+ def unregister_condition ( name \\ __MODULE__ , module , line ) do
34
+ GenServer . cast ( name , { :unregister_condition , { module , line } } )
35
35
end
36
36
37
- @ spec has_condition? ( module , module , [ non_neg_integer ] ) :: boolean
38
- def has_condition? ( name \\ __MODULE__ , module , lines ) do
39
- GenServer . call ( name , { :has_condition? , { module , lines } } )
37
+ @ spec has_condition? ( module , module , non_neg_integer ) :: boolean
38
+ def has_condition? ( name \\ __MODULE__ , module , line ) do
39
+ GenServer . call ( name , { :has_condition? , { module , line } } )
40
40
end
41
41
42
42
@ spec get_condition ( module , non_neg_integer ) ::
@@ -96,7 +96,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
96
96
97
97
@ impl GenServer
98
98
def handle_call (
99
- { :register_condition , key , condition , log_message , hit_count } ,
99
+ { :register_condition , key , env , condition , log_message , hit_count } ,
100
100
_from ,
101
101
% { free: free , conditions: conditions } = state
102
102
) do
@@ -111,7 +111,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
111
111
state
112
112
| free: rest ,
113
113
conditions:
114
- conditions |> Map . put ( key , { number , { condition , log_message , hit_count } } )
114
+ conditions |> Map . put ( key , { number , { env , condition , log_message , hit_count } } )
115
115
}
116
116
117
117
{ :reply , { :ok , { __MODULE__ , :"check_#{ number } " } } , state }
@@ -120,7 +120,8 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
120
120
{ number , _old_condition } ->
121
121
state = % {
122
122
state
123
- | conditions: conditions |> Map . put ( key , { number , { condition , log_message , hit_count } } )
123
+ | conditions:
124
+ conditions |> Map . put ( key , { number , { env , condition , log_message , hit_count } } )
124
125
}
125
126
126
127
{ :reply , { :ok , { __MODULE__ , :"check_#{ number } " } } , state }
@@ -132,11 +133,11 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
132
133
end
133
134
134
135
def handle_call ( { :get_condition , number } , _from , % { conditions: conditions , hits: hits } = state ) do
135
- { condition , log_message , hit_count } =
136
+ { env , condition , log_message , hit_count } =
136
137
conditions |> Map . values ( ) |> Enum . find ( fn { n , _c } -> n == number end ) |> elem ( 1 )
137
138
138
139
hits = hits |> Map . get ( number , 0 )
139
- { :reply , { condition , log_message , hit_count , hits } , state }
140
+ { :reply , { env , condition , log_message , hit_count , hits } , state }
140
141
end
141
142
142
143
def handle_call ( :clear , _from , _state ) do
@@ -177,14 +178,20 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
177
178
for i <- @ range do
178
179
@ spec unquote ( :"check_#{ i } " ) ( term ) :: boolean
179
180
def unquote ( :"check_#{ i } " ) ( binding ) do
180
- { condition , log_message , hit_count , hits } = get_condition ( unquote ( i ) )
181
+ { env , condition , log_message , hit_count_condition , hits } = get_condition ( unquote ( i ) )
181
182
elixir_binding = binding |> ElixirLS.DebugAdapter.Binding . to_elixir_variable_names ( )
182
- result = eval_condition ( condition , elixir_binding )
183
+ result = eval_condition ( condition , elixir_binding , env )
183
184
184
185
result =
185
186
if result do
186
187
register_hit ( unquote ( i ) )
187
188
# do not break if hit count not reached
189
+ # the spec requires:
190
+ # If both this property and `condition` are specified, `hitCondition` should
191
+ # be evaluated only if the `condition` is met, and the debug adapter should
192
+ # stop only if both conditions are met.
193
+
194
+ hit_count = eval_hit_condition ( hit_count_condition , elixir_binding , env )
188
195
hits + 1 > hit_count
189
196
else
190
197
result
@@ -194,20 +201,22 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
194
201
# Debug Adapter Protocol:
195
202
# If this attribute exists and is non-empty, the backend must not 'break' (stop)
196
203
# but log the message instead. Expressions within {} are interpolated.
197
- Output . debugger_console ( interpolate ( log_message , elixir_binding ) )
204
+ # If either `hitCondition` or `condition` is specified, then the message
205
+ # should only be logged if those conditions are met.
206
+ Output . debugger_console ( interpolate ( log_message , elixir_binding , env ) )
198
207
false
199
208
else
200
209
result
201
210
end
202
211
end
203
212
end
204
213
205
- @ spec eval_condition ( String . t ( ) , keyword ) :: boolean
206
- def eval_condition ( "true" , _binding ) , do: true
214
+ @ spec eval_condition ( String . t ( ) , keyword , Macro.Env . t ( ) ) :: boolean
215
+ def eval_condition ( "true" , _binding , _env ) , do: true
207
216
208
- def eval_condition ( condition , elixir_binding ) do
217
+ def eval_condition ( condition , elixir_binding , env ) do
209
218
try do
210
- { term , _bindings } = Code . eval_string ( condition , elixir_binding )
219
+ { term , _bindings } = Code . eval_string ( condition , elixir_binding , env )
211
220
if term , do: true , else: false
212
221
catch
213
222
kind , error ->
@@ -219,9 +228,29 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
219
228
end
220
229
end
221
230
222
- def eval_string ( expression , elixir_binding ) do
231
+ @ spec eval_hit_condition ( String . t ( ) , keyword , Macro.Env . t ( ) ) :: number
232
+ def eval_hit_condition ( "0" , _binding , _env ) , do: 0
233
+
234
+ def eval_hit_condition ( condition , elixir_binding , env ) do
235
+ try do
236
+ { term , _bindings } = Code . eval_string ( condition , elixir_binding , env )
237
+
238
+ if is_number ( term ) do
239
+ term
240
+ else
241
+ raise "Hit count evaluated to non number #{ inspect ( term ) } "
242
+ end
243
+ catch
244
+ kind , error ->
245
+ Output . debugger_important ( "Error in hit count: " <> Exception . format_banner ( kind , error ) )
246
+
247
+ 0
248
+ end
249
+ end
250
+
251
+ def eval_string ( expression , elixir_binding , env ) do
223
252
try do
224
- { term , _bindings } = Code . eval_string ( expression , elixir_binding )
253
+ { term , _bindings } = Code . eval_string ( expression , elixir_binding , env )
225
254
to_string ( term )
226
255
catch
227
256
kind , error ->
@@ -233,39 +262,39 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
233
262
end
234
263
end
235
264
236
- def interpolate ( format_string , elixir_binding ) do
237
- interpolate ( format_string , [ ] , elixir_binding )
265
+ def interpolate ( format_string , elixir_binding , env ) do
266
+ interpolate ( format_string , [ ] , elixir_binding , env )
238
267
|> Enum . reverse ( )
239
268
|> IO . iodata_to_binary ( )
240
269
end
241
270
242
- def interpolate ( << >> , acc , _elixir_binding ) , do: acc
271
+ def interpolate ( << >> , acc , _elixir_binding , _env ) , do: acc
243
272
244
- def interpolate ( << "\\ {" , rest :: binary >> , acc , elixir_binding ) ,
245
- do: interpolate ( rest , [ "{" | acc ] , elixir_binding )
273
+ def interpolate ( << "\\ {" , rest :: binary >> , acc , elixir_binding , env ) ,
274
+ do: interpolate ( rest , [ "{" | acc ] , elixir_binding , env )
246
275
247
- def interpolate ( << "\\ }" , rest :: binary >> , acc , elixir_binding ) ,
248
- do: interpolate ( rest , [ "}" | acc ] , elixir_binding )
276
+ def interpolate ( << "\\ }" , rest :: binary >> , acc , elixir_binding , env ) ,
277
+ do: interpolate ( rest , [ "}" | acc ] , elixir_binding , env )
249
278
250
- def interpolate ( << "{" , rest :: binary >> , acc , elixir_binding ) do
279
+ def interpolate ( << "{" , rest :: binary >> , acc , elixir_binding , env ) do
251
280
case parse_expression ( rest , [ ] ) do
252
281
{ :ok , expression_iolist , expression_rest } ->
253
282
expression =
254
283
expression_iolist
255
284
|> Enum . reverse ( )
256
285
|> IO . iodata_to_binary ( )
257
286
258
- eval_result = eval_string ( expression , elixir_binding )
259
- interpolate ( expression_rest , [ eval_result | acc ] , elixir_binding )
287
+ eval_result = eval_string ( expression , elixir_binding , env )
288
+ interpolate ( expression_rest , [ eval_result | acc ] , elixir_binding , env )
260
289
261
290
:error ->
262
291
Output . debugger_important ( "Log message has unpaired or nested `{}`" )
263
292
acc
264
293
end
265
294
end
266
295
267
- def interpolate ( << char :: binary - size ( 1 ) , rest :: binary >> , acc , elixir_binding ) ,
268
- do: interpolate ( rest , [ char | acc ] , elixir_binding )
296
+ def interpolate ( << char :: binary - size ( 1 ) , rest :: binary >> , acc , elixir_binding , env ) ,
297
+ do: interpolate ( rest , [ char | acc ] , elixir_binding , env )
269
298
270
299
def parse_expression ( << >> , _acc ) , do: :error
271
300
def parse_expression ( << "\\ {" , rest :: binary >> , acc ) , do: parse_expression ( rest , [ "{" | acc ] )
0 commit comments