byebug-dap 0.1.2 → 0.1.3
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/CHANGELOG.md +14 -3
- data/README.md +5 -5
- data/bin/byebug-dap +25 -8
- data/lib/byebug/dap.rb +48 -18
- data/lib/byebug/dap/command.rb +250 -0
- data/lib/byebug/dap/command_processor.rb +50 -49
- data/lib/byebug/dap/commands/attach.rb +13 -0
- data/lib/byebug/dap/commands/breakpoint_locations.rb +28 -0
- data/lib/byebug/dap/commands/configuration_done.rb +12 -0
- data/lib/byebug/dap/commands/continue.rb +18 -0
- data/lib/byebug/dap/commands/disconnect.rb +16 -0
- data/lib/byebug/dap/commands/evaluate.rb +27 -0
- data/lib/byebug/dap/commands/exception_info.rb +40 -0
- data/lib/byebug/dap/commands/initialize.rb +27 -0
- data/lib/byebug/dap/commands/launch.rb +13 -0
- data/lib/byebug/dap/commands/next.rb +20 -0
- data/lib/byebug/dap/commands/pause.rb +20 -0
- data/lib/byebug/dap/commands/scopes.rb +56 -0
- data/lib/byebug/dap/commands/set_breakpoints.rb +48 -0
- data/lib/byebug/dap/commands/set_exception_breakpoints.rb +28 -0
- data/lib/byebug/dap/commands/set_function_breakpoints.rb +96 -0
- data/lib/byebug/dap/commands/source.rb +12 -0
- data/lib/byebug/dap/commands/stack_trace.rb +50 -0
- data/lib/byebug/dap/commands/step_in.rb +24 -0
- data/lib/byebug/dap/commands/step_out.rb +21 -0
- data/lib/byebug/dap/commands/threads.rb +20 -0
- data/lib/byebug/dap/commands/variables.rb +33 -0
- data/lib/byebug/dap/contextual_command.rb +30 -0
- data/lib/byebug/dap/helpers/captured_io.rb +65 -0
- data/lib/byebug/dap/helpers/captured_output.rb +21 -0
- data/lib/byebug/dap/{channel.rb → helpers/channel.rb} +0 -0
- data/lib/byebug/dap/{child_spawned_event_body.rb → helpers/child_spawned_event_body.rb} +0 -0
- data/lib/byebug/dap/{handles.rb → helpers/handles.rb} +0 -0
- data/lib/byebug/dap/{invalid_request_argument_error.rb → helpers/invalid_request_argument_error.rb} +0 -0
- data/lib/byebug/dap/helpers/safe_helpers.rb +17 -0
- data/lib/byebug/dap/helpers/scalar.rb +19 -0
- data/lib/byebug/dap/helpers/stdio.rb +21 -0
- data/lib/byebug/dap/helpers/value_helpers.rb +60 -0
- data/lib/byebug/dap/server.rb +52 -36
- data/lib/byebug/dap/session.rb +176 -0
- data/lib/byebug/gem.rb +11 -0
- metadata +38 -10
- data/lib/byebug/dap/controller.rb +0 -252
- data/lib/byebug/dap/interface.rb +0 -303
- data/lib/byebug/dap/safe_helpers.rb +0 -55
@@ -12,16 +12,21 @@ module Byebug
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_reader :context, :
|
15
|
+
attr_reader :context, :last_exception
|
16
|
+
attr_writer :pause_requested
|
16
17
|
|
17
|
-
def initialize(context,
|
18
|
+
def initialize(context, session)
|
18
19
|
@context = context
|
19
|
-
@
|
20
|
+
@session = session
|
20
21
|
@requests = Channel.new
|
21
22
|
@exec_mu = Mutex.new
|
22
23
|
@exec_ch = Channel.new
|
23
24
|
end
|
24
25
|
|
26
|
+
def log(*args)
|
27
|
+
@session.log(*args)
|
28
|
+
end
|
29
|
+
|
25
30
|
def <<(message)
|
26
31
|
@requests.push(message, timeout: 1) { raise TimeoutError.new(context) }
|
27
32
|
end
|
@@ -54,67 +59,61 @@ module Byebug
|
|
54
59
|
next
|
55
60
|
end
|
56
61
|
|
57
|
-
|
58
|
-
|
59
|
-
# "The request starts the debuggee to run again.
|
60
|
-
|
61
|
-
break
|
62
|
-
|
63
|
-
when 'pause'
|
64
|
-
# "The request suspends the debuggee.
|
65
|
-
# "The debug adapter first sends the response and then a ‘stopped’ event (with reason ‘pause’) after the thread has been paused successfully.
|
62
|
+
break if ContextualCommand.execute(@session, request, self) == :stop
|
63
|
+
end
|
66
64
|
|
67
|
-
|
65
|
+
@last_exception = nil
|
66
|
+
@session.invalidate_handles!
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
rescue StandardError => e
|
69
|
+
log "\n! #{e.message} (#{e.class})", *e.backtrace
|
70
|
+
end
|
72
71
|
|
73
|
-
|
74
|
-
|
72
|
+
def logpoint!
|
73
|
+
return false unless @last_breakpoint
|
75
74
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# "The debug adapter first sends the response and then a ‘stopped’ event (with reason ‘step’) after the step has completed.
|
80
|
-
# "If there are multiple function/method calls (or other targets) on the source line,
|
81
|
-
# "the optional argument ‘targetId’ can be used to control into which target the ‘stepIn’ should occur.
|
82
|
-
# "The list of possible targets for a given source line can be retrieved via the ‘stepInTargets’ request.
|
75
|
+
breakpoint, @last_breakpoint = @last_breakpoint, nil
|
76
|
+
expr = @session.get_log_point(breakpoint)
|
77
|
+
return false unless expr
|
83
78
|
|
84
|
-
|
85
|
-
|
79
|
+
binding = @context.frame._binding
|
80
|
+
msg = expr.gsub(/\{([^\}]+)\}/) do |x|
|
81
|
+
safe(binding, :eval, x[1...-1]) { return true } # ignore bad log points
|
82
|
+
end
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
body = {
|
85
|
+
category: 'console',
|
86
|
+
output: msg + "\n",
|
87
|
+
}
|
90
88
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
if breakpoint.pos.is_a?(Integer)
|
90
|
+
body[:line] = breakpoint.pos
|
91
|
+
body[:source] = {
|
92
|
+
name: File.basename(breakpoint.source),
|
93
|
+
path: breakpoint.source,
|
94
|
+
}
|
95
95
|
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
rescue StandardError => e
|
100
|
-
STDERR.puts "\n! #{e.message} (#{e.class})", *e.backtrace
|
97
|
+
@session.event! 'output', **body
|
98
|
+
return true
|
101
99
|
end
|
102
100
|
|
103
101
|
def stopped!
|
102
|
+
return if logpoint!
|
103
|
+
|
104
104
|
case context.stop_reason
|
105
105
|
when :breakpoint
|
106
106
|
args = {
|
107
107
|
reason: 'breakpoint',
|
108
108
|
description: 'Hit breakpoint',
|
109
|
-
text: "
|
109
|
+
text: "Hit breakpoint at #{context.location}",
|
110
110
|
}
|
111
111
|
|
112
112
|
when :catchpoint
|
113
|
-
# TODO this is probably not the right message
|
114
113
|
args = {
|
115
|
-
reason: '
|
114
|
+
reason: 'exception',
|
116
115
|
description: 'Hit catchpoint',
|
117
|
-
text: "
|
116
|
+
text: "Hit catchpoint at #{context.location}",
|
118
117
|
}
|
119
118
|
|
120
119
|
when :step
|
@@ -122,20 +121,22 @@ module Byebug
|
|
122
121
|
@pause_requested = false
|
123
122
|
args = {
|
124
123
|
reason: 'pause',
|
125
|
-
|
124
|
+
description: 'Paused',
|
125
|
+
text: "Paused at #{context.location}"
|
126
126
|
}
|
127
127
|
else
|
128
128
|
args = {
|
129
129
|
reason: 'step',
|
130
|
-
|
130
|
+
description: 'Stepped',
|
131
|
+
text: "Stepped at #{context.location}"
|
131
132
|
}
|
132
133
|
end
|
133
134
|
|
134
135
|
else
|
135
|
-
|
136
|
+
log "Stopped for unknown reason: #{context.stop_reason}"
|
136
137
|
end
|
137
138
|
|
138
|
-
|
139
|
+
@session.event! 'stopped', threadId: context.thnum, **args if args
|
139
140
|
|
140
141
|
process_requests
|
141
142
|
end
|
@@ -153,17 +154,17 @@ module Byebug
|
|
153
154
|
end
|
154
155
|
|
155
156
|
# def at_tracing
|
156
|
-
#
|
157
|
+
# @session.puts "Tracing: #{context.full_location}"
|
157
158
|
|
158
159
|
# # run_auto_cmds(2)
|
159
160
|
# end
|
160
161
|
|
161
162
|
def at_breakpoint(breakpoint)
|
162
|
-
@
|
163
|
+
@last_breakpoint = breakpoint
|
163
164
|
end
|
164
165
|
|
165
166
|
def at_catchpoint(exception)
|
166
|
-
@
|
167
|
+
@last_exception = exception
|
167
168
|
end
|
168
169
|
end
|
169
170
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Attach < Command
|
3
|
+
# "The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running.
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute
|
8
|
+
stopped!
|
9
|
+
@session.start!(:attached)
|
10
|
+
respond!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::BreakpointLocations < Command
|
3
|
+
# "The ‘breakpointLocations’ request returns all possible locations for source breakpoints in a given range.
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute
|
8
|
+
return unless path = can_read_file!(args.source.path)
|
9
|
+
lines = potential_breakpoint_lines(path) { |e|
|
10
|
+
respond! success: false, message: "Failed to resolve breakpoints for #{path}"
|
11
|
+
return
|
12
|
+
}
|
13
|
+
|
14
|
+
unless args.endLine
|
15
|
+
if lines.include?(args.line)
|
16
|
+
respond! body: { breakpoints: [{ line: args.line }] }
|
17
|
+
else
|
18
|
+
respond! body: { breakpoints: [] }
|
19
|
+
end
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
range = [args.line..args.endLine]
|
24
|
+
lines.filter! { |l| range.include?(l) }
|
25
|
+
respond! body: { breakpoints: lines.map { |l| { line: l } } }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Continue < ContextualCommand
|
3
|
+
# "The request starts the debuggee to run again.
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute_in_context
|
8
|
+
:stop
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def forward_to_context(ctx)
|
14
|
+
super
|
15
|
+
respond!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Disconnect < Command
|
3
|
+
# "The ‘disconnect’ request is sent from the client to the debug adapter in order to stop debugging.
|
4
|
+
# "It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter.
|
5
|
+
# "If the debuggee has been started with the ‘launch’ request, the ‘disconnect’ request terminates the debuggee.
|
6
|
+
# "If the ‘attach’ request was used to connect to the debuggee, ‘disconnect’ does not terminate the debuggee.
|
7
|
+
# "This behavior can be controlled with the ‘terminateDebuggee’ argument (if supported by the debug adapter).
|
8
|
+
|
9
|
+
register!
|
10
|
+
|
11
|
+
def execute
|
12
|
+
@session.stop!
|
13
|
+
respond!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Evaluate < Command
|
3
|
+
# "Evaluates the given expression in the context of the top most stack frame.
|
4
|
+
# "The expression has access to any variables and arguments that are in scope.
|
5
|
+
|
6
|
+
include ValueHelpers
|
7
|
+
|
8
|
+
register!
|
9
|
+
|
10
|
+
def execute
|
11
|
+
started!
|
12
|
+
|
13
|
+
respond! body: evaluate
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def evaluate
|
19
|
+
return prepare_value_response(0, 0, :evaluate) { TOPLEVEL_BINDING.eval(args.expression) } unless args.frameId
|
20
|
+
|
21
|
+
frame, thnum, frnum = resolve_frame_id(args.frameId)
|
22
|
+
return unless frame
|
23
|
+
|
24
|
+
prepare_value_response(thnum, frnum, :evaluate) { frame._binding.eval(args.expression) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::ExceptionInfo < ContextualCommand
|
3
|
+
# "Retrieves the details of the exception that caused this event to be raised.
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute_in_context
|
8
|
+
unless ex = @processor.last_exception
|
9
|
+
respond! success: false, message: 'Not in a catchpoint context'
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
class_name = safe(ex, [:class, :name]) { "Unknown" }
|
14
|
+
|
15
|
+
respond! body: {
|
16
|
+
exceptionId: class_name,
|
17
|
+
description: exception_description(ex),
|
18
|
+
breakMode: ::DAP::ExceptionBreakMode::ALWAYS,
|
19
|
+
details: details(ex, '$!'),
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def details(ex, eval_name)
|
26
|
+
class_name = safe(ex, [:class, :name]) { nil }
|
27
|
+
type_name = class_name.split('::').last if class_name
|
28
|
+
inner = safe(ex, :cause) { nil }
|
29
|
+
|
30
|
+
{
|
31
|
+
message: safe(ex, :message) { nil },
|
32
|
+
typeName: type_name,
|
33
|
+
fullTypeName: class_name,
|
34
|
+
evaluateName: eval_name,
|
35
|
+
stackTrace: safe(ex, :backtrace) { [] }.join("\n"),
|
36
|
+
innerException: inner.nil? ? [] : [details(inner, "#{eval_name}.#{cause}")],
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Initialize < Command
|
3
|
+
# "The ‘initialize’ request is sent as the first request from the client to the debug adapter
|
4
|
+
# "in order to configure it with client capabilities and to retrieve capabilities from the debug adapter.
|
5
|
+
# "Until the debug adapter has responded to with an ‘initialize’ response, the client must not send any additional requests or events to the debug adapter.
|
6
|
+
# "In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an ‘initialize’ response.
|
7
|
+
# "The ‘initialize’ request may only be sent once.
|
8
|
+
|
9
|
+
register!
|
10
|
+
|
11
|
+
def execute
|
12
|
+
respond! body: {
|
13
|
+
supportsConfigurationDoneRequest: true,
|
14
|
+
supportsFunctionBreakpoints: true,
|
15
|
+
supportsConditionalBreakpoints: true,
|
16
|
+
supportsHitConditionalBreakpoints: true,
|
17
|
+
supportsLogPoints: true,
|
18
|
+
supportsBreakpointLocationsRequest: true,
|
19
|
+
supportsDelayedStackTraceLoading: true,
|
20
|
+
exceptionBreakpointFilters: Command::SetExceptionBreakpoints::FILTERS,
|
21
|
+
supportsExceptionInfoRequest: true,
|
22
|
+
}
|
23
|
+
|
24
|
+
event! 'initialized'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Launch < Command
|
3
|
+
# "This launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if ‘noDebug’ is true).
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute
|
8
|
+
stopped!
|
9
|
+
@session.start!(:launched) unless args.noDebug
|
10
|
+
respond!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Next < ContextualCommand
|
3
|
+
# "The request starts the debuggee to run again for one step.
|
4
|
+
# "The debug adapter first sends the response and then a ‘stopped’ event (with reason ‘step’) after the step has completed.
|
5
|
+
|
6
|
+
register!
|
7
|
+
|
8
|
+
def execute_in_context
|
9
|
+
@context.step_over(1, @context.frame.pos)
|
10
|
+
:stop
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def forward_to_context(ctx)
|
16
|
+
super
|
17
|
+
respond!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Pause < ContextualCommand
|
3
|
+
# "The request suspends the debuggee.
|
4
|
+
# "The debug adapter first sends the response and then a ‘stopped’ event (with reason ‘pause’) after the thread has been paused successfully.
|
5
|
+
|
6
|
+
register!
|
7
|
+
|
8
|
+
def execute_in_context
|
9
|
+
@processor.pause_requested = true
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def forward_to_context(ctx)
|
15
|
+
ctx.interrupt
|
16
|
+
super
|
17
|
+
respond!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class Command::Scopes < Command
|
3
|
+
# "The request returns the variable scopes for a given stackframe ID.
|
4
|
+
|
5
|
+
register!
|
6
|
+
|
7
|
+
def execute
|
8
|
+
started!
|
9
|
+
|
10
|
+
frame, thnum, frnum = resolve_frame_id(args.frameId)
|
11
|
+
return unless frame
|
12
|
+
|
13
|
+
scopes = []
|
14
|
+
|
15
|
+
locals = frame_local_names(frame).sort
|
16
|
+
unless locals.empty?
|
17
|
+
scopes << ::DAP::Scope.new(
|
18
|
+
name: 'Locals',
|
19
|
+
presentationHint: 'locals',
|
20
|
+
variablesReference: @session.save_variables(thnum, frnum, :locals, locals),
|
21
|
+
namedVariables: locals.size,
|
22
|
+
indexedVariables: 0,
|
23
|
+
expensive: false)
|
24
|
+
.validate!
|
25
|
+
end
|
26
|
+
|
27
|
+
globals = global_names.sort
|
28
|
+
unless globals.empty?
|
29
|
+
scopes << ::DAP::Scope.new(
|
30
|
+
name: 'Globals',
|
31
|
+
presentationHint: 'globals',
|
32
|
+
variablesReference: @session.save_variables(thnum, frnum, :globals, globals),
|
33
|
+
namedVariables: globals.size,
|
34
|
+
indexedVariables: 0,
|
35
|
+
expensive: true)
|
36
|
+
.validate!
|
37
|
+
end
|
38
|
+
|
39
|
+
respond! body: ::DAP::ScopesResponseBody.new(scopes: scopes)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def frame_local_names(frame)
|
45
|
+
locals = frame.locals
|
46
|
+
locals = locals.keys unless locals == [] # BUG in Byebug?
|
47
|
+
locals << :self if frame._self.to_s != 'main'
|
48
|
+
locals << :$! if frame.pos == 0 && !frame.context.processor.last_exception.nil?
|
49
|
+
locals
|
50
|
+
end
|
51
|
+
|
52
|
+
def global_names
|
53
|
+
global_variables - %i[$IGNORECASE $= $KCODE $-K $binding]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|