debug 1.0.0.alpha1 → 1.0.0.beta5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,77 @@
1
- module DEBUGGER__
2
- class SourceRepository
3
- def initialize
4
- @files = {} # filename => [src, iseq]
5
- end
6
-
7
- def add iseq, src
8
- begin
9
- src = File.read(iseq.path)
10
- rescue
11
- src = nil
12
- end unless src
13
- @files[iseq.path] = [src, iseq]
14
- end
15
-
16
- def get path
17
- @files[path]
18
- end
19
- end
20
- end
1
+ require_relative 'color'
2
+
3
+ module DEBUGGER__
4
+ class SourceRepository
5
+ SrcInfo = Struct.new(:src, :colored)
6
+
7
+ def initialize
8
+ @files = {} # filename => SrcInfo
9
+ end
10
+
11
+ def add iseq, src
12
+ if (path = iseq.absolute_path) && File.exist?(path)
13
+ add_path path
14
+ elsif src
15
+ add_iseq iseq, src
16
+ end
17
+ end
18
+
19
+ def all_iseq iseq, rs = []
20
+ rs << iseq
21
+ iseq.each_child{|ci|
22
+ all_iseq(ci, rs)
23
+ }
24
+ rs
25
+ end
26
+
27
+ private def add_iseq iseq, src
28
+ line = iseq.first_line
29
+ if line > 1
30
+ src = ("\n" * (line - 1)) + src
31
+ end
32
+ si = SrcInfo.new(src.lines)
33
+
34
+ all_iseq(iseq).each{|e|
35
+ e.instance_variable_set(:@debugger_si, si)
36
+ e.freeze
37
+ }
38
+ end
39
+
40
+ private def add_path path
41
+ begin
42
+ src = File.read(path)
43
+ src = src.gsub("\r\n", "\n") # CRLF -> LF
44
+ @files[path] = SrcInfo.new(src.lines)
45
+ rescue SystemCallError
46
+ end
47
+ end
48
+
49
+ private def get_si iseq
50
+ return unless iseq
51
+
52
+ if iseq.instance_variable_defined?(:@debugger_si)
53
+ iseq.instance_variable_get(:@debugger_si)
54
+ elsif @files.has_key?(path = iseq.absolute_path)
55
+ @files[path]
56
+ elsif path
57
+ add_path(path)
58
+ end
59
+ end
60
+
61
+ def get iseq
62
+ if si = get_si(iseq)
63
+ si.src
64
+ end
65
+ end
66
+
67
+ include Color
68
+
69
+ def get_colored iseq
70
+ if si = get_si(iseq)
71
+ si.colored || begin
72
+ si.colored = colorize_code(si.src.join).lines
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
File without changes
@@ -1,25 +1,89 @@
1
+ require 'objspace'
2
+ require 'pp'
3
+
4
+ require_relative 'frame_info'
5
+ require_relative 'color'
6
+
1
7
  module DEBUGGER__
2
8
  class ThreadClient
3
9
  def self.current
4
10
  Thread.current[:DEBUGGER__ThreadClient] || begin
5
- tc = SESSION.thread_client
11
+ tc = ::DEBUGGER__::SESSION.thread_client
6
12
  Thread.current[:DEBUGGER__ThreadClient] = tc
7
13
  end
8
14
  end
9
15
 
