1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
# frozen_string_literal: true
# taken from irb
# Implements tab completion for Readline in Pry
class Pry
class InputCompleter
NUMERIC_REGEXP = /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/.freeze
ARRAY_REGEXP = /^([^\]]*\])\.([^.]*)$/.freeze
SYMBOL_REGEXP = /^(:[^:.]*)$/.freeze
SYMBOL_METHOD_CALL_REGEXP = /^(:[^:.]+)\.([^.]*)$/.freeze
REGEX_REGEXP = %r{^(/[^/]*/)\.([^.]*)$}.freeze
PROC_OR_HASH_REGEXP = /^([^\}]*\})\.([^.]*)$/.freeze
TOPLEVEL_LOOKUP_REGEXP = /^::([A-Z][^:\.\(]*)$/.freeze
CONSTANT_REGEXP = /^([A-Z][A-Za-z0-9]*)$/.freeze
CONSTANT_OR_METHOD_REGEXP = /^([A-Z].*)::([^:.]*)$/.freeze
HEX_REGEXP = /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/.freeze
GLOBALVARIABLE_REGEXP = /^(\$[^.]*)$/.freeze
VARIABLE_REGEXP = /^([^."].*)\.([^.]*)$/.freeze
RESERVED_WORDS = %w[
BEGIN END
alias and
begin break
case class
def defined do
else elsif end ensure
false for
if in
module
next nil not
or
redo rescue retry return
self super
then true
undef unless until
when while
yield
].freeze
WORD_ESCAPE_STR = " \t\n\"\\'`><=;|&{(".freeze
def initialize(input, pry = nil)
@pry = pry
@input = input
if @input.respond_to?(:basic_word_break_characters=)
@input.basic_word_break_characters = WORD_ESCAPE_STR
end
return unless @input.respond_to?(:completion_append_character=)
@input.completion_append_character = nil
end
# Return a new completion proc for use by Readline.
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def call(str, options = {})
custom_completions = options[:custom_completions] || []
# if there are multiple contexts e.g. cd 1/2/3
# get new target for 1/2 and find candidates for 3
path, input = build_path(str)
if path.call.empty?
target = options[:target]
else
# Assume the user is tab-completing the 'cd' command
begin
target = Pry::ObjectPath.new(path.call, @pry.binding_stack).resolve.last
# but if that doesn't work, assume they're doing division with no spaces
rescue Pry::CommandError
target = options[:target]
end
end
begin
bind = target
# Complete stdlib symbols
case input
when REGEX_REGEXP # Regexp
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
candidates = Regexp.instance_methods.collect(&:to_s)
select_message(path, receiver, message, candidates)
when ARRAY_REGEXP # Array
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
candidates = Array.instance_methods.collect(&:to_s)
select_message(path, receiver, message, candidates)
when PROC_OR_HASH_REGEXP # Proc or Hash
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
candidates = Proc.instance_methods.collect(&:to_s)
candidates |= Hash.instance_methods.collect(&:to_s)
select_message(path, receiver, message, candidates)
when SYMBOL_REGEXP # Symbol
if Symbol.respond_to?(:all_symbols)
sym = Regexp.quote(Regexp.last_match(1))
candidates = Symbol.all_symbols.collect { |s| ":" + s.id2name }
candidates.grep(/^#{sym}/)
else
[]
end
when TOPLEVEL_LOOKUP_REGEXP # Absolute Constant or class methods
receiver = Regexp.last_match(1)
candidates = Object.constants.collect(&:to_s)
candidates.grep(/^#{receiver}/).collect { |e| "::" + e }
when CONSTANT_REGEXP # Constant
message = Regexp.last_match(1)
begin
context = target.eval("self")
context = context.class unless context.respond_to? :constants
candidates = context.constants.collect(&:to_s)
rescue StandardError
candidates = []
end
candidates = candidates.grep(/^#{message}/).collect(&path)
when CONSTANT_OR_METHOD_REGEXP # Constant or class methods
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
begin
candidates = eval( # rubocop:disable Security/Eval
"#{receiver}.constants.collect(&:to_s)", bind, __FILE__, __LINE__
)
candidates |= eval( # rubocop:disable Security/Eval
"#{receiver}.methods.collect(&:to_s)", bind, __FILE__, __LINE__
)
rescue Pry::RescuableException
candidates = []
end
candidates.grep(/^#{message}/).collect { |e| receiver + "::" + e }
when SYMBOL_METHOD_CALL_REGEXP # method call on a Symbol
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
candidates = Symbol.instance_methods.collect(&:to_s)
select_message(path, receiver, message, candidates)
when NUMERIC_REGEXP
# Numeric
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(5))
begin
# rubocop:disable Security/Eval
candidates = eval(receiver, bind).methods.collect(&:to_s)
# rubocop:enable Security/Eval
rescue Pry::RescuableException
candidates = []
end
select_message(path, receiver, message, candidates)
when HEX_REGEXP
# Numeric(0xFFFF)
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
begin
# rubocop:disable Security/Eval
candidates = eval(receiver, bind).methods.collect(&:to_s)
# rubocop:enable Security/Eval
rescue Pry::RescuableException
candidates = []
end
select_message(path, receiver, message, candidates)
when GLOBALVARIABLE_REGEXP # global
regmessage = Regexp.new(Regexp.quote(Regexp.last_match(1)))
candidates = global_variables.collect(&:to_s).grep(regmessage)
when VARIABLE_REGEXP # variable
receiver = Regexp.last_match(1)
message = Regexp.quote(Regexp.last_match(2))
gv = eval("global_variables", bind, __FILE__, __LINE__).collect(&:to_s)
lv = eval("local_variables", bind, __FILE__, __LINE__).collect(&:to_s)
cv = eval("self.class.constants", bind, __FILE__, __LINE__).collect(&:to_s)
if (gv | lv | cv).include?(receiver) || /^[A-Z]/ =~ receiver && /\./ !~ receiver
# foo.func and foo is local var. OR
# Foo::Bar.func
begin
candidates = eval( # rubocop:disable Security/Eval
"#{receiver}.methods", bind, __FILE__, __LINE__
).collect(&:to_s)
rescue Pry::RescuableException
candidates = []
end
else
# func1.func2
require 'set'
candidates = Set.new
to_ignore = ignored_modules
ObjectSpace.each_object(Module) do |m|
next if begin
to_ignore.include?(m)
rescue StandardError
true
end
# jruby doesn't always provide #instance_methods() on each
# object.
if m.respond_to?(:instance_methods)
candidates.merge m.instance_methods(false).collect(&:to_s)
end
end
end
select_message(path, receiver, message, candidates.sort)
when /^\.([^.]*)$/
# Unknown(maybe String)
receiver = ""
message = Regexp.quote(Regexp.last_match(1))
candidates = String.instance_methods(true).collect(&:to_s)
select_message(path, receiver, message, candidates)
else
candidates = eval(
"methods | private_methods | local_variables | " \
"self.class.constants | instance_variables",
bind, __FILE__, __LINE__ - 2
).collect(&:to_s)
if eval("respond_to?(:class_variables)", bind, __FILE__, __LINE__)
candidates += eval(
"class_variables", bind, __FILE__, __LINE__
).collect(&:to_s)
end
candidates =
(candidates | RESERVED_WORDS | custom_completions)
.grep(/^#{Regexp.quote(input)}/)
candidates.collect(&path)
end
rescue Pry::RescuableException
[]
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
def select_message(path, receiver, message, candidates)
candidates.grep(/^#{message}/).collect do |e|
next unless e =~ /^[a-zA-Z_]/
path.call(receiver + "." + e)
end.compact
end
# build_path seperates the input into two parts: path and input.
# input is the partial string that should be completed
# path is a proc that takes an input and builds a full path.
def build_path(input)
# check to see if the input is a regex
return proc { |i| i.to_s }, input if input[%r{/\.}]
trailing_slash = input.end_with?('/')
contexts = input.chomp('/').split(%r{/})
input = contexts[-1]
path = proc do |i|
p = contexts[0..-2].push(i).join('/')
p += '/' if trailing_slash && !i.nil?
p
end
[path, input]
end
def ignored_modules
# We could cache the result, but IRB is not loaded by default.
# And this is very fast anyway.
# By using this approach, we avoid Module#name calls, which are
# relatively slow when there are a lot of anonymous modules defined.
s = Set.new
scanner = lambda do |m|
next if s.include?(m) # IRB::ExtendCommandBundle::EXCB recurses.
s << m
m.constants(false).each do |c|
value = m.const_get(c)
scanner.call(value) if value.is_a?(Module)
end
end
# FIXME: Add Pry here as well?
%i[IRB SLex RubyLex RubyToken].each do |module_name|
next unless Object.const_defined?(module_name)
scanner.call(Object.const_get(module_name))
end
s.delete(IRB::Context) if defined?(IRB::Context)
s
end
end
end
|