debug 1.4.0 → 1.6.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.
@@ -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,