10
- attr_reader :location, :thread
16
+ include Color
17
+
18
+ attr_reader :location, :thread, :mode, :id
19
+
20
+ def assemble_arguments(args)
21
+ args.map do |arg|
22
+ "#{colorize_cyan(arg[:name])}=#{arg[:value]}"
23
+ end.join(", ")
24
+ end
25
+
26
+ def default_frame_formatter frame
27
+ call_identifier_str =
28
+ case frame.frame_type
29
+ when :block
30
+ level, block_loc, args = frame.block_identifier
31
+
32
+ if !args.empty?
33
+ args_str = " {|#{assemble_arguments(args)}|}"
34
+ end
35
+
36
+ "#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}"
37
+ when :method
38
+ ci, args = frame.method_identifier
39
+
40
+ if !args.empty?
41
+ args_str = "(#{assemble_arguments(args)})"
42
+ end
43
+
44
+ "#{colorize_blue(ci)}#{args_str}"
45
+ when :c
46
+ colorize_blue(frame.c_identifier)
47
+ when :other
48
+ colorize_blue(frame.other_identifier)
49
+ end
50
+
51
+ location_str = colorize(frame.location_str, [:GREEN])
52
+ result = "#{call_identifier_str} at #{location_str}"
53
+
54
+ if return_str = frame.return_str
55
+ return_str = colorize(frame.return_str, [:MAGENTA, :BOLD])
56
+ result += " #=> #{return_str}"
57
+ end
58
+
59
+ result
60
+ end
11
61
 
12
- def initialize q_evt, q_cmd, thr = Thread.current
62
+ def initialize id, q_evt, q_cmd, thr = Thread.current
63
+ @id = id
13
64
  @thread = thr
65
+ @target_frames = nil
14
66
  @q_evt = q_evt
15
67
  @q_cmd = q_cmd
16
68
  @step_tp = nil
17
69
  @output = []
18
- @mode = nil
70
+ @src_lines_on_stop = (::DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
71
+ @show_frames_on_stop = (::DEBUGGER__::CONFIG[:show_frames] || 2).to_i
72
+ @frame_formatter = method(:default_frame_formatter)
73
+ @var_map = {} # { thread_local_var_id => obj } for DAP
74
+ set_mode nil
75
+ end
76
+
77
+ def name
78
+ "##{@id} #{@thread.name || @thread.backtrace.last}"
79
+ end
80
+
81
+ def close
82
+ @q_cmd.close
19
83
  end
20
84
 
21
85
  def inspect
22
- "#<DEBUGGER__ThreadClient #{@thread}>"
86
+ "#<DBG:TC #{self.id}:#{self.mode}@#{@thread.backtrace[-1]}>"
23
87
  end
24
88
 
25
89
  def puts str = ''
@@ -37,18 +101,24 @@ module DEBUGGER__
37
101
  @q_cmd << req
38
102
  end
39
103
 
104
+ def generate_info
105
+ return unless current_frame
106
+
107
+ { location: current_frame.location_str, line: current_frame.location.lineno }
108
+ end
109
+
40
110
  def event! ev, *args
41
- @q_evt << [self, @output, ev, *args]
111
+ @q_evt << [self, @output, ev, generate_info, *args]
42
112
  @output = []
43
113
  end
44
114
 
45
115
  ## events
46
116
 
47
- def on_trap
48
- if @mode == :wait_next_action
49
- raise Interrupt
117
+ def on_trap sig
118
+ if self.mode == :wait_next_action
119
+ # raise Interrupt
50
120
  else
51
- on_suspend :trap
121
+ on_suspend :trap, sig: sig
52
122
  end
53
123
  end
54
124
 
@@ -56,18 +126,24 @@ module DEBUGGER__
56
126
  on_suspend :pause
57
127
  end
58
128
 
129
+ def on_thread_begin th
130
+ event! :thread_begin, th
131
+ wait_next_action
132
+ end
133
+
59
134
  def on_load iseq, eval_src
60
135
  event! :load, iseq, eval_src
61
136
  wait_next_action
62
137
  end
63
138
 
64
- def on_breakpoint tp
65
- on_suspend tp.event, tp
139
+ def on_breakpoint tp, bp
140
+ on_suspend tp.event, tp, bp: bp
66
141
  end
67
142
 
68
- def on_suspend event, tp = nil
143
+ def on_suspend event, tp = nil, bp: nil, sig: nil
69
144
  @current_frame_index = 0
70
- @target_frames = target_frames
145
+ @target_frames = DEBUGGER__.capture_frames __dir__
146
+
71
147
  cf = @target_frames.first
72
148
  if cf
73
149
  @location = cf.location
@@ -76,222 +152,277 @@ module DEBUGGER__
76
152
  cf.has_return_value = true
77
153
  cf.return_value = tp.return_value
78
154
  end
155
+
156
+ if CatchBreakpoint === bp
157
+ cf.has_raised_exception = true
158
+ cf.raised_exception = bp.last_exc
159
+ end
79
160
  end
80
161
 
81
162
  if event != :pause
82
- show_src
83
- print_frames 3
84
- event! :suspend, :breakpoint
163
+ show_src max_lines: @src_lines_on_stop
164
+ show_frames @show_frames_on_stop
165
+
166
+ if bp
167
+ event! :suspend, :breakpoint, bp.key
168
+ elsif sig
169
+ event! :suspend, :trap, sig
170
+ else
171
+ event! :suspend, event
172
+ end
85
173
  end
86
174
 
87
175
  wait_next_action
88
176
  end
89
-
177
+
90
178
  ## control all
91
179
 
180
+ begin
181
+ TracePoint.new(:raise){}.enable(target_thread: Thread.current)
182
+ SUPPORT_TARGET_THREAD = true
183
+ rescue ArgumentError
184
+ SUPPORT_TARGET_THREAD = false
185
+ end
186
+
92
187
  def step_tp
93
188
  @step_tp.disable if @step_tp
94
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
95
- next if SESSION.break? tp.path, tp.lineno
96
- next if !yield
97
- tp.disable
98
- on_suspend tp.event, tp
99
- }
100
-
101
- @step_tp.enable(target_thread: Thread.current)
102
- end
103
-
104
- FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :has_return_value, :return_value)
105
-
106
- def target_frames
107
- RubyVM::DebugInspector.open{|dc|
108
- locs = dc.backtrace_locations
109
- locs.map.with_index{|e, i|
110
- unless File.dirname(e.path) == File.dirname(__FILE__)
111
- FrameInfo.new(
112
- e,
113
- dc.frame_self(i),
114
- dc.frame_binding(i),
115
- dc.frame_iseq(i),
116
- dc.frame_class(i))
117
- end
118
- }.compact
119
- }
120
- end
121
189
 
