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