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
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: byebug-dap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan Reesor
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.1.2
|
41
41
|
description: Implements a Debug Adapter Protocol interface for Byebug
|
42
|
-
email:
|
42
|
+
email:
|
43
43
|
executables:
|
44
44
|
- byebug-dap
|
45
45
|
extensions: []
|
@@ -51,15 +51,43 @@ files:
|
|
51
51
|
- README.md
|
52
52
|
- bin/byebug-dap
|
53
53
|
- lib/byebug/dap.rb
|
54
|
-
- lib/byebug/dap/
|
55
|
-
- lib/byebug/dap/child_spawned_event_body.rb
|
54
|
+
- lib/byebug/dap/command.rb
|
56
55
|
- lib/byebug/dap/command_processor.rb
|
57
|
-
- lib/byebug/dap/
|
58
|
-
- lib/byebug/dap/
|
59
|
-
- lib/byebug/dap/
|
60
|
-
- lib/byebug/dap/
|
61
|
-
- lib/byebug/dap/
|
56
|
+
- lib/byebug/dap/commands/attach.rb
|
57
|
+
- lib/byebug/dap/commands/breakpoint_locations.rb
|
58
|
+
- lib/byebug/dap/commands/configuration_done.rb
|
59
|
+
- lib/byebug/dap/commands/continue.rb
|
60
|
+
- lib/byebug/dap/commands/disconnect.rb
|
61
|
+
- lib/byebug/dap/commands/evaluate.rb
|
62
|
+
- lib/byebug/dap/commands/exception_info.rb
|
63
|
+
- lib/byebug/dap/commands/initialize.rb
|
64
|
+
- lib/byebug/dap/commands/launch.rb
|
65
|
+
- lib/byebug/dap/commands/next.rb
|
66
|
+
- lib/byebug/dap/commands/pause.rb
|
67
|
+
- lib/byebug/dap/commands/scopes.rb
|
68
|
+
- lib/byebug/dap/commands/set_breakpoints.rb
|
69
|
+
- lib/byebug/dap/commands/set_exception_breakpoints.rb
|
70
|
+
- lib/byebug/dap/commands/set_function_breakpoints.rb
|
71
|
+
- lib/byebug/dap/commands/source.rb
|
72
|
+
- lib/byebug/dap/commands/stack_trace.rb
|
73
|
+
- lib/byebug/dap/commands/step_in.rb
|
74
|
+
- lib/byebug/dap/commands/step_out.rb
|
75
|
+
- lib/byebug/dap/commands/threads.rb
|
76
|
+
- lib/byebug/dap/commands/variables.rb
|
77
|
+
- lib/byebug/dap/contextual_command.rb
|
78
|
+
- lib/byebug/dap/helpers/captured_io.rb
|
79
|
+
- lib/byebug/dap/helpers/captured_output.rb
|
80
|
+
- lib/byebug/dap/helpers/channel.rb
|
81
|
+
- lib/byebug/dap/helpers/child_spawned_event_body.rb
|
82
|
+
- lib/byebug/dap/helpers/handles.rb
|
83
|
+
- lib/byebug/dap/helpers/invalid_request_argument_error.rb
|
84
|
+
- lib/byebug/dap/helpers/safe_helpers.rb
|
85
|
+
- lib/byebug/dap/helpers/scalar.rb
|
86
|
+
- lib/byebug/dap/helpers/stdio.rb
|
87
|
+
- lib/byebug/dap/helpers/value_helpers.rb
|
62
88
|
- lib/byebug/dap/server.rb
|
89
|
+
- lib/byebug/dap/session.rb
|
90
|
+
- lib/byebug/gem.rb
|
63
91
|
homepage: https://gitlab.com/firelizzard/byebug-dap
|
64
92
|
licenses:
|
65
93
|
- Apache-2.0
|
@@ -1,252 +0,0 @@
|
|
1
|
-
module Byebug
|
2
|
-
module DAP
|
3
|
-
class Controller
|
4
|
-
def initialize(interface, &block)
|
5
|
-
@interface = interface
|
6
|
-
@on_configured = block
|
7
|
-
|
8
|
-
@trace = TracePoint.new(:thread_begin, :thread_end) { |t| process_trace t }
|
9
|
-
end
|
10
|
-
|
11
|
-
def run
|
12
|
-
loop do
|
13
|
-
@request = @interface.receive
|
14
|
-
result = process_command @request
|
15
|
-
return if result == :stop
|
16
|
-
end
|
17
|
-
|
18
|
-
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
19
|
-
STDERR.puts "\nClient disconnected"
|
20
|
-
|
21
|
-
ensure
|
22
|
-
exit if @exit_on_disconnect
|
23
|
-
|
24
|
-
@interface.stop!
|
25
|
-
@trace.disable
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def process_trace(trace)
|
31
|
-
return unless Byebug.started?
|
32
|
-
|
33
|
-
ctx = Byebug.contexts.find { |c| c.thread == Thread.current }
|
34
|
-
|
35
|
-
case trace.event
|
36
|
-
when :thread_begin
|
37
|
-
@interface.event! 'thread', reason: 'started', threadId: ctx.thnum
|
38
|
-
when :thread_end
|
39
|
-
@interface.event! 'thread', reason: 'exited', threadId: ctx.thnum
|
40
|
-
end
|
41
|
-
|
42
|
-
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
43
|
-
# client disconnected, ignore error
|
44
|
-
end
|
45
|
-
|
46
|
-
def process_command(request)
|
47
|
-
case request.command
|
48
|
-
when 'attach', 'launch'
|
49
|
-
if Byebug.started?
|
50
|
-
respond! success: false, message: "Cannot #{request.command} - debugger is already running"
|
51
|
-
return
|
52
|
-
end
|
53
|
-
when 'pause', 'next', 'stepIn', 'stepOut', 'continue', 'evaluate', 'variables', 'scopes', 'threads', 'stackTrace'
|
54
|
-
unless Byebug.started?
|
55
|
-
respond! success: false, message: "Cannot #{request.command} - debugger is not running"
|
56
|
-
return
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
case request.command
|
61
|
-
when 'initialize'
|
62
|
-
# "The ‘initialize’ request is sent as the first request from the client to the debug adapter
|
63
|
-
# "in order to configure it with client capabilities and to retrieve capabilities from the debug adapter.
|
64
|
-
# "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.
|
65
|
-
# "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.
|
66
|
-
# "The ‘initialize’ request may only be sent once.
|
67
|
-
|
68
|
-
respond! body: ::DAP::Capabilities.new(
|
69
|
-
supportsConfigurationDoneRequest: true)
|
70
|
-
|
71
|
-
@interface.event! 'initialized'
|
72
|
-
return
|
73
|
-
|
74
|
-
when 'disconnect'
|
75
|
-
# "The ‘disconnect’ request is sent from the client to the debug adapter in order to stop debugging.
|
76
|
-
# "It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter.
|
77
|
-
# "If the debuggee has been started with the ‘launch’ request, the ‘disconnect’ request terminates the debuggee.
|
78
|
-
# "If the ‘attach’ request was used to connect to the debuggee, ‘disconnect’ does not terminate the debuggee.
|
79
|
-
# "This behavior can be controlled with the ‘terminateDebuggee’ argument (if supported by the debug adapter).
|
80
|
-
|
81
|
-
respond!
|
82
|
-
return :stop
|
83
|
-
|
84
|
-
when 'attach'
|
85
|
-
# "The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running.
|
86
|
-
|
87
|
-
Byebug.mode = :attached
|
88
|
-
Byebug.start
|
89
|
-
@trace.enable
|
90
|
-
|
91
|
-
respond!
|
92
|
-
return
|
93
|
-
|
94
|
-
when 'launch'
|
95
|
-
# "This launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if ‘noDebug’ is true).
|
96
|
-
|
97
|
-
unless request.arguments.noDebug
|
98
|
-
Byebug.mode = :launched
|
99
|
-
Byebug.start
|
100
|
-
@trace.enable
|
101
|
-
end
|
102
|
-
|
103
|
-
@exit_on_disconnect = true
|
104
|
-
|
105
|
-
respond!
|
106
|
-
return
|
107
|
-
|
108
|
-
when 'configurationDone'
|
109
|
-
# "This optional request indicates that the client has finished initialization of the debug adapter.
|
110
|
-
|
111
|
-
|
112
|
-
@on_configured&.call
|
113
|
-
respond!
|
114
|
-
return
|
115
|
-
|
116
|
-
when 'pause', 'next', 'stepIn', 'stepOut', 'continue'
|
117
|
-
ctx = @interface.find_thread(request.arguments.threadId)
|
118
|
-
ctx.interrupt if request.command == 'pause'
|
119
|
-
|
120
|
-
ctx.__send__(:processor) << request
|
121
|
-
respond!
|
122
|
-
|
123
|
-
when 'evaluate'
|
124
|
-
# "Evaluates the given expression in the context of the top most stack frame.
|
125
|
-
# "The expression has access to any variables and arguments that are in scope.
|
126
|
-
|
127
|
-
respond! body: @interface.evaluate(request.arguments.frameId, request.arguments.expression)
|
128
|
-
|
129
|
-
when 'variables'
|
130
|
-
# "Retrieves all child variables for the given variable reference.
|
131
|
-
# "An optional filter can be used to limit the fetched children to either named or indexed children
|
132
|
-
|
133
|
-
variables = @interface.variables(
|
134
|
-
request.arguments.variablesReference,
|
135
|
-
at: request.arguments.start,
|
136
|
-
count: request.arguments.count,
|
137
|
-
filter: request.arguments.filter)
|
138
|
-
|
139
|
-
respond! body: ::DAP::VariablesResponseBody.new(variables: variables)
|
140
|
-
|
141
|
-
when 'scopes'
|
142
|
-
# "The request returns the variable scopes for a given stackframe ID.
|
143
|
-
|
144
|
-
respond! body: ::DAP::ScopesResponseBody.new(
|
145
|
-
scopes: @interface.scopes(request.arguments.frameId))
|
146
|
-
|
147
|
-
when 'threads'
|
148
|
-
# "The request retrieves a list of all threads.
|
149
|
-
|
150
|
-
respond! body: ::DAP::ThreadsResponseBody.new(threads: @interface.threads)
|
151
|
-
|
152
|
-
when 'stackTrace'
|
153
|
-
# "The request returns a stacktrace from the current execution state.
|
154
|
-
|
155
|
-
frames, stack_size = @interface.frames(
|
156
|
-
request.arguments.threadId,
|
157
|
-
at: request.arguments.startFrame,
|
158
|
-
count: request.arguments.levels)
|
159
|
-
|
160
|
-
respond! body: ::DAP::StackTraceResponseBody.new(
|
161
|
-
stackFrames: frames,
|
162
|
-
totalFrames: stack_size)
|
163
|
-
|
164
|
-
when 'source'
|
165
|
-
# "The request retrieves the source code for a given source reference.
|
166
|
-
|
167
|
-
path = request.arguments.source.path
|
168
|
-
if File.readable?(path)
|
169
|
-
respond! body: ::DAP::SourceResponseBody.new(content: IO.read(path))
|
170
|
-
|
171
|
-
elsif File.exist?(path)
|
172
|
-
respond! success: false, message: "Source file '#{path}' exists but cannot be read"
|
173
|
-
|
174
|
-
else
|
175
|
-
respond! success: false, message: "No source file available for '#{path}'"
|
176
|
-
end
|
177
|
-
|
178
|
-
when 'setBreakpoints'
|
179
|
-
# "Sets multiple breakpoints for a single source and clears all previous breakpoints in that source.
|
180
|
-
# "To clear all breakpoint for a source, specify an empty array.
|
181
|
-
# "When a breakpoint is hit, a ‘stopped’ event (with reason ‘breakpoint’) is generated.
|
182
|
-
|
183
|
-
unless File.exist?(request.arguments.source.path)
|
184
|
-
# file doesn't exist, no breakpoints set
|
185
|
-
respond! body: ::DAP::SetBreakpointsResponseBody.new(breakpoints: [])
|
186
|
-
return
|
187
|
-
end
|
188
|
-
|
189
|
-
path = File.realpath(request.arguments.source.path)
|
190
|
-
::Byebug.breakpoints.each { |bp| ::Byebug::Breakpoint.remove(bp.id) if bp.source == path }
|
191
|
-
|
192
|
-
lines = ::Byebug::Breakpoint.potential_lines(path)
|
193
|
-
verified = []
|
194
|
-
request.arguments.breakpoints.each do |requested|
|
195
|
-
next unless lines.include? requested.line
|
196
|
-
|
197
|
-
bp = ::Byebug::Breakpoint.add(path, requested.line)
|
198
|
-
verified << ::DAP::Breakpoint.new(
|
199
|
-
id: bp.id,
|
200
|
-
verified: true,
|
201
|
-
line: requested.line)
|
202
|
-
end
|
203
|
-
|
204
|
-
respond! body: ::DAP::SetBreakpointsResponseBody.new(breakpoints: verified)
|
205
|
-
|
206
|
-
else
|
207
|
-
respond! success: false, message: 'Invalid command'
|
208
|
-
end
|
209
|
-
|
210
|
-
rescue InvalidRequestArgumentError => e
|
211
|
-
case e.error
|
212
|
-
when :missing_argument
|
213
|
-
respond! success: false, message: "Missing #{e.scope}"
|
214
|
-
|
215
|
-
when :missing_entry
|
216
|
-
respond! success: false, message: "Invalid #{e.scope} #{e.value}"
|
217
|
-
|
218
|
-
when :missing_thread
|
219
|
-
respond! success: false, message: "Cannot locate thread ##{e.value}"
|
220
|
-
|
221
|
-
when :missing_frame
|
222
|
-
respond! success: false, message: "Cannot locate frame ##{e.value}"
|
223
|
-
|
224
|
-
when :invalid_entry
|
225
|
-
respond! success: false, message: "Error resolving #{e.scope}: #{e.value}"
|
226
|
-
|
227
|
-
else
|
228
|
-
respond! success: false, message: "An internal error occured"
|
229
|
-
STDERR.puts "#{e.message} (#{e.class})", *e.backtrace
|
230
|
-
end
|
231
|
-
|
232
|
-
rescue CommandProcessor::TimeoutError => e
|
233
|
-
respond! success: false, message: "Debugger on thread ##{e.context.thnum} is not responding"
|
234
|
-
|
235
|
-
rescue StandardError => e
|
236
|
-
respond! success: false, message: "An internal error occured"
|
237
|
-
STDERR.puts "#{e.message} (#{e.class})", *e.backtrace
|
238
|
-
end
|
239
|
-
|
240
|
-
def respond!(body = {}, success: true, message: 'Success', **values)
|
241
|
-
# TODO make body default to nil?
|
242
|
-
@interface << ::DAP::Response.new(
|
243
|
-
request_seq: @request.seq,
|
244
|
-
command: @request.command,
|
245
|
-
success: success,
|
246
|
-
message: message,
|
247
|
-
body: body,
|
248
|
-
**values)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
data/lib/byebug/dap/interface.rb
DELETED
@@ -1,303 +0,0 @@
|
|
1
|
-
module Byebug
|
2
|
-
module DAP
|
3
|
-
class Interface
|
4
|
-
include SafeHelpers
|
5
|
-
|
6
|
-
@@children = []
|
7
|
-
|
8
|
-
def self.child_spawned(name, pid, socket)
|
9
|
-
child = ChildSpawnedEventBody.new(name: name, pid: pid, socket: socket)
|
10
|
-
@@children << child
|
11
|
-
|
12
|
-
interface = Context.interface
|
13
|
-
interface.event! child if interface.is_a?(Byebug::DAP::Interface)
|
14
|
-
|
15
|
-
return true
|
16
|
-
|
17
|
-
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
18
|
-
return false
|
19
|
-
end
|
20
|
-
|
21
|
-
attr_reader :socket
|
22
|
-
|
23
|
-
def initialize(socket)
|
24
|
-
@socket = socket
|
25
|
-
|
26
|
-
begin
|
27
|
-
@@children.each { |c| event! c }
|
28
|
-
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def stop!
|
33
|
-
Byebug.mode = :off
|
34
|
-
Byebug.stop
|
35
|
-
socket.close
|
36
|
-
end
|
37
|
-
|
38
|
-
def <<(message)
|
39
|
-
STDERR.puts "#{Process.pid} > #{message.to_wire}" if Debug.protocol
|
40
|
-
message.validate!
|
41
|
-
socket.write ::DAP::Encoding.encode(message)
|
42
|
-
end
|
43
|
-
|
44
|
-
def event!(event, **values)
|
45
|
-
if (cls = event.class.name.split('::').last) && cls.end_with?('EventBody')
|
46
|
-
body, event = event, cls[0].downcase + cls[1...-9]
|
47
|
-
|
48
|
-
elsif event.is_a?(String) && !values.empty?
|
49
|
-
body = ::DAP.const_get("#{event[0].upcase}#{event[1..]}EventBody").new(values)
|
50
|
-
end
|
51
|
-
|
52
|
-
self << ::DAP::Event.new(event: event, body: body)
|
53
|
-
end
|
54
|
-
|
55
|
-
def receive
|
56
|
-
m = ::DAP::Encoding.decode(socket)
|
57
|
-
STDERR.puts "#{Process.pid} < #{m.to_wire}" if Debug.protocol
|
58
|
-
m
|
59
|
-
end
|
60
|
-
|
61
|
-
def invalidate_handles!
|
62
|
-
frame_ids.clear!
|
63
|
-
variable_refs.clear!
|
64
|
-
end
|
65
|
-
|
66
|
-
def threads
|
67
|
-
Byebug.contexts
|
68
|
-
.filter { |ctx| !ctx.thread.is_a?(DebugThread) }
|
69
|
-
.map { |ctx| ::DAP::Thread.new(
|
70
|
-
id: ctx.thnum,
|
71
|
-
name: ctx.thread.name || "Thread ##{ctx.thnum}")
|
72
|
-
.validate! }
|
73
|
-
end
|
74
|
-
|
75
|
-
def find_thread(id)
|
76
|
-
raise InvalidRequestArgumentError.new(:missing_argument, scope: 'thread ID') unless id
|
77
|
-
|
78
|
-
ctx = Byebug.contexts.find { |c| c.thnum == id }
|
79
|
-
raise InvalidRequestArgumentError.new(:missing_thread, value: id) unless ctx
|
80
|
-
|
81
|
-
ctx
|
82
|
-
end
|
83
|
-
|
84
|
-
def resolve_frame_id(id)
|
85
|
-
entry = frame_ids[id]
|
86
|
-
raise InvalidRequestArgumentError.new(:missing_entry, value: id, scope: 'frame ID') unless entry
|
87
|
-
|
88
|
-
thnum, frnum = entry
|
89
|
-
ctx = Byebug.contexts.find { |c| c.thnum == thnum }
|
90
|
-
raise InvalidRequestArgumentError.new(:missing_thread, value: thnum) unless ctx
|
91
|
-
raise InvalidRequestArgumentError.new(:missing_frame, value: frnum) unless frnum < ctx.stack_size
|
92
|
-
|
93
|
-
return ::Byebug::Frame.new(ctx, frnum), thnum, frnum
|
94
|
-
end
|
95
|
-
|
96
|
-
def frames(thnum, at:, count:)
|
97
|
-
ctx = find_thread(thnum)
|
98
|
-
|
99
|
-
first = at || 0
|
100
|
-
if !count
|
101
|
-
last = ctx.stack_size
|
102
|
-
else
|
103
|
-
last = first + count
|
104
|
-
if last > ctx.stack_size
|
105
|
-
last = ctx.stack_size
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
frames = (first...last).map do |i|
|
110
|
-
frame = ::Byebug::Frame.new(ctx, i)
|
111
|
-
::DAP::StackFrame.new(
|
112
|
-
id: frame_ids << [ctx.thnum, i],
|
113
|
-
name: frame_name(frame),
|
114
|
-
source: ::DAP::Source.new(
|
115
|
-
name: File.basename(frame.file),
|
116
|
-
path: File.expand_path(frame.file)),
|
117
|
-
line: frame.line,
|
118
|
-
column: 0) # TODO real column
|
119
|
-
.validate!
|
120
|
-
end
|
121
|
-
|
122
|
-
return frames, ctx.stack_size
|
123
|
-
end
|
124
|
-
|
125
|
-
def resolve_variable_reference(varRef)
|
126
|
-
raise InvalidRequestArgumentError.new(:missing_argument, scope: 'variables reference') unless varRef
|
127
|
-
|
128
|
-
entry = variable_refs[varRef]
|
129
|
-
raise InvalidRequestArgumentError.new(:missing_entry, value: ref, scope: 'variables reference') unless entry
|
130
|
-
|
131
|
-
entry
|
132
|
-
end
|
133
|
-
|
134
|
-
def scopes(frameId)
|
135
|
-
raise InvalidRequestArgumentError.new(:missing_argument, scope: 'frame ID') unless frameId
|
136
|
-
|
137
|
-
frame, thnum, frnum = resolve_frame_id(frameId)
|
138
|
-
return unless frame
|
139
|
-
|
140
|
-
scopes = []
|
141
|
-
|
142
|
-
locals = frame_local_names(frame).sort
|
143
|
-
unless locals.empty?
|
144
|
-
scopes << ::DAP::Scope.new(
|
145
|
-
name: 'Locals',
|
146
|
-
presentationHint: 'locals',
|
147
|
-
variablesReference: variable_refs << [thnum, frnum, :locals, locals],
|
148
|
-
namedVariables: locals.size,
|
149
|
-
indexedVariables: 0,
|
150
|
-
expensive: false)
|
151
|
-
.validate!
|
152
|
-
end
|
153
|
-
|
154
|
-
globals = global_names.sort
|
155
|
-
unless globals.empty?
|
156
|
-
scopes << ::DAP::Scope.new(
|
157
|
-
name: 'Globals',
|
158
|
-
presentationHint: 'globals',
|
159
|
-
variablesReference: variable_refs << [thnum, frnum, :globals, globals],
|
160
|
-
namedVariables: globals.size,
|
161
|
-
indexedVariables: 0,
|
162
|
-
expensive: true)
|
163
|
-
.validate!
|
164
|
-
end
|
165
|
-
|
166
|
-
scopes
|
167
|
-
end
|
168
|
-
|
169
|
-
def variables(varRef, at:, count:, filter: nil)
|
170
|
-
thnum, frnum, kind, *entry = resolve_variable_reference(varRef)
|
171
|
-
|
172
|
-
case kind
|
173
|
-
when :locals, :globals
|
174
|
-
ctx = find_thread(thnum)
|
175
|
-
raise InvalidRequestArgumentError.new(:missing_frame, value: frnum) unless frnum < ctx.stack_size
|
176
|
-
|
177
|
-
frame = ::Byebug::Frame.new(ctx, frnum)
|
178
|
-
end
|
179
|
-
|
180
|
-
case kind
|
181
|
-
when :locals
|
182
|
-
named, indexed = entry[0], []
|
183
|
-
get = ->(key) {
|
184
|
-
return frame._self if key == :self
|
185
|
-
values ||= frame.locals
|
186
|
-
values[key]
|
187
|
-
}
|
188
|
-
|
189
|
-
when :globals
|
190
|
-
named, indexed = entry[0], []
|
191
|
-
get = ->(key) { frame._binding.eval(key.to_s) }
|
192
|
-
|
193
|
-
when :variable, :evalate
|
194
|
-
value, named, indexed = entry
|
195
|
-
get = ->(key) { value.instance_eval { binding }.eval(key.to_s) }
|
196
|
-
index = ->(key) { value[key] }
|
197
|
-
|
198
|
-
else
|
199
|
-
raise InvalidRequestArgumentError.new(:invalid_entry, value: kind, scope: 'variable scope')
|
200
|
-
end
|
201
|
-
|
202
|
-
case filter
|
203
|
-
when 'named'
|
204
|
-
indexed = []
|
205
|
-
when 'indexed'
|
206
|
-
named = []
|
207
|
-
end
|
208
|
-
|
209
|
-
vars = named.map { |k| [k, get] } + indexed.map { |k| [k, index] }
|
210
|
-
|
211
|
-
first = at || 0
|
212
|
-
last = count ? first + count : vars.size
|
213
|
-
last = vars.size unless last < vars.size
|
214
|
-
|
215
|
-
vars[first...last].map { |var, get| prepare_value_response(thnum, frnum, :variable, name: var) { get.call(var) } }
|
216
|
-
end
|
217
|
-
|
218
|
-
def evaluate(frameId, expression)
|
219
|
-
return prepare_value_response(0, 0, :evaluate) { TOPLEVEL_BINDING.eval(expression) } unless frameId
|
220
|
-
|
221
|
-
frame, thnum, frnum = resolve_frame_id(frameId)
|
222
|
-
return unless frame
|
223
|
-
|
224
|
-
prepare_value_response(thnum, frnum, :evaluate) { frame._binding.eval(expression) }
|
225
|
-
end
|
226
|
-
|
227
|
-
private
|
228
|
-
|
229
|
-
def frame_name(frame)
|
230
|
-
frame.deco_call
|
231
|
-
rescue
|
232
|
-
frame.deco_block + frame.deco_class + frame.deco_method + "(?)"
|
233
|
-
end
|
234
|
-
|
235
|
-
def describe_thread(context)
|
236
|
-
if context.thread.name
|
237
|
-
"##{context.thnum} (#{context.thread.name})"
|
238
|
-
else
|
239
|
-
"##{context.thnum}"
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
def prepare_value_response(thnum, frnum, kind, name: nil, &block)
|
244
|
-
err = nil
|
245
|
-
if thnum == 0
|
246
|
-
raw = safe(block, :call) { |e| err = e; nil }
|
247
|
-
else
|
248
|
-
processor = find_thread(thnum).__send__(:processor)
|
249
|
-
raw = safe(-> { processor.execute(&block) }, :call) { |e| err = e; nil }
|
250
|
-
end
|
251
|
-
|
252
|
-
if err.nil?
|
253
|
-
value, type, named, indexed = prepare_value(raw) { next "*Error in evaluation*", nil, [], [] }
|
254
|
-
else
|
255
|
-
type, named, indexed = nil, [], []
|
256
|
-
if err.is_a?(CommandProcessor::TimeoutError)
|
257
|
-
value = "*Thread #{describe_thread err.context} unresponsive*"
|
258
|
-
else
|
259
|
-
value = "*Error in evaluation*"
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
case kind
|
264
|
-
when :variable
|
265
|
-
klazz = ::DAP::Variable
|
266
|
-
args = { name: safe(name, :to_s) { safe(name, :inspect) { '???' } }, value: value, type: type }
|
267
|
-
when :evaluate
|
268
|
-
klazz = ::DAP::EvaluateResponseBody
|
269
|
-
args = { result: value, type: type }
|
270
|
-
end
|
271
|
-
|
272
|
-
if named.empty? && indexed.empty?
|
273
|
-
args[:variablesReference] = 0
|
274
|
-
else
|
275
|
-
args[:variablesReference] = variable_refs << [thnum, frnum, kind, raw, named, indexed]
|
276
|
-
args[:namedVariables] = named.size
|
277
|
-
args[:indexedVariables] = indexed.size
|
278
|
-
end
|
279
|
-
|
280
|
-
klazz.new(args).validate!
|
281
|
-
end
|
282
|
-
|
283
|
-
def frame_ids
|
284
|
-
@frame_ids ||= Handles.new
|
285
|
-
end
|
286
|
-
|
287
|
-
def variable_refs
|
288
|
-
@variable_refs ||= Handles.new
|
289
|
-
end
|
290
|
-
|
291
|
-
def frame_local_names(frame)
|
292
|
-
locals = frame.locals
|
293
|
-
locals = locals.keys unless locals == [] # BUG in Byebug?
|
294
|
-
locals << :self if frame._self.to_s != 'main'
|
295
|
-
locals
|
296
|
-
end
|
297
|
-
|
298
|
-
def global_names
|
299
|
-
global_variables - %i[$IGNORECASE $= $KCODE $-K $binding]
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|