122
- def target_frames_count
123
- RubyVM::DebugInspector.open{|dc|
124
- locs = dc.backtrace_locations
125
- locs.count{|e|
126
- e.path != __FILE__
190
+ thread = Thread.current
191
+
192
+ if SUPPORT_TARGET_THREAD
193
+ @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
194
+ next if SESSION.break? tp.path, tp.lineno
195
+ next if !yield
196
+ next if tp.path.start_with?(__dir__)
197
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
198
+
199
+ tp.disable
200
+ on_suspend tp.event, tp
201
+ }
202
+ @step_tp.enable(target_thread: thread)
203
+ else
204
+ @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
205
+ next if thread != Thread.current
206
+ next if SESSION.break? tp.path, tp.lineno
207
+ next if !yield
208
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
209
+
210
+ tp.disable
211
+ on_suspend tp.event, tp
127
212
  }
128
- }
213
+ @step_tp.enable
214
+ end
129
215
  end
130
216
 
131
217
  def current_frame
132
- @target_frames[@current_frame_index]
133
- end
134
-
135
- def file_lines path
136
- if (src = SESSION.source(path)) && src[0]
137
- src[0].lines
138
- elsif File.exist?(path)
139
- File.readlines(path)
218
+ if @target_frames
219
+ @target_frames[@current_frame_index]
220
+ else
221
+ nil
140
222
  end
141
223
  end
142
224
 
