pry 0.10.0.pre2-universal-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +702 -0
- data/LICENSE +25 -0
- data/README.md +406 -0
- data/bin/pry +16 -0
- data/lib/pry.rb +161 -0
- data/lib/pry/cli.rb +220 -0
- data/lib/pry/code.rb +346 -0
- data/lib/pry/code/code_file.rb +103 -0
- data/lib/pry/code/code_range.rb +71 -0
- data/lib/pry/code/loc.rb +92 -0
- data/lib/pry/code_object.rb +172 -0
- data/lib/pry/color_printer.rb +55 -0
- data/lib/pry/command.rb +692 -0
- data/lib/pry/command_set.rb +443 -0
- data/lib/pry/commands.rb +6 -0
- data/lib/pry/commands/amend_line.rb +99 -0
- data/lib/pry/commands/bang.rb +20 -0
- data/lib/pry/commands/bang_pry.rb +17 -0
- data/lib/pry/commands/cat.rb +62 -0
- data/lib/pry/commands/cat/abstract_formatter.rb +27 -0
- data/lib/pry/commands/cat/exception_formatter.rb +77 -0
- data/lib/pry/commands/cat/file_formatter.rb +67 -0
- data/lib/pry/commands/cat/input_expression_formatter.rb +43 -0
- data/lib/pry/commands/cd.rb +41 -0
- data/lib/pry/commands/change_inspector.rb +27 -0
- data/lib/pry/commands/change_prompt.rb +26 -0
- data/lib/pry/commands/code_collector.rb +165 -0
- data/lib/pry/commands/disable_pry.rb +27 -0
- data/lib/pry/commands/disabled_commands.rb +2 -0
- data/lib/pry/commands/easter_eggs.rb +112 -0
- data/lib/pry/commands/edit.rb +195 -0
- data/lib/pry/commands/edit/exception_patcher.rb +25 -0
- data/lib/pry/commands/edit/file_and_line_locator.rb +36 -0
- data/lib/pry/commands/exit.rb +42 -0
- data/lib/pry/commands/exit_all.rb +29 -0
- data/lib/pry/commands/exit_program.rb +23 -0
- data/lib/pry/commands/find_method.rb +193 -0
- data/lib/pry/commands/fix_indent.rb +19 -0
- data/lib/pry/commands/gem_cd.rb +26 -0
- data/lib/pry/commands/gem_install.rb +32 -0
- data/lib/pry/commands/gem_list.rb +33 -0
- data/lib/pry/commands/gem_open.rb +29 -0
- data/lib/pry/commands/gist.rb +101 -0
- data/lib/pry/commands/help.rb +164 -0
- data/lib/pry/commands/hist.rb +180 -0
- data/lib/pry/commands/import_set.rb +22 -0
- data/lib/pry/commands/install_command.rb +53 -0
- data/lib/pry/commands/jump_to.rb +29 -0
- data/lib/pry/commands/list_inspectors.rb +35 -0
- data/lib/pry/commands/list_prompts.rb +35 -0
- data/lib/pry/commands/ls.rb +114 -0
- data/lib/pry/commands/ls/constants.rb +47 -0
- data/lib/pry/commands/ls/formatter.rb +49 -0
- data/lib/pry/commands/ls/globals.rb +48 -0
- data/lib/pry/commands/ls/grep.rb +21 -0
- data/lib/pry/commands/ls/instance_vars.rb +39 -0
- data/lib/pry/commands/ls/interrogatable.rb +18 -0
- data/lib/pry/commands/ls/jruby_hacks.rb +49 -0
- data/lib/pry/commands/ls/local_names.rb +35 -0
- data/lib/pry/commands/ls/local_vars.rb +39 -0
- data/lib/pry/commands/ls/ls_entity.rb +70 -0
- data/lib/pry/commands/ls/methods.rb +57 -0
- data/lib/pry/commands/ls/methods_helper.rb +46 -0
- data/lib/pry/commands/ls/self_methods.rb +32 -0
- data/lib/pry/commands/nesting.rb +25 -0
- data/lib/pry/commands/play.rb +103 -0
- data/lib/pry/commands/pry_backtrace.rb +25 -0
- data/lib/pry/commands/pry_version.rb +17 -0
- data/lib/pry/commands/raise_up.rb +32 -0
- data/lib/pry/commands/reload_code.rb +62 -0
- data/lib/pry/commands/reset.rb +18 -0
- data/lib/pry/commands/ri.rb +60 -0
- data/lib/pry/commands/save_file.rb +61 -0
- data/lib/pry/commands/shell_command.rb +48 -0
- data/lib/pry/commands/shell_mode.rb +25 -0
- data/lib/pry/commands/show_doc.rb +83 -0
- data/lib/pry/commands/show_info.rb +195 -0
- data/lib/pry/commands/show_input.rb +17 -0
- data/lib/pry/commands/show_source.rb +50 -0
- data/lib/pry/commands/simple_prompt.rb +22 -0
- data/lib/pry/commands/stat.rb +40 -0
- data/lib/pry/commands/switch_to.rb +23 -0
- data/lib/pry/commands/toggle_color.rb +24 -0
- data/lib/pry/commands/watch_expression.rb +105 -0
- data/lib/pry/commands/watch_expression/expression.rb +38 -0
- data/lib/pry/commands/whereami.rb +190 -0
- data/lib/pry/commands/wtf.rb +57 -0
- data/lib/pry/config.rb +24 -0
- data/lib/pry/config/behavior.rb +139 -0
- data/lib/pry/config/convenience.rb +26 -0
- data/lib/pry/config/default.rb +165 -0
- data/lib/pry/core_extensions.rb +131 -0
- data/lib/pry/editor.rb +133 -0
- data/lib/pry/exceptions.rb +77 -0
- data/lib/pry/helpers.rb +5 -0
- data/lib/pry/helpers/base_helpers.rb +113 -0
- data/lib/pry/helpers/command_helpers.rb +156 -0
- data/lib/pry/helpers/documentation_helpers.rb +75 -0
- data/lib/pry/helpers/options_helpers.rb +27 -0
- data/lib/pry/helpers/table.rb +109 -0
- data/lib/pry/helpers/text.rb +107 -0
- data/lib/pry/history.rb +125 -0
- data/lib/pry/history_array.rb +121 -0
- data/lib/pry/hooks.rb +230 -0
- data/lib/pry/indent.rb +406 -0
- data/lib/pry/input_completer.rb +242 -0
- data/lib/pry/input_lock.rb +132 -0
- data/lib/pry/inspector.rb +27 -0
- data/lib/pry/last_exception.rb +61 -0
- data/lib/pry/method.rb +546 -0
- data/lib/pry/method/disowned.rb +53 -0
- data/lib/pry/method/patcher.rb +125 -0
- data/lib/pry/method/weird_method_locator.rb +186 -0
- data/lib/pry/module_candidate.rb +136 -0
- data/lib/pry/object_path.rb +82 -0
- data/lib/pry/output.rb +50 -0
- data/lib/pry/pager.rb +234 -0
- data/lib/pry/plugins.rb +103 -0
- data/lib/pry/prompt.rb +26 -0
- data/lib/pry/pry_class.rb +375 -0
- data/lib/pry/pry_instance.rb +654 -0
- data/lib/pry/rbx_path.rb +22 -0
- data/lib/pry/repl.rb +202 -0
- data/lib/pry/repl_file_loader.rb +74 -0
- data/lib/pry/rubygem.rb +82 -0
- data/lib/pry/terminal.rb +79 -0
- data/lib/pry/test/helper.rb +170 -0
- data/lib/pry/version.rb +3 -0
- data/lib/pry/wrapped_module.rb +373 -0
- metadata +248 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
class Pry
|
2
|
+
# A history array is an array to which you can only add elements. Older
|
3
|
+
# entries are removed progressively, so that the array never contains more than
|
4
|
+
# N elements.
|
5
|
+
#
|
6
|
+
# History arrays are used by Pry to store the output of the last commands.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ary = Pry::HistoryArray.new 10
|
10
|
+
# ary << 1 << 2 << 3
|
11
|
+
# ary[0] # => 1
|
12
|
+
# ary[1] # => 2
|
13
|
+
# 10.times { |n| ary << n }
|
14
|
+
# ary[0] # => nil
|
15
|
+
# ary[-1] # => 9
|
16
|
+
class HistoryArray
|
17
|
+
include Enumerable
|
18
|
+
|
19
|
+
# @param [Integer] size Maximum amount of objects in the array
|
20
|
+
def initialize(size)
|
21
|
+
@max_size = size
|
22
|
+
|
23
|
+
@hash = {}
|
24
|
+
@count = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# Pushes an object at the end of the array
|
28
|
+
# @param [Object] value Object to be added
|
29
|
+
def <<(value)
|
30
|
+
@hash[@count] = value
|
31
|
+
|
32
|
+
if @hash.size > max_size
|
33
|
+
@hash.delete(@count - max_size)
|
34
|
+
end
|
35
|
+
|
36
|
+
@count += 1
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# @overload [](index)
|
42
|
+
# @param [Integer] index Index of the item to access.
|
43
|
+
# @return [Object, nil] Item at that index or nil if it has been removed.
|
44
|
+
# @overload [](index, size)
|
45
|
+
# @param [Integer] index Index of the first item to access.
|
46
|
+
# @param [Integer] size Amount of items to access
|
47
|
+
# @return [Array, nil] The selected items. Nil if index is greater than
|
48
|
+
# the size of the array.
|
49
|
+
# @overload [](range)
|
50
|
+
# @param [Range<Integer>] range Range of indices to access.
|
51
|
+
# @return [Array, nil] The selected items. Nil if index is greater than
|
52
|
+
# the size of the array.
|
53
|
+
def [](index_or_range, size = nil)
|
54
|
+
if index_or_range.is_a? Integer
|
55
|
+
index = convert_index(index_or_range)
|
56
|
+
|
57
|
+
if size
|
58
|
+
end_index = index + size
|
59
|
+
index > @count ? nil : (index...[end_index, @count].min).map do |n|
|
60
|
+
@hash[n]
|
61
|
+
end
|
62
|
+
else
|
63
|
+
@hash[index]
|
64
|
+
end
|
65
|
+
else
|
66
|
+
range = convert_range(index_or_range)
|
67
|
+
range.begin > @count ? nil : range.map { |n| @hash[n] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Integer] Amount of objects in the array
|
72
|
+
def size
|
73
|
+
@count
|
74
|
+
end
|
75
|
+
alias count size
|
76
|
+
alias length size
|
77
|
+
|
78
|
+
def empty?
|
79
|
+
size == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
def each
|
83
|
+
((@count - size)...@count).each do |n|
|
84
|
+
yield @hash[n]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_a
|
89
|
+
((@count - size)...@count).map { |n| @hash[n] }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns [Hash] copy of the internal @hash history
|
93
|
+
def to_h
|
94
|
+
@hash.dup
|
95
|
+
end
|
96
|
+
|
97
|
+
def pop!
|
98
|
+
@hash.delete @count - 1
|
99
|
+
@count -= 1
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
"#<#{self.class} size=#{size} first=#{@count - size} max_size=#{max_size}>"
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Integer] Maximum amount of objects in the array
|
107
|
+
attr_reader :max_size
|
108
|
+
|
109
|
+
private
|
110
|
+
def convert_index(n)
|
111
|
+
n >= 0 ? n : @count + n
|
112
|
+
end
|
113
|
+
|
114
|
+
def convert_range(range)
|
115
|
+
end_index = convert_index(range.end)
|
116
|
+
end_index += 1 unless range.exclude_end?
|
117
|
+
|
118
|
+
Range.new(convert_index(range.begin), [end_index, @count].min, true)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/pry/hooks.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
class Pry
|
2
|
+
|
3
|
+
# Implements a hooks system for Pry. A hook is a callable that is
|
4
|
+
# associated with an event. A number of events are currently
|
5
|
+
# provided by Pry, these include: `:when_started`, `:before_session`, `:after_session`.
|
6
|
+
# A hook must have a name, and is connected with an event by the
|
7
|
+
# `Pry::Hooks#add_hook` method.
|
8
|
+
# @example Adding a hook for the `:before_session` event.
|
9
|
+
# Pry.config.hooks.add_hook(:before_session, :say_hi) do
|
10
|
+
# puts "hello"
|
11
|
+
# end
|
12
|
+
class Hooks
|
13
|
+
|
14
|
+
# Converts a hash to a `Pry::Hooks` instance. All hooks defined
|
15
|
+
# this way are anonymous. This functionality is primarily to
|
16
|
+
# provide backwards-compatibility with the old hash-based hook
|
17
|
+
# system in Pry versions < 0.9.8
|
18
|
+
# @param [Hash] hash The hash to convert to `Pry::Hooks`.
|
19
|
+
# @return [Pry::Hooks] The resulting `Pry::Hooks` instance.
|
20
|
+
def self.from_hash(hash)
|
21
|
+
return hash if hash.instance_of?(self)
|
22
|
+
instance = new
|
23
|
+
hash.each do |k, v|
|
24
|
+
instance.add_hook(k, nil, v)
|
25
|
+
end
|
26
|
+
instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@hooks = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Ensure that duplicates have their @hooks object
|
34
|
+
def initialize_copy(orig)
|
35
|
+
hooks_dup = @hooks.dup
|
36
|
+
@hooks.each do |k, v|
|
37
|
+
hooks_dup[k] = v.dup
|
38
|
+
end
|
39
|
+
|
40
|
+
@hooks = hooks_dup
|
41
|
+
end
|
42
|
+
|
43
|
+
def hooks
|
44
|
+
@hooks
|
45
|
+
end
|
46
|
+
protected :hooks
|
47
|
+
|
48
|
+
def errors
|
49
|
+
@errors ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Destructively merge the contents of two `Pry:Hooks` instances.
|
53
|
+
# @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
|
54
|
+
# @return [Pry:Hooks] Returns the receiver.
|
55
|
+
# @example
|
56
|
+
# hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
57
|
+
# Pry::Hooks.new.merge!(hooks)
|
58
|
+
def merge!(other)
|
59
|
+
@hooks.merge!(other.dup.hooks) do |key, v1, v2|
|
60
|
+
merge_arrays(v1, v2)
|
61
|
+
end
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge_arrays(array1, array2)
|
67
|
+
uniq_keeping_last(array1 + array2, &:first)
|
68
|
+
end
|
69
|
+
private :merge_arrays
|
70
|
+
|
71
|
+
def uniq_keeping_last(input, &block)
|
72
|
+
hash, output = {}, []
|
73
|
+
input.reverse.each{ |i| hash[block[i]] ||= (output.unshift i) }
|
74
|
+
output
|
75
|
+
end
|
76
|
+
private :uniq_keeping_last
|
77
|
+
|
78
|
+
# Return a new `Pry::Hooks` instance containing a merge of the contents of two `Pry:Hooks` instances,
|
79
|
+
# @param [Pry::Hooks] other The `Pry::Hooks` instance to merge
|
80
|
+
# @return [Pry::Hooks] The new hash.
|
81
|
+
# @example
|
82
|
+
# hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
83
|
+
# Pry::Hooks.new.merge(hooks)
|
84
|
+
def merge(other)
|
85
|
+
self.dup.tap do |v|
|
86
|
+
v.merge!(other)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add a new hook to be executed for the `name` even.
|
91
|
+
# @param [Symbol] event_name The name of the event.
|
92
|
+
# @param [Symbol] hook_name The name of the hook.
|
93
|
+
# @param [#call] callable The callable.
|
94
|
+
# @yield The block to use as the callable (if `callable` parameter not provided)
|
95
|
+
# @return [Pry:Hooks] Returns the receiver.
|
96
|
+
# @example
|
97
|
+
# Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
98
|
+
def add_hook(event_name, hook_name, callable=nil, &block)
|
99
|
+
@hooks[event_name] ||= []
|
100
|
+
|
101
|
+
# do not allow duplicates, but allow multiple `nil` hooks
|
102
|
+
# (anonymous hooks)
|
103
|
+
if hook_exists?(event_name, hook_name) && !hook_name.nil?
|
104
|
+
raise ArgumentError, "Hook with name '#{hook_name}' already defined!"
|
105
|
+
end
|
106
|
+
|
107
|
+
if !block && !callable
|
108
|
+
raise ArgumentError, "Must provide a block or callable."
|
109
|
+
end
|
110
|
+
|
111
|
+
# ensure we only have one anonymous hook
|
112
|
+
@hooks[event_name].delete_if { |h, k| h.nil? } if hook_name.nil?
|
113
|
+
|
114
|
+
if block
|
115
|
+
@hooks[event_name] << [hook_name, block]
|
116
|
+
elsif callable
|
117
|
+
@hooks[event_name] << [hook_name, callable]
|
118
|
+
end
|
119
|
+
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Execute the list of hooks for the `event_name` event.
|
124
|
+
# @param [Symbol] event_name The name of the event.
|
125
|
+
# @param [Array] args The arguments to pass to each hook function.
|
126
|
+
# @return [Object] The return value of the last executed hook.
|
127
|
+
# @example
|
128
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
129
|
+
# my_hooks.exec_hook(:before_session) #=> OUTPUT: "hi!"
|
130
|
+
def exec_hook(event_name, *args, &block)
|
131
|
+
@hooks[event_name] ||= []
|
132
|
+
|
133
|
+
@hooks[event_name].map do |hook_name, callable|
|
134
|
+
begin
|
135
|
+
callable.call(*args, &block)
|
136
|
+
rescue RescuableException => e
|
137
|
+
errors << e
|
138
|
+
e
|
139
|
+
end
|
140
|
+
end.last
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return the number of hook functions registered for the `event_name` event.
|
144
|
+
# @param [Symbol] event_name The name of the event.
|
145
|
+
# @return [Fixnum] The number of hook functions for `event_name`.
|
146
|
+
# @example
|
147
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
148
|
+
# my_hooks.count(:before_session) #=> 1
|
149
|
+
def hook_count(event_name)
|
150
|
+
@hooks[event_name] ||= []
|
151
|
+
@hooks[event_name].size
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return a specific hook for a given event.
|
155
|
+
# @param [Symbol] event_name The name of the event.
|
156
|
+
# @param [Symbol] hook_name The name of the hook
|
157
|
+
# @return [#call] The requested hook.
|
158
|
+
# @example
|
159
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
160
|
+
# my_hooks.get_hook(:before_session, :say_hi).call #=> "hi!"
|
161
|
+
def get_hook(event_name, hook_name)
|
162
|
+
@hooks[event_name] ||= []
|
163
|
+
hook = @hooks[event_name].find { |current_hook_name, callable| current_hook_name == hook_name }
|
164
|
+
hook.last if hook
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return the hash of hook names / hook functions for a
|
168
|
+
# given event. (Note that modifying the returned hash does not
|
169
|
+
# alter the hooks, use add_hook/delete_hook for that).
|
170
|
+
# @param [Symbol] event_name The name of the event.
|
171
|
+
# @return [Hash] The hash of hook names / hook functions.
|
172
|
+
# @example
|
173
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
174
|
+
# my_hooks.get_hooks(:before_session) #=> {:say_hi=>#<Proc:0x00000101645e18@(pry):9>}
|
175
|
+
def get_hooks(event_name)
|
176
|
+
@hooks[event_name] ||= []
|
177
|
+
Hash[@hooks[event_name]]
|
178
|
+
end
|
179
|
+
|
180
|
+
# Delete a hook for an event.
|
181
|
+
# @param [Symbol] event_name The name of the event.
|
182
|
+
# @param [Symbol] hook_name The name of the hook.
|
183
|
+
# to delete.
|
184
|
+
# @return [#call] The deleted hook.
|
185
|
+
# @example
|
186
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
187
|
+
# my_hooks.delete_hook(:before_session, :say_hi)
|
188
|
+
def delete_hook(event_name, hook_name)
|
189
|
+
@hooks[event_name] ||= []
|
190
|
+
deleted_callable = nil
|
191
|
+
|
192
|
+
@hooks[event_name].delete_if do |current_hook_name, callable|
|
193
|
+
if current_hook_name == hook_name
|
194
|
+
deleted_callable = callable
|
195
|
+
true
|
196
|
+
else
|
197
|
+
false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
deleted_callable
|
201
|
+
end
|
202
|
+
|
203
|
+
# Clear all hooks functions for a given event.
|
204
|
+
# @param [String] event_name The name of the event.
|
205
|
+
# @example
|
206
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
207
|
+
# my_hooks.delete_hook(:before_session)
|
208
|
+
def delete_hooks(event_name)
|
209
|
+
@hooks[event_name] = []
|
210
|
+
end
|
211
|
+
|
212
|
+
alias_method :clear, :delete_hooks
|
213
|
+
|
214
|
+
# Remove all events and hooks, clearing out the Pry::Hooks
|
215
|
+
# instance completely.
|
216
|
+
# @example
|
217
|
+
# my_hooks = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
|
218
|
+
# my_hooks.clear_all
|
219
|
+
def clear_all
|
220
|
+
@hooks = {}
|
221
|
+
end
|
222
|
+
|
223
|
+
# @param [Symbol] event_name Name of the event.
|
224
|
+
# @param [Symbol] hook_name Name of the hook.
|
225
|
+
# @return [Boolean] Whether the hook by the name `hook_name`
|
226
|
+
def hook_exists?(event_name, hook_name)
|
227
|
+
!!(@hooks[event_name] && @hooks[event_name].find { |name, _| name == hook_name })
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
data/lib/pry/indent.rb
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'coderay'
|
2
|
+
|
3
|
+
class Pry
|
4
|
+
##
|
5
|
+
# Pry::Indent is a class that can be used to indent a number of lines
|
6
|
+
# containing Ruby code similar as to how IRB does it (but better). The class
|
7
|
+
# works by tokenizing a string using CodeRay and then looping over those
|
8
|
+
# tokens. Based on the tokens in a line of code that line (or the next one)
|
9
|
+
# will be indented or un-indented by correctly.
|
10
|
+
#
|
11
|
+
class Indent
|
12
|
+
include Helpers::BaseHelpers
|
13
|
+
|
14
|
+
# Raised if {#module_nesting} would not work.
|
15
|
+
class UnparseableNestingError < StandardError; end
|
16
|
+
|
17
|
+
# @return [String] String containing the spaces to be inserted before the next line.
|
18
|
+
attr_reader :indent_level
|
19
|
+
|
20
|
+
# @return [Array<String>] The stack of open tokens.
|
21
|
+
attr_reader :stack
|
22
|
+
|
23
|
+
# The amount of spaces to insert for each indent level.
|
24
|
+
SPACES = ' '
|
25
|
+
|
26
|
+
# Hash containing all the tokens that should increase the indentation
|
27
|
+
# level. The keys of this hash are open tokens, the values the matching
|
28
|
+
# tokens that should prevent a line from being indented if they appear on
|
29
|
+
# the same line.
|
30
|
+
OPEN_TOKENS = {
|
31
|
+
'def' => 'end',
|
32
|
+
'class' => 'end',
|
33
|
+
'module' => 'end',
|
34
|
+
'do' => 'end',
|
35
|
+
'if' => 'end',
|
36
|
+
'unless' => 'end',
|
37
|
+
'while' => 'end',
|
38
|
+
'until' => 'end',
|
39
|
+
'for' => 'end',
|
40
|
+
'case' => 'end',
|
41
|
+
'begin' => 'end',
|
42
|
+
'[' => ']',
|
43
|
+
'{' => '}',
|
44
|
+
'(' => ')'
|
45
|
+
}
|
46
|
+
|
47
|
+
# Which tokens can either be open tokens, or appear as modifiers on
|
48
|
+
# a single-line.
|
49
|
+
SINGLELINE_TOKENS = %w(if while until unless rescue)
|
50
|
+
|
51
|
+
# Which tokens can be followed by an optional "do" keyword.
|
52
|
+
OPTIONAL_DO_TOKENS = %w(for while until)
|
53
|
+
|
54
|
+
# Collection of token types that should be ignored. Without this list
|
55
|
+
# keywords such as "class" inside strings would cause the code to be
|
56
|
+
# indented incorrectly.
|
57
|
+
#
|
58
|
+
# :pre_constant and :preserved_constant are the CodeRay 0.9.8 and 1.0.0
|
59
|
+
# classifications of "true", "false", and "nil".
|
60
|
+
IGNORE_TOKENS = [:space, :content, :string, :method, :ident,
|
61
|
+
:constant, :pre_constant, :predefined_constant]
|
62
|
+
|
63
|
+
# Tokens that indicate the end of a statement (i.e. that, if they appear
|
64
|
+
# directly before an "if" indicates that that if applies to the same line,
|
65
|
+
# not the next line)
|
66
|
+
#
|
67
|
+
# :reserved and :keywords are the CodeRay 0.9.8 and 1.0.0 respectively
|
68
|
+
# classifications of "super", "next", "return", etc.
|
69
|
+
STATEMENT_END_TOKENS = IGNORE_TOKENS + [:regexp, :integer, :float, :keyword,
|
70
|
+
:delimiter, :reserved]
|
71
|
+
|
72
|
+
# Collection of tokens that should appear dedented even though they
|
73
|
+
# don't affect the surrounding code.
|
74
|
+
MIDWAY_TOKENS = %w(when else elsif ensure rescue)
|
75
|
+
|
76
|
+
# Clean the indentation of a fragment of ruby.
|
77
|
+
#
|
78
|
+
# @param [String] str
|
79
|
+
# @return [String]
|
80
|
+
def self.indent(str)
|
81
|
+
new.indent(str)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get the module nesting at the given point in the given string.
|
85
|
+
#
|
86
|
+
# NOTE If the line specified contains a method definition, then the nesting
|
87
|
+
# at the start of the method definition is used. Otherwise the nesting from
|
88
|
+
# the end of the line is used.
|
89
|
+
#
|
90
|
+
# @param [String] str The ruby code to analyze
|
91
|
+
# @param [Fixnum] line_number The line number (starting from 1)
|
92
|
+
# @return [Array<String>]
|
93
|
+
def self.nesting_at(str, line_number)
|
94
|
+
indent = new
|
95
|
+
lines = str.split("\n")
|
96
|
+
n = line_number - 1
|
97
|
+
to_indent = lines[0...n] << (lines[n] || "").split("def").first(1)
|
98
|
+
indent.indent(to_indent.join("\n") << "\n")
|
99
|
+
indent.module_nesting
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize
|
103
|
+
reset
|
104
|
+
end
|
105
|
+
|
106
|
+
# reset internal state
|
107
|
+
def reset
|
108
|
+
@stack = []
|
109
|
+
@indent_level = ''
|
110
|
+
@heredoc_queue = []
|
111
|
+
@close_heredocs = {}
|
112
|
+
@string_start = nil
|
113
|
+
@awaiting_class = false
|
114
|
+
@module_nesting = []
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Indents a string and returns it. This string can either be a single line
|
119
|
+
# or multiple ones.
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# str = <<TXT
|
123
|
+
# class User
|
124
|
+
# attr_accessor :name
|
125
|
+
# end
|
126
|
+
# TXT
|
127
|
+
#
|
128
|
+
# # This would result in the following being displayed:
|
129
|
+
# #
|
130
|
+
# # class User
|
131
|
+
# # attr_accessor :name
|
132
|
+
# # end
|
133
|
+
# #
|
134
|
+
# puts Pry::Indent.new.indent(str)
|
135
|
+
#
|
136
|
+
# @param [String] input The input string to indent.
|
137
|
+
# @return [String] The indented version of +input+.
|
138
|
+
#
|
139
|
+
def indent(input)
|
140
|
+
output = ''
|
141
|
+
prefix = indent_level
|
142
|
+
|
143
|
+
input.lines.each do |line|
|
144
|
+
|
145
|
+
if in_string?
|
146
|
+
tokens = tokenize("#{open_delimiters_line}\n#{line}")
|
147
|
+
tokens = tokens.drop_while{ |token, type| !(String === token && token.include?("\n")) }
|
148
|
+
previously_in_string = true
|
149
|
+
else
|
150
|
+
tokens = tokenize(line)
|
151
|
+
previously_in_string = false
|
152
|
+
end
|
153
|
+
|
154
|
+
before, after = indentation_delta(tokens)
|
155
|
+
|
156
|
+
before.times{ prefix.sub! SPACES, '' }
|
157
|
+
new_prefix = prefix + SPACES * after
|
158
|
+
|
159
|
+
line = prefix + line.lstrip unless previously_in_string
|
160
|
+
|
161
|
+
output += line
|
162
|
+
|
163
|
+
prefix = new_prefix
|
164
|
+
end
|
165
|
+
|
166
|
+
@indent_level = prefix
|
167
|
+
|
168
|
+
return output
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get the indentation for the start of the next line.
|
172
|
+
#
|
173
|
+
# This is what's used between the prompt and the cursor in pry.
|
174
|
+
#
|
175
|
+
# @return String The correct number of spaces
|
176
|
+
#
|
177
|
+
def current_prefix
|
178
|
+
in_string? ? '' : indent_level
|
179
|
+
end
|
180
|
+
|
181
|
+
# Get the change in indentation indicated by the line.
|
182
|
+
#
|
183
|
+
# By convention, you remove indent from the line containing end tokens,
|
184
|
+
# but add indent to the line *after* that which contains the start tokens.
|
185
|
+
#
|
186
|
+
# This method returns a pair, where the first number is the number of closings
|
187
|
+
# on this line (i.e. the number of indents to remove before the line) and the
|
188
|
+
# second is the number of openings (i.e. the number of indents to add after
|
189
|
+
# this line)
|
190
|
+
#
|
191
|
+
# @param [Array] tokens A list of tokens to scan.
|
192
|
+
# @return [Array[Integer]]
|
193
|
+
#
|
194
|
+
def indentation_delta(tokens)
|
195
|
+
|
196
|
+
# We need to keep track of whether we've seen a "for" on this line because
|
197
|
+
# if the line ends with "do" then that "do" should be discounted (i.e. we're
|
198
|
+
# only opening one level not two) To do this robustly we want to keep track
|
199
|
+
# of the indent level at which we saw the for, so we can differentiate
|
200
|
+
# between "for x in [1,2,3] do" and "for x in ([1,2,3].map do" properly
|
201
|
+
seen_for_at = []
|
202
|
+
|
203
|
+
# When deciding whether an "if" token is the start of a multiline statement,
|
204
|
+
# or just the middle of a single-line if statement, we just look at the
|
205
|
+
# preceding token, which is tracked here.
|
206
|
+
last_token, last_kind = [nil, nil]
|
207
|
+
|
208
|
+
# delta keeps track of the total difference from the start of each line after
|
209
|
+
# the given token, 0 is just the level at which the current line started for
|
210
|
+
# reference.
|
211
|
+
remove_before, add_after = [0, 0]
|
212
|
+
|
213
|
+
# If the list of tokens contains a matching closing token the line should
|
214
|
+
# not be indented (and thus we should return true).
|
215
|
+
tokens.each do |token, kind|
|
216
|
+
is_singleline_if = (SINGLELINE_TOKENS.include?(token)) && end_of_statement?(last_token, last_kind)
|
217
|
+
is_optional_do = (token == "do" && seen_for_at.include?(add_after - 1))
|
218
|
+
|
219
|
+
last_token, last_kind = token, kind unless kind == :space
|
220
|
+
next if IGNORE_TOKENS.include?(kind)
|
221
|
+
|
222
|
+
track_module_nesting(token, kind)
|
223
|
+
|
224
|
+
seen_for_at << add_after if OPTIONAL_DO_TOKENS.include?(token)
|
225
|
+
|
226
|
+
if kind == :delimiter
|
227
|
+
track_delimiter(token)
|
228
|
+
elsif OPEN_TOKENS.keys.include?(token) && !is_optional_do && !is_singleline_if
|
229
|
+
@stack << token
|
230
|
+
add_after += 1
|
231
|
+
elsif token == OPEN_TOKENS[@stack.last]
|
232
|
+
popped = @stack.pop
|
233
|
+
track_module_nesting_end(popped)
|
234
|
+
if add_after == 0
|
235
|
+
remove_before += 1
|
236
|
+
else
|
237
|
+
add_after -= 1
|
238
|
+
end
|
239
|
+
elsif MIDWAY_TOKENS.include?(token)
|
240
|
+
if add_after == 0
|
241
|
+
remove_before += 1
|
242
|
+
add_after += 1
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
return [remove_before, add_after]
|
248
|
+
end
|
249
|
+
|
250
|
+
# If the code just before an "if" or "while" token on a line looks like the end of a statement,
|
251
|
+
# then we want to treat that "if" as a singleline, not multiline statement.
|
252
|
+
def end_of_statement?(last_token, last_kind)
|
253
|
+
(last_token =~ /^[)\]}\/]$/ || STATEMENT_END_TOKENS.include?(last_kind))
|
254
|
+
end
|
255
|
+
|
256
|
+
# Are we currently in the middle of a string literal.
|
257
|
+
#
|
258
|
+
# This is used to determine whether to re-indent a given line, we mustn't re-indent
|
259
|
+
# within string literals because to do so would actually change the value of the
|
260
|
+
# String!
|
261
|
+
#
|
262
|
+
# @return Boolean
|
263
|
+
def in_string?
|
264
|
+
!open_delimiters.empty?
|
265
|
+
end
|
266
|
+
|
267
|
+
# Given a string of Ruby code, use CodeRay to export the tokens.
|
268
|
+
#
|
269
|
+
# @param [String] string The Ruby to lex
|
270
|
+
# @return [Array] An Array of pairs of [token_value, token_type]
|
271
|
+
def tokenize(string)
|
272
|
+
tokens = CodeRay.scan(string, :ruby)
|
273
|
+
tokens = tokens.tokens.each_slice(2) if tokens.respond_to?(:tokens) # Coderay 1.0.0
|
274
|
+
tokens.to_a
|
275
|
+
end
|
276
|
+
|
277
|
+
# Update the internal state about what kind of strings are open.
|
278
|
+
#
|
279
|
+
# Most of the complication here comes from the fact that HEREDOCs can be nested. For
|
280
|
+
# normal strings (which can't be nested) we assume that CodeRay correctly pairs
|
281
|
+
# open-and-close delimiters so we don't bother checking what they are.
|
282
|
+
#
|
283
|
+
# @param [String] token The token (of type :delimiter)
|
284
|
+
def track_delimiter(token)
|
285
|
+
case token
|
286
|
+
when /^<<-(["'`]?)(.*)\\1/
|
287
|
+
@heredoc_queue << token
|
288
|
+
@close_heredocs[token] = /^\s*$2/
|
289
|
+
when @close_heredocs[@heredoc_queue.first]
|
290
|
+
@heredoc_queue.shift
|
291
|
+
else
|
292
|
+
if @string_start
|
293
|
+
@string_start = nil
|
294
|
+
else
|
295
|
+
@string_start = token
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# All the open delimiters, in the order that they first appeared.
|
301
|
+
#
|
302
|
+
# @return [String]
|
303
|
+
def open_delimiters
|
304
|
+
@heredoc_queue + [@string_start].compact
|
305
|
+
end
|
306
|
+
|
307
|
+
# Return a string which restores the CodeRay string status to the correct value by
|
308
|
+
# opening HEREDOCs and strings.
|
309
|
+
#
|
310
|
+
# @return String
|
311
|
+
def open_delimiters_line
|
312
|
+
"puts #{open_delimiters.join(", ")}"
|
313
|
+
end
|
314
|
+
|
315
|
+
# Update the internal state relating to module nesting.
|
316
|
+
#
|
317
|
+
# It's responsible for adding to the @module_nesting array, which looks
|
318
|
+
# something like:
|
319
|
+
#
|
320
|
+
# [ ["class", "Foo"], ["module", "Bar::Baz"], ["class <<", "self"] ]
|
321
|
+
#
|
322
|
+
# A nil value in the @module_nesting array happens in two places: either
|
323
|
+
# when @awaiting_class is true and we're still waiting for the string to
|
324
|
+
# fill that space, or when a parse was rejected.
|
325
|
+
#
|
326
|
+
# At the moment this function is quite restricted about what formats it will
|
327
|
+
# parse, for example we disallow expressions after the class keyword. This
|
328
|
+
# could maybe be improved in the future.
|
329
|
+
#
|
330
|
+
# @param [String] token a token from Coderay
|
331
|
+
# @param [Symbol] kind the kind of that token
|
332
|
+
def track_module_nesting(token, kind)
|
333
|
+
if kind == :keyword && (token == "class" || token == "module")
|
334
|
+
@module_nesting << [token, nil]
|
335
|
+
@awaiting_class = true
|
336
|
+
elsif @awaiting_class
|
337
|
+
if kind == :operator && token == "<<" && @module_nesting.last[0] == "class"
|
338
|
+
@module_nesting.last[0] = "class <<"
|
339
|
+
@awaiting_class = true
|
340
|
+
elsif kind == :class && token =~ /\A(self|[A-Z:][A-Za-z0-9_:]*)\z/
|
341
|
+
@module_nesting.last[1] = token if kind == :class
|
342
|
+
@awaiting_class = false
|
343
|
+
else
|
344
|
+
# leave @module_nesting[-1]
|
345
|
+
@awaiting_class = false
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Update the internal state relating to module nesting on 'end'.
|
351
|
+
#
|
352
|
+
# If the current 'end' pairs up with a class or a module then we should
|
353
|
+
# pop an array off of @module_nesting
|
354
|
+
#
|
355
|
+
# @param [String] token a token from Coderay
|
356
|
+
# @param [Symbol] kind the kind of that token
|
357
|
+
def track_module_nesting_end(token, kind=:keyword)
|
358
|
+
if kind == :keyword && (token == "class" || token == "module")
|
359
|
+
@module_nesting.pop
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Return a list of strings which can be used to re-construct the Module.nesting at
|
364
|
+
# the current point in the file.
|
365
|
+
#
|
366
|
+
# Returns nil if the syntax of the file was not recognizable.
|
367
|
+
#
|
368
|
+
# @return [Array<String>]
|
369
|
+
def module_nesting
|
370
|
+
@module_nesting.map do |(kind, token)|
|
371
|
+
raise UnparseableNestingError, @module_nesting.inspect if token.nil?
|
372
|
+
|
373
|
+
"#{kind} #{token}"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# Return a string which, when printed, will rewrite the previous line with
|
378
|
+
# the correct indentation. Mostly useful for fixing 'end'.
|
379
|
+
#
|
380
|
+
# @param [String] prompt The user's prompt
|
381
|
+
# @param [String] code The code the user just typed in.
|
382
|
+
# @param [Fixnum] overhang (0) The number of chars to erase afterwards (i.e.,
|
383
|
+
# the difference in length between the old line and the new one).
|
384
|
+
# @return [String]
|
385
|
+
def correct_indentation(prompt, code, overhang=0)
|
386
|
+
prompt = prompt.delete("\001\002")
|
387
|
+
line_to_measure = Pry::Helpers::Text.strip_color(prompt) << code
|
388
|
+
whitespace = ' ' * overhang
|
389
|
+
|
390
|
+
_, cols = Terminal.screen_size
|
391
|
+
|
392
|
+
cols = cols.to_i
|
393
|
+
lines = (cols != 0 ? (line_to_measure.length / cols + 1) : 1).to_i
|
394
|
+
|
395
|
+
if Pry::Helpers::BaseHelpers.windows_ansi?
|
396
|
+
move_up = "\e[#{lines}F"
|
397
|
+
move_down = "\e[#{lines}E"
|
398
|
+
else
|
399
|
+
move_up = "\e[#{lines}A\e[0G"
|
400
|
+
move_down = "\e[#{lines}B\e[0G"
|
401
|
+
end
|
402
|
+
|
403
|
+
"#{move_up}#{prompt}#{colorize_code(code)}#{whitespace}#{move_down}"
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|