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.



72
73
74
# File 'lib/irb/ext/completion.rb', line 72

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

#format_methods(receiver, methods, filter) ⇒ Object



144
145
146
# File 'lib/irb/ext/completion.rb', line 144

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

#instance_methodsObject



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

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

#instance_methods_of(klass) ⇒ Object



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

def instance_methods_of(klass)
  evaluate(klass).instance_methods
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



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/irb/ext/completion.rb', line 126

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

#methods_of_object(root) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/irb/ext/completion.rb', line 148

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



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/irb/ext/completion.rb', line 166

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 :@gvar
      eval(name).methods if global_variables.include?(name.to_sym)
    when :@const
      evaluate(name).methods if constants.include?(name)
    end
  end
end

#resultsObject



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

def results
  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
  
  if sexp = Ripper::SexpBuilder.new(source).parse
    # [:program, [:stmts_add, [:stmts_new], [x, …]]]
    #                                        ^
    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
    
    if call
      format_methods(receiver, methods || methods_of_object(root), filter)
    else
      match_methods_vars_or_consts_in_scope(root)
    end.sort.uniq
  end
end

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



116
117
118
119
120
121
122
123
124
# File 'lib/irb/ext/completion.rb', line 116

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