143
- def show_src frame_index = @current_frame_index, max_lines: 10
144
- if current_line = @target_frames[frame_index]&.location
145
- puts
146
- path, line = current_line.path, current_line.lineno - 1
147
- if file_lines = file_lines(path)
148
- lines = file_lines.map.with_index{|e, i|
149
- if i == line
225
+ def show_src(frame_index: @current_frame_index,
226
+ update_line: false,
227
+ max_lines: 10,
228
+ start_line: nil,
229
+ end_line: nil,
230
+ dir: +1)
231
+ if @target_frames && frame = @target_frames[frame_index]
232
+ if file_lines = frame.file_lines
233
+ frame_line = frame.location.lineno - 1
234
+
235
+ lines = file_lines.map.with_index do |e, i|
236
+ if i == frame_line
150
237
  "=> #{'%4d' % (i+1)}| #{e}"
151
238
  else
152
239
  " #{'%4d' % (i+1)}| #{e}"
153
240
  end
154
- }
155
- min = [0, line - max_lines/2].max
156
- max = [min+max_lines, lines.size].min
157
- puts "[#{min+1}, #{max}] in #{path}"
158
- puts lines[min ... max]
241
+ end
242
+
243
+ unless start_line
244
+ if frame.show_line
245
+ if dir > 0
246
+ start_line = frame.show_line
247
+ else
248
+ end_line = frame.show_line - max_lines
249
+ start_line = [end_line - max_lines, 0].max
250
+ end
251
+ else
252
+ start_line = [frame_line - max_lines/2, 0].max
253
+ end
254
+ end
255
+
256
+ unless end_line
257
+ end_line = [start_line + max_lines, lines.size].min
258
+ end
259
+
260
+ if update_line
261
+ frame.show_line = end_line
262
+ end
263
+
264
+ if start_line != end_line && max_lines
265
+ puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
266
+ puts lines[start_line ... end_line]
267
+ end
268
+ else # no file lines
269
+ puts "# No sourcefile available for #{frame.path}"
159
270
  end
160
271
  end
272
+ rescue Exception => e
273
+ p e
274
+ pp e.backtrace
275
+ exit!
276
+ end
277
+
278
+ def show_by_editor path = nil
279
+ unless path
280
+ if @target_frames && frame = @target_frames[@current_frame_index]
281
+ path = frame.path
282
+ else
283
+ return # can't get path
284
+ end
285
+ end
286
+
287
+ if File.exist?(path)
288
+ if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
289
+ puts "command: #{editor}"
290
+ puts " path: #{path}"
291
+ system(editor, path)
292
+ else
293
+ puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
294
+ end
295
+ else
296
+ puts "Can not find file: #{path}"
297
+ end
161
298
  end
162
299
 
163
300
  def show_locals
164
301
  if s = current_frame&.self
165
- puts " %self => #{s}"
302
+ puts " #{colorize_cyan("%self")} => #{colored_inspect(s)}"
166
303
  end
167
304
  if current_frame&.has_return_value
168
- puts " %return => #{current_frame.return_value}"
305
+ puts " #{colorize_cyan("%return")} => #{colored_inspect(current_frame.return_value)}"
306
+ end
307
+ if current_frame&.has_raised_exception
308
+ puts " #{colorize_cyan("%raised")} => #{colored_inspect(current_frame.raised_exception)}"
169
309
  end
170
310
  if b = current_frame&.binding
171
311
  b.local_variables.each{|loc|
172
- puts " #{loc} => #{b.local_variable_get(loc).inspect}"
312
+ value = b.local_variable_get(loc)
313
+ puts " #{colorize_cyan(loc)} => #{colored_inspect(value)}"
173
314
  }
174
315
  end
175
316
  end
176
317
 
177
318
  def show_ivars
178
319
  if s = current_frame&.self
