debug 1.6.1 → 1.9.1

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.
@@ -18,7 +18,7 @@ module DEBUGGER__
18
18
  end
19
19
 
20
20
  at_exit do
21
- CONFIG.skip_all
21
+ DEBUGGER__.skip_all
22
22
  FileUtils.rm_rf dir if tempdir
23
23
  end
24
24
 
@@ -125,10 +125,12 @@ module DEBUGGER__
125
125
  def dap_setup bytes
126
126
  CONFIG.set_config no_color: true
127
127
  @seq = 0
128
+ @send_lock = Mutex.new
128
129
 
129
130
  case self
130
131
  when UI_UnixDomainServer
131
- UI_DAP.local_fs_map_set true
132
+ # If the user specified a mapping, respect it, otherwise, make sure that no mapping is used
133
+ UI_DAP.local_fs_map_set CONFIG[:local_fs_map] || true
132
134
  when UI_TcpServer
133
135
  # TODO: loopback address can be used to connect other FS env, like Docker containers
134
136
  # UI_DAP.local_fs_set if @local_addr.ipv4_loopback? || @local_addr.ipv6_loopback?
@@ -198,15 +200,26 @@ module DEBUGGER__
198
200
  # supportsInstructionBreakpoints:
199
201
  )
200
202
  send_event 'initialized'
203
+ puts <<~WELCOME
204
+ Ruby REPL: You can run any Ruby expression here.
205
+ Note that output to the STDOUT/ERR printed on the TERMINAL.
206
+ [experimental]
207
+ `,COMMAND` runs `COMMAND` debug command (ex: `,info`).
208
+ `,help` to list all debug commands.
209
+ WELCOME
201
210
  end
202
211
 
203
212
  def send **kw
204
213
  if sock = @sock
205
214
  kw[:seq] = @seq += 1
206
215
  str = JSON.dump(kw)
207
- sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
216
+ @send_lock.synchronize do
217
+ sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
218
+ end
208
219
  show_protocol '<', str
209
220
  end
221
+ rescue Errno::EPIPE => e
222
+ $stderr.puts "#{e.inspect} rescued during sending message"
210
223
  end
211
224
 
212
225
  def send_response req, success: true, message: nil, **kw
@@ -238,17 +251,17 @@ module DEBUGGER__
238
251
  end
239
252
 
240
253
  def recv_request
241
- r = IO.select([@sock])
254
+ IO.select([@sock])
242
255
 
243
256
  @session.process_group.sync do
244
257
  raise RetryBecauseCantRead unless IO.select([@sock], nil, nil, 0)
245
258
 
246
- case header = @sock.gets
259
+ case @sock.gets
247
260
  when /Content-Length: (\d+)/
248
261
  b = @sock.read(2)
249
262
  raise b.inspect unless b == "\r\n"
250
263
 
251
- l = @sock.read(s = $1.to_i)
264
+ l = @sock.read($1.to_i)
252
265
  show_protocol :>, l
253
266
  JSON.load(l)
254
267
  when nil
@@ -261,178 +274,225 @@ module DEBUGGER__
261
274
  retry
262
275
  end
263
276
 
277
+ def load_extensions req
278
+ if exts = req.dig('arguments', 'rdbgExtensions')
279
+ exts.each{|ext|
280
+ require_relative "dap_custom/#{File.basename(ext)}"
281
+ }
282
+ end
283
+
284
+ if scripts = req.dig('arguments', 'rdbgInitialScripts')
285
+ scripts.each do |script|
286
+ begin
287
+ eval(script)
288
+ rescue Exception => e
289
+ puts e.message
290
+ puts e.backtrace.inspect
291
+ end
292
+ end
293
+ end
294
+ end
295
+
264
296
  def process
265
297
  while req = recv_request
266
- raise "not a request: #{req.inpsect}" unless req['type'] == 'request'
267
- args = req.dig('arguments')
298
+ process_request(req)
299
+ end
300
+ ensure
301
+ send_event :terminated unless @sock.closed?
302
+ end
268
303
 
269
- case req['command']
304
+ def process_request req
305
+ raise "not a request: #{req.inspect}" unless req['type'] == 'request'
306
+ args = req.dig('arguments')
270
307
 
