debug 1.4.0 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,52 +3,51 @@
3
3
  require 'json'
4
4
  require 'irb/completion'
5
5
  require 'tmpdir'
6
- require 'json'
7
6
  require 'fileutils'
8
7
 
9
8
  module DEBUGGER__
10
9
  module UI_DAP
11
10
  SHOW_PROTOCOL = ENV['DEBUG_DAP_SHOW_PROTOCOL'] == '1' || ENV['RUBY_DEBUG_DAP_SHOW_PROTOCOL'] == '1'
12
11
 
13
- def self.setup sock_path
14
- dir = Dir.mktmpdir("ruby-debug-vscode-")
15
- at_exit{
16
- CONFIG[:skip_path] = [//] # skip all
17
- FileUtils.rm_rf dir
18
- }
12
+ def self.setup debug_port
13
+ if File.directory? '.vscode'
14
+ dir = Dir.pwd
15
+ else
16
+ dir = Dir.mktmpdir("ruby-debug-vscode-")
17
+ tempdir = true
18
+ end
19
+
20
+ at_exit do
21
+ CONFIG.skip_all
22
+ FileUtils.rm_rf dir if tempdir
23
+ end
24
+
25
+ key = rand.to_s
26
+
19
27
  Dir.chdir(dir) do
20
- Dir.mkdir('.vscode')
21
- open('README.rb', 'w'){|f|
22
- f.puts <<~MSG
23
- # Wait for starting the attaching to the Ruby process
24
- # This file will be removed at the end of the debuggee process.
25
- #
26
- # Note that vscode-rdbg extension is needed. Please install if you don't have.
27
- MSG
28
- }
29
- open('.vscode/launch.json', 'w'){|f|
28
+ Dir.mkdir('.vscode') if tempdir
29
+
30
+ # vscode-rdbg 0.0.9 or later is needed
31
+ open('.vscode/rdbg_autoattach.json', 'w') do |f|
30
32
  f.puts JSON.pretty_generate({
31
- version: '0.2.0',
32
- configurations: [
33
- {
34
- type: "rdbg",
35
- name: "Attach with rdbg",
36
- request: "attach",
37
- rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
38
- debugPort: sock_path,
39
- autoAttach: true,
40
- }
41
- ]
33
+ type: "rdbg",
34
+ name: "Attach with rdbg",
35
+ request: "attach",
36
+ rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
37
+ debugPort: debug_port,
38
+ localfs: true,
39
+ autoAttach: key,
42
40
  })
43
- }
41
+ end
44
42
  end
45
43
 
46
- cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
44
+ cmds = ['code', "#{dir}/"]
47
45
  cmdline = cmds.join(' ')
48
- ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
46
+ ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/"
49
47
 
50
48
  STDERR.puts "Launching: #{cmdline}"
51
49
  env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
50
+ env['RUBY_DEBUG_AUTOATTACH'] = key
52
51
 
53
52
  unless system(env, *cmds)
54
53
  DEBUGGER__.warn <<~MESSAGE
@@ -71,10 +70,70 @@ module DEBUGGER__
71
70
  end
72
71
  end
73
72
 
73
+ # true: all localfs
74
+ # Array: part of localfs
75
+ # nil: no localfs
76
+ @local_fs_map = nil
77
+
78
+ def self.remote_to_local_path path
79
+ case @local_fs_map
80
+ when nil
81
+ nil
82
+ when true
83
+ path
84
+ else # Array
85
+ @local_fs_map.each do |(remote_path_prefix, local_path_prefix)|
86
+ if path.start_with? remote_path_prefix
87
+ return path.sub(remote_path_prefix){ local_path_prefix }
88
+ end
89
+ end
90
+
91
+ nil
92
+ end
93
+ end
94
+
95
+ def self.local_to_remote_path path
96
+ case @local_fs_map
97
+ when nil
98
+ nil
99
+ when true
100
+ path
101
+ else # Array
102
+ @local_fs_map.each do |(remote_path_prefix, local_path_prefix)|
103
+ if path.start_with? local_path_prefix
104
+ return path.sub(local_path_prefix){ remote_path_prefix }
105
+ end
106
+ end
107
+
108
+ nil
109
+ end
110
+ end
111
+
112
+ def self.local_fs_map_set map
113
+ return if @local_fs_map # already setup
114
+
115
+ case map
116
+ when String
117
+ @local_fs_map = map.split(',').map{|e| e.split(':').map{|path| path.delete_suffix('/') + '/'}}
118
+ when true
119
+ @local_fs_map = map
120
+ when nil
121
+ @local_fs_map = CONFIG[:local_fs_map]
122
+ end
123
+ end
124
+
74
125
  def dap_setup bytes
75
126
  CONFIG.set_config no_color: true
76
127
  @seq = 0
77
128
 
129
+ case self
130
+ when UI_UnixDomainServer
131
+ UI_DAP.local_fs_map_set true
132
+ when UI_TcpServer
133
+ # TODO: loopback address can be used to connect other FS env, like Docker containers
134
+ # UI_DAP.local_fs_set if @local_addr.ipv4_loopback? || @local_addr.ipv6_loopback?
135
+ end
136
+
78
137
  show_protocol :>, bytes
79
138
  req = JSON.load(bytes)
80
139
 
@@ -90,14 +149,13 @@ module DEBUGGER__
90
149
  {
91
150
  filter: 'any',
92
151
  label: 'rescue any exception',
93
- #supportsCondition: true,
152
+ supportsCondition: true,
94
153
  #conditionDescription: '',
95
154
  },
96
155
  {
97
156
  filter: 'RuntimeError',
98
157
  label: 'rescue RuntimeError',
99
- default: true,
100
- #supportsCondition: true,
158
+ supportsCondition: true,
101
159
  #conditionDescription: '',
102
160
  },
103
161
  ],