179
- puts " self => #{s}"
180
- s.instance_variables.eaach{|iv|
181
- puts " #{iv} => #{s.instance_variable_get(iv)}"
320
+ s.instance_variables.each{|iv|
321
+ value = s.instance_variable_get(iv)
322
+ puts " #{colorize_cyan(iv)} => #{colored_inspect(value)}"
182
323
  }
183
324
  end
184
325
  end
185
326
 
186
- def frame_eval src, failed_value: nil
327
+ def frame_eval src, re_raise: false
187
328
  begin
329
+ @success_last_eval = false
330
+
188
331
  b = current_frame.binding
189
- if b
190
- b.eval(src)
191
- else
192
- frame_self = current_frame.self
193
- frame_self.instance_eval(src)
194
- # puts "eval is not supported on this frame."
195
- end
332
+ result = if b
333
+ f, _l = b.source_location
334
+ b.eval(src, "(rdbg)/#{f}")
335
+ else
336
+ frame_self = current_frame.self
337
+ frame_self.instance_eval(src)
338
+ end
339
+ @success_last_eval = true
340
+ result
341
+
196
342
  rescue Exception => e
197
- return failed_value if failed_value
343
+ return yield(e) if block_given?
344
+
345
+ puts "eval error: #{e}"
198
346
 
199
- puts "Error: #{e}"
200
347
  e.backtrace_locations.each do |loc|
201
348
  break if loc.path == __FILE__
202
349
  puts " #{loc}"
203
350
  end
204
- nil
351
+ raise if re_raise
205
352
  end
206
353
  end
207
354
 
208
- def parameters_info b, vars
209
- vars.map{|var|
210
- "#{var}=#{short_inspect(b.eval(var.to_s))}"
211
- }.join(', ')
355
+ def frame_str(i)
356
+ cur_str = (@current_frame_index == i ? '=>' : ' ')
357
+ prefix = "#{cur_str}##{i}"
358
+ frame = @target_frames[i]
359
+ frame_string = @frame_formatter.call(frame)
360
+ "#{prefix}\t#{frame_string}"
212
361
  end
213
362
 
214
- def klass_sig frame
215
- klass = frame.class
216
- if klass == frame.self.singleton_class
217
- "#{frame.self}."
218
- else
219
- "#{frame.class}#"
363
+ def show_frames max = (@target_frames || []).size
364
+ if max > 0 && @target_frames
365
+ size = @target_frames.size
366
+ max += 1 if size == max + 1
367
+ max.times{|i|
368
+ break if i >= size
369
+ puts frame_str(i)
370
+ }
371
+ puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
220
372
  end
221
373
  end
222
374
 
223
- SHORT_INSPECT_LENGTH = 40
224
-
225
- def short_inspect obj
226
- str = obj.inspect
227
- if str.length > SHORT_INSPECT_LENGTH
228
- str[0...SHORT_INSPECT_LENGTH] + '...'
229
- else
230
- str
231
- end
375
+ def show_frame i=0
376
+ puts frame_str(i)
232
377
  end
233
378
 
234
- def frame_str i
235
- buff = ''.dup
236
- frame = @target_frames[i]
237
- b = frame.binding
238
-
239
- buff << (@current_frame_index == i ? '--> ' : ' ')
240
- if b
241
- buff << "##{i}\t#{frame.location}"
379
+ def show_object_info expr
380
+ begin
381
+ result = frame_eval(expr, re_raise: true)
382
+ rescue Exception
383
+ # ignore
242
384
  else
243
- buff << "##{i}\t[C] #{frame.location}"
244
- end
245
-
246
- if b && (iseq = frame.iseq)
247
- if iseq.type == :block
248
- if (argc = iseq.argc) > 0
249
- args = parameters_info b, iseq.locals[0...iseq.argc]
250
- buff << " {|#{args}|}"
385
+ klass = ObjectSpace.internal_class_of(result)
386
+ exists = []
387
+ klass.ancestors.each{|k|
388
+ puts "= #{k}"
389
+ if (ms = (k.instance_methods(false) - exists)).size > 0
390
+ puts ms.sort.join("\t")
391
+ exists |= ms
251
392
  end
252
- else
253
- callee = b.eval('__callee__')
254
- if callee && (m = frame.self.method(callee))
255
- args = parameters_info b, m.parameters.map{|type, v| v}
256
- ksig = klass_sig frame
257
- buff << " #{ksig}#{callee}(#{args})"
258
- end
259
- end
393
+ }
394
+ end
395
+ end
260
396
 
261
- if frame.has_return_value
262
- buff << " #=> #{short_inspect(frame.return_value)}"
397
+ def add_breakpoint args
398
+ case args.first
399
+ when :method
400
+ klass_name, op, method_name, cond = args[1..]
401
+ bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond)
402
+ begin
403
+ bp.enable
404
+ rescue Exception => e
405
+ puts e.message
406
+ ::DEBUGGER__::METHOD_ADDED_TRACKER.enable
263
407
  end
