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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +22 -10
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +84 -55
- data/Rakefile +8 -3
- data/TODO.md +8 -8
- data/debug.gemspec +3 -3
- data/exe/rdbg +19 -4
- data/ext/debug/debug.c +33 -5
- data/ext/debug/extconf.rb +1 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +15 -11
- data/lib/debug/client.rb +26 -8
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +69 -23
- data/lib/debug/console.rb +8 -29
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +9 -0
- data/lib/debug/irb_integration.rb +27 -0
- data/lib/debug/local.rb +16 -10
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +2 -1
- data/lib/debug/server.rb +32 -27
- data/lib/debug/server_cdp.rb +360 -155
- data/lib/debug/server_dap.rb +330 -197
- data/lib/debug/session.rb +494 -258
- data/lib/debug/source_repository.rb +41 -21
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +241 -82
- data/lib/debug/tracer.rb +4 -5
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +50 -44
- metadata +13 -10
data/lib/debug/server_dap.rb
CHANGED
@@ -18,7 +18,7 @@ module DEBUGGER__
|
|
18
18
|
end
|
19
19
|
|
20
20
|
at_exit do
|
21
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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(
|
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
|
-
|
267
|
-
|
298
|
+
process_request(req)
|
299
|
+
end
|
300
|
+
ensure
|
301
|
+
send_event :terminated unless @sock.closed?
|
302
|
+
end
|
268
303
|
|
269
|
-
|
304
|
+
def process_request req
|
305
|
+
raise "not a request: #{req.inspect}" unless req['type'] == 'request'
|
306
|
+
args = req.dig('arguments')
|
270
307
|
|
271
|
-
|
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
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
283
|
-
send_response req
|
317
|
+
load_extensions req
|
284
318
|
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
-
|
296
|
-
|
297
|
-
|
323
|
+
if req.dig('arguments', 'nonstop') == true
|
324
|
+
@nonstop = true
|
325
|
+
else
|
326
|
+
@nonstop = false
|
327
|
+
end
|
298
328
|
|
299
|
-
|
300
|
-
SESSION.clear_line_breakpoints path
|
329
|
+
load_extensions req
|
301
330
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
317
|
-
|
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
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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
|
-
|
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
|
-
|
343
|
-
process_filter.call(
|
386
|
+
filters = args.fetch('filters').map {|filter_id|
|
387
|
+
process_filter.call(filter_id)
|
344
388
|
}
|
345
389
|
|
346
|
-
|
390
|
+
filters += args.fetch('filterOptions', {}).map{|bp_info|
|
391
|
+
process_filter.call(bp_info['filterId'], bp_info['condition'])
|
392
|
+
}
|
347
393
|
|
348
|
-
|
349
|
-
terminate = args.fetch("terminateDebuggee", false)
|
394
|
+
send_response req, breakpoints: filters
|
350
395
|
|
351
|
-
|
352
|
-
|
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
|
-
|
365
|
-
|
399
|
+
SESSION.clear_all_breakpoints
|
400
|
+
send_response req
|
366
401
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
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
|
-
|
392
|
-
|
393
|
-
@
|
394
|
-
|
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
|
-
|
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
|
-
|
404
|
-
|
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
|
-
|
407
|
-
when 'reverseContinue'
|
434
|
+
rescue PostmortemError
|
408
435
|
send_response req,
|
409
|
-
success: false, message: '
|
410
|
-
result: "
|
411
|
-
|
412
|
-
|
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
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
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
|
-
|
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
|
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
|
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 =
|
538
|
-
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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:
|
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! :
|
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! :
|
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:
|
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! :
|
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
|
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
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
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! :
|
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
|
-
|
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
|
-
|
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! :
|
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! :
|
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
|
-
|
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
|
-
|
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
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
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
|
-
|
1062
|
+
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size
|
946
1063
|
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
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 # #
|
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
|