rubinius-debugger 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,147 @@
1
+ class Rubinius::Debugger
2
+ class BreakPoint
3
+
4
+ def self.for_ip(exec, ip, name=:anon)
5
+ line = exec.line_from_ip(ip)
6
+
7
+ BreakPoint.new(name, exec, ip, line)
8
+ end
9
+
10
+ def initialize(descriptor, method, ip, line, condition=nil)
11
+ @descriptor = descriptor
12
+ @method = method
13
+ @ip = ip
14
+ @line = line
15
+ @for_step = false
16
+ @paired_bp = nil
17
+ @temp = false
18
+ @commands = nil
19
+ @condition = condition
20
+
21
+ @set = false
22
+ end
23
+
24
+ attr_reader :method, :ip, :line, :paired_bp, :descriptor, :commands, :condition
25
+
26
+ def location
27
+ "#{@method.active_path}:#{@line} (+#{ip})"
28
+ end
29
+
30
+ def describe
31
+ "#{descriptor} - #{location}"
32
+ end
33
+
34
+ def for_step!(scope)
35
+ @temp = true
36
+ @for_step = scope
37
+ end
38
+
39
+ def set_temp!
40
+ @temp = true
41
+ end
42
+
43
+ def for_step?
44
+ @for_step
45
+ end
46
+
47
+ def paired_with(bp)
48
+ @paired_bp = bp
49
+ end
50
+
51
+ def activate
52
+ @set = true
53
+ @method.set_breakpoint @ip, self
54
+ end
55
+
56
+ def remove!
57
+ return unless @set
58
+
59
+ @set = false
60
+ @method.clear_breakpoint(@ip)
61
+ end
62
+
63
+ def hit!(loc)
64
+ return true unless @temp
65
+
66
+ if @for_step
67
+ return false unless loc.variables == @for_step
68
+ end
69
+
70
+ remove!
71
+
72
+ @paired_bp.remove! if @paired_bp
73
+
74
+ return true
75
+ end
76
+
77
+ def delete!
78
+ remove!
79
+ end
80
+
81
+ def set_commands(commands)
82
+ @commands = commands
83
+ end
84
+
85
+ def has_commands?
86
+ !@commands.nil?
87
+ end
88
+
89
+ def set_condition(condition)
90
+ @condition = condition
91
+ end
92
+
93
+ def has_condition?
94
+ !@condition.nil?
95
+ end
96
+ end
97
+
98
+ class DeferredBreakPoint
99
+ def initialize(debugger, frame, klass, which, name, line=nil, list=nil)
100
+ @debugger = debugger
101
+ @frame = frame
102
+ @klass_name = klass
103
+ @which = which
104
+ @name = name
105
+ @line = line
106
+ @list = list
107
+ end
108
+
109
+ def descriptor
110
+ "#{@klass_name}#{@which}#{@name}"
111
+ end
112
+
113
+ def resolve!
114
+ begin
115
+ klass = @frame.run(@klass_name)
116
+ rescue NameError
117
+ return false
118
+ end
119
+
120
+ begin
121
+ if @which == "#"
122
+ method = klass.instance_method(@name)
123
+ else
124
+ method = klass.method(@name)
125
+ end
126
+ rescue NameError
127
+ return false
128
+ end
129
+
130
+ @debugger.info "Resolved breakpoint for #{@klass_name}#{@which}#{@name}"
131
+
132
+ @debugger.set_breakpoint_method descriptor, method, @line
133
+
134
+ return true
135
+ end
136
+
137
+ def describe
138
+ "#{descriptor} - unknown location (deferred)"
139
+ end
140
+
141
+ def delete!
142
+ if @list
143
+ @list.delete self
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,745 @@
1
+ require 'rubinius/debugger/display'
2
+
3
+ class Rubinius::Debugger
4
+ class CommandDescription
5
+ attr_accessor :klass, :patterns, :help, :ext_help
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+
11
+ def name
12
+ @klass.name
13
+ end
14
+ end
15
+
16
+ class Command
17
+ include Rubinius::Debugger::Display
18
+
19
+ @commands = []
20
+
21
+ def self.commands
22
+ @commands
23
+ end
24
+
25
+ def self.descriptor
26
+ @descriptor ||= CommandDescription.new(self)
27
+ end
28
+
29
+ def self.pattern(*strs)
30
+ Command.commands << self
31
+ descriptor.patterns = strs
32
+ end
33
+
34
+ def self.help(str)
35
+ descriptor.help = str
36
+ end
37
+
38
+ def self.ext_help(str)
39
+ descriptor.ext_help = str
40
+ end
41
+
42
+ def self.match?(cmd)
43
+ descriptor.patterns.include?(cmd)
44
+ end
45
+
46
+ def initialize(debugger)
47
+ @debugger = debugger
48
+ end
49
+
50
+ def run_code(str)
51
+ @debugger.current_frame.run(str)
52
+ end
53
+
54
+ def current_method
55
+ @debugger.current_frame.method
56
+ end
57
+
58
+ def current_frame
59
+ @debugger.current_frame
60
+ end
61
+
62
+ def variables
63
+ @debugger.variables
64
+ end
65
+
66
+ def listen(step=false)
67
+ @debugger.listen(step)
68
+ end
69
+
70
+ # ===== Commands =====
71
+ #
72
+ # These classes are in the order they should appear in the help output.
73
+ # As such, they're grouped by similar action.
74
+
75
+ class Help < Command
76
+ pattern "help", "h"
77
+ help "Show information about debugger commands"
78
+
79
+ def run(args)
80
+
81
+ if args and !args.empty?
82
+ klass = Command.commands.find { |k| k.match?(args.strip) }
83
+ if klass
84
+ des = klass.descriptor
85
+ puts "Help for #{des.name}:"
86
+ puts " Accessed using: #{des.patterns.join(', ')}"
87
+ puts "\n#{des.help}."
88
+ puts "\n#{des.ext_help}" if des.ext_help
89
+ else
90
+ puts "Unknown command: #{args}"
91
+ end
92
+ else
93
+ Command.commands.each do |klass|
94
+ des = klass.descriptor
95
+
96
+ puts "%20s: #{des.help}" % des.patterns.join(', ')
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ class SetBreakPoint < Command
104
+ pattern "b", "break", "brk"
105
+ help "Set a breakpoint at a point in a method"
106
+ ext_help <<-HELP
107
+ The breakpoint must be specified using the following notation:
108
+ Klass[.#]method:line
109
+
110
+ Thus, to set a breakpoint for the instance method pop in
111
+ Array on line 33, use:
112
+ Array#pop:33
113
+
114
+ To breakpoint on class method start of Debugger line 4, use:
115
+ Debugger.start:4
116
+
117
+ Conditional breakpoints can be created in this way:
118
+ <breakpoint declaration> if <condition>
119
+ The breakpoint will be triggered only when the evaluation of the specified condition returns true.
120
+ HELP
121
+
122
+ # provide this method so it can be overriden for other languages wanting to use this debugger
123
+ def match_method(method_identifier)
124
+ /([A-Z]\w*(?:::[A-Z]\w*)*)([.#]|::)([a-zA-Z0-9_\[\]]+[!?=]?)(?:[:](\d+))?(\s+if\s+.*)?/.match(method_identifier)
125
+ end
126
+
127
+ def run(args, temp=false)
128
+ m = match_method(args)
129
+ unless m
130
+ error "Unrecognized position: '#{args}'"
131
+ return
132
+ end
133
+
134
+ klass_name = m[1]
135
+ which = m[2]
136
+ name = m[3]
137
+ line = m[4] ? m[4].to_i : nil
138
+ condition = m[5] ? m[5].sub(/\A\s+if\s+/, '') : nil
139
+
140
+ begin
141
+ klass = run_code(klass_name)
142
+ rescue NameError
143
+ error "Unable to find class/module: #{m[1]}"
144
+ ask_deferred klass_name, which, name, line
145
+ return
146
+ end
147
+
148
+ begin
149
+ if which == "#"
150
+ method = klass.instance_method(name)
151
+ else
152
+ method = klass.method(name)
153
+ end
154
+ rescue NameError
155
+ error "Unable to find method '#{name}' in #{klass}"
156
+ ask_deferred klass_name, which, name, line
157
+ return
158
+ end
159
+
160
+ bp = @debugger.set_breakpoint_method args.strip, method, line, condition
161
+
162
+ bp.set_temp! if temp
163
+
164
+ return bp
165
+ end
166
+
167
+ def ask_deferred(klass_name, which, name, line)
168
+ answer = ask "Would you like to defer this breakpoint to later? [y/n] "
169
+
170
+ if answer.strip.downcase[0] == ?y
171
+ @debugger.add_deferred_breakpoint(klass_name, which, name, line)
172
+ info "Deferred breakpoint created."
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ class SetTempBreakPoint < SetBreakPoint
179
+ pattern "tb", "tbreak", "tbrk"
180
+ help "Set a temporary breakpoint"
181
+ ext_help "Same as break, but the breakpoint is deleted when it is hit"
182
+
183
+ def run(args)
184
+ super args, true
185
+ end
186
+ end
187
+
188
+ class DeleteBreakpoint < Command
189
+ pattern "d", "del", "delete"
190
+ help "Delete a breakpoint"
191
+ ext_help "Specify the breakpoint by number, use 'info break' to see the numbers"
192
+
193
+ def run(args)
194
+ if !args or args.empty?
195
+ error "Please specify which breakpoint by number"
196
+ return
197
+ end
198
+
199
+ begin
200
+ i = Integer(args.strip)
201
+ rescue ArgumentError
202
+ error "'#{args}' is not a number"
203
+ return
204
+ end
205
+
206
+ @debugger.delete_breakpoint(i)
207
+ end
208
+ end
209
+
210
+ class Next < Command
211
+ pattern "n", "next"
212
+ help "Move to the next line or conditional branch"
213
+ ext_help <<-HELP
214
+ Attempt to continue execution and stop at the next line. If there is
215
+ a conditional branch between the current position and the next line,
216
+ execution is stopped within the conditional branch instead.
217
+
218
+ The optional argument is a number which specifies how many lines to
219
+ attempt to skip past before stopping execution.
220
+
221
+ If the current line is the last in a method, execution is stopped
222
+ at the current position of the caller.
223
+ HELP
224
+
225
+ def run(args)
226
+ if !args or args.empty?
227
+ step = 1
228
+ else
229
+ step = args.to_i
230
+ end
231
+
232
+ if step <= 0
233
+ error "Invalid step count - #{step}"
234
+ return
235
+ end
236
+
237
+ step_over_by(step)
238
+ @debugger.listen
239
+ end
240
+
241
+ def step_over_by(step)
242
+ f = current_frame
243
+
244
+ ip = -1
245
+
246
+ exec = f.method
247
+ possible_line = f.line + step
248
+ fin_ip = exec.first_ip_on_line possible_line, f.ip
249
+
250
+ if !fin_ip
251
+ return step_to_parent
252
+ end
253
+
254
+ set_breakpoints_between(exec, f.ip, fin_ip)
255
+ end
256
+
257
+ def step_to_parent
258
+ f = @debugger.frame(current_frame.number + 1)
259
+ unless f
260
+ info "Unable to find frame to step to next"
261
+ return
262
+ end
263
+
264
+ exec = f.method
265
+ ip = f.ip
266
+
267
+ bp = BreakPoint.for_ip(exec, ip)
268
+ bp.for_step!(f.variables)
269
+ bp.activate
270
+
271
+ return bp
272
+ end
273
+
274
+ def set_breakpoints_between(exec, start_ip, fin_ip)
275
+ ips = goto_between(exec, start_ip, fin_ip)
276
+ if ips.kind_of? Fixnum
277
+ ip = ips
278
+ else
279
+ one, two = ips
280
+ bp1 = BreakPoint.for_ip(exec, one)
281
+ bp2 = BreakPoint.for_ip(exec, two)
282
+
283
+ bp1.paired_with(bp2)
284
+ bp2.paired_with(bp1)
285
+
286
+ bp1.for_step!(current_frame.variables)
287
+ bp2.for_step!(current_frame.variables)
288
+
289
+ bp1.activate
290
+ bp2.activate
291
+
292
+ return bp1
293
+ end
294
+
295
+ if ip == -1
296
+ error "No place to step to"
297
+ return nil
298
+ end
299
+
300
+ bp = BreakPoint.for_ip(exec, ip)
301
+ bp.for_step!(current_frame.variables)
302
+ bp.activate
303
+
304
+ return bp
305
+ end
306
+
307
+ def next_interesting(exec, ip)
308
+ pop = Rubinius::InstructionSet.opcodes_map[:pop]
309
+
310
+ if exec.iseq[ip] == pop
311
+ return ip + 1
312
+ end
313
+
314
+ return ip
315
+ end
316
+
317
+ def goto_between(exec, start, fin)
318
+ goto = Rubinius::InstructionSet.opcodes_map[:goto]
319
+ git = Rubinius::InstructionSet.opcodes_map[:goto_if_true]
320
+ gif = Rubinius::InstructionSet.opcodes_map[:goto_if_false]
321
+
322
+ iseq = exec.iseq
323
+
324
+ i = start
325
+ while i < fin
326
+ op = iseq[i]
327
+ case op
328
+ when goto
329
+ return next_interesting(exec, iseq[i + 1]) # goto target
330
+ when git, gif
331
+ return [next_interesting(exec, iseq[i + 1]),
332
+ next_interesting(exec, i + 2)] # target and next ip
333
+ else
334
+ op = Rubinius::InstructionSet[op]
335
+ i += (op.arg_count + 1)
336
+ end
337
+ end
338
+
339
+ return next_interesting(exec, fin)
340
+ end
341
+
342
+ end
343
+
344
+ class StepInto < Next
345
+ pattern "s", "step"
346
+ help "Step into next method call or to next line"
347
+ ext_help <<-HELP
348
+ Behaves like next, but if there is a method call on the current line,
349
+ execution is stopped in the called method.
350
+ HELP
351
+
352
+ def run(args)
353
+ max = step_over_by(1)
354
+
355
+ listen(true)
356
+
357
+ # We remove the max position breakpoint no matter what
358
+ max.remove! if max
359
+
360
+ end
361
+ end
362
+
363
+ class NextInstruction < Next
364
+ pattern "ni", "nexti"
365
+ help "Move to the next bytecode instruction"
366
+ ext_help <<-HELP
367
+ Continue but stop execution at the next bytecode instruction.
368
+
369
+ Does not step into send instructions.
370
+ HELP
371
+
372
+ def run(args)
373
+ if args and !args.empty?
374
+ step = args.to_i
375
+ else
376
+ step = 1
377
+ end
378
+
379
+ exec = current_method
380
+ insn = Rubinius::InstructionSet[exec.iseq[current_frame.ip]]
381
+
382
+ next_ip = current_frame.ip + insn.width
383
+
384
+ if next_ip >= exec.iseq.size
385
+ step_to_parent
386
+ elsif is_a_goto(exec, current_frame.ip)
387
+ set_breakpoints_between(exec, current_frame.ip, next_ip)
388
+ else
389
+ line = exec.line_from_ip(next_ip)
390
+
391
+ bp = BreakPoint.for_ip(exec, next_ip)
392
+ bp.for_step!(current_frame.variables)
393
+ bp.activate
394
+ end
395
+
396
+ listen
397
+ end
398
+
399
+ def is_a_goto(exec, ip)
400
+ goto = Rubinius::InstructionSet.opcodes_map[:goto]
401
+ git = Rubinius::InstructionSet.opcodes_map[:goto_if_true]
402
+ gif = Rubinius::InstructionSet.opcodes_map[:goto_if_false]
403
+
404
+ i = exec.iseq[ip]
405
+
406
+ case i
407
+ when goto, git, gif
408
+ return true
409
+ end
410
+
411
+ return false
412
+ end
413
+ end
414
+
415
+ class SetFrame < Command
416
+ pattern "f", "frame"
417
+ help "Make a specific frame in the call stack the current frame"
418
+ ext_help <<-HELP
419
+ The argument must be a number corrisponding to the frame numbers reported by
420
+ 'bt'.
421
+
422
+ The frame specified is made the current frame.
423
+ HELP
424
+
425
+ def run(args)
426
+ unless m = /(\d+)/.match(args)
427
+ error "Invalid frame number: #{args}"
428
+ return
429
+ end
430
+
431
+ num = m[1].to_i
432
+
433
+ if num >= @debugger.locations.size
434
+ error "Frame #{num} too big"
435
+ return
436
+ end
437
+
438
+ @debugger.set_frame(num)
439
+
440
+ info current_frame.describe
441
+ @debugger.show_code
442
+ end
443
+ end
444
+
445
+ class Continue < Command
446
+ pattern "c", "cont", "continue"
447
+ help "Continue running the target thread"
448
+ ext_help <<-HELP
449
+ Continue execution until another breakpoint is hit.
450
+ HELP
451
+
452
+ def run(args)
453
+ listen
454
+ end
455
+ end
456
+
457
+ class Backtrace < Command
458
+ pattern "bt", "backtrace"
459
+ help "Show the current call stack"
460
+ ext_help <<-HELP
461
+ Show the call stack as a simple list.
462
+
463
+ Passing "-v" will also show the values of all locals variables
464
+ in each frame.
465
+ HELP
466
+
467
+ def run(args)
468
+ verbose = (args =~ /-v/)
469
+
470
+ if m = /(\d+)/.match(args)
471
+ count = m[1].to_i
472
+ else
473
+ count = nil
474
+ end
475
+
476
+ info "Backtrace:"
477
+
478
+ @debugger.each_frame(current_frame) do |frame|
479
+ return if count and frame.number >= count
480
+
481
+ info "%4d %s" % [frame.number, frame.describe]
482
+
483
+ if verbose
484
+ frame.local_variables.each do |local|
485
+ info " #{local} = #{frame.run(local.to_s).inspect}"
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ class EvalCode < Command
493
+ pattern "p", "eval"
494
+ help "Run code in the current context"
495
+ ext_help <<-HELP
496
+ Run code in the context of the current frame.
497
+
498
+ The value of the expression is stored into a global variable so it
499
+ may be used again easily. The name of the global variable is printed
500
+ next to the inspect output of the value.
501
+ HELP
502
+
503
+ def run(args)
504
+ @debugger.eval_code(args)
505
+ end
506
+ end
507
+
508
+ class Disassemble < Command
509
+ pattern "dis", "disassemble"
510
+ help "Show the bytecode for the current line or method"
511
+ ext_help <<-HELP
512
+ Disassemble bytecode for the current method. By default, the bytecode
513
+ for the current line is disassembled only.
514
+
515
+ If the argument is 'all', the entire method is shown as bytecode.
516
+ HELP
517
+
518
+ def run(args)
519
+ if args and args.strip == "all"
520
+ section "Bytecode for #{current_frame.method.name}"
521
+ puts current_method.decode
522
+ else
523
+ @debugger.show_bytecode
524
+ end
525
+ end
526
+ end
527
+
528
+ class ShowInfo < Command
529
+ pattern "i", "info"
530
+ help "Show information about things"
531
+ ext_help <<-HELP
532
+ Subcommands are:
533
+ break, breakpoints, bp: List all breakpoints
534
+ HELP
535
+
536
+ def run(args)
537
+ if args
538
+ case args.strip
539
+ when "break", "breakpoints", "bp"
540
+ section "Breakpoints"
541
+ if @debugger.breakpoints.empty?
542
+ info "No breakpoints set"
543
+ end
544
+
545
+ @debugger.breakpoints.each_with_index do |bp, i|
546
+ if bp
547
+ info "%3d: %s" % [i+1, bp.describe]
548
+ if bp.has_commands?
549
+ info " #{bp.commands}"
550
+ end
551
+ if bp.has_condition?
552
+ info " stop only if #{bp.condition}"
553
+ end
554
+ end
555
+ end
556
+ else
557
+ error "Unknown info: '#{args}'"
558
+ end
559
+ else
560
+ error "No info subcommand"
561
+ end
562
+ end
563
+ end
564
+
565
+ class SetVariable < Command
566
+ pattern "set"
567
+ help "Set a debugger config variable"
568
+ ext_help <<-HELP
569
+ Set a debugger configuration variable. Use 'show' to see all variables.
570
+ HELP
571
+
572
+ def run(args)
573
+ var, val = args.split(/\s+/, 2)
574
+
575
+ if val
576
+ case val.strip
577
+ when "true", "on", "yes", ""
578
+ val = true
579
+ when "false", "off", "no"
580
+ val = false
581
+ when "nil"
582
+ val = nil
583
+ when /\d+/
584
+ val = val.to_i
585
+ end
586
+ else
587
+ val = true
588
+ end
589
+
590
+ info "Set '#{var}' = #{val.inspect}"
591
+
592
+ @debugger.variables[var.to_sym] = val
593
+ end
594
+ end
595
+
596
+ class ShowVariable < Command
597
+ pattern "show"
598
+ help "Display the value of a variable or variables"
599
+ ext_help <<-HELP
600
+ Show debugger variables and user created variables. By default,
601
+ shows all variables.
602
+
603
+ The optional argument is which variable specificly to show the value of.
604
+ HELP
605
+
606
+ def run(args)
607
+ if !args or args.strip.empty?
608
+ variables.each do |name, val|
609
+ info "var '#{name}' = #{val.inspect}"
610
+ end
611
+
612
+ if @debugger.user_variables > 0
613
+ section "User variables"
614
+ (0...@debugger.user_variables).each do |i|
615
+ str = "$d#{i}"
616
+ val = Rubinius::Globals[str.to_sym]
617
+ info "var #{str} = #{val.inspect}"
618
+ end
619
+ end
620
+ else
621
+ var = args.strip.to_sym
622
+ if variables.key?(var)
623
+ info "var '#{var}' = #{variables[var].inspect}"
624
+ else
625
+ error "No variable set named '#{var}'"
626
+ end
627
+ end
628
+
629
+ end
630
+ end
631
+
632
+ class Quit < Command
633
+ pattern "quit", "q", "exit", "ex"
634
+ help "Quit the debugger"
635
+ ext_help <<-HELP
636
+ Quits your current session and shuts down the complete process
637
+ HELP
638
+
639
+ def run(args)
640
+ Process.exit!(1)
641
+ end
642
+ end
643
+
644
+ class CommandsList < Command
645
+ pattern "commands", "command"
646
+ help "execute code every time breakpoint is reached"
647
+ ext_help <<-HELP
648
+ Set commands to be executed when a breakpoint is hit.
649
+ Give breakpoint number as argument after "commands".
650
+ With no argument, the targeted breakpoint is the last one set.
651
+ The commands themselves follow starting on the next line.
652
+ Type a line containing "end" to indicate the end of them.
653
+ Give "silent" as the first line to make the breakpoint silent;
654
+ then no output is printed when it is hit, except what the commands print.
655
+ HELP
656
+
657
+ def run(args)
658
+ bp = @debugger.breakpoints[args[:bp_id] - 1]
659
+ bp.set_commands(args[:code])
660
+ end
661
+ end
662
+
663
+ class Condition < Command
664
+ pattern "condition", "cond"
665
+ help "New condition expression on breakpoint N"
666
+ ext_help <<-HELP
667
+ Specify breakpoint number N to break only if COND is true.
668
+ Usage is `condition N COND', where N is an integer and COND is an
669
+ expression to be evaluated whenever breakpoint N is reached.
670
+ HELP
671
+
672
+ def run(args)
673
+ bp_id, condition = args.split(/\s+/, 2)
674
+ bp_id = bp_id.to_i
675
+
676
+ if @debugger.breakpoints.empty?
677
+ error "No breakpoint set"
678
+ return
679
+ elsif bp_id > @debugger.breakpoints.size || bp_id < 1
680
+ error "Invalid breakpoint number."
681
+ return
682
+ end
683
+
684
+ bp = @debugger.breakpoints[bp_id - 1]
685
+ bp.set_condition(condition)
686
+ end
687
+ end
688
+
689
+ class ListCode < Command
690
+ pattern "l", "list"
691
+ help "List code"
692
+ ext_help <<-HELP
693
+ List specified function or line.
694
+ With no argument, lists ten more lines after or around previous listing.
695
+ "list -" lists the ten lines before a previous ten-line listing.
696
+ One argument specifies a line, and ten lines are listed around that line.
697
+ Two arguments with comma between specify starting and ending lines to list.
698
+ Lines can be specified in these ways:
699
+ LINENUM, to list around that line in current file,
700
+ FILE:LINENUM, to list around that line in that file,
701
+ HELP
702
+
703
+ def run(args)
704
+ path = nil
705
+ line = nil
706
+ lines_around = 10
707
+
708
+ if args =~ /^[\w#{File::Separator}]+(\.rb)?:\d+$/
709
+ path, line = args.split(':')
710
+ line = line.to_i
711
+ elsif args.nil?
712
+ line = if @debugger.variables[:list_command_history][:center_line]
713
+ @debugger.variables[:list_command_history][:center_line] + 1 + lines_around
714
+ else
715
+ @debugger.current_frame.line.to_i
716
+ end
717
+ path = @debugger.variables[:list_command_history][:path] || @debugger.current_frame.method.active_path
718
+ elsif args == "-"
719
+ if @debugger.variables[:list_command_history][:center_line].nil? || @debugger.variables[:list_command_history][:path].nil?
720
+ return
721
+ else
722
+ line = @debugger.variables[:list_command_history][:center_line] - lines_around
723
+ path = @debugger.variables[:list_command_history][:path]
724
+ end
725
+ elsif args =~ /^\d+$/
726
+ line = args.to_i
727
+ path = @debugger.current_frame.method.active_path
728
+ elsif match = /^(\d+),(\d+)$/.match(args)
729
+ start_line = match[1].to_i
730
+ end_line = match[2].to_i
731
+ path = @debugger.current_frame.method.active_path
732
+
733
+ @debugger.list_code_range(path, start_line, end_line, end_line)
734
+ return
735
+ else
736
+ error 'Invalid args for list'
737
+ return
738
+ end
739
+
740
+ @debugger.list_code_around_line(path, line, lines_around)
741
+ end
742
+ end
743
+ end
744
+
745
+ end