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