@@ -143,10 +201,12 @@ module DEBUGGER__
143
201
  end
144
202
 
145
203
  def send **kw
146
- kw[:seq] = @seq += 1
147
- str = JSON.dump(kw)
148
- show_protocol '<', str
149
- @sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
204
+ if sock = @sock
205
+ kw[:seq] = @seq += 1
206
+ str = JSON.dump(kw)
207
+ sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
208
+ show_protocol '<', str
209
+ end
150
210
  end
151
211
 
152
212
  def send_response req, success: true, message: nil, **kw
@@ -211,67 +271,97 @@ module DEBUGGER__
211
271
  ## boot/configuration
212
272
  when 'launch'
213
273
  send_response req
214
- @is_attach = false
274
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
275
+ @is_launch = true
276
+
215
277
  when 'attach'
216
278
  send_response req
217
- Process.kill(:SIGURG, Process.pid)
218
- @is_attach = true
219
- when 'setBreakpoints'
220
- path = args.dig('source', 'path')
221
- bp_args = args['breakpoints']
222
- bps = []
223
- bp_args.each{|bp|
224
- line = bp['line']
225
- if cond = bp['condition']
226
- bps << SESSION.add_line_breakpoint(path, line, cond: cond)
227
- else
228
- bps << SESSION.add_line_breakpoint(path, line)
279
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
280
+ @is_launch = false
281
+
282
+ when 'configurationDone'
283
+ send_response req
284
+
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
229
292
  end
230
- }
231
- send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
293
+ end
294
+
295
+ when 'setBreakpoints'
296
+ req_path = args.dig('source', 'path')
297
+ path = UI_DAP.local_to_remote_path(req_path)
298
+
299
+ if path
300
+ SESSION.clear_line_breakpoints path
301
+
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"
314
+ end
315
+
232
316
  when 'setFunctionBreakpoints'
233
317
  send_response req
318
+
234
319
  when 'setExceptionBreakpoints'
235
- process_filter = ->(filter_id) {
236
- case filter_id
237
- when 'any'
238
- bp = SESSION.add_catch_breakpoint 'Exception'
239
- when 'RuntimeError'
240
- bp = SESSION.add_catch_breakpoint 'RuntimeError'
241
- else
242
- bp = nil
243
- end
244
- {
245
- verified: bp ? true : false,
246
- message: bp.inspect,
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
+ }
247
334
  }