271
- ## boot/configuration
272
- when 'launch'
273
- send_response req
274
- UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
275
- @is_launch = true
308
+ case req['command']
276
309
 
277
- when 'attach'
278
- send_response req
279
- UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
280
- @is_launch = false
310
+ ## boot/configuration
311
+ when 'launch'
312
+ send_response req
313
+ # `launch` runs on debuggee on the same file system
314
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') || true
315
+ @nonstop = true
281
316
 
282
- when 'configurationDone'
283
- send_response req
317
+ load_extensions req
284
318
 
285
- if @is_launch
286
- @q_msg << 'continue'
287
- else
288
- if SESSION.in_subsession?
289
- send_event 'stopped', reason: 'pause',
290
- threadId: 1, # maybe ...
291
- allThreadsStopped: true
292
- end
293
- end
319
+ when 'attach'
320
+ send_response req
321
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
294
322
 
295
- when 'setBreakpoints'
296
- req_path = args.dig('source', 'path')
297
- path = UI_DAP.local_to_remote_path(req_path)
323
+ if req.dig('arguments', 'nonstop') == true
324
+ @nonstop = true
325
+ else
326
+ @nonstop = false
327
+ end
298
328
 
299
- if path
300
- SESSION.clear_line_breakpoints path
329
+ load_extensions req
301
330
 
302
- bps = []
303
- args['breakpoints'].each{|bp|
304
- line = bp['line']
305
- if cond = bp['condition']
306
- bps << SESSION.add_line_breakpoint(path, line, cond: cond)
307
- else
308
- bps << SESSION.add_line_breakpoint(path, line)
309
- end
310
- }
311
- send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
312
- else
313
- send_response req, success: false, message: "#{req_path} is not available"
331
+ when 'configurationDone'
332
+ send_response req
333
+
334
+ if @nonstop
335
+ @q_msg << 'continue'
336
+ else
337
+ if SESSION.in_subsession?
338
+ send_event 'stopped', reason: 'pause',
339
+ threadId: 1, # maybe ...
340
+ allThreadsStopped: true
314
341
  end
342
+ end
315
343
 
316
- when 'setFunctionBreakpoints'
317
- send_response req
344
+ when 'setBreakpoints'
345
+ req_path = args.dig('source', 'path')
346
+ path = UI_DAP.local_to_remote_path(req_path)
347
+ if path
348
+ SESSION.clear_line_breakpoints path
349
+
350
+ bps = []
351
+ args['breakpoints'].each{|bp|
352
+ line = bp['line']
353
+ if cond = bp['condition']
354
+ bps << SESSION.add_line_breakpoint(path, line, cond: cond)
355
+ else
356
+ bps << SESSION.add_line_breakpoint(path, line)
357
+ end
358
+ }
359
+ send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
360
+ else
361
+ send_response req, success: false, message: "#{req_path} is not available"
362
+ end
318
363
 
