pry-moves 0.1.1 → 0.1.2
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/README.md +43 -14
- data/dynamic_debug_experiments.rb +42 -0
- data/lib/pry-moves/backtrace.rb +110 -0
- data/lib/pry-moves/commands.rb +14 -4
- data/lib/pry-moves/helpers.rb +28 -0
- data/lib/pry-moves/pry_ext.rb +45 -7
- data/lib/pry-moves/pry_wrapper.rb +69 -0
- data/lib/pry-moves/tracer.rb +99 -88
- data/lib/pry-moves/version.rb +1 -1
- data/lib/pry-moves/watch.rb +57 -0
- data/lib/pry-moves.rb +27 -0
- data/lib/pry-stack_explorer/commands.rb +276 -0
- data/lib/pry-stack_explorer/frame_manager.rb +88 -0
- data/lib/pry-stack_explorer/pry-stack_explorer.rb +139 -0
- data/lib/pry-stack_explorer/when_started_hook.rb +111 -0
- data/playground/demo.rb +37 -6
- data/playground/sand.rb +5 -5
- data/playground/threads.rb +26 -0
- data/pry-moves.gemspec +1 -1
- metadata +18 -8
data/lib/pry-moves/tracer.rb
CHANGED
@@ -2,82 +2,71 @@ require 'pry' unless defined? Pry
|
|
2
2
|
|
3
3
|
module PryMoves
|
4
4
|
class Tracer
|
5
|
-
def initialize(pry_start_options = {}, &block)
|
6
|
-
@pry_start_options = pry_start_options # Options to use for Pry.start
|
7
|
-
end
|
8
5
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
stop_tracing unless RUBY_VERSION == '1.9.2'
|
6
|
+
def initialize(command, pry_start_options)
|
7
|
+
@command = command
|
8
|
+
@pry_start_options = pry_start_options
|
9
|
+
end
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
11
|
+
def trace
|
12
|
+
@action = @command[:action]
|
13
|
+
binding_ = @command[:binding]
|
14
|
+
set_traced_method binding_
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
PryMoves.current_remote_server.teardown
|
16
|
+
case @action
|
17
|
+
when :step
|
18
|
+
@step_info_funcs = nil
|
19
|
+
if (func = @command[:param])
|
20
|
+
@step_info_funcs = [func]
|
21
|
+
@step_info_funcs << 'initialize' if func == 'new'
|
28
22
|
end
|
23
|
+
when :finish
|
24
|
+
@method_to_finish = @method
|
25
|
+
@block_to_finish =
|
26
|
+
(binding_.frame_type == :block) &&
|
27
|
+
frame_digest(binding_)
|
29
28
|
end
|
30
29
|
|
31
|
-
|
30
|
+
start_tracing
|
32
31
|
end
|
33
32
|
|
34
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
def start_tracing
|
36
|
+
#puts "##trace_obj #{trace_obj}"
|
35
37
|
Pry.config.disable_breakpoints = true
|
36
|
-
|
37
|
-
case @action
|
38
|
-
when :finish
|
39
|
-
@method_to_finish = @method
|
40
|
-
end
|
41
|
-
set_trace_func method(:tracer).to_proc
|
38
|
+
trace_obj.set_trace_func method(:tracing_func).to_proc
|
42
39
|
end
|
43
40
|
|
44
41
|
def stop_tracing
|
45
42
|
Pry.config.disable_breakpoints = false
|
46
|
-
set_trace_func nil
|
43
|
+
trace_obj.set_trace_func nil
|
47
44
|
end
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@step_info_funcs = [func]
|
58
|
-
@step_info_funcs << :initialize if func == :new
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
[:step, :next, :finish].include? @action
|
46
|
+
# You can't call set_trace_func or Thread.current.set_trace_func recursively
|
47
|
+
# even in different threads 😪
|
48
|
+
# But! 💡
|
49
|
+
# The hack is - you can call Thread.current.set_trace_func
|
50
|
+
# from inside of set_trace_func! 🤗
|
51
|
+
def trace_obj
|
52
|
+
Thread.current[:pry_moves_debug] ?
|
53
|
+
Thread.current : Kernel
|
63
54
|
end
|
64
55
|
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
56
|
def set_traced_method(binding)
|
69
57
|
@recursion_level = 0
|
70
58
|
|
71
59
|
method = binding.eval 'method(__method__) if __method__'
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
60
|
+
if method
|
61
|
+
source = method.source_location
|
62
|
+
set_method({
|
63
|
+
file: source[0],
|
64
|
+
start: source[1],
|
65
|
+
end: (source[1] + method.source.count("\n") - 1)
|
66
|
+
})
|
67
|
+
else
|
68
|
+
set_method({file: binding.eval('__FILE__')})
|
69
|
+
end
|
81
70
|
end
|
82
71
|
|
83
72
|
def set_method(method)
|
@@ -85,26 +74,66 @@ class Tracer
|
|
85
74
|
@method = method
|
86
75
|
end
|
87
76
|
|
88
|
-
def
|
89
|
-
#
|
77
|
+
def frame_digest(binding_)
|
78
|
+
#puts "frame_digest for: #{binding_.eval '__callee__'}"
|
79
|
+
Digest::MD5.hexdigest binding_.instance_variable_get('@iseq').disasm
|
80
|
+
end
|
81
|
+
|
82
|
+
def tracing_func(event, file, line, id, binding_, klass)
|
83
|
+
#printf "#{trace_obj}: %8s %s:%-2d %10s %8s rec:#{@recursion_level}\n", event, file, line, id, klass
|
84
|
+
|
90
85
|
# Ignore traces inside pry-moves code
|
91
86
|
return if file && TRACE_IGNORE_FILES.include?(File.expand_path(file))
|
92
87
|
|
88
|
+
catch (:skip) do
|
89
|
+
if send "trace_#{@action}", event, file, line, binding_
|
90
|
+
stop_tracing
|
91
|
+
Pry.start(binding_, @pry_start_options)
|
92
|
+
|
93
|
+
# for cases when currently traced method called more times recursively
|
94
|
+
elsif %w(call return).include?(event) and within_current_method?(file, line)
|
95
|
+
@recursion_level += event == 'call' ? 1 : -1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def trace_step(event, file, line, binding_)
|
101
|
+
return unless event == 'line'
|
102
|
+
if @step_info_funcs
|
103
|
+
method = binding_.eval('__callee__').to_s
|
104
|
+
@step_info_funcs.any? {|pattern| method.include? pattern}
|
105
|
+
else
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def trace_next(event, file, line, binding_)
|
93
111
|
traced_method_exit = (@recursion_level < 0 and %w(line call).include? event)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
when 'line'
|
99
|
-
#debug_info file, line, id
|
100
|
-
if break_here?(file, line, binding_, traced_method_exit)
|
101
|
-
Pry.start(binding_, @pry_start_options)
|
102
|
-
end
|
103
|
-
when 'call', 'return'
|
104
|
-
if within_current_method?(file, line) and !traced_method_exit
|
105
|
-
recursion_step event
|
106
|
-
end
|
112
|
+
if traced_method_exit
|
113
|
+
# Set new traced method, because we left previous one
|
114
|
+
set_traced_method binding_
|
115
|
+
throw :skip if event == 'call'
|
107
116
|
end
|
117
|
+
|
118
|
+
event == 'line' and
|
119
|
+
@recursion_level == 0 and
|
120
|
+
within_current_method?(file, line)
|
121
|
+
end
|
122
|
+
|
123
|
+
def trace_finish(event, file, line, binding_)
|
124
|
+
return unless event == 'line'
|
125
|
+
return true if @recursion_level < 0 or @method_to_finish != @method
|
126
|
+
|
127
|
+
# for finishing blocks inside current method
|
128
|
+
if @block_to_finish
|
129
|
+
within_current_method?(file, line) and
|
130
|
+
@block_to_finish != frame_digest(binding_.of_caller(3))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def trace_debug(event, file, line, binding_)
|
135
|
+
event == 'line' and file == 'sand.rb' and
|
136
|
+
not [47, 48, 49, 50].include?(line)
|
108
137
|
end
|
109
138
|
|
110
139
|
def debug_info(file, line, id)
|
@@ -112,19 +141,6 @@ class Tracer
|
|
112
141
|
puts "#{id} #{@method[:start]} > #{line} > #{@method[:end]}"
|
113
142
|
end
|
114
143
|
|
115
|
-
def break_here?(file, line, binding_, traced_method_exit)
|
116
|
-
case @action
|
117
|
-
when :step
|
118
|
-
@step_info_funcs ?
|
119
|
-
@step_info_funcs.include?(binding_.eval('__callee__'))
|
120
|
-
: true
|
121
|
-
when :finish
|
122
|
-
@method_to_finish = @method if @method_to_finish != @method
|
123
|
-
when :next
|
124
|
-
@recursion_level == 0 and within_current_method?(file, line)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
144
|
def within_current_method?(file, line)
|
129
145
|
@method[:file] == file and (
|
130
146
|
@method[:start].nil? or
|
@@ -132,10 +148,5 @@ class Tracer
|
|
132
148
|
)
|
133
149
|
end
|
134
150
|
|
135
|
-
def recursion_step(event)
|
136
|
-
#puts "recursion_step #{event} #{'call' ? 1 : -1}"
|
137
|
-
@recursion_level += event == 'call' ? 1 : -1
|
138
|
-
end
|
139
|
-
|
140
151
|
end
|
141
152
|
end
|
data/lib/pry-moves/version.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
class PryMoves::Watch
|
4
|
+
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :list
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@list = Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_cmd(cmd, binding_)
|
14
|
+
case cmd
|
15
|
+
when nil, ''
|
16
|
+
if @list.count > 0
|
17
|
+
print binding_
|
18
|
+
else
|
19
|
+
puts "Watch list is empty"
|
20
|
+
end
|
21
|
+
when '-clear', '-c'
|
22
|
+
@list.clear
|
23
|
+
else
|
24
|
+
add cmd, binding_
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(cmd, binding_)
|
29
|
+
@list << cmd
|
30
|
+
puts eval_cmd(cmd, binding_)
|
31
|
+
end
|
32
|
+
|
33
|
+
def print(binding_)
|
34
|
+
puts output(binding_) if @list.count > 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def output(binding_)
|
38
|
+
@list.map do |cmd|
|
39
|
+
eval_cmd(cmd, binding_)
|
40
|
+
end.join "; "
|
41
|
+
end
|
42
|
+
|
43
|
+
def eval_cmd(cmd, binding_)
|
44
|
+
"\033[1m#{cmd}\033[0m: #{format binding_.eval(cmd)}"
|
45
|
+
rescue NameError
|
46
|
+
"\033[1m#{cmd}\033[0m: <undefined>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def format(text)
|
50
|
+
Pry::ColorPrinter.pp(text, "").strip
|
51
|
+
end
|
52
|
+
|
53
|
+
def empty?
|
54
|
+
@list.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/pry-moves.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'pry-moves/version'
|
2
2
|
require 'pry-moves/pry_ext'
|
3
3
|
require 'pry-moves/commands'
|
4
|
+
require 'pry-moves/pry_wrapper'
|
4
5
|
require 'pry-moves/tracer'
|
5
6
|
require 'pry-moves/backtrace'
|
7
|
+
require 'pry-moves/watch'
|
8
|
+
require 'pry-moves/helpers'
|
9
|
+
|
10
|
+
require 'pry-stack_explorer/pry-stack_explorer'
|
6
11
|
|
7
12
|
# Optionally load pry-remote monkey patches
|
8
13
|
require 'pry-moves/pry_remote_ext' if defined? PryRemote
|
@@ -12,6 +17,8 @@ module PryMoves
|
|
12
17
|
|
13
18
|
extend self
|
14
19
|
|
20
|
+
attr_accessor :is_open
|
21
|
+
|
15
22
|
# Checks that a binding is in a local file context. Extracted from
|
16
23
|
# https://github.com/pry/pry/blob/master/lib/pry/default_commands/context.rb
|
17
24
|
def check_file_context(target)
|
@@ -19,6 +26,26 @@ module PryMoves
|
|
19
26
|
file == Pry.eval_path || (file !~ /(\(.*\))|<.*>/ && file != '' && file != '-e')
|
20
27
|
end
|
21
28
|
|
29
|
+
def semaphore
|
30
|
+
@semaphore ||= Mutex.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def lock
|
34
|
+
semaphore.lock unless semaphore.locked?
|
35
|
+
end
|
36
|
+
|
37
|
+
def unlock
|
38
|
+
semaphore.unlock unless Thread.current[:pry_moves_debug]
|
39
|
+
end
|
40
|
+
|
41
|
+
def open?
|
42
|
+
@is_open
|
43
|
+
end
|
44
|
+
|
45
|
+
def synchronize_threads
|
46
|
+
semaphore.synchronize {} unless Thread.current[:pry_moves_debug]
|
47
|
+
end
|
48
|
+
|
22
49
|
# Reference to currently running pry-remote server. Used by the tracer.
|
23
50
|
attr_accessor :current_remote_server
|
24
51
|
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
module PryStackExplorer
|
2
|
+
module FrameHelpers
|
3
|
+
private
|
4
|
+
|
5
|
+
# @return [PryStackExplorer::FrameManager] The active frame manager for
|
6
|
+
# the current `Pry` instance.
|
7
|
+
def frame_manager
|
8
|
+
PryStackExplorer.frame_manager(_pry_)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Array<PryStackExplorer::FrameManager>] All the frame
|
12
|
+
# managers for the current `Pry` instance.
|
13
|
+
def frame_managers
|
14
|
+
PryStackExplorer.frame_managers(_pry_)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean] Whether there is a context to return to once
|
18
|
+
# the current `frame_manager` is popped.
|
19
|
+
def prior_context_exists?
|
20
|
+
frame_managers.count > 1 || frame_manager.prior_binding
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return a description of the frame (binding).
|
24
|
+
# This is only useful for regular old bindings that have not been
|
25
|
+
# enhanced by `#of_caller`.
|
26
|
+
# @param [Binding] b The binding.
|
27
|
+
# @return [String] A description of the frame (binding).
|
28
|
+
def frame_description(b)
|
29
|
+
b_self = b.eval('self')
|
30
|
+
b_method = b.eval('__method__')
|
31
|
+
|
32
|
+
if b_method && b_method != :__binding__ && b_method != :__binding_impl__
|
33
|
+
b_method.to_s
|
34
|
+
elsif b_self.instance_of?(Module)
|
35
|
+
"<module:#{b_self}>"
|
36
|
+
elsif b_self.instance_of?(Class)
|
37
|
+
"<class:#{b_self}>"
|
38
|
+
else
|
39
|
+
"<main>"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a description of the passed binding object. Accepts an
|
44
|
+
# optional `verbose` parameter.
|
45
|
+
# @param [Binding] b The binding.
|
46
|
+
# @param [Boolean] verbose Whether to generate a verbose description.
|
47
|
+
# @return [String] The description of the binding.
|
48
|
+
def frame_info(b, verbose = false)
|
49
|
+
b_self = b.eval('self')
|
50
|
+
type = b.frame_type ? "[#{b.frame_type}]".ljust(9) : ""
|
51
|
+
desc = b.frame_description ? "#{b.frame_description}" : "#{frame_description(b)}"
|
52
|
+
sig = PryMoves::Helpers.method_signature_with_owner b
|
53
|
+
|
54
|
+
self_clipped = "#{Pry.view_clip(b_self)}"
|
55
|
+
path = "@ #{b.eval('__FILE__')}:#{b.eval('__LINE__')}"
|
56
|
+
|
57
|
+
if !verbose
|
58
|
+
"#{type} #{desc} #{sig}"
|
59
|
+
else
|
60
|
+
"#{type} #{desc} #{sig}\n in #{self_clipped} #{path}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_frame_by_regex(regex, up_or_down)
|
65
|
+
frame_index = find_frame_by_block(up_or_down) do |b|
|
66
|
+
(b.eval('"#{__FILE__}:#{__LINE__}"') =~ regex) or
|
67
|
+
(b.eval("__method__").to_s =~ regex)
|
68
|
+
end
|
69
|
+
|
70
|
+
frame_index || raise(Pry::CommandError, "No frame that matches #{regex.source} found")
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_frame_by_block(up_or_down)
|
74
|
+
start_index = frame_manager.binding_index
|
75
|
+
|
76
|
+
if up_or_down == :down
|
77
|
+
enum = start_index == 0 ? [].each :
|
78
|
+
frame_manager.bindings[0..start_index - 1].reverse_each
|
79
|
+
else
|
80
|
+
enum = frame_manager.bindings[start_index + 1..-1]
|
81
|
+
end
|
82
|
+
|
83
|
+
new_frame = enum.find do |b|
|
84
|
+
yield(b)
|
85
|
+
end
|
86
|
+
|
87
|
+
frame_manager.bindings.index(new_frame)
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_frame_by_direction(up_or_down, step_into_vapid: false)
|
91
|
+
frame_index = find_frame_by_block(up_or_down) do |b|
|
92
|
+
step_into_vapid or
|
93
|
+
not b.local_variable_defined?(:vapid_frame)
|
94
|
+
end
|
95
|
+
|
96
|
+
frame_index ||
|
97
|
+
raise(Pry::CommandError, "At #{up_or_down == :up ? 'top' : 'bottom'} of stack, cannot go further")
|
98
|
+
end
|
99
|
+
|
100
|
+
def move(direction, param)
|
101
|
+
raise Pry::CommandError, "Nowhere to go" unless frame_manager
|
102
|
+
|
103
|
+
if param == '+' or param.nil?
|
104
|
+
index = find_frame_by_direction direction, step_into_vapid: param == '+'
|
105
|
+
frame_manager.change_frame_to index
|
106
|
+
else
|
107
|
+
index = find_frame_by_regex(Regexp.new(param), direction)
|
108
|
+
frame_manager.change_frame_to index
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
Commands = Pry::CommandSet.new do
|
115
|
+
create_command "up", "Go up to the caller's context." do
|
116
|
+
include FrameHelpers
|
117
|
+
|
118
|
+
banner <<-BANNER
|
119
|
+
Usage: up [OPTIONS]
|
120
|
+
Go up to the caller's context. Accepts optional numeric parameter for how many frames to move up.
|
121
|
+
Also accepts a string (regex) instead of numeric; for jumping to nearest parent method frame which matches the regex.
|
122
|
+
e.g: up #=> Move up normally
|
123
|
+
e.g: up + #=> Move up including vapid frames
|
124
|
+
e.g: up meth #=> Jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`.
|
125
|
+
BANNER
|
126
|
+
|
127
|
+
def process
|
128
|
+
move :up, args.first
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
create_command "down", "Go down to the callee's context." do
|
133
|
+
include FrameHelpers
|
134
|
+
|
135
|
+
banner <<-BANNER
|
136
|
+
Usage: down [OPTIONS]
|
137
|
+
Go down to the callee's context. Accepts optional numeric parameter for how many frames to move down.
|
138
|
+
Also accepts a string (regex) instead of numeric; for jumping to nearest child method frame which matches the regex.
|
139
|
+
e.g: down #=> Move down normally
|
140
|
+
e.g: down + #=> Move down including vapid frames
|
141
|
+
e.g: down meth #=> Jump to nearest child stack frame whose method matches /meth/ regex, i.e `my_method`.
|
142
|
+
BANNER
|
143
|
+
|
144
|
+
def process
|
145
|
+
move :down, args.first
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
create_command "top", "Top" do
|
150
|
+
include FrameHelpers
|
151
|
+
def process
|
152
|
+
frame_manager.change_frame_to frame_manager.bindings.size - 1
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
create_command "bottom", "Bottom" do
|
157
|
+
include FrameHelpers
|
158
|
+
def process
|
159
|
+
frame_manager.change_frame_to 0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
create_command "frame", "Switch to a particular frame." do
|
164
|
+
include FrameHelpers
|
165
|
+
|
166
|
+
banner <<-BANNER
|
167
|
+
Usage: frame [OPTIONS]
|
168
|
+
Switch to a particular frame. Accepts numeric parameter (or regex for method name) for the target frame to switch to (use with show-stack).
|
169
|
+
Negative frame numbers allowed. When given no parameter show information about the current frame.
|
170
|
+
|
171
|
+
e.g: frame 4 #=> jump to the 4th frame
|
172
|
+
e.g: frame meth #=> jump to nearest parent stack frame whose method matches /meth/ regex, i.e `my_method`
|
173
|
+
e.g: frame -2 #=> jump to the second-to-last frame
|
174
|
+
e.g: frame #=> show information info about current frame
|
175
|
+
BANNER
|
176
|
+
|
177
|
+
def process
|
178
|
+
if !frame_manager
|
179
|
+
raise Pry::CommandError, "nowhere to go!"
|
180
|
+
else
|
181
|
+
|
182
|
+
if args[0] =~ /\d+/
|
183
|
+
frame_manager.change_frame_to args[0].to_i
|
184
|
+
elsif match = /^([A-Z]+[^#.]*)(#|\.)(.+)$/.match(args[0])
|
185
|
+
new_frame_index = find_frame_by_object_regex(Regexp.new(match[1]), Regexp.new(match[3]), :up)
|
186
|
+
frame_manager.change_frame_to new_frame_index
|
187
|
+
elsif args[0] =~ /^[^-].*$/
|
188
|
+
new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up)
|
189
|
+
frame_manager.change_frame_to new_frame_index
|
190
|
+
else
|
191
|
+
output.puts "##{frame_manager.binding_index} #{frame_info(target, true)}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
create_command "show-stack", "Show all frames" do
|
198
|
+
include FrameHelpers
|
199
|
+
|
200
|
+
banner <<-BANNER
|
201
|
+
Usage: show-stack [OPTIONS]
|
202
|
+
Show all accessible stack frames.
|
203
|
+
e.g: show-stack -v
|
204
|
+
BANNER
|
205
|
+
|
206
|
+
def options(opt)
|
207
|
+
opt.on :v, :verbose, "Include extra information."
|
208
|
+
opt.on :H, :head, "Display the first N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
|
209
|
+
opt.on :T, :tail, "Display the last N stack frames (defaults to 10).", :optional_argument => true, :as => Integer, :default => 10
|
210
|
+
opt.on :c, :current, "Display N frames either side of current frame (default to 5).", :optional_argument => true, :as => Integer, :default => 5
|
211
|
+
end
|
212
|
+
|
213
|
+
def memoized_info(index, b, verbose)
|
214
|
+
frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] }
|
215
|
+
|
216
|
+
if verbose
|
217
|
+
frame_manager.user[:frame_info][:v][index] ||= frame_info(b, verbose)
|
218
|
+
else
|
219
|
+
frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
private :memoized_info
|
224
|
+
|
225
|
+
# @return [Array<Fixnum, Array<Binding>>] Return tuple of
|
226
|
+
# base_frame_index and the array of frames.
|
227
|
+
def selected_stack_frames
|
228
|
+
if opts.present?(:head)
|
229
|
+
[0, frame_manager.bindings[0..(opts[:head] - 1)]]
|
230
|
+
|
231
|
+
elsif opts.present?(:tail)
|
232
|
+
tail = opts[:tail]
|
233
|
+
if tail > frame_manager.bindings.size
|
234
|
+
tail = frame_manager.bindings.size
|
235
|
+
end
|
236
|
+
|
237
|
+
base_frame_index = frame_manager.bindings.size - tail
|
238
|
+
[base_frame_index, frame_manager.bindings[base_frame_index..-1]]
|
239
|
+
|
240
|
+
elsif opts.present?(:current)
|
241
|
+
first_frame_index = frame_manager.binding_index - (opts[:current])
|
242
|
+
first_frame_index = 0 if first_frame_index < 0
|
243
|
+
last_frame_index = frame_manager.binding_index + (opts[:current])
|
244
|
+
[first_frame_index, frame_manager.bindings[first_frame_index..last_frame_index]]
|
245
|
+
|
246
|
+
else
|
247
|
+
[0, frame_manager.bindings]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private :selected_stack_frames
|
252
|
+
|
253
|
+
def process
|
254
|
+
if !frame_manager
|
255
|
+
output.puts "No caller stack available!"
|
256
|
+
else
|
257
|
+
content = ""
|
258
|
+
content << "\n#{text.bold("Showing all accessible frames in stack (#{frame_manager.bindings.size} in total):")}\n--\n"
|
259
|
+
|
260
|
+
base_frame_index, frames = selected_stack_frames
|
261
|
+
frames.each_with_index do |b, index|
|
262
|
+
i = index + base_frame_index
|
263
|
+
if i == frame_manager.binding_index
|
264
|
+
content << "=> ##{i} #{memoized_info(i, b, opts[:v])}\n"
|
265
|
+
else
|
266
|
+
content << " ##{i} #{memoized_info(i, b, opts[:v])}\n"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
stagger_output content
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|