248
- }
249
335
 
250
- filters = args.fetch('filters').map {|filter_id|
251
- process_filter.call(filter_id)
252
- }
336
+ SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
337
+
338
+ filters = args.fetch('filters').map {|filter_id|
339
+ process_filter.call(filter_id)
340
+ }
253
341
 
254
- filters += args.fetch('filterOptions', {}).map{|bp_info|
255
- process_filter.call(bp_info.dig('filterId'))
342
+ filters += args.fetch('filterOptions', {}).map{|bp_info|
343
+ process_filter.call(bp_info['filterId'], bp_info['condition'])
256
344
  }
257
345
 
258
346
  send_response req, breakpoints: filters
259
- when 'configurationDone'
260
- send_response req
261
- if defined?(@is_attach) && @is_attach
262
- @q_msg << 'p'
263
- send_event 'stopped', reason: 'pause',
264
- threadId: 1,
265
- allThreadsStopped: true
266
- else
267
- @q_msg << 'continue'
268
- end
347
+
269
348
  when 'disconnect'
270
- if args.fetch("terminateDebuggee", false)
271
- @q_msg << 'kill!'
349
+ terminate = args.fetch("terminateDebuggee", false)
350
+
351
+ if SESSION.in_subsession?
352
+ if terminate
353
+ @q_msg << 'kill!'
354
+ else
355
+ @q_msg << 'continue'
356
+ end
272
357
  else
273
- @q_msg << 'continue'
358
+ if terminate
359
+ @q_msg << 'kill!'
360
+ pause
361
+ end
274
362
  end
363
+
364
+ SESSION.clear_all_breakpoints
275
365
  send_response req
276
366
 
277
367
  ## control
@@ -313,7 +403,7 @@ module DEBUGGER__
313
403
  exit
314
404
  when 'pause'
315
405
  send_response req
316
- Process.kill(:SIGURG, Process.pid)
406
+ Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
317
407
  when 'reverseContinue'
318
408
  send_response req,
319
409
  success: false, message: 'cancelled',
@@ -341,18 +431,12 @@ module DEBUGGER__
341
431
  raise "Unknown request: #{req.inspect}"
342
432
  end
343
433
  end
434
+ ensure
435
+ send_event :terminated unless @sock.closed?
344
436
  end
345
437
 
346
438
  ## called by the SESSION thread
347
439
 
348
- def readline prompt
349
- @q_msg.pop || 'kill!'
350
- end
351
-
352
- def sock skip: false
353
- yield $stderr
354
- end
355
-
356
440
  def respond req, res
357
441
  send_response(req, **res)
358
442
  end
@@ -364,6 +448,16 @@ module DEBUGGER__
364
448
 
365
449
  def event type, *args
366
450
  case type
451
+ when :load
452
+ file_path, reloaded = *args
453
+
454
+ if file_path
455
+ send_event 'loadedSource',
456
+ reason: (reloaded ? :changed : :new),
457
+ source: {
458
+ path: file_path,
459
+ }
460
+ end
367
461
  when :suspend_bp
368
462
  _i, bp, tid = *args
369
463
  if bp.kind_of?(CatchBreakpoint)
@@ -410,15 +504,16 @@ module DEBUGGER__
410
504
  case req['command']
411
505
  when 'stepBack'
412
506
  if @tc.recorder&.can_step_back?
413
- @tc << [:step, :back]
507
+ request_tc [:step, :back]
414
508
  else
415
509
  fail_response req, message: 'cancelled'
416
510
  end
417
511
 
418
512
  when 'stackTrace'
419
513
  tid = req.dig('arguments', 'threadId')
514
+
420
515
  if tc = find_waiting_tc(tid)
421
- tc << [:dap, :backtrace, req]
516
+ request_tc [:dap, :backtrace, req]
422
517
  else
423
518
  fail_response req
424
519
  end
@@ -427,7 +522,7 @@ module DEBUGGER__
427
522
  if @frame_map[frame_id]
428
523
  tid, fid = @frame_map[frame_id]
429
524
  if tc = find_waiting_tc(tid)
430
- tc << [:dap, :scopes, req, fid]
525
+ request_tc [:dap, :scopes, req, fid]
431
526
  else
432
527
  fail_response req
433
528
  end
@@ -459,7 +554,7 @@ module DEBUGGER__
459
554
  tid, fid = @frame_map[frame_id]
460
555
 
461
556
  if tc = find_waiting_tc(tid)
462
- tc << [:dap, :scope, req, fid]
557
+ request_tc [:dap, :scope, req, fid]
463
558
  else
464
559
  fail_response req
465
560
  end
@@ -468,7 +563,7 @@ module DEBUGGER__
468
563
  tid, vid = ref[1], ref[2]
469
564
 
470
565
  if tc = find_waiting_tc(tid)
471
- tc << [:dap, :variable, req, vid]
566
+ request_tc [:dap, :variable, req, vid]
472
567
  else
473
568
  fail_response req
474
569
  end
@@ -486,7 +581,7 @@ module DEBUGGER__
486
581
  tid, fid = @frame_map[frame_id]
487
582
  expr = req.dig('arguments', 'expression')
488
583
  if tc = find_waiting_tc(tid)
489
- tc << [:dap, :evaluate, req, fid, expr, context]
584
+ request_tc [:dap, :evaluate, req, fid, expr, context]
490
585
  else
491
586
  fail_response req
492
587
  end
@@ -496,7 +591,7 @@ module DEBUGGER__
496
591
  when 'source'
497
592
  ref = req.dig('arguments', 'sourceReference')
498
593
  if src = @src_map[ref]
499
- @ui.respond req, content: src.join
594
+ @ui.respond req, content: src.join("\n")
500
595
  else
501
596
  fail_response req, message: 'not found...'
502
597
  end
@@ -512,7 +607,7 @@ module DEBUGGER__
512
607
  if col = req.dig('arguments', 'column')
513
608
  text = text.split(/\n/)[line.to_i - 1][0...(col.to_i - 1)]
514
609
  end
515
- tc << [:dap, :completions, req, fid, text]
610
+ request_tc [:dap, :completions, req, fid, text]
516
611
  else
517
612
  fail_response req
518
613
  end
@@ -527,13 +622,18 @@ module DEBUGGER__
527
622
 
528
623
  case type
529
624
  when :backtrace
530
- result[:stackFrames].each.with_index{|fi, i|
625
+ result[:stackFrames].each{|fi|
626
+ frame_depth = fi[:id]
531
627
  fi[:id] = id = @frame_map.size + 1
532
- @frame_map[id] = [req.dig('arguments', 'threadId'), i]
533
- if fi[:source] && src = fi[:source][:sourceReference]
534
- src_id = @src_map.size + 1
535
- @src_map[src_id] = src
536
- fi[:source][:sourceReference] = src_id
628
+ @frame_map[id] = [req.dig('arguments', 'threadId'), frame_depth]
629
+ if fi[:source]
630
+ if src = fi[:source][:sourceReference]
631
+ src_id = @src_map.size + 1
632
+ @src_map[src_id] = src
633
+ fi[:source][:sourceReference] = src_id
634
+ else
635
+ fi[:source][:sourceReference] = 0
636
+ end
537
637
  end
538
638
  }
539
639
  @ui.respond req, result
@@ -585,6 +685,11 @@ module DEBUGGER__
585
685
  end
586
686
 
587
687
  class ThreadClient
688
+ def value_inspect obj
689
+ # TODO: max length should be configuarable?
690
+ DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024
691
+ end
692
+
588
693
  def process_dap args
589
694
  # pp tc: self, args: args
590
695
  type = args.shift
@@ -592,27 +697,42 @@ module DEBUGGER__
592
697
 
593
698
  case type
594
699
  when :backtrace
595
- event! :dap_result, :backtrace, req, {
596
- stackFrames: @target_frames.map{|frame|
597
- path = frame.realpath || frame.path
598
- ref = frame.file_lines unless path && File.exist?(path)
599
-
600
- {
601
- # id: ??? # filled by SESSION
602
- name: frame.name,
603
- line: frame.location.lineno,
604
- column: 1,
605
- source: {
606
- name: File.basename(frame.path),
607
- path: path,
608
- sourceReference: ref,
609
- },
610
- }
700
+ start_frame = req.dig('arguments', 'startFrame') || 0
701
+ levels = req.dig('arguments', 'levels') || 1_000
702
+ frames = []
703
+ @target_frames.each_with_index do |frame, i|
704
+ next if i < start_frame
705
+ break if (levels -= 1) < 0
706
+
707
+ path = frame.realpath || frame.path
708
+ source_name = path ? File.basename(path) : frame.location.to_s
709
+
710
+ if (path && File.exist?(path)) && (local_path = UI_DAP.remote_to_local_path(path))
711
+ # ok
712
+ else
713
+ ref = frame.file_lines
714
+ end
715
+
716
+ frames << {
717
+ id: i, # id is refilled by SESSION
718
+ name: frame.name,
719
+ line: frame.location.lineno,
720
+ column: 1,
721
+ source: {
722
+ name: source_name,
723
+ path: (local_path || path),
724
+ sourceReference: ref,
725
+ },
611
726
  }
727
+ end
728
+
729
+ event! :dap_result, :backtrace, req, {
730
+ stackFrames: frames,
731
+ totalFrames: @target_frames.size,
612
732
  }
613
733
  when :scopes
614
734
  fid = args.shift
615
- frame = @target_frames[fid]
735
+ frame = get_frame(fid)
616
736
 
617
737
  lnum =
618
738
  if frame.binding
@@ -640,28 +760,12 @@ module DEBUGGER__
640
760
  }]
641
761
  when :scope
642
762
  fid = args.shift
643
- frame = @target_frames[fid]
644
- if b = frame.binding
645
- vars = b.local_variables.map{|name|
646
- v = b.local_variable_get(name)
647
- variable(name, v)
648
- }
649
- special_local_variables frame do |name, val|
650
- vars.unshift variable(name, val)
651
- end
652
- vars.unshift variable('%self', b.receiver)
653
- elsif lvars = frame.local_variables
654
- vars = lvars.map{|var, val|
655
- variable(var, val)
656
- }
657
- else
658
- vars = [variable('%self', frame.self)]
659
- special_local_variables frame do |name, val|
660
- vars.push variable(name, val)
661
- end
763
+ frame = get_frame(fid)
764
+ vars = collect_locals(frame).map do |var, val|
765
+ variable(var, val)
662
766
  end
663
- event! :dap_result, :scope, req, variables: vars, tid: self.id
664
767
 
768
+ event! :dap_result, :scope, req, variables: vars, tid: self.id
665
769
  when :variable
666
770
  vid = args.shift
667
771
  obj = @var_map[vid]
@@ -679,7 +783,7 @@ module DEBUGGER__
679
783
  case obj
680
784
  when Hash
681
785
  vars = obj.map{|k, v|
682
- variable(DEBUGGER__.safe_inspect(k), v,)
786
+ variable(value_inspect(k), v,)
683
787
  }
684
788
  when Struct
685
789
  vars = obj.members.map{|m|
@@ -702,22 +806,21 @@ module DEBUGGER__
702
806
  ]
703
807
  end
704
808
 
705
- vars += obj.instance_variables.map{|iv|
706
- variable(iv, obj.instance_variable_get(iv))
809
+ vars += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
810
+ variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
707
811
  }
708
- vars.unshift variable('#class', obj.class)
812
+ vars.unshift variable('#class', M_CLASS.bind_call(obj))
709
813
  end
710
814
  end
711
815
  event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
712
816
 
713
817
  when :evaluate
714
818
  fid, expr, context = args
715
- frame = @target_frames[fid]
819
+ frame = get_frame(fid)
716
820
  message = nil
717
821
 
718
- if frame && (b = frame.binding)
719
- b = b.dup
720
- special_local_variables current_frame do |name, var|
822
+ if frame && (b = frame.eval_binding)
823
+ special_local_variables frame do |name, var|
721
824
  b.local_variable_set(name, var) if /\%/ !~ name
722
825
  end
723
826
 
@@ -733,8 +836,7 @@ module DEBUGGER__
733
836
  case expr
734
837
  when /\A\@\S/
735
838
  begin
736
- (r = b.receiver).instance_variable_defined?(expr) or raise(NameError)
737
- result = r.instance_variable_get(expr)
839
+ result = M_INSTANCE_VARIABLE_GET.bind_call(b.receiver, expr)
738
840
  rescue NameError
739
841
  message = "Error: Not defined instance variable: #{expr.inspect}"
740
842
  end
@@ -745,19 +847,19 @@ module DEBUGGER__
745
847
  break false
746
848
  end
747
849
  } and (message = "Error: Not defined global variable: #{expr.inspect}")
748
- when /\A[A-Z]/
749
- unless result = search_const(b, expr)
850
+ when /\Aself$/
851
+ result = b.receiver
852
+ when /(\A((::[A-Z]|[A-Z])\w*)+)/
853
+ unless result = search_const(b, $1)
750
854
  message = "Error: Not defined constants: #{expr.inspect}"
751
855
  end
752
856
  else
753
857
  begin
754
- # try to check local variables
755
- b.local_variable_defined?(expr) or raise NameError
756
858
  result = b.local_variable_get(expr)
757
859
  rescue NameError
758
860
  # try to check method
759
- if b.receiver.respond_to? expr, include_all: true
760
- result = b.receiver.method(expr)
861
+ if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
862
+ result = M_METHOD.bind_call(b.receiver, expr)
761
863
  else
762
864
  message = "Error: Can not evaluate: #{expr.inspect}"
763
865
  end
@@ -774,13 +876,15 @@ module DEBUGGER__
774
876
 
775
877
  when :completions
776
878
  fid, text = args
777
- frame = @target_frames[fid]
879
+ frame = get_frame(fid)
778
880
 
779
881
  if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last
780
882
  words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
781
883
  end
782
884
 
783
885
  event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
886
+ detail = nil
887
+
784
888
  if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
785
889
  w = $1
786
890
  else
@@ -788,16 +892,15 @@ module DEBUGGER__
788
892
  end
789
893
 
790
894
  begin
791
- if b&.local_variable_defined?(w)
792
- v = b.local_variable_get(w)
793
- phrase += " (variable:#{DEBUGGER__.safe_inspect(v)})"
794
- end
895
+ v = b.local_variable_get(w)
896
+ detail ="(variable: #{value_inspect(v)})"
795
897
  rescue NameError
796
898
  end
797
899
 
798
900
  {
799
901
  label: phrase,
800
902
  text: w,
903
+ detail: detail,
801
904
  }
802
905
  }