408
+ event! :result, :method_breakpoint, bp
264
409
  else
265
- # p frame.self
410
+ raise "unknown breakpoint: #{args}"
266
411
  end
267
-
268
- buff
269
- end
270
-
271
- def show_frame_all
272
- @target_frames.size.times{|i|
273
- puts frame_str(i)
274
- }
275
- end
276
-
277
- def print_frame i
278
- puts frame_str(i)
279
412
  end
280
413
 
281
- def print_frames n
282
- size = @target_frames.size
283
- ([size, n].min).times{|i|
284
- print_frame i
285
- }
286
- if n < size
287
- puts " # and #{size - n} frames (use `bt' command for all frames)"
288
- end
414
+ def set_mode mode
415
+ @mode = mode
289
416
  end
290
417
 
291
418
  def wait_next_action
292
- @mode = :wait_next_action
419
+ set_mode :wait_next_action
420
+
421
+ SESSION.check_forked
293
422
 
294
423
  while cmds = @q_cmd.pop
424
+ # pp [self, cmds: cmds]
425
+
295
426
  cmd, *args = *cmds
296
427
 
297
428
  case cmd
@@ -303,53 +434,105 @@ module DEBUGGER__
303
434
  when :in
304
435
  step_tp{true}
305
436
  when :next
306
- size = @target_frames.size
437
+ frame = @target_frames.first
438
+ path = frame.location.absolute_path || "!eval:#{frame.path}"
439
+ line = frame.location.lineno
440
+ frame.iseq.traceable_lines_norec(lines = {})
441
+ next_line = lines.keys.bsearch{|e| e > line}
442
+ if !next_line && (last_line = frame.iseq.last_line) > line
443
+ next_line = last_line
444
+ end
445
+ depth = @target_frames.first.frame_depth
446
+
307
447
  step_tp{
308
- target_frames_count() <= size
448
+ loc = caller_locations(2, 1).first
449
+ loc_path = loc.absolute_path || "!eval:#{loc.path}"
450
+
451
+ # same stack depth
452
+ (DEBUGGER__.frame_depth - 3 <= depth) ||
453
+
454
+ # different frame
455
+ (next_line && loc_path == path &&
456
+ (loc_lineno = loc.lineno) > line &&
457
+ loc_lineno <= next_line)
309
458
  }
310
459
  when :finish
311
- size = @target_frames.size
312
- step_tp{target_frames_count() < size}
460
+ depth = @target_frames.first.frame_depth
461
+ step_tp{
462
+ # 3 is debugger's frame count
463
+ DEBUGGER__.frame_depth - 3 < depth
464
+ }
313
465
  else
314
466
  raise
315
467
  end
316
468
  break
317
469
  when :eval
318
470
  eval_type, eval_src = *args
319
- result = frame_eval(eval_src)
471
+
472
+ case eval_type
473
+ when :display, :try_display
474
+ else
475
+ result = frame_eval(eval_src)
476
+ end
477
+ result_type = nil
320
478
 
321
479
  case eval_type
322
480
  when :p
323
481
  puts "=> " + result.inspect
324
482
  when :pp
325
483
  puts "=> "
326
- PP.pp(result, out = ''.dup)
484
+ PP.pp(result, out = ''.dup, SESSION.width)
327
485
  puts out
328
486
  when :call
329
487
  result = frame_eval(eval_src)
