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    let [lnum,lcol] = searchpos(''.a:v.'\>\s*[+\-*/]*=\s*\([^ \t]\+.\(now\|new\|open\|get_instance\)\>\|[\[{"''/]\|%r{\)','nb',stopline)
94	if lnum != 0 && lcol != 0
95        let str = matchstr(getline(lnum),'=\s*\([^ \t]\+.\(now\|new\|open\|get_instance\)\>\|[\[{"''/]\|%r{\)',lcol)
96		let str = substitute(str,'^=\s*','','')
97		call setpos('.',pos)
98		if str == '"' || str == ''''
99			return 'String'
100		elseif str == '['
101			return 'Array'
102		elseif str == '{'
103			return 'Hash'
104        elseif str == '/' || str == '%r{'
105            return 'Regexp'
106		elseif strlen(str) > 4
107            let l = stridx(str,'.')
108			return str[0:l-1]
109		end
110		return ''
111	endif
112	call setpos('.',pos)
113    return ''
114endfunction
115
116"}}} vim-side support functions
117
118function! rubycomplete#Complete(findstart, base)
119     "findstart = 1 when we need to get the text length
120    if a:findstart
121        let line = getline('.')
122        let idx = col('.')
123        while idx > 0
124            let idx -= 1
125            let c = line[idx-1]
126            if c =~ '\w'
127                continue
128            elseif ! c =~ '\.'
129                idx = -1
130                break
131            else
132                break
133            endif
134        endwhile
135
136        return idx
137    "findstart = 0 when we need to return the list of completions
138    else
139        let g:rubycomplete_completions = []
140        execute "ruby get_completions('" . a:base . "')"
141        return g:rubycomplete_completions
142    endif
143endfunction
144
145
146function! s:DefRuby()
147ruby << RUBYEOF
148# {{{ ruby completion
149RailsWords = [
150      "has_many", "has_one",
151      "belongs_to",
152    ]
153
154ReservedWords = [
155      "BEGIN", "END",
156      "alias", "and",
157      "begin", "break",
158      "case", "class",
159      "def", "defined", "do",
160      "else", "elsif", "end", "ensure",
161      "false", "for",
162      "if", "in",
163      "module",
164      "next", "nil", "not",
165      "or",
166      "redo", "rescue", "retry", "return",
167      "self", "super",
168      "then", "true",
169      "undef", "unless", "until",
170      "when", "while",
171      "yield",
172    ]
173
174Operators = [ "%", "&", "*", "**", "+",  "-",  "/",
175      "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>",
176      "[]", "[]=", "^", ]
177
178
179def load_requires
180  buf = VIM::Buffer.current
181  enum = buf.line_number
182  nums = Range.new( 1, enum )
183  nums.each do |x|
184    ln = buf[x]
185    begin
186      eval( "require %s" % $1 ) if /.*require\s*(.*)$/.match( ln )
187    rescue Exception
188      #ignore?
189    end
190  end
191end
192
193def load_buffer_class(name)
194  classdef = get_buffer_entity(name, 'GetBufferRubyClass("%s")')
195  return if classdef == nil
196
197  pare = /^\s*class\s*(.*)\s*<\s*(.*)\s*\n/.match( classdef )
198  load_buffer_class( $2 ) if pare != nil
199
200  mixre = /.*\n\s*include\s*(.*)\s*\n/.match( classdef )
201  load_buffer_module( $2 ) if mixre != nil
202
203  eval classdef
204end
205
206def load_buffer_module(name)
207  classdef = get_buffer_entity(name, 'GetBufferRubyModule("%s")')
208  return if classdef == nil
209
210  eval classdef
211end
212
213def get_buffer_entity(name, vimfun)
214  buf = VIM::Buffer.current
215  nums = eval( VIM::evaluate( vimfun % name ) )
216  return nil if nums == nil
217  return nil if nums.min == nums.max && nums.min == 0
218
219  cur_line = VIM::Buffer.current.line_number
220  classdef = ""
221  nums.each do |x|
222    if x != cur_line
223      ln = buf[x]
224      classdef += "%s\n" % ln
225    end
226  end
227
228  return classdef
229end
230
231def get_buffer_classes()
232  # this will be a little expensive.
233  allow_aggressive_load = VIM::evaluate('g:rubycomplete_classes_in_global')
234  return [] if allow_aggressive_load != '1'
235
236  buf = VIM::Buffer.current
237  eob = buf.length
238  ret = []
239  rg = 1..eob
240
241  rg.each do |x|
242    if /^\s*class\s*([A-Za-z0-9_-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*/.match( buf[x] )
243      ret.push $1
244    end
245  end
246
247  return ret
248end
249
250def load_rails()
251  allow_rails = VIM::evaluate('g:rubycomplete_rails')
252  return if allow_rails != '1'
253
254  buf_path = VIM::evaluate('expand("%:p")')
255  file_name = VIM::evaluate('expand("%:t")')
256  path = buf_path.gsub( file_name, '' )
257  path.gsub!( /\\/, "/" )
258  pup = [ "../", "../../", "../../../", "../../../../" ]
259  pok = nil
260
261  pup.each do |sup|
262    tpok = "%s%sconfig" % [ path, sup ]
263    if File.exists?( tpok )
264        pok = tpok
265        break
266    end
267  end
268
269  return if pok == nil
270  bootfile = pok + "/boot.rb"
271  if File.exists?( bootfile )
272    require bootfile
273    VIM::evaluate('let g:rubycomplete_rails_loaded = 1')
274  end
275end
276
277def get_rails_helpers
278  allow_rails = VIM::evaluate('g:rubycomplete_rails')
279  rails_loaded = VIM::evaluate('g:rubycomplete_rails_loaded')
280  return [] if allow_rails != '1' || rails_loaded != '1'
281  return RailsWords
282end
283
284def get_completions(base)
285  load_requires
286  load_rails
287
288  input = VIM::evaluate('expand("<cWORD>")')
289  input += base
290  input.lstrip!
291  if input.length == 0
292    input = VIM::Buffer.current.line
293    input.strip!
294  end
295  message = nil
296
297
298  case input
299    when /^(\/[^\/]*\/)\.([^.]*)$/
300      # Regexp
301      receiver = $1
302      message = Regexp.quote($2)
303
304      candidates = Regexp.instance_methods(true)
305      select_message(receiver, message, candidates)
306
307    when /^([^\]]*\])\.([^.]*)$/
308      # Array
309      receiver = $1
310      message = Regexp.quote($2)
311
312      candidates = Array.instance_methods(true)
313      select_message(receiver, message, candidates)
314
315    when /^([^\}]*\})\.([^.]*)$/
316      # Proc or Hash
317      receiver = $1
318      message = Regexp.quote($2)
319
320      candidates = Proc.instance_methods(true) | Hash.instance_methods(true)
321      select_message(receiver, message, candidates)
322
323    when /^(:[^:.]*)$/
324      # Symbol
325      if Symbol.respond_to?(:all_symbols)
326        sym = $1
327        candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name}
328        candidates.grep(/^#{sym}/)
329        candidates.delete_if do |c|
330            c.match( /'/ )
331        end
332        candidates.uniq!
333        candidates.sort!
334      else
335        []
336      end
337
338    when /^::([A-Z][^:\.\(]*)$/
339      # Absolute Constant or class methods
340      receiver = $1
341      candidates = Object.constants
342      candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
343
344    when /^(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/
345      # Constant or class methods
346      receiver = $1
347      message = Regexp.quote($4)
348      begin
349        candidates = eval("#{receiver}.constants | #{receiver}.methods")
350      rescue Exception
351        candidates = []
352      end
353      candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
354
355    when /^(:[^:.]+)\.([^.]*)$/
356      # Symbol
357      receiver = $1
358      message = Regexp.quote($2)
359
360      candidates = Symbol.instance_methods(true)
361      select_message(receiver, message, candidates)
362
363    when /^([0-9_]+(\.[0-9_]+)?(e[0-9]+)?)\.([^.]*)$/
364      # Numeric
365      receiver = $1
366      message = Regexp.quote($4)
367
368      begin
369        candidates = eval(receiver).methods
370      rescue Exception
371        candidates
372      end
373      select_message(receiver, message, candidates)
374
375    when /^(\$[^.]*)$/
376	  candidates = global_variables.grep(Regexp.new(Regexp.quote($1)))
377
378#   when /^(\$?(\.?[^.]+)+)\.([^.]*)$/
379    when /^((\.?[^.]+)+)\.([^.]*)$/
380      # variable
381      receiver = $1
382      message = Regexp.quote($3)
383      load_buffer_class( receiver )
384
385      cv = eval("self.class.constants")
386
387      vartype = VIM::evaluate("GetRubyVarType('%s')" % receiver)
388      if vartype != ''
389        load_buffer_class( vartype )
390
391        begin
392          candidates = eval("#{vartype}.instance_methods")
393        rescue Exception
394          candidates = []
395        end
396      elsif (cv).include?(receiver)
397        # foo.func and foo is local var.
398        candidates = eval("#{receiver}.methods")
399      elsif /^[A-Z]/ =~ receiver and /\./ !~ receiver
400        # Foo::Bar.func
401        begin
402          candidates = eval("#{receiver}.methods")
403        rescue Exception
404          candidates = []
405        end
406      else
407        # func1.func2
408        candidates = []
409        ObjectSpace.each_object(Module){|m|
410          next if m.name != "IRB::Context" and
411            /^(IRB|SLex|RubyLex|RubyToken)/ =~ m.name
412          candidates.concat m.instance_methods(false)
413        }
414        candidates.sort!
415        candidates.uniq!
416      end
417      #identify_type( receiver )
418      select_message(receiver, message, candidates)
419
420    #when /^((\.?[^.]+)+)\.([^.]*)\(\s*\)*$/
421        #function call
422        #obj = $1
423        #func = $3
424
425    when /^\.([^.]*)$/
426	# unknown(maybe String)
427
428      receiver = ""
429      message = Regexp.quote($1)
430
431      candidates = String.instance_methods(true)
432      select_message(receiver, message, candidates)
433
434  else
435    inclass = eval( VIM::evaluate("IsInClassDef()") )
436
437    if inclass != nil
438      classdef = "%s\n" % VIM::Buffer.current[ inclass.min ]
439      found = /^\s*class\s*([A-Za-z0-9_-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*\n$/.match( classdef )
440
441      if found != nil
442        receiver = $1
443        message = input
444        load_buffer_class( receiver )
445        begin
446          candidates = eval( "#{receiver}.instance_methods" )
447          candidates += get_rails_helpers
448          select_message(receiver, message, candidates)
449        rescue Exception
450          found = nil
451        end
452      end
453    end
454
455    if inclass == nil || found == nil
456      candidates = eval("self.class.constants")
457      candidates += get_buffer_classes
458      candidates.uniq!
459      candidates.sort!
460      (candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/)
461    end
462  end
463
464    #print candidates
465  if message != nil && message.length > 0
466    rexp = '^%s' % message.downcase
467    candidates.delete_if do |c|
468        c.downcase.match( rexp )
469        $~ == nil
470    end
471  end
472
473  outp = ""
474
475  #    tags = VIM::evaluate("taglist('^%s$')" %
476  valid = (candidates-Object.instance_methods)
477
478  rg = 0..valid.length
479  rg.step(150) do |x|
480    stpos = 0+x
481    enpos = 150+x
482    valid[stpos..enpos].each { |c| outp += "{'word':'%s','item':'%s'}," % [ c, c ] }
483    outp.sub!(/,$/, '')
484
485    VIM::command("call extend(g:rubycomplete_completions, [%s])" % outp)
486    outp = ""
487  end
488end
489
490
491def select_message(receiver, message, candidates)
492  #tags = VIM::evaluate("taglist('%s')" % receiver)
493  #print tags
494  candidates.grep(/^#{message}/).collect do |e|
495    case e
496      when /^[a-zA-Z_]/
497        receiver + "." + e
498      when /^[0-9]/
499      when *Operators
500        #receiver + " " + e
501    end
502  end
503  candidates.delete_if { |x| x == nil }
504  candidates.uniq!
505  candidates.sort!
506end
507
508# }}} ruby completion
509RUBYEOF
510endfunction
511
512let g:rubycomplete_rails_loaded = 0
513
514call s:DefRuby()
515" vim:tw=78:sw=4:ts=8:ft=vim:norl:
516