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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +195 -3
- data/Gemfile +1 -0
- data/README.md +55 -32
- data/Rakefile +28 -10
- data/debug.gemspec +7 -5
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +76 -14
- data/ext/debug/extconf.rb +22 -0
- data/lib/debug/breakpoint.rb +90 -66
- data/lib/debug/client.rb +21 -5
- data/lib/debug/config.rb +55 -24
- data/lib/debug/console.rb +43 -16
- data/lib/debug/frame_info.rb +32 -26
- data/lib/debug/local.rb +1 -1
- data/lib/debug/server.rb +105 -40
- data/lib/debug/server_cdp.rb +373 -152
- data/lib/debug/server_dap.rb +273 -170
- data/lib/debug/session.rb +450 -236
- data/lib/debug/source_repository.rb +104 -51
- data/lib/debug/thread_client.rb +252 -103
- data/lib/debug/tracer.rb +7 -12
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +34 -14
- metadata +6 -16
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/server_dap.rb
CHANGED
@@ -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
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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}/"
|
44
|
+
cmds = ['code', "#{dir}/"]
|
47
45
|
cmdline = cmds.join(' ')
|
48
|
-
ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/
|
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
|
-
|
152
|
+
supportsCondition: true,
|
94
153
|
#conditionDescription: '',
|
95
154
|
},
|
96
155
|
{
|
97
156
|
filter: 'RuntimeError',
|
98
157
|
label: 'rescue RuntimeError',
|
99
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
218
|
-
@
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
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
|
-
|
251
|
-
|
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
|
-
|
255
|
-
process_filter.call(bp_info
|
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
|
-
|
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
|
-
|
271
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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'),
|
533
|
-
if fi[:source]
|
534
|
-
|
535
|
-
|
536
|
-
|
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
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
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 =
|
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 =
|
644
|
-
|
645
|
-
|
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(
|
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.
|
706
|
-
variable(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
|
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 =
|
819
|
+
frame = get_frame(fid)
|
716
820
|
message = nil
|
717
821
|
|
718
|
-
if frame && (b = frame.
|
719
|
-
|
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
|
-
|
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 /\
|
749
|
-
|
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
|
760
|
-
result = b.receiver
|
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 =
|
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
|
-
|
792
|
-
|
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] =
|
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.
|
945
|
+
ivnum = M_INSTANCE_VARIABLES.bind_call(obj).size
|
843
946
|
|
844
947
|
{ name: name,
|
845
|
-
value:
|
846
|
-
type: obj.
|
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,
|