Class: IRB::Completion

Inherits:
Object
  • Object
show all
Defined in:
lib/irb/ext/completion.rb

Constant Summary collapse

TYPE =

Convenience constants for sexp access of Ripper::SexpBuilder.

0
VALUE =
1
CALLEE =
3
RESERVED_UPCASE_WORDS =
%w{
  BEGIN  END
}
RESERVED_DOWNCASE_WORDS =
%w{
  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
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#sourceObject (readonly)

Returns the value of attribute source.



39
40
41
# File 'lib/irb/ext/completion.rb', line 39

def source
  @source
end

Instance Method Details

#call(source) ⇒ Object

Returns an array of possible completion results, with the current IRB::Context.

This is meant to be used with Readline which takes a completion proc.



49
50
51
52
# File 'lib/irb/ext/completion.rb', line 49

def call(source)
  @source = source
  results
end

#constantsObject

TODO: test and or fix the fact that we need to get constants from the singleton class.



80
81
82
# File 'lib/irb/ext/completion.rb', line 80

def constants
  evaluate('Object.constants + self.class.constants + (class << self; constants; end)').map(&:to_s)
end

#contextObject



41
42
43
# File 'lib/irb/ext/completion.rb', line 41

def context
  IRB::Driver.current.context
end

#evaluate(s) ⇒ Object



54
55
56
# File 'lib/irb/ext/completion.rb', line 54

def evaluate(s)
  context.__evaluate__(s)
end

#expand_path(source) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/irb/ext/completion.rb', line 131

def expand_path(source)
  tokens         = Ripper.lex(source)
  path           = tokens[0][2]
  string_open    = tokens[1][2]
  end_with_slash = path.length > 1 && path.end_with?('/')
  path           = File.expand_path(path)
  path          << '/' if end_with_slash
  Dir.glob("#{path}*", File::FNM_CASEFOLD).map { |f| "#{string_open}#{f}" }
end

#format_methods(receiver, methods, filter) ⇒ Object



172
173
174
# File 'lib/irb/ext/completion.rb', line 172

def format_methods(receiver, methods, filter)
  (filter ? methods.grep(/^#{filter}/) : methods).map { |m| "#{receiver}.#{m}" }
end

#global_variablesObject



66
67
68
# File 'lib/irb/ext/completion.rb', line 66

def global_variables
  super.map(&:to_s)
end

#instance_methodsObject



70
71
72
# File 'lib/irb/ext/completion.rb', line 70

def instance_methods
  context.object.methods.map(&:to_s)
end

#instance_methods_of(klass) ⇒ Object



74
75
76
# File 'lib/irb/ext/completion.rb', line 74

def instance_methods_of(klass)
  evaluate(klass).instance_methods
end

#instance_variablesObject



62
63
64
# File 'lib/irb/ext/completion.rb', line 62

def instance_variables
  context.object.instance_variables.map(&:to_s)
end

#local_variablesObject



58
59
60
# File 'lib/irb/ext/completion.rb', line 58

def local_variables
  evaluate('local_variables').map(&:to_s)
end

#match_methods_vars_or_consts_in_scope(symbol) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/irb/ext/completion.rb', line 151

def match_methods_vars_or_consts_in_scope(symbol)
  var    = symbol[VALUE]
  filter = var[VALUE]
  result = case var[TYPE]
  when :@ident
    local_variables + instance_methods + RESERVED_DOWNCASE_WORDS
  when :@ivar
    instance_variables
  when :@gvar
    global_variables
  when :@const
    if symbol[TYPE] == :top_const_ref
      filter = "::#{filter}"
      Object.constants.map { |c| "::#{c}" }
    else
      constants + RESERVED_UPCASE_WORDS
    end
  end
  (result && filter) ? result.grep(/^#{Regexp.quote(filter)}/) : result
end

#methods_of_object(root) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/irb/ext/completion.rb', line 176

def methods_of_object(root)
  result = case root[TYPE]
  # [:unary, :-@, [x, …]]
  #               ^
  when :unary                          then return methods_of_object(root[2]) # TODO: do we really need this?
  when :var_ref, :top_const_ref        then return methods_of_object_in_variable(root)
  when :array, :words_add, :qwords_add then Array
  when :@int                           then Fixnum
  when :@float                         then Float
  when :hash                           then Hash
  when :lambda                         then Proc
  when :dot2, :dot3                    then Range
  when :regexp_literal                 then Regexp
  when :string_literal                 then String
  when :symbol_literal, :dyna_symbol   then Symbol
  end.instance_methods
end

#methods_of_object_in_variable(path) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/irb/ext/completion.rb', line 194

def methods_of_object_in_variable(path)
  type, name = path[VALUE][0..1]
  
  if path[TYPE] == :top_const_ref
    if type == :@const && Object.constants.include?(name.to_sym)
      evaluate("::#{name}").methods
    end
  else
    case type
    when :@ident
      evaluate(name).methods if local_variables.include?(name)
    when :@ivar
      evaluate(name).methods if instance_variables.include?(name)
    when :@gvar
      eval(name).methods if global_variables.include?(name)
    when :@const
      evaluate(name).methods if constants.include?(name)
    end
  end
end

#resultsObject



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
# File 'lib/irb/ext/completion.rb', line 84

def results
  return if @source.strip.empty?

  source = @source
  filter = nil
  
  # if ends with period, remove it to remove the syntax error it causes
  call = (source[-1,1] == '.')
  receiver = source = source[0..-2] if call
  
  # root node:
  # [:program, [:stmts_add, [:stmts_new], [x, …]]]
  #                                        ^
  if (sexp = Ripper::SexpBuilder.new(source).parse) && root = sexp[1][2]
    # [:call, [:hash, nil], :".", [:@ident, x, …]]
    if root[TYPE] == :call
      call  = true
      stack = unwind_callstack(root)
      # [[:var_ref, [:@const, "Klass", [1, 0]]], [:call, "new"]]
      # [[:var_ref, [:@ident, "klass", [1, 0]]], [:call, "new"], [:call, "filter"]]
      if stack[1][VALUE] == 'new'
        klass    = stack[0][VALUE][VALUE]
        filter   = stack[2][VALUE] if stack[2]
        receiver = "#{klass}.new"
        methods  = instance_methods_of(klass)
      else
        filter   = root[CALLEE][VALUE]
        filter   = stack[1][VALUE]
        receiver = source[0..-(filter.length + 2)]
        root     = root[VALUE]
      end
    end
    
    result = if call
      if m = (methods || methods_of_object(root))
        format_methods(receiver, m, filter)
      end
    elsif root[TYPE] == :string_literal && root[VALUE][TYPE] == :string_content
      # in the form of: "~/code/
      expand_path(source)
    else
      match_methods_vars_or_consts_in_scope(root)
    end
    result.sort.uniq if result
  end
end

#unwind_callstack(root, stack = []) ⇒ Object



141
142
143
144
145
146
147
148
149
# File 'lib/irb/ext/completion.rb', line 141

def unwind_callstack(root, stack = [])
  if root[TYPE] == :call
    stack.unshift [:call, root[CALLEE][VALUE]]
    unwind_callstack(root[VALUE], stack)
  else
    stack.unshift root
  end
  stack
end