ed-precompiled_debug 1.11.0

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.
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ module DEBUGGER__
3
+ class Console
4
+ begin
5
+ raise LoadError if CONFIG[:no_reline]
6
+ require 'reline'
7
+
8
+ require_relative 'color'
9
+
10
+ include Color
11
+
12
+ def parse_input buff, commands
13
+ c, rest = get_command buff
14
+ case
15
+ when commands.keys.include?(c)
16
+ :command
17
+ when !rest && /\A\s*[a-z]*\z/ =~ c
18
+ nil
19
+ else
20
+ :ruby
21
+ end
22
+ end
23
+
24
+ def readline_setup prompt
25
+ load_history_if_not_loaded
26
+ commands = DEBUGGER__.commands
27
+
28
+ prev_completion_proc = Reline.completion_proc
29
+ prev_output_modifier_proc = Reline.output_modifier_proc
30
+ prev_prompt_proc = Reline.prompt_proc
31
+
32
+ # prompt state
33
+ state = nil # :command, :ruby, nil (unknown)
34
+
35
+ Reline.prompt_proc = -> args, *kw do
36
+ case state = parse_input(args.first, commands)
37
+ when nil, :command
38
+ [prompt]
39
+ when :ruby
40
+ [prompt.sub('rdbg'){colorize('ruby', [:RED])}]
41
+ end * args.size
42
+ end
43
+
44
+ Reline.completion_proc = -> given do
45
+ buff = Reline.line_buffer
46
+ Reline.completion_append_character= ' '
47
+
48
+ if /\s/ =~ buff # second parameters
49
+ given = File.expand_path(given + 'a').sub(/a\z/, '')
50
+ files = Dir.glob(given + '*')
51
+ if files.size == 1 && File.directory?(files.first)
52
+ Reline.completion_append_character= '/'
53
+ end
54
+ files
55
+ else
56
+ commands.keys.grep(/\A#{Regexp.escape(given)}/)
57
+ end
58
+ end
59
+
60
+ Reline.output_modifier_proc = -> buff, **kw do
61
+ c, rest = get_command buff
62
+
63
+ case state
64
+ when :command
65
+ cmd = colorize(c, [:CYAN, :UNDERLINE])
66
+
67
+ if commands[c] == c
68
+ rprompt = colorize(" # command", [:DIM])
69
+ else
70
+ rprompt = colorize(" # #{commands[c]} command", [:DIM])
71
+ end
72
+
73
+ rest = rest ? colorize_code(rest) : ''
74
+ cmd + rest + rprompt
75
+ when nil
76
+ buff
77
+ when :ruby
78
+ colorize_code(buff)
79
+ end
80
+ end unless CONFIG[:no_hint]
81
+
82
+ yield
83
+
84
+ ensure
85
+ Reline.completion_proc = prev_completion_proc
86
+ Reline.output_modifier_proc = prev_output_modifier_proc
87
+ Reline.prompt_proc = prev_prompt_proc
88
+ end
89
+
90
+ private def get_command line
91
+ case line.chomp
92
+ when /\A(\s*[a-z]+)(\s.*)?\z$/
93
+ return $1.strip, $2
94
+ else
95
+ line.strip
96
+ end
97
+ end
98
+
99
+ def readline prompt
100
+ readline_setup prompt do
101
+ Reline.readmultiline(prompt, true){ true }
102
+ end
103
+ end
104
+
105
+ def history
106
+ Reline::HISTORY
107
+ end
108
+
109
+ rescue LoadError
110
+ begin
111
+ require 'readline.so'
112
+
113
+ def readline_setup
114
+ load_history_if_not_loaded
115
+ commands = DEBUGGER__.commands
116
+
117
+ Readline.completion_proc = proc{|given|
118
+ buff = Readline.line_buffer
119
+ Readline.completion_append_character= ' '
120
+
121
+ if /\s/ =~ buff # second parameters
122
+ given = File.expand_path(given + 'a').sub(/a\z/, '')
123
+ files = Dir.glob(given + '*')
124
+ if files.size == 1 && File.directory?(files.first)
125
+ Readline.completion_append_character= '/'
126
+ end
127
+ files
128
+ else
129
+ commands.keys.grep(/\A#{given}/)
130
+ end
131
+ }
132
+ end
133
+
134
+ def readline prompt
135
+ readline_setup
136
+ Readline.readline(prompt, true)
137
+ end
138
+
139
+ def history
140
+ Readline::HISTORY
141
+ end
142
+
143
+ rescue LoadError
144
+ def readline prompt
145
+ print prompt
146
+ $stdin.gets
147
+ end
148
+
149
+ def history
150
+ nil
151
+ end
152
+ end
153
+ end
154
+
155
+ def history_file
156
+ case
157
+ when (path = CONFIG[:history_file]) && !path.empty?
158
+ path = File.expand_path(path)
159
+ when (path = File.expand_path("~/.rdbg_history")) && File.exist?(path) # for compatibility
160
+ # path
161
+ else
162
+ state_dir = ENV['XDG_STATE_HOME'] || File.join(Dir.home, '.local', 'state')
163
+ path = File.join(File.expand_path(state_dir), 'rdbg', 'history')
164
+ end
165
+
166
+ FileUtils.mkdir_p(File.dirname(path)) unless File.exist?(path)
167
+ path
168
+ end
169
+
170
+ FH = "# Today's OMIKUJI: "
171
+
172
+ def read_history_file
173
+ if history && File.exist?(path = history_file())
174
+ f = (['', 'DAI-', 'CHU-', 'SHO-'].map{|e| e+'KICHI'}+['KYO']).sample
175
+ # Read history file and scrub invalid characters to prevent encoding errors
176
+ lines = File.readlines(path).map(&:scrub)
177
+ ["#{FH}#{f}".dup] + lines
178
+ else
179
+ []
180
+ end
181
+ end
182
+
183
+ def initialize
184
+ @init_history_lines = nil
185
+ end
186
+
187
+ def load_history_if_not_loaded
188
+ return if @init_history_lines
189
+
190
+ @init_history_lines = load_history
191
+ end
192
+
193
+ def deactivate
194
+ if history && @init_history_lines
195
+ added_records = history.to_a[@init_history_lines .. -1]
196
+ path = history_file()
197
+ max = CONFIG[:save_history]
198
+
199
+ if !added_records.empty? && !path.empty?
200
+ orig_records = read_history_file
201
+ open(history_file(), 'w'){|f|
202
+ (orig_records + added_records).last(max).each{|line|
203
+ # Use scrub to handle encoding issues gracefully
204
+ scrubbed_line = line.scrub.strip
205
+ if !line.start_with?(FH) && !scrubbed_line.empty?
206
+ f.puts scrubbed_line
207
+ end
208
+ }
209
+ }
210
+ end
211
+ end
212
+ end
213
+
214
+ def load_history
215
+ read_history_file.each{|line|
216
+ # Use scrub to handle encoding issues gracefully, then strip
217
+ line.scrub!
218
+ line.strip!
219
+ history << line unless line.empty?
220
+ } if history.empty?
221
+ history.count
222
+ end
223
+ end # class Console
224
+ end
@@ -0,0 +1,336 @@
1
+ module DEBUGGER__
2
+ module DAP_TraceInspector
3
+ class MultiTracer < Tracer
4
+ def initialize ui, evts, trace_params, max_log_size: nil, **kw
5
+ @evts = evts
6
+ @log = []
7
+ @trace_params = trace_params
8
+ if max_log_size
9
+ @max_log_size = max_log_size
10
+ else
11
+ @max_log_size = 50000
12
+ end
13
+ @dropped_trace_cnt = 0
14
+ super(ui, **kw)
15
+ @type = 'multi'
16
+ @name = 'TraceInspector'
17
+ end
18
+
19
+ attr_accessor :dropped_trace_cnt
20
+ attr_reader :log
21
+
22
+ def setup
23
+ @tracer = TracePoint.new(*@evts){|tp|
24
+ next if skip?(tp)
25
+
26
+ case tp.event
27
+ when :call, :c_call, :b_call
28
+ if @trace_params
29
+ params = parameters_info tp
30
+ end
31
+ append(call_trace_log(tp, params: params))
32
+ when :return, :c_return, :b_return
33
+ return_str = DEBUGGER__.safe_inspect(tp.return_value, short: true, max_length: 4096)
34
+ append(call_trace_log(tp, return_str: return_str))
35
+ when :line
36
+ append(line_trace_log(tp))
37
+ end
38
+ }
39
+ end
40
+
41
+ def parameters_info tp
42
+ b = tp.binding
43
+ tp.parameters.map{|_type, name|
44
+ begin
45
+ { name: name, value: DEBUGGER__.safe_inspect(b.local_variable_get(name), short: true, max_length: 4096) }
46
+ rescue NameError, TypeError
47
+ nil
48
+ end
49
+ }.compact
50
+ end
51
+
52
+ def call_identifier_str tp
53
+ if tp.defined_class
54
+ minfo(tp)
55
+ else
56
+ "block"
57
+ end
58
+ end
59
+
60
+ def append log
61
+ if @log.size >= @max_log_size
62
+ @dropped_trace_cnt += 1
63
+ @log.shift
64
+ end
65
+ @log << log
66
+ end
67
+
68
+ def call_trace_log tp, return_str: nil, params: nil
69
+ log = {
70
+ depth: DEBUGGER__.frame_depth,
71
+ name: call_identifier_str(tp),
72
+ threadId: Thread.current.instance_variable_get(:@__thread_client_id),
73
+ location: {
74
+ path: tp.path,
75
+ line: tp.lineno
76
+ }
77
+ }
78
+ log[:returnValue] = return_str if return_str
79
+ log[:parameters] = params if params && params.size > 0
80
+ log
81
+ end
82
+
83
+ def line_trace_log tp
84
+ {
85
+ depth: DEBUGGER__.frame_depth,
86
+ threadId: Thread.current.instance_variable_get(:@__thread_client_id),
87
+ location: {
88
+ path: tp.path,
89
+ line: tp.lineno
90
+ }
91
+ }
92
+ end
93
+
94
+ def skip? tp
95
+ super || !@evts.include?(tp.event)
96
+ end
97
+
98
+ def skip_with_pattern?(tp)
99
+ super && !tp.method_id&.match?(@pattern)
100
+ end
101
+ end
102
+
103
+ class Custom_Recorder < ThreadClient::Recorder
104
+ def initialize max_log_size: nil
105
+ if max_log_size
106
+ @max_log_size = max_log_size
107
+ else
108
+ @max_log_size = 50000
109
+ end
110
+ @dropped_trace_cnt = 0
111
+ super()
112
+ end
113
+
114
+ attr_accessor :dropped_trace_cnt
115
+
116
+ def append frames
117
+ if @log.size >= @max_log_size
118
+ @dropped_trace_cnt += 1
119
+ @log.shift
120
+ end
121
+ @log << frames
122
+ end
123
+ end
124
+
125
+ module Custom_UI_DAP
126
+ def custom_dap_request_rdbgTraceInspector(req)
127
+ @q_msg << req
128
+ end
129
+ end
130
+
131
+ module Custom_Session
132
+ def process_trace_cmd req
133
+ cmd = req.dig('arguments', 'subCommand')
134
+ case cmd
135
+ when 'enable'
136
+ events = req.dig('arguments', 'events')
137
+ evts = []
138
+ trace_params = false
139
+ filter = req.dig('arguments', 'filterRegExp')
140
+ max_log_size = req.dig('arguments', 'maxLogSize')
141
+ events.each{|evt|
142
+ case evt
143
+ when 'traceLine'
144
+ evts << :line
145
+ when 'traceCall'
146
+ evts << :call
147
+ evts << :b_call
148
+ when 'traceReturn'
149
+ evts << :return
150
+ evts << :b_return
151
+ when 'traceParams'
152
+ trace_params = true
153
+ when 'traceClanguageCall'
154
+ evts << :c_call
155
+ when 'traceClanguageReturn'
156
+ evts << :c_return
157
+ else
158
+ raise "unknown trace type #{evt}"
159
+ end
160
+ }
161
+ add_tracer MultiTracer.new @ui, evts, trace_params, max_log_size: max_log_size, pattern: filter
162
+ @ui.respond req, {}
163
+ when 'disable'
164
+ if t = find_multi_trace
165
+ t.disable
166
+ end
167
+ @ui.respond req, {}
168
+ when 'collect'
169
+ logs = []
170
+ if t = find_multi_trace
171
+ logs = t.log
172
+ if t.dropped_trace_cnt > 0
173
+ @ui.puts "Return #{logs.size} traces and #{t.dropped_trace_cnt} traces are dropped"
174
+ else
175
+ @ui.puts "Return #{logs.size} traces"
176
+ end
177
+ t.dropped_trace_cnt = 0
178
+ end
179
+ @ui.respond req, logs: logs
180
+ else
181
+ raise "Unknown trace sub command #{cmd}"
182
+ end
183
+ return :retry
184
+ end
185
+
186
+ def find_multi_trace
187
+ @tracers.values.each{|t|
188
+ if t.type == 'multi'
189
+ return t
190
+ end
191
+ }
192
+ return nil
193
+ end
194
+
195
+ def process_record_cmd req
196
+ cmd = req.dig('arguments', 'subCommand')
197
+ case cmd
198
+ when 'enable'
199
+ @tc << [:dap, :rdbgTraceInspector, req]
200
+ when 'disable'
201
+ @tc << [:dap, :rdbgTraceInspector, req]
202
+ when 'step'
203
+ tid = req.dig('arguments', 'threadId')
204
+ count = req.dig('arguments', 'count')
205
+ if tc = find_waiting_tc(tid)
206
+ @ui.respond req, {}
207
+ tc << [:step, :in, count]
208
+ else
209
+ fail_response req
210
+ end
211
+ when 'stepBack'
212
+ tid = req.dig('arguments', 'threadId')
213
+ count = req.dig('arguments', 'count')
214
+ if tc = find_waiting_tc(tid)
215
+ @ui.respond req, {}
216
+ tc << [:step, :back, count]
217
+ else
218
+ fail_response req
219
+ end
220
+ when 'collect'
221
+ tid = req.dig('arguments', 'threadId')
222
+ if tc = find_waiting_tc(tid)
223
+ tc << [:dap, :rdbgTraceInspector, req]
224
+ else
225
+ fail_response req
226
+ end
227
+ else
228
+ raise "Unknown record sub command #{cmd}"
229
+ end
230
+ end
231
+
232
+ def custom_dap_request_rdbgTraceInspector(req)
233
+ cmd = req.dig('arguments', 'command')
234
+ case cmd
235
+ when 'trace'
236
+ process_trace_cmd req
237
+ when 'record'
238
+ process_record_cmd req
239
+ else
240
+ raise "Unknown command #{cmd}"
241
+ end
242
+ end
243
+
244
+ def custom_dap_request_event_rdbgTraceInspector(req, result)
245
+ cmd = req.dig('arguments', 'command')
246
+ case cmd
247
+ when 'record'
248
+ process_event_record_cmd(req, result)
249
+ else
250
+ raise "Unknown command #{cmd}"
251
+ end
252
+ end
253
+
254
+ def process_event_record_cmd(req, result)
255
+ cmd = req.dig('arguments', 'subCommand')
256
+ case cmd
257
+ when 'enable'
258
+ @ui.respond req, {}
259
+ when 'disable'
260
+ @ui.respond req, {}
261
+ when 'collect'
262
+ cnt = result.delete :dropped_trace_cnt
263
+ if cnt > 0
264
+ @ui.puts "Return #{result[:logs].size} traces and #{cnt} traces are dropped"
265
+ else
266
+ @ui.puts "Return #{result[:logs].size} traces"
267
+ end
268
+ @ui.respond req, result
269
+ else
270
+ raise "Unknown command #{cmd}"
271
+ end
272
+ end
273
+ end
274
+
275
+ module Custom_ThreadClient
276
+ def custom_dap_request_rdbgTraceInspector(req)
277
+ cmd = req.dig('arguments', 'command')
278
+ case cmd
279
+ when 'record'
280
+ process_record_cmd(req)
281
+ else
282
+ raise "Unknown command #{cmd}"
283
+ end
284
+ end
285
+
286
+ def process_record_cmd(req)
287
+ cmd = req.dig('arguments', 'subCommand')
288
+ case cmd
289
+ when 'enable'
290
+ size = req.dig('arguments', 'maxLogSize')
291
+ @recorder = Custom_Recorder.new max_log_size: size
292
+ @recorder.enable
293
+ event! :protocol_result, :rdbgTraceInspector, req
294
+ when 'disable'
295
+ if @recorder&.enabled?
296
+ @recorder.disable
297
+ end
298
+ @recorder = nil
299
+ event! :protocol_result, :rdbgTraceInspector, req
300
+ when 'collect'
301
+ logs = []
302
+ log_index = nil
303
+ trace_cnt = 0
304
+ unless @recorder.nil?
305
+ log_index = @recorder.log_index
306
+ @recorder.log.each{|frames|
307
+ crt_frame = frames[0]
308
+ log = {
309
+ name: crt_frame.name,
310
+ location: {
311
+ path: crt_frame.location.path,
312
+ line: crt_frame.location.lineno,
313
+ },
314
+ depth: crt_frame.frame_depth
315
+ }
316
+ if params = crt_frame.iseq_parameters_info
317
+ log[:parameters] = params
318
+ end
319
+ if return_str = crt_frame.return_str
320
+ log[:returnValue] = return_str
321
+ end
322
+ logs << log
323
+ }
324
+ trace_cnt = @recorder.dropped_trace_cnt
325
+ @recorder.dropped_trace_cnt = 0
326
+ end
327
+ event! :protocol_result, :rdbgTraceInspector, req, logs: logs, stoppedIndex: log_index, dropped_trace_cnt: trace_cnt
328
+ else
329
+ raise "Unknown command #{cmd}"
330
+ end
331
+ end
332
+ end
333
+
334
+ ::DEBUGGER__::SESSION.extend_feature session: Custom_Session, thread_client: Custom_ThreadClient, ui: Custom_UI_DAP
335
+ end
336
+ end