330
- when :display
488
+ when :display, :try_display
489
+ failed_results = []
331
490
  eval_src.each_with_index{|src, i|
332
- puts "#{i}: #{src} = #{frame_eval(src, failed_value: :error).inspect}"
491
+ result = frame_eval(src){|e|
492
+ failed_results << [i, e.message]
493
+ "<error: #{e.message}>"
494
+ }
495
+ puts "#{i}: #{src} = #{result}"
333
496
  }
334
- result = :ok
497
+
498
+ result_type = eval_type
499
+ result = failed_results
500
+ when :watch
501
+ if @success_last_eval
502
+ if eval_src.match?(/@\w+/)
503
+ object =
504
+ if b = current_frame.binding
505
+ b.receiver
506
+ else
507
+ current_frame.self
508
+ end
509
+ puts "#{object} #{eval_src} = #{result}"
510
+ result = WatchIVarBreakpoint.new(eval_src, object, result)
511
+ end
512
+
513
+ result_type = :watch
514
+ else
515
+ result = nil
516
+ end
335
517
  else
336
- raise "unknown error option: #{args.inspec}"
518
+ raise "unknown error option: #{args.inspect}"
337
519
  end
338
- event! :result, result
520
+
521
+ event! :result, result_type, result
339
522
  when :frame
340
523
  type, arg = *args
341
524
  case type
342
525
  when :up
343
526
  if @current_frame_index + 1 < @target_frames.size
344
- @current_frame_index += 1
527
+ @current_frame_index += 1
345
528
  show_src max_lines: 1
346
- print_frame(@current_frame_index)
529
+ show_frame(@current_frame_index)
347
530
  end
348
531
  when :down
349
532
  if @current_frame_index > 0
350
533
  @current_frame_index -= 1
351
534
  show_src max_lines: 1
352
- print_frame(@current_frame_index)
535
+ show_frame(@current_frame_index)
353
536
  end
354
537
  when :set
355
538
  if arg
@@ -361,28 +544,47 @@ module DEBUGGER__
361
544
  end
362
545
  end
363
546
  show_src max_lines: 1
364
- print_frame(@current_frame_index)
547
+ show_frame(@current_frame_index)
365
548
  else
366
549
  raise "unsupported frame operation: #{arg.inspect}"
367
550
  end
368
551
  event! :result, nil
369
552
  when :show
370
- type, = *args
553
+ type = args.shift
554
+
371
555
  case type
372
556
  when :backtrace
373
- show_frame_all
557
+ show_frames
558
+
374
559
  when :list
375
- show_src
376
- when :locals
560
+ show_src(update_line: true, **(args.first || {}))
561
+
562
+ when :edit
563
+ show_by_editor(args.first)
564
+
565
+ when :local
566
+ show_frame
377
567
  show_locals
378
- when :ivars
379
568
  show_ivars
569
+
570
+ when :object_info
571
+ expr = args.shift
572
+ show_object_info expr
573
+
380
574
  else
381
- raise "unknown show param: " + args.inspect
575
+ raise "unknown show param: " + [type, *args].inspect
382
576
  end
577
+
383
578
  event! :result, nil
579
+
580
+ when :breakpoint
581
+ add_breakpoint args
582
+
583
+ when :dap
584
+ process_dap args
585
+
384
586
  else
385
- raise [ev, *args].inspect
587
+ raise [cmd, *args].inspect
386
588
  end
387
589
  end
388
590
 
@@ -392,11 +594,20 @@ module DEBUGGER__
392
594
  pp [__FILE__, __LINE__, e, e.backtrace]
393
595
  raise
394
596
  ensure
395
- @mode = nil
597
+ set_mode nil
396
598
  end
397
599
 
398
600
  def to_s
399
- "(#{@thread.name || @thread.status})@#{current_frame&.location}"
601
+ loc = current_frame&.location
602
+
603
+ if loc
604
+ str = "(#{@thread.name || @thread.status})@#{loc}"
605
+ else
606
+ str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
607
+ end
608
+
609
+ str += " (not under control)" unless self.mode
610
+ str
400
611
  end
401
612
  end
402
613
  end