1" Vim completion script
2" Language:             Ruby
3" Maintainer:           Mark Guzman <[email protected]>
4" Info:                 $Id$
5" URL:                  http://vim-ruby.rubyforge.org
6" Anon CVS:             See above site
7" Release Coordinator:  Doug Kearns <[email protected]>
8" ----------------------------------------------------------------------------
9"
10" Ruby IRB/Complete author: Keiju ISHITSUKA([email protected])
11" ----------------------------------------------------------------------------
12
13" {{{ requirement checks
14if !has('ruby')
15    echohl ErrorMsg
16    echo "Error: Required vim compiled with +ruby"
17    echohl None
18    finish
19endif
20
21if version < 700
22    echohl ErrorMsg
23    echo "Error: Required vim >= 7.0"
24    echohl None
25    finish
26endif
27" }}} requirement checks
28
29if !exists("g:rubycomplete_rails")
30    let g:rubycomplete_rails = 0
31endif
32
33if !exists("g:rubycomplete_classes_in_global")
34    let g:rubycomplete_classes_in_global = 0
35endif
36
37" {{{ vim-side support functions
38function! GetBufferRubyModule(name)
39    let [snum,enum] = GetBufferRubyEntity(a:name, "module")
40    return snum . '..' . enum
41endfunction
42
43function! GetBufferRubyClass(name)
44    let [snum,enum] = GetBufferRubyEntity(a:name, "class")
45    return snum . '..' . enum
46endfunction
47
48function! GetBufferRubySingletonMethods(name)
49endfunction
50
51function! GetBufferRubyEntity( name, type )
52    let stopline = 1
53    let crex = '^\s*' . a:type . '\s*' . a:name . '\s*\(<\s*.*\s*\)\?\n*\(\(\s\|#\).*\n*\)*\n*\s*end$'
54    let [lnum,lcol] = searchpos( crex, 'nbw')
55    if lnum == 0 && lcol == 0
56        return [0,0]
57    endif
58
59    let [enum,ecol] = searchpos( crex, 'nebw')
60    if lnum > enum
61        let realdef = getline( lnum )
62        let crexb = '^' . realdef . '\n*\(\(\s\|#\).*\n*\)*\n*\s*end$'
63        let [enum,ecol] = searchpos( crexb, 'necw' )
64    endif
65    " we found a the class def
66    return [lnum,enum]
67endfunction
68
69function! IsInClassDef()
70    let [snum,enum] = GetBufferRubyEntity( '.*', "class" )
71    let ret = 'nil'
72    let pos = line('.')
73
74    if snum < pos && pos < enum
75        let ret = snum . '..' . enum
76    endif
77
78    return ret
79endfunction
80
81function! GetRubyVarType(v)
82    let stopline = 1
83    let vtp = ''
84    let pos = getpos('.')
85    let [lnum,lcol] = searchpos('^\s*#\s*@var\s*'.a:v.'\>\s\+[^ \t]\+\s*$','nb',stopline)
86    if lnum != 0 && lcol != 0
87        call setpos('.',pos)
88        let str = getline(lnum)
89        let vtp = substitute(str,'^\s*#\s*@var\s*'.a:v.'\>\s\+\([^ \t]\+\)\s*$','\1','')
90        return vtp
91    endif
92    call setpos('.',pos)
93    if g:rubycomplete_rails == 1 && g:rubycomplete_rails_loaded == 1
94        let ctors = '\(now\|new\|open\|get_instance\|find\|create\)'
95    else
96        let ctors = '\(now\|new\|open\|get_instance\)'
97    endif
98
99    let [lnum,lcol] = searchpos(''.a:v.'\>\s*[+\-*/]*=\s*\([^ \t]\+.' . ctors .'\>\|[\[{"''/]\|%r{\|[A-Za-z0-9@:\-()]\+...\?\)','nb',stopline)
100    if lnum != 0 && lcol != 0
101        let str = matchstr(getline(lnum),'=\s*\([^ \t]\+.' . ctors . '\>\|[\[{"''/]\|%r{\|[A-Za-z0-9@:\-()]\+...\?\)',lcol)
102        let str = substitute(str,'^=\s*','','')
103        call setpos('.',pos)
104        if str == '"' || str == ''''
105            return 'String'
106        elseif str == '['
107            return 'Array'
108        elseif str == '{'
109            return 'Hash'
110        elseif str == '/' || str == '%r{'
111            return 'Regexp'
112        elseif strlen(str) >= 4 && stridx(str,'..') != -1
113            return 'Range'
114        elseif strlen(str) > 4
115            let l = stridx(str,'.')
116            return str[0:l-1]
117        end
118        return ''
119    endif
120    call setpos('.',pos)
121    return ''
122endfunction
123
124"}}} vim-side support functions
125
126function! rubycomplete#Complete(findstart, base)
127     "findstart = 1 when we need to get the text length
128    if a:findstart
129        let line = getline('.')
130        let idx = col('.')
131        while idx > 0
132            let idx -= 1
133            let c = line[idx-1]
134            if c =~ '\w'
135                continue
136            elseif ! c =~ '\.'
137                idx = -1
138                break
139            else
140                break
141            endif
142        endwhile
143
144        return idx
145    "findstart = 0 when we need to return the list of completions
146    else
147        let g:rubycomplete_completions = []
148        execute "ruby get_completions('" . a:base . "')"
149        return g:rubycomplete_completions
150    endif
151endfunction
152
153
154function! s:DefRuby()
155ruby << RUBYEOF
156# {{{ ruby completion
157RailsWords = [
158      "has_many", "has_one",
159      "belongs_to",
160    ]
161
162ReservedWords = [
163      "BEGIN", "END",
164      "alias", "and",
165      "begin", "break",
166      "case", "class",
167      "def", "defined", "do",
168      "else", "elsif", "end", "ensure",
169      "false", "for",
170      "if", "in",
171      "module",
172      "next", "nil", "not",
173      "or",
174      "redo", "rescue", "retry", "return",
175      "self", "super",
176      "then", "true",
177      "undef", "unless", "until",
178      "when", "while",
179      "yield",
180    ]
181
182Operators = [ "%", "&", "*", "**", "+",  "-",  "/",
183      "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>",
184      "[]", "[]=", "^", ]
185
186
187def load_requires
188  buf = VIM::Buffer.current
189  enum = buf.line_number
190  nums = Range.new( 1, enum )
191  nums.each do |x|
192    ln = buf[x]
193    begin
194      eval( "require %s" % $1 ) if /.*require\s*(.*)$/.match( ln )
195    rescue Exception
196      #ignore?
197    end
198  end
199end
200
201def load_buffer_class(name)
202  classdef = get_buffer_entity(name, 'GetBufferRubyClass("%s")')
203  return if classdef == nil
204
205  pare = /^\s*class\s*(.*)\s*<\s*(.*)\s*\n/.match( classdef )
206  load_buffer_class( $2 ) if pare != nil
207
208  mixre = /.*\n\s*include\s*(.*)\s*\n/.match( classdef )
209  load_buffer_module( $2 ) if mixre != nil
210
211  eval classdef
212end
213
214def load_buffer_module(name)
215  classdef = get_buffer_entity(name, 'GetBufferRubyModule("%s")')
216  return if classdef == nil
217
218  eval classdef
219end
220
221def get_buffer_entity(name, vimfun)
222  return nil if /(\"|\')+/.match( name )
223  buf = VIM::Buffer.current
224  nums = eval( VIM::evaluate( vimfun % name ) )
225  return nil if nums == nil
226  return nil if nums.min == nums.max && nums.min == 0
227
228  cur_line = VIM::Buffer.current.line_number
229  classdef = ""
230  nums.each do |x|
231    if x != cur_line
232      ln = buf[x]
233      classdef += "%s\n" % ln
234    end
235  end
236
237  return classdef
238end
239
240def get_var_type( receiver )
241  if /(\"|\')+/.match( receiver )
242    "String"
243  else
244    VIM::evaluate("GetRubyVarType('%s')" % receiver)
245  end
246end
247
248def get_buffer_classes()
249  # this will be a little expensive.
250  allow_aggressive_load = VIM::evaluate('g:rubycomplete_classes_in_global')
251  return [] if allow_aggressive_load != '1'
252
253  buf = VIM::Buffer.current
254  eob = buf.length
255  ret = []
256  rg = 1..eob
257
258  rg.each do |x|
259    if /^\s*class\s*([A-Za-z0-9_-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*/.match( buf[x] )
260      ret.push $1
261    end
262  end
263
264  return ret
265end
266
267def load_rails()
268  allow_rails = VIM::evaluate('g:rubycomplete_rails')
269  return if allow_rails != '1'
270
271  buf_path = VIM::evaluate('expand("%:p")')
272  file_name = VIM::evaluate('expand("%:t")')
273  path = buf_path.gsub( file_name, '' )
274  path.gsub!( /\\/, "/" )
275  pup = [ "./", "../", "../../", "../../../", "../../../../" ]
276  pok = nil
277
278  pup.each do |sup|
279    tpok = "%s%sconfig" % [ path, sup ]
280    if File.exists?( tpok )
281      pok = tpok
282      break
283    end
284  end
285
286  return if pok == nil
287
288  bootfile = pok + "/boot.rb"
289  envfile = pok + "/environment.rb"
290  if File.exists?( bootfile ) && File.exists?( envfile )
291    begin
292      require bootfile
293      require envfile
294      require 'console_app'
295      require 'console_with_helpers'
296      VIM::command('let g:rubycomplete_rails_loaded = 1')
297    rescue
298      print "Error loading rails environment"
299    end
300  end
301end
302
303def get_rails_helpers
304  allow_rails = VIM::evaluate('g:rubycomplete_rails')
305  rails_loaded = VIM::evaluate('g:rubycomplete_rails_loaded')
306  return [] if allow_rails != '1' || rails_loaded != '1'
307  return RailsWords
308end
309
310def get_completions(base)
311  load_requires
312  load_rails
313
314  input = VIM::Buffer.current.line
315  cpos = VIM::Window.current.cursor[1] - 1
316  input = input[0..cpos] if cpos != 0
317  input += base
318
319  rip = input.rindex(/\s/,cpos)
320  if rip
321    input = input[rip..input.length]
322  end
323
324  asn = /^.*(\+|\-|\*|=|\(|\[)=?(\s*[A-Za-z0-9_:@.-]*)(\s*(\{|\+|\-|\*|\%|\/)?\s*).*/
325  if asn.match(input)
326    input = $2
327  end
328
329  input.strip!
330  message = nil
331  receiver = nil
332  candidates = []
333
334  case input
335    when /^(\/[^\/]*\/)\.([^.]*)$/ # Regexp
336      receiver = $1
337      message = Regexp.quote($2)
338      candidates = Regexp.instance_methods(true)
339
340    when /^([^\]]*\])\.([^.]*)$/ # Array
341      receiver = $1
342      message = Regexp.quote($2)
343      candidates = Array.instance_methods(true)
344
345    when /^([^\}]*\})\.([^.]*)$/ # Proc or Hash
346      receiver = $1
347      message = Regexp.quote($2)
348      candidates = Proc.instance_methods(true) | Hash.instance_methods(true)
349
350    when /^(:[^:.]*)$/ # Symbol
351      if Symbol.respond_to?(:all_symbols)
352        receiver = $1
353        candidates = Symbol.all_symbols.collect{|s| s.id2name}
354        candidates.delete_if { |c| c.match( /'/ ) }
355      end
356
357    when /^::([A-Z][^:\.\(]*)$/ # Absolute Constant or class methods
358      receiver = $1
359      candidates = Object.constants
360      candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
361
362    when /^(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/ # Constant or class methods
363      receiver = $1
364      message = Regexp.quote($4)
365      begin
366        candidates = eval("#{receiver}.constants | #{receiver}.methods")
367      rescue Exception
368        candidates = []
369      end
370      candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
371
372    when /^(:[^:.]+)\.([^.]*)$/ # Symbol
373      receiver = $1
374      message = Regexp.quote($2)
375      candidates = Symbol.instance_methods(true)
376
377    when /^([0-9_]+(\.[0-9_]+)?(e[0-9]+)?)\.([^.]*)$/ # Numeric
378      receiver = $1
379      message = Regexp.quote($4)
380      begin
381        candidates = eval(receiver).methods
382      rescue Exception
383        candidates
384      end
385
386    when /^(\$[^.]*)$/ #global
387      candidates = global_variables.grep(Regexp.new(Regexp.quote($1)))
388
389    when /^((\.?[^.]+)+)\.([^.]*)$/ # variable
390      receiver = $1
391      message = Regexp.quote($3)
392      load_buffer_class( receiver )
393
394      cv = eval("self.class.constants")
395      vartype = get_var_type( receiver )
396      if vartype != ''
397        load_buffer_class( vartype )
398
399        begin
400          candidates = eval("#{vartype}.instance_methods")
401        rescue Exception
402          candidates = []
403        end
404      elsif (cv).include?(receiver)
405        # foo.func and foo is local var.
406        candidates = eval("#{receiver}.methods")
407      elsif /^[A-Z]/ =~ receiver and /\./ !~ receiver
408        # Foo::Bar.func
409        begin
410          candidates = eval("#{receiver}.methods")
411        rescue Exception
412          candidates = []
413        end
414      else
415        # func1.func2
416        candidates = []
417        ObjectSpace.each_object(Module){|m|
418          next if m.name != "IRB::Context" and
419            /^(IRB|SLex|RubyLex|RubyToken)/ =~ m.name
420          candidates.concat m.instance_methods(false)
421        }
422      end
423
424    when /^\(?\s*[A-Za-z0-9:^@.%\/+*\(\)]+\.\.\.?[A-Za-z0-9:^@.%\/+*\(\)]+\s*\)?\.([^.]*)/
425      message = $1
426      candidates = Range.instance_methods(true)
427
428    when /^\[(\s*[A-Za-z0-9:^@.%\/+*\(\)\[\]\{\}.\'\"],?)*\].([^.]*)/
429      message = $2
430      candidates = Array.instance_methods(true)
431
432    when /^\.([^.]*)$/ # unknown(maybe String)
433      message = Regexp.quote($1)
434      candidates = String.instance_methods(true)
435
436  else
437    inclass = eval( VIM::evaluate("IsInClassDef()") )
438
439    if inclass != nil
440      classdef = "%s\n" % VIM::Buffer.current[ inclass.min ]
441      found = /^\s*class\s*([A-Za-z0-9_-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*\n$/.match( classdef )
442
443      if found != nil
444        receiver = $1
445        message = input
446        load_buffer_class( receiver )
447        begin
448          candidates = eval( "#{receiver}.instance_methods" )
449          candidates += get_rails_helpers
450        rescue Exception
451          found = nil
452        end
453      end
454    end
455
456    if inclass == nil || found == nil
457      candidates = eval("self.class.constants")
458      candidates += get_buffer_classes
459      message = receiver = input
460    end
461  end
462
463  candidates.delete_if { |x| x == nil }
464  candidates.uniq!
465  candidates.sort!
466  candidates = candidates.grep(/^#{Regexp.quote(message)}/) if message != nil
467
468  outp = ""
469  valid = (candidates-Object.instance_methods)
470
471  rg = 0..valid.length
472  rg.step(150) do |x|
473    stpos = 0+x
474    enpos = 150+x
475    valid[stpos..enpos].each { |c| outp += "{'word':'%s','item':'%s'}," % [ c, c ] }
476    outp.sub!(/,$/, '')
477
478    VIM::command("call extend(g:rubycomplete_completions, [%s])" % outp)
479    outp = ""
480  end
481end
482
483# }}} ruby completion
484RUBYEOF
485endfunction
486
487let g:rubycomplete_rails_loaded = 0
488
489call s:DefRuby()
490" vim:tw=78:sw=4:ts=8:et:ft=vim:norl:
491