803
906
 
@@ -807,7 +910,7 @@ module DEBUGGER__
807
910
  end
808
911
 
809
912
  def search_const b, expr
810
- cs = expr.split('::')
913
+ cs = expr.delete_prefix('::').split('::')
811
914
  [Object, *b.eval('Module.nesting')].reverse_each{|mod|
812
915
  if cs.all?{|c|
813
916
  if mod.const_defined?(c)
@@ -827,7 +930,7 @@ module DEBUGGER__
827
930
  v = variable nil, r
828
931
  v.delete :name
829
932
  v.delete :value
830
- v[:result] = DEBUGGER__.safe_inspect(r)
933
+ v[:result] = value_inspect(r)
831
934
  v
832
935
  end
833
936
 
@@ -839,11 +942,11 @@ module DEBUGGER__
839
942
  vid = 0
840
943
  end
841
944
 
842
- ivnum = obj.instance_variables.size
945
+ ivnum = M_INSTANCE_VARIABLES.bind_call(obj).size
843
946
 
844
947
  { name: name,
845
- value: DEBUGGER__.safe_inspect(obj),
846
- type: obj.class.name || obj.class.to_s,
948
+ value: value_inspect(obj),
949
+ type: (klass = M_CLASS.bind_call(obj)).name || klass.to_s,
847
950
  variablesReference: vid,
848
951
  indexedVariables: indexedVariables,
849
952
  namedVariables: namedVariables + ivnum,