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