Class: Pry::InputCompleter

Inherits:
Object show all
Defined in:
lib/pry/input_completer.rb

Constant Summary collapse

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

Instance Method Summary collapse

Constructor Details

#initialize(input, pry = nil) ⇒ InputCompleter

Returns a new instance of InputCompleter


42
43
44
45
46
47
48
49
50
51
52
# File 'lib/pry/input_completer.rb', line 42

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

Instance Method Details

#build_path(input) ⇒ Object

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.


240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/pry/input_completer.rb', line 240

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

#call(str, options = {}) ⇒ Object

Return a new completion proc for use by Readline. rubocop:disable Metrics/AbcSize, Metrics/MethodLength


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
# File 'lib/pry/input_completer.rb', line 56

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

#ignored_modulesObject


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
# File 'lib/pry/input_completer.rb', line 255

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?
  [: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

#select_message(path, receiver, message, candidates) ⇒ Object

rubocop:enable Metrics/AbcSize, Metrics/MethodLength


229
230
231
232
233
234
235
# File 'lib/pry/input_completer.rb', line 229

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