# File debug-1.4.0/lib/debug/server_dap.rb, line 13
def self.setup sock_path
dir = Dir.mktmpdir("ruby-debug-vscode-")
at_exit{
CONFIG[:skip_path] = [//] # skip all
FileUtils.rm_rf dir
}
Dir.chdir(dir) do
Dir.mkdir('.vscode')
open('README.rb', 'w'){|f|
f.puts <<~MSG
# Wait for starting the attaching to the Ruby process
# This file will be removed at the end of the debuggee process.
#
# Note that vscode-rdbg extension is needed. Please install if you don't have.
MSG
}
open('.vscode/launch.json', 'w'){|f|
f.puts JSON.pretty_generate({
version: '0.2.0',
configurations: [
{
type: "rdbg",
name: "Attach with rdbg",
request: "attach",
rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
debugPort: sock_path,
autoAttach: true,
}
]
})
}
end
cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
cmdline = cmds.join(' ')
ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
STDERR.puts "Launching: #{cmdline}"
env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
unless system(env, *cmds)
DEBUGGER__.warn <<~MESSAGE
Can not invoke the command.
Use the command-line on your terminal (with modification if you need).
#{cmdline}
If your application is running on a SSH remote host, please try:
#{ssh_cmdline}
MESSAGE
end
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 74
def dap_setup bytes
CONFIG.set_config no_color: true
@seq = 0
show_protocol :>, bytes
req = JSON.load(bytes)
# capability
send_response(req,
## Supported
supportsConfigurationDoneRequest: true,
supportsFunctionBreakpoints: true,
supportsConditionalBreakpoints: true,
supportTerminateDebuggee: true,
supportsTerminateRequest: true,
exceptionBreakpointFilters: [
{
filter: 'any',
label: 'rescue any exception',
#supportsCondition: true,
#conditionDescription: '',
},
{
filter: 'RuntimeError',
label: 'rescue RuntimeError',
default: true,
#supportsCondition: true,
#conditionDescription: '',
},
],
supportsExceptionFilterOptions: true,
supportsStepBack: true,
supportsEvaluateForHovers: true,
supportsCompletionsRequest: true,
## Will be supported
# supportsExceptionOptions: true,
# supportsHitConditionalBreakpoints:
# supportsSetVariable: true,
# supportSuspendDebuggee:
# supportsLogPoints:
# supportsLoadedSourcesRequest:
# supportsDataBreakpoints:
# supportsBreakpointLocationsRequest:
## Possible?
# supportsRestartFrame:
# completionTriggerCharacters:
# supportsModulesRequest:
# additionalModuleColumns:
# supportedChecksumAlgorithms:
# supportsRestartRequest:
# supportsValueFormattingOptions:
# supportsExceptionInfoRequest:
# supportsDelayedStackTraceLoading:
# supportsTerminateThreadsRequest:
# supportsSetExpression:
# supportsClipboardContext:
## Never
# supportsGotoTargetsRequest:
# supportsStepInTargetsRequest:
# supportsReadMemoryRequest:
# supportsDisassembleRequest:
# supportsCancelRequest:
# supportsSteppingGranularity:
# supportsInstructionBreakpoints:
)
send_event 'initialized'
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 365
def event type, *args
case type
when :suspend_bp
_i, bp, tid = *args
if bp.kind_of?(CatchBreakpoint)
reason = 'exception'
text = bp.description
else
reason = 'breakpoint'
text = bp ? bp.description : 'temporary bp'
end
send_event 'stopped', reason: reason,
description: text,
text: text,
threadId: tid,
allThreadsStopped: true
when :suspend_trap
_sig, tid = *args
send_event 'stopped', reason: 'pause',
threadId: tid,
allThreadsStopped: true
when :suspended
tid, = *args
send_event 'stopped', reason: 'step',
threadId: tid,
allThreadsStopped: true
end
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 204
def process
while req = recv_request
raise "not a request: #{req.inpsect}" unless req['type'] == 'request'
args = req.dig('arguments')
case req['command']
## boot/configuration
when 'launch'
send_response req
@is_attach = false
when 'attach'
send_response req
Process.kill(:SIGURG, Process.pid)
@is_attach = true
when 'setBreakpoints'
path = args.dig('source', 'path')
bp_args = args['breakpoints']
bps = []
bp_args.each{|bp|
line = bp['line']
if cond = bp['condition']
bps << SESSION.add_line_breakpoint(path, line, cond: cond)
else
bps << SESSION.add_line_breakpoint(path, line)
end
}
send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
when 'setFunctionBreakpoints'
send_response req
when 'setExceptionBreakpoints'
process_filter = ->(filter_id) {
case filter_id
when 'any'
bp = SESSION.add_catch_breakpoint 'Exception'
when 'RuntimeError'
bp = SESSION.add_catch_breakpoint 'RuntimeError'
else
bp = nil
end
{
verified: bp ? true : false,
message: bp.inspect,
}
}
filters = args.fetch('filters').map {|filter_id|
process_filter.call(filter_id)
}
filters += args.fetch('filterOptions', {}).map{|bp_info|
process_filter.call(bp_info.dig('filterId'))
}
send_response req, breakpoints: filters
when 'configurationDone'
send_response req
if defined?(@is_attach) && @is_attach
@q_msg << 'p'
send_event 'stopped', reason: 'pause',
threadId: 1,
allThreadsStopped: true
else
@q_msg << 'continue'
end
when 'disconnect'
if args.fetch("terminateDebuggee", false)
@q_msg << 'kill!'
else
@q_msg << 'continue'
end
send_response req
## control
when 'continue'
@q_msg << 'c'
send_response req, allThreadsContinued: true
when 'next'
begin
@session.check_postmortem
@q_msg << 'n'
send_response req
rescue PostmortemError
send_response req,
success: false, message: 'postmortem mode',
result: "'Next' is not supported while postmortem mode"
end
when 'stepIn'
begin
@session.check_postmortem
@q_msg << 's'
send_response req
rescue PostmortemError
send_response req,
success: false, message: 'postmortem mode',
result: "'stepIn' is not supported while postmortem mode"
end
when 'stepOut'
begin
@session.check_postmortem
@q_msg << 'fin'
send_response req
rescue PostmortemError
send_response req,
success: false, message: 'postmortem mode',
result: "'stepOut' is not supported while postmortem mode"
end
when 'terminate'
send_response req
exit
when 'pause'
send_response req
Process.kill(:SIGURG, Process.pid)
when 'reverseContinue'
send_response req,
success: false, message: 'cancelled',
result: "Reverse Continue is not supported. Only \"Step back\" is supported."
when 'stepBack'
@q_msg << req
## query
when 'threads'
send_response req, threads: SESSION.managed_thread_clients.map{|tc|
{ id: tc.id,
name: tc.name,
}
}
when 'stackTrace',
'scopes',
'variables',
'evaluate',
'source',
'completions'
@q_msg << req
else
raise "Unknown request: #{req.inspect}"
end
end
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 360
def puts result
# STDERR.puts "puts: #{result}"
# send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
end
called by the SESSION thread
# File debug-1.4.0/lib/debug/server_dap.rb, line 348
def readline prompt
@q_msg.pop || 'kill!'
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 180
def recv_request
r = IO.select([@sock])
@session.process_group.sync do
raise RetryBecauseCantRead unless IO.select([@sock], nil, nil, 0)
case header = @sock.gets
when /Content-Length: (\d+)/
b = @sock.read(2)
raise b.inspect unless b == "\r\n"
l = @sock.read(s = $1.to_i)
show_protocol :>, l
JSON.load(l)
when nil
nil
else
raise "unrecognized line: #{l} (#{l.size} bytes)"
end
end
rescue RetryBecauseCantRead
retry
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 356
def respond req, res
send_response(req, **res)
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 145
def send **kw
kw[:seq] = @seq += 1
str = JSON.dump(kw)
show_protocol '<', str
@sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 169
def send_event name, **kw
if kw.empty?
send type: 'event', event: name
else
send type: 'event', event: name, body: kw
end
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 152
def send_response req, success: true, message: nil, **kw
if kw.empty?
send type: 'response',
command: req['command'],
request_seq: req['seq'],
success: success,
message: message || (success ? 'Success' : 'Failed')
else
send type: 'response',
command: req['command'],
request_seq: req['seq'],
success: success,
message: message || (success ? 'Success' : 'Failed'),
body: kw
end
end