319
- when 'setExceptionBreakpoints'
320
- process_filter = ->(filter_id, cond = nil) {
321
- bp =
322
- case filter_id
323
- when 'any'
324
- SESSION.add_catch_breakpoint 'Exception', cond: cond
325
- when 'RuntimeError'
326
- SESSION.add_catch_breakpoint 'RuntimeError', cond: cond
327
- else
328
- nil
329
- end
330
- {
331
- verified: !bp.nil?,
332
- message: bp.inspect,
333
- }
364
+ when 'setFunctionBreakpoints'
365
+ send_response req
366
+
367
+ when 'setExceptionBreakpoints'
368
+ process_filter = ->(filter_id, cond = nil) {
369
+ bp =
370
+ case filter_id
371
+ when 'any'
372
+ SESSION.add_catch_breakpoint 'Exception', cond: cond
373
+ when 'RuntimeError'
374
+ SESSION.add_catch_breakpoint 'RuntimeError', cond: cond
375
+ else
376
+ nil
377
+ end
378
+ {
379
+ verified: !bp.nil?,
380
+ message: bp.inspect,
334
381
  }
382
+ }
335
383
 
336
- SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
337
-
338
- filters = args.fetch('filters').map {|filter_id|
339
- process_filter.call(filter_id)
340
- }
384
+ SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
341
385
 
342
- filters += args.fetch('filterOptions', {}).map{|bp_info|
343
- process_filter.call(bp_info['filterId'], bp_info['condition'])
386
+ filters = args.fetch('filters').map {|filter_id|
387
+ process_filter.call(filter_id)
344
388
  }
345
389
 
346
- send_response req, breakpoints: filters
390
+ filters += args.fetch('filterOptions', {}).map{|bp_info|
391
+ process_filter.call(bp_info['filterId'], bp_info['condition'])
392
+ }
347
393
 
348
- when 'disconnect'
349
- terminate = args.fetch("terminateDebuggee", false)
394
+ send_response req, breakpoints: filters
350
395
 
351
- if SESSION.in_subsession?
352
- if terminate
353
- @q_msg << 'kill!'
354
- else
355
- @q_msg << 'continue'
356
- end
357
- else
358
- if terminate
359
- @q_msg << 'kill!'
360
- pause
361
- end
362
- end
396
+ when 'disconnect'
397
+ terminate = args.fetch("terminateDebuggee", false)
363
398
 
364
- SESSION.clear_all_breakpoints
365
- send_response req
399
+ SESSION.clear_all_breakpoints
400
+ send_response req
366
401
 
367
- ## control
368
- when 'continue'
369
- @q_msg << 'c'
370
- send_response req, allThreadsContinued: true
371
- when 'next'
372
- begin
373
- @session.check_postmortem
374
- @q_msg << 'n'
375
- send_response req
376
- rescue PostmortemError
377
- send_response req,
378
- success: false, message: 'postmortem mode',
379
- result: "'Next' is not supported while postmortem mode"
380
- end
381
- when 'stepIn'
382
- begin
383
- @session.check_postmortem
384
- @q_msg << 's'
385
- send_response req
386
- rescue PostmortemError
387
- send_response req,
388
- success: false, message: 'postmortem mode',
389
- result: "'stepIn' is not supported while postmortem mode"
402
+ if SESSION.in_subsession?
403
+ if terminate
404
+ @q_msg << 'kill!'
405
+ else
406
+ @q_msg << 'continue'
390
407
  end
391
- when 'stepOut'
392
- begin
393
- @session.check_postmortem
394
- @q_msg << 'fin'
395
- send_response req
396
- rescue PostmortemError
397
- send_response req,
398
- success: false, message: 'postmortem mode',
399
- result: "'stepOut' is not supported while postmortem mode"
408
+ else
409
+ if terminate
410
+ @q_msg << 'kill!'
411
+ pause
400
412
  end
401
- when 'terminate'
413
+ end
414
+
415
+ ## control
416
+ when 'continue'
417
+ @q_msg << 'c'
418
+ send_response req, allThreadsContinued: true
419
+ when 'next'
420
+ begin
421
+ @session.check_postmortem
422
+ @q_msg << 'n'
402
423
  send_response req
403
- exit
404
- when 'pause'
424
+ rescue PostmortemError
425
+ send_response req,
426
+ success: false, message: 'postmortem mode',
427
+ result: "'Next' is not supported while postmortem mode"
428
+ end
429
+ when 'stepIn'
430
+ begin
431
+ @session.check_postmortem
432
+ @q_msg << 's'
405
433
  send_response req
406
- Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
407
- when 'reverseContinue'
434
+ rescue PostmortemError
408
435
  send_response req,
409
- success: false, message: 'cancelled',
410
- result: "Reverse Continue is not supported. Only \"Step back\" is supported."
411
- when 'stepBack'
412
- @q_msg << req
436
+ success: false, message: 'postmortem mode',
437
+ result: "'stepIn' is not supported while postmortem mode"
438
+ end
439
+ when 'stepOut'
440
+ begin
441
+ @session.check_postmortem
442
+ @q_msg << 'fin'
443
+ send_response req
444
+ rescue PostmortemError
445
+ send_response req,
446
+ success: false, message: 'postmortem mode',
447
+ result: "'stepOut' is not supported while postmortem mode"
448
+ end
449
+ when 'terminate'
450
+ send_response req
451
+ exit
452
+ when 'pause'
453
+ send_response req
454
+ Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
455
+ when 'reverseContinue'
456
+ send_response req,
457
+ success: false, message: 'cancelled',
458
+ result: "Reverse Continue is not supported. Only \"Step back\" is supported."
459
+ when 'stepBack'
460
+ @q_msg << req
413
461
 
414
- ## query
415
- when 'threads'
416
- send_response req, threads: SESSION.managed_thread_clients.map{|tc|
417
- { id: tc.id,
418
- name: tc.name,
419
- }
462
+ ## query
463
+ when 'threads'
464
+ send_response req, threads: SESSION.managed_thread_clients.map{|tc|
465
+ { id: tc.id,
466
+ name: tc.name,
420
467
  }
468
+ }
469
+
470
+ when 'evaluate'
471
+ expr = req.dig('arguments', 'expression')
472
+ if /\A\s*,(.+)\z/ =~ expr
473
+ dbg_expr = $1.strip
474
+ dbg_expr.split(';;') { |cmd| @q_msg << cmd }
421
475
 
422
- when 'stackTrace',
423
- 'scopes',
424
- 'variables',
425
- 'evaluate',
426
- 'source',
427
- 'completions'
476
+ send_response req,
477
+ result: "(rdbg:command) #{dbg_expr}",
478
+ variablesReference: 0
479
+ else
428
480
  @q_msg << req
481
+ end
482
+ when 'stackTrace',
483
+ 'scopes',
484
+ 'variables',
485
+ 'source',
486
+ 'completions'
487
+ @q_msg << req
429
488
 
489
+ else
490
+ if respond_to? mid = "custom_dap_request_#{req['command']}"
491
+ __send__ mid, req
430
492
  else
431
493
  raise "Unknown request: #{req.inspect}"
432
494
  end
433
495
  end
434
- ensure
435
- send_event :terminated unless @sock.closed?
436
496
  end
437
497
 
438
498
  ## called by the SESSION thread
@@ -443,7 +503,11 @@ module DEBUGGER__
443
503
 
444
504
  def puts result
445
505
  # STDERR.puts "puts: #{result}"
446
- # send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
506
+ send_event 'output', category: 'console', output: "#{result&.chomp}\n"
507
+ end
508
+
509
+ def ignore_output_on_suspend?
510
+ true
447
511
  end
448
512
 
449
513
  def event type, *args
@@ -488,6 +552,8 @@ module DEBUGGER__
488
552
  end
489
553
 
490
554
  class Session
555
+ include GlobalVariablesHelper
556
+
491
557
  def find_waiting_tc id
492
558
  @th_clients.each{|th, tc|
493
559
  return tc if tc.id == id && tc.waiting?
@@ -512,7 +578,7 @@ module DEBUGGER__
512
578
  when 'stackTrace'
513
579
  tid = req.dig('arguments', 'threadId')
514
580
 
515
- if tc = find_waiting_tc(tid)
581
+ if find_waiting_tc(tid)
516
582
  request_tc [:dap, :backtrace, req]
517
583
  else
518
584
  fail_response req
@@ -521,7 +587,7 @@ module DEBUGGER__
521
587
  frame_id = req.dig('arguments', 'frameId')
522
588
  if @frame_map[frame_id]
523
589
  tid, fid = @frame_map[frame_id]
524
- if tc = find_waiting_tc(tid)
590
+ if find_waiting_tc(tid)
525
591
  request_tc [:dap, :scopes, req, fid]
526
592
  else
527
593
  fail_response req
@@ -534,8 +600,12 @@ module DEBUGGER__
534
600
  if ref = @var_map[varid]
535
601
  case ref[0]
536
602
  when :globals
537
- vars = global_variables.map do |name|
538
- gv = 'Not implemented yet...'
603
+ vars = safe_global_variables.sort.map do |name|
604
+ begin
605
+ gv = eval(name.to_s)
606
+ rescue Exception => e
607
+ gv = e.inspect
608
+ end
539
609
  {
540
610
  name: name,
541
611
  value: gv.inspect,
@@ -553,7 +623,7 @@ module DEBUGGER__
553
623
  frame_id = ref[1]
554
624
  tid, fid = @frame_map[frame_id]
555
625
 
556
- if tc = find_waiting_tc(tid)
626
+ if find_waiting_tc(tid)
557
627
  request_tc [:dap, :scope, req, fid]
558
628
  else
559
629
  fail_response req
@@ -562,7 +632,7 @@ module DEBUGGER__
562
632
  when :variable
563
633
  tid, vid = ref[1], ref[2]
564
634
 
565
- if tc = find_waiting_tc(tid)
635
+ if find_waiting_tc(tid)
566
636
  request_tc [:dap, :variable, req, vid]
567
637
  else
568
638
  fail_response req
@@ -580,7 +650,9 @@ module DEBUGGER__
580
650
  if @frame_map[frame_id]
581
651
  tid, fid = @frame_map[frame_id]
582
652
  expr = req.dig('arguments', 'expression')
583
- if tc = find_waiting_tc(tid)
653
+
654
+ if find_waiting_tc(tid)
655
+ restart_all_threads
584
656
  request_tc [:dap, :evaluate, req, fid, expr, context]
585
657
  else
586
658
  fail_response req
@@ -601,7 +673,7 @@ module DEBUGGER__
601
673
  frame_id = req.dig('arguments', 'frameId')
602
674
  tid, fid = @frame_map[frame_id]
603
675
 
604
- if tc = find_waiting_tc(tid)
676
+ if find_waiting_tc(tid)
605
677
  text = req.dig('arguments', 'text')
606
678
  line = req.dig('arguments', 'line')
607
679
  if col = req.dig('arguments', 'column')
@@ -612,11 +684,15 @@ module DEBUGGER__
612
684
  fail_response req
613
685
  end
614
686
  else
615
- raise "Unknown DAP request: #{req.inspect}"
687
+ if respond_to? mid = "custom_dap_request_#{req['command']}"
688
+ __send__ mid, req
689
+ else
690
+ raise "Unknown request: #{req.inspect}"
691
+ end
616
692
  end
617
693
  end
618
694
 
619
- def dap_event args
695
+ def process_protocol_result args
620
696
  # puts({dap_event: args}.inspect)
621
697
  type, req, result = args
622
698
 
@@ -653,6 +729,7 @@ module DEBUGGER__
653
729
  register_vars result[:variables], tid
654
730
  @ui.respond req, result
655
731
  when :evaluate
732
+ stop_all_threads
656
733
  message = result.delete :message
657
734
  if message
658
735
  @ui.respond req, success: false, message: message
@@ -664,7 +741,11 @@ module DEBUGGER__
664
741
  when :completions
665
742
  @ui.respond req, result
666
743
  else
667
- raise "unsupported: #{args.inspect}"
744
+ if respond_to? mid = "custom_dap_request_event_#{type}"
745
+ __send__ mid, req, result
746
+ else
747
+ raise "unsupported: #{args.inspect}"
748
+ end
668
749
  end
669
750
  end
670
751
 
@@ -684,10 +765,35 @@ module DEBUGGER__
684
765
  end
685
766
  end
686
767
 
768
+ class NaiveString
769
+ attr_reader :str
770
+ def initialize str
771
+ @str = str
772
+ end
773
+ end
774
+
687
775
  class ThreadClient
688
- def value_inspect obj
776
+ MAX_LENGTH = 180
777
+
778
+ def value_inspect obj, short: true
689
779
  # TODO: max length should be configuarable?
690
- DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024
780
+ str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH
781
+
782
+ if str.encoding == Encoding::UTF_8
783
+ str.scrub
784
+ else
785
+ str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
786
+ end
787
+ end
788
+
789
+ def dap_eval b, expr, _context, prompt: '(repl_eval)'
790
+ begin
791
+ tp_allow_reentry do
792
+ b.eval(expr.to_s, prompt)
793
+ end
794
+ rescue Exception => e
795
+ e
796
+ end
691
797
  end
692
798
 
693
799
  def process_dap args
@@ -702,9 +808,10 @@ module DEBUGGER__
702
808
  frames = []
703
809
  @target_frames.each_with_index do |frame, i|
704
810
  next if i < start_frame
705
- break if (levels -= 1) < 0
706
811
 
707
812
  path = frame.realpath || frame.path
813
+ next if skip_path?(path) && !SESSION.stop_stepping?(path, frame.location.lineno)
814
+ break if (levels -= 1) < 0
708
815
  source_name = path ? File.basename(path) : frame.location.to_s
709
816
 
710
817
  if (path && File.exist?(path)) && (local_path = UI_DAP.remote_to_local_path(path))
@@ -726,7 +833,7 @@ module DEBUGGER__
726
833
  }
727
834
  end
728
835
 
729
- event! :dap_result, :backtrace, req, {
836
+ event! :protocol_result, :backtrace, req, {
730
837
  stackFrames: frames,
731
838
  totalFrames: @target_frames.size,
732
839
  }
@@ -743,7 +850,7 @@ module DEBUGGER__
743
850
  0
744
851
  end
745
852
 
746
- event! :dap_result, :scopes, req, scopes: [{
853
+ event! :protocol_result, :scopes, req, scopes: [{
747
854
  name: 'Local variables',
748
855
  presentationHint: 'locals',
749
856
  # variablesReference: N, # filled by SESSION
@@ -754,7 +861,7 @@ module DEBUGGER__
754
861
  name: 'Global variables',
755
862
  presentationHint: 'globals',
756
863
  variablesReference: 1, # GLOBAL
757
- namedVariables: global_variables.size,
864
+ namedVariables: safe_global_variables.size,
758
865
  indexedVariables: 0,
759
866
  expensive: false,
760
867
  }]
@@ -765,7 +872,7 @@ module DEBUGGER__
765
872
  variable(var, val)
766
873
  end
767
874
 
768
- event! :dap_result, :scope, req, variables: vars, tid: self.id
875
+ event! :protocol_result, :scope, req, variables: vars, tid: self.id
769
876
  when :variable
770
877
  vid = args.shift
771
878
  obj = @var_map[vid]
@@ -792,13 +899,12 @@ module DEBUGGER__
792
899
  when String
793
900
  vars = [
794
901
  variable('#length', obj.length),
795
- variable('#encoding', obj.encoding)
902
+ variable('#encoding', obj.encoding),
796
903
  ]
904
+ printed_str = value_inspect(obj)
905
+ vars << variable('#dump', NaiveString.new(obj)) if printed_str.end_with?('...')
797
906
  when Class, Module
798
- vars = obj.instance_variables.map{|iv|
799
- variable(iv, obj.instance_variable_get(iv))
800
- }
801
- vars.unshift variable('%ancestors', obj.ancestors[1..])
907
+ vars << variable('%ancestors', obj.ancestors[1..])
802
908
  when Range
803
909
  vars = [
804
910
  variable('#begin', obj.begin),
@@ -806,13 +912,15 @@ module DEBUGGER__
806
912
  ]
807
913
  end
808
914
 
809
- vars += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
810
- variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
811
- }
812
- vars.unshift variable('#class', M_CLASS.bind_call(obj))
915
+ unless NaiveString === obj
916
+ vars += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
917
+ variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
918
+ }
919
+ vars.unshift variable('#class', M_CLASS.bind_call(obj))
920
+ end
813
921
  end
814
922
  end
815
- event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
923
+ event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
816
924
 
817
925
  when :evaluate
818
926
  fid, expr, context = args
@@ -826,12 +934,7 @@ module DEBUGGER__
826
934
 
827
935
  case context
828
936
  when 'repl', 'watch'
829
- begin
830
- result = b.eval(expr.to_s, '(DEBUG CONSOLE)')
831
- rescue Exception => e
832
- result = e
833
- end
834
-
937
+ result = dap_eval b, expr, context, prompt: '(DEBUG CONSOLE)'
835
938
  when 'hover'
836
939
  case expr
837
940
  when /\A\@\S/
@@ -841,7 +944,7 @@ module DEBUGGER__
841
944
  message = "Error: Not defined instance variable: #{expr.inspect}"
842
945
  end
843
946
  when /\A\$\S/
844
- global_variables.each{|gvar|
947
+ safe_global_variables.each{|gvar|
845
948
  if gvar.to_s == expr
846
949
  result = eval(gvar.to_s)
847
950
  break false
@@ -872,7 +975,7 @@ module DEBUGGER__
872
975
  result = 'Error: Can not evaluate on this frame'
873
976
  end
874
977
 
875
- event! :dap_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
978
+ event! :protocol_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
876
979
 
877
980
  when :completions
878
981
  fid, text = args
@@ -882,7 +985,7 @@ module DEBUGGER__
882
985
  words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
883
986
  end
884
987
 
885
- event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
988
+ event! :protocol_result, :completions, req, targets: (words || []).map{|phrase|
886
989
  detail = nil
887
990
 
888
991
  if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
@@ -905,16 +1008,24 @@ module DEBUGGER__
905
1008
  }
906
1009
 
907
1010
  else
908
- raise "Unknown req: #{args.inspect}"
1011
+ if respond_to? mid = "custom_dap_request_#{type}"
1012
+ __send__ mid, req
1013
+ else
1014
+ raise "Unknown request: #{args.inspect}"
1015
+ end
909
1016
  end
910
1017
  end
911
1018
 
912
1019
  def search_const b, expr
913
1020
  cs = expr.delete_prefix('::').split('::')
914
- [Object, *b.eval('Module.nesting')].reverse_each{|mod|
1021
+ [Object, *b.eval('::Module.nesting')].reverse_each{|mod|
915
1022
  if cs.all?{|c|
916
1023
  if mod.const_defined?(c)
917
- mod = mod.const_get(c)
1024
+ begin
1025
+ mod = mod.const_get(c)
1026
+ rescue Exception
1027
+ false
1028
+ end
918
1029
  else
919
1030
  false
920
1031
  end
@@ -927,11 +1038,17 @@ module DEBUGGER__
927
1038
  end
928
1039
 
929
1040
  def evaluate_result r
930
- v = variable nil, r
931
- v.delete :name
932
- v.delete :value
933
- v[:result] = value_inspect(r)
934
- v
1041
+ variable nil, r
1042
+ end
1043
+
1044
+ def type_name obj
1045
+ klass = M_CLASS.bind_call(obj)
1046
+
1047
+ begin
1048
+ M_NAME.bind_call(klass) || klass.to_s
1049
+ rescue Exception => e
1050
+ "<Error: #{e.message} (#{e.backtrace.first}>"
1051
+ end
935
1052
  end
936
1053
 
937
1054
  def variable_ name, obj, indexedVariables: 0, namedVariables: 0
@@ -942,15 +1059,31 @@ module DEBUGGER__
942
1059
  vid = 0
943
1060
  end
944
1061
 
945
- ivnum = M_INSTANCE_VARIABLES.bind_call(obj).size
1062
+ namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
946
1063
 
947
- { name: name,
948
- value: value_inspect(obj),
949
- type: (klass = M_CLASS.bind_call(obj)).name || klass.to_s,
950
- variablesReference: vid,
951
- indexedVariables: indexedVariables,
952
- namedVariables: namedVariables + ivnum,
953
- }
1064
+ if NaiveString === obj
1065
+ str = obj.str.dump
1066
+ vid = indexedVariables = namedVariables = 0
1067
+ else
1068
+ str = value_inspect(obj)
1069
+ end
1070
+
1071
+ if name
1072
+ { name: name,
1073
+ value: str,
1074
+ type: type_name(obj),
1075
+ variablesReference: vid,
1076
+ indexedVariables: indexedVariables,
1077
+ namedVariables: namedVariables,
1078
+ }
1079
+ else
1080
+ { result: str,
1081
+ type: type_name(obj),
1082
+ variablesReference: vid,
1083
+ indexedVariables: indexedVariables,
1084
+ namedVariables: namedVariables,
1085
+ }
1086
+ end
954
1087
  end
955
1088
 
956
1089
  def variable name, obj
@@ -960,7 +1093,7 @@ module DEBUGGER__
960
1093
  when Hash
961
1094
  variable_ name, obj, namedVariables: obj.size
962
1095
  when String
963
- variable_ name, obj, namedVariables: 3 # #to_str, #length, #encoding
1096
+ variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str
964
1097
  when Struct
965
1098
  variable_ name, obj, namedVariables: obj.size
966
1099
  when Class, Module