blob: 6ead0fd83756317cc3e9f6149fdf22e5d5bc4ac3 [file] [log] [blame]
Bram Moolenaarc6249bb2006-04-15 20:25:09 +00001" Vim completion script
Bram Moolenaard09091d2019-01-17 16:07:22 +01002" Language: Ruby
3" Maintainer: Mark Guzman <segfault@hasno.info>
4" URL: https://github.com/vim-ruby/vim-ruby
dkearnsd08059a2024-01-02 04:58:57 +11005" Last Change: 2023 Dec 31
Bram Moolenaarc6249bb2006-04-15 20:25:09 +00006" ----------------------------------------------------------------------------
7"
8" Ruby IRB/Complete author: Keiju ISHITSUKA(keiju@ishitsuka.com)
9" ----------------------------------------------------------------------------
10
Bram Moolenaareb3593b2006-04-22 22:33:57 +000011" {{{ requirement checks
Bram Moolenaarec7944a2013-06-12 21:29:15 +020012
13function! s:ErrMsg(msg)
14 echohl ErrorMsg
15 echo a:msg
16 echohl None
17endfunction
18
Bram Moolenaarc6249bb2006-04-15 20:25:09 +000019if !has('ruby')
Bram Moolenaarec7944a2013-06-12 21:29:15 +020020 call s:ErrMsg( "Error: Rubycomplete requires vim compiled with +ruby" )
21 call s:ErrMsg( "Error: falling back to syntax completion" )
Bram Moolenaar9964e462007-05-05 17:54:07 +000022 " lets fall back to syntax completion
23 setlocal omnifunc=syntaxcomplete#Complete
Bram Moolenaarc6249bb2006-04-15 20:25:09 +000024 finish
25endif
26
27if version < 700
Bram Moolenaarec7944a2013-06-12 21:29:15 +020028 call s:ErrMsg( "Error: Required vim >= 7.0" )
Bram Moolenaarc6249bb2006-04-15 20:25:09 +000029 finish
30endif
Bram Moolenaareb3593b2006-04-22 22:33:57 +000031" }}} requirement checks
Bram Moolenaarc6249bb2006-04-15 20:25:09 +000032
Bram Moolenaar9964e462007-05-05 17:54:07 +000033" {{{ configuration failsafe initialization
Bram Moolenaareb3593b2006-04-22 22:33:57 +000034if !exists("g:rubycomplete_rails")
35 let g:rubycomplete_rails = 0
36endif
Bram Moolenaarfc1421e2006-04-20 22:17:20 +000037
Bram Moolenaareb3593b2006-04-22 22:33:57 +000038if !exists("g:rubycomplete_classes_in_global")
39 let g:rubycomplete_classes_in_global = 0
40endif
41
Bram Moolenaar9964e462007-05-05 17:54:07 +000042if !exists("g:rubycomplete_buffer_loading")
Bram Moolenaara7241f52008-06-24 20:39:31 +000043 let g:rubycomplete_buffer_loading = 0
Bram Moolenaar9964e462007-05-05 17:54:07 +000044endif
45
46if !exists("g:rubycomplete_include_object")
47 let g:rubycomplete_include_object = 0
48endif
49
50if !exists("g:rubycomplete_include_objectspace")
51 let g:rubycomplete_include_objectspace = 0
52endif
53" }}} configuration failsafe initialization
54
Bram Moolenaar2ed639a2019-12-09 23:11:18 +010055" {{{ regex patterns
56
57" Regex that defines the start-match for the 'end' keyword.
58let s:end_start_regex =
59 \ '\C\%(^\s*\|[=,*/%+\-|;{]\|<<\|>>\|:\s\)\s*\zs' .
60 \ '\<\%(module\|class\|if\|for\|while\|until\|case\|unless\|begin' .
61 \ '\|\%(\K\k*[!?]\?\s\+\)\=def\):\@!\>' .
62 \ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>'
63
64" Regex that defines the middle-match for the 'end' keyword.
65let s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|elsif\):\@!\>'
66
67" Regex that defines the end-match for the 'end' keyword.
68let s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end:\@!\>'
69
70" }}} regex patterns
71
Bram Moolenaareb3593b2006-04-22 22:33:57 +000072" {{{ vim-side support functions
Bram Moolenaar9964e462007-05-05 17:54:07 +000073let s:rubycomplete_debug = 0
74
Bram Moolenaar9964e462007-05-05 17:54:07 +000075function! s:dprint(msg)
76 if s:rubycomplete_debug == 1
77 echom a:msg
78 endif
79endfunction
80
81function! s:GetBufferRubyModule(name, ...)
82 if a:0 == 1
83 let [snum,enum] = s:GetBufferRubyEntity(a:name, "module", a:1)
84 else
85 let [snum,enum] = s:GetBufferRubyEntity(a:name, "module")
86 endif
Bram Moolenaarfc1421e2006-04-20 22:17:20 +000087 return snum . '..' . enum
88endfunction
89
Bram Moolenaar9964e462007-05-05 17:54:07 +000090function! s:GetBufferRubyClass(name, ...)
91 if a:0 >= 1
92 let [snum,enum] = s:GetBufferRubyEntity(a:name, "class", a:1)
93 else
94 let [snum,enum] = s:GetBufferRubyEntity(a:name, "class")
95 endif
Bram Moolenaarfc1421e2006-04-20 22:17:20 +000096 return snum . '..' . enum
97endfunction
98
Bram Moolenaar9964e462007-05-05 17:54:07 +000099function! s:GetBufferRubySingletonMethods(name)
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000100endfunction
101
Bram Moolenaar9964e462007-05-05 17:54:07 +0000102function! s:GetBufferRubyEntity( name, type, ... )
103 let lastpos = getpos(".")
104 let lastline = lastpos
105 if (a:0 >= 1)
106 let lastline = [ 0, a:1, 0, 0 ]
107 call cursor( a:1, 0 )
108 endif
109
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000110 let stopline = 1
Bram Moolenaar9964e462007-05-05 17:54:07 +0000111
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200112 let crex = '^\s*\<' . a:type . '\>\s*\<' . escape(a:name, '*') . '\>\s*\(<\s*.*\s*\)\?'
Bram Moolenaar9964e462007-05-05 17:54:07 +0000113 let [lnum,lcol] = searchpos( crex, 'w' )
114 "let [lnum,lcol] = searchpairpos( crex . '\zs', '', '\(end\|}\)', 'w' )
115
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000116 if lnum == 0 && lcol == 0
Bram Moolenaar9964e462007-05-05 17:54:07 +0000117 call cursor(lastpos[1], lastpos[2])
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000118 return [0,0]
119 endif
120
Bram Moolenaar9964e462007-05-05 17:54:07 +0000121 let curpos = getpos(".")
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100122 let [enum,ecol] = searchpairpos( s:end_start_regex, s:end_middle_regex, s:end_end_regex, 'W' )
Bram Moolenaar9964e462007-05-05 17:54:07 +0000123 call cursor(lastpos[1], lastpos[2])
124
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000125 if lnum > enum
Bram Moolenaar9964e462007-05-05 17:54:07 +0000126 return [0,0]
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000127 endif
128 " we found a the class def
129 return [lnum,enum]
130endfunction
131
Bram Moolenaar9964e462007-05-05 17:54:07 +0000132function! s:IsInClassDef()
133 return s:IsPosInClassDef( line('.') )
134endfunction
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000135
Bram Moolenaar9964e462007-05-05 17:54:07 +0000136function! s:IsPosInClassDef(pos)
137 let [snum,enum] = s:GetBufferRubyEntity( '.*', "class" )
138 let ret = 'nil'
139
140 if snum < a:pos && a:pos < enum
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000141 let ret = snum . '..' . enum
142 endif
143
144 return ret
145endfunction
146
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100147function! s:IsInComment(pos)
148 let stack = synstack(a:pos[0], a:pos[1])
149 if !empty(stack)
150 return synIDattr(stack[0], 'name') =~ 'ruby\%(.*Comment\|Documentation\)'
151 else
152 return 0
153 endif
154endfunction
155
Bram Moolenaar9964e462007-05-05 17:54:07 +0000156function! s:GetRubyVarType(v)
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000157 let stopline = 1
158 let vtp = ''
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100159 let curpos = getpos('.')
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200160 let sstr = '^\s*#\s*@var\s*'.escape(a:v, '*').'\>\s\+[^ \t]\+\s*$'
Bram Moolenaar9964e462007-05-05 17:54:07 +0000161 let [lnum,lcol] = searchpos(sstr,'nb',stopline)
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000162 if lnum != 0 && lcol != 0
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100163 call setpos('.',curpos)
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000164 let str = getline(lnum)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000165 let vtp = substitute(str,sstr,'\1','')
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000166 return vtp
167 endif
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100168 call setpos('.',curpos)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000169 let ctors = '\(now\|new\|open\|get_instance'
170 if exists('g:rubycomplete_rails') && g:rubycomplete_rails == 1 && s:rubycomplete_rails_loaded == 1
171 let ctors = ctors.'\|find\|create'
Bram Moolenaar551dbcc2006-04-25 22:13:59 +0000172 else
Bram Moolenaar551dbcc2006-04-25 22:13:59 +0000173 endif
Bram Moolenaar9964e462007-05-05 17:54:07 +0000174 let ctors = ctors.'\)'
Bram Moolenaar551dbcc2006-04-25 22:13:59 +0000175
Bram Moolenaar9964e462007-05-05 17:54:07 +0000176 let fstr = '=\s*\([^ \t]\+.' . ctors .'\>\|[\[{"''/]\|%[xwQqr][(\[{@]\|[A-Za-z0-9@:\-()\.]\+...\?\|lambda\|&\)'
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200177 let sstr = ''.escape(a:v, '*').'\>\s*[+\-*/]*'.fstr
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100178 let pos = searchpos(sstr,'bW')
179 while pos != [0,0] && s:IsInComment(pos)
180 let pos = searchpos(sstr,'bW')
181 endwhile
182 if pos != [0,0]
183 let [lnum, col] = pos
184 let str = matchstr(getline(lnum),fstr,col)
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000185 let str = substitute(str,'^=\s*','','')
Bram Moolenaar9964e462007-05-05 17:54:07 +0000186
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000187 call setpos('.',pos)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000188 if str == '"' || str == '''' || stridx(tolower(str), '%q[') != -1
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000189 return 'String'
Bram Moolenaar9964e462007-05-05 17:54:07 +0000190 elseif str == '[' || stridx(str, '%w[') != -1
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000191 return 'Array'
192 elseif str == '{'
193 return 'Hash'
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000194 elseif str == '/' || str == '%r{'
195 return 'Regexp'
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000196 elseif strlen(str) >= 4 && stridx(str,'..') != -1
197 return 'Range'
Bram Moolenaar9964e462007-05-05 17:54:07 +0000198 elseif stridx(str, 'lambda') != -1 || str == '&'
199 return 'Proc'
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000200 elseif strlen(str) > 4
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000201 let l = stridx(str,'.')
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000202 return str[0:l-1]
203 end
204 return ''
205 endif
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100206 call setpos('.',curpos)
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000207 return ''
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000208endfunction
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000209
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000210"}}} vim-side support functions
211
Bram Moolenaar9964e462007-05-05 17:54:07 +0000212"{{{ vim-side completion function
213function! rubycomplete#Init()
214 execute "ruby VimRubyCompletion.preload_rails"
215endfunction
216
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000217function! rubycomplete#Complete(findstart, base)
218 "findstart = 1 when we need to get the text length
219 if a:findstart
220 let line = getline('.')
221 let idx = col('.')
222 while idx > 0
223 let idx -= 1
224 let c = line[idx-1]
225 if c =~ '\w'
226 continue
227 elseif ! c =~ '\.'
Bram Moolenaar45758762016-10-12 23:08:06 +0200228 let idx = -1
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000229 break
230 else
231 break
232 endif
233 endwhile
234
235 return idx
236 "findstart = 0 when we need to return the list of completions
237 else
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000238 let g:rubycomplete_completions = []
Bram Moolenaar9964e462007-05-05 17:54:07 +0000239 execute "ruby VimRubyCompletion.get_completions('" . a:base . "')"
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000240 return g:rubycomplete_completions
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000241 endif
242endfunction
Bram Moolenaar9964e462007-05-05 17:54:07 +0000243"}}} vim-side completion function
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000244
Bram Moolenaar9964e462007-05-05 17:54:07 +0000245"{{{ ruby-side code
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000246function! s:DefRuby()
247ruby << RUBYEOF
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000248# {{{ ruby completion
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000249
Bram Moolenaar9964e462007-05-05 17:54:07 +0000250begin
251 require 'rubygems' # let's assume this is safe...?
252rescue Exception
253 #ignore?
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000254end
Bram Moolenaar9964e462007-05-05 17:54:07 +0000255class VimRubyCompletion
256# {{{ constants
257 @@debug = false
258 @@ReservedWords = [
259 "BEGIN", "END",
260 "alias", "and",
261 "begin", "break",
262 "case", "class",
263 "def", "defined", "do",
264 "else", "elsif", "end", "ensure",
265 "false", "for",
266 "if", "in",
267 "module",
268 "next", "nil", "not",
269 "or",
270 "redo", "rescue", "retry", "return",
271 "self", "super",
272 "then", "true",
273 "undef", "unless", "until",
274 "when", "while",
275 "yield",
276 ]
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000277
Bram Moolenaar9964e462007-05-05 17:54:07 +0000278 @@Operators = [ "%", "&", "*", "**", "+", "-", "/",
279 "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>",
280 "[]", "[]=", "^", ]
281# }}} constants
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000282
Bram Moolenaar9964e462007-05-05 17:54:07 +0000283# {{{ buffer analysis magic
284 def load_requires
Bram Moolenaard09091d2019-01-17 16:07:22 +0100285
286 custom_paths = VIM::evaluate("get(g:, 'rubycomplete_load_paths', [])")
287
288 if !custom_paths.empty?
289 $LOAD_PATH.concat(custom_paths).uniq!
290 end
291
Bram Moolenaar9964e462007-05-05 17:54:07 +0000292 buf = VIM::Buffer.current
293 enum = buf.line_number
294 nums = Range.new( 1, enum )
295 nums.each do |x|
Bram Moolenaard09091d2019-01-17 16:07:22 +0100296
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000297 ln = buf[x]
Bram Moolenaar9964e462007-05-05 17:54:07 +0000298 begin
Bram Moolenaard09091d2019-01-17 16:07:22 +0100299 if /.*require_relative\s*(.*)$/.match( ln )
300 eval( "require %s" % File.expand_path($1) )
301 elsif /.*require\s*(["'].*?["'])/.match( ln )
302 eval( "require %s" % $1 )
303 end
304 rescue Exception => e
305 dprint e.inspect
Bram Moolenaar9964e462007-05-05 17:54:07 +0000306 end
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000307 end
308 end
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000309
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200310 def load_gems
311 fpath = VIM::evaluate("get(g:, 'rubycomplete_gemfile_path', 'Gemfile')")
312 return unless File.file?(fpath) && File.readable?(fpath)
313 want_bundler = VIM::evaluate("get(g:, 'rubycomplete_use_bundler')")
314 parse_file = !want_bundler
315 begin
316 require 'bundler'
317 Bundler.setup
318 Bundler.require
319 rescue Exception
320 parse_file = true
321 end
322 if parse_file
323 File.new(fpath).each_line do |line|
324 begin
325 require $1 if /\s*gem\s*['"]([^'"]+)/.match(line)
326 rescue Exception
327 end
328 end
329 end
330 end
331
Bram Moolenaar9964e462007-05-05 17:54:07 +0000332 def load_buffer_class(name)
333 dprint "load_buffer_class(%s) START" % name
334 classdef = get_buffer_entity(name, 's:GetBufferRubyClass("%s")')
335 return if classdef == nil
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000336
Bram Moolenaar9964e462007-05-05 17:54:07 +0000337 pare = /^\s*class\s*(.*)\s*<\s*(.*)\s*\n/.match( classdef )
338 load_buffer_class( $2 ) if pare != nil && $2 != name # load parent class if needed
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000339
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200340 mixre = /.*\n\s*(include|prepend)\s*(.*)\s*\n/.match( classdef )
Bram Moolenaar9964e462007-05-05 17:54:07 +0000341 load_buffer_module( $2 ) if mixre != nil && $2 != name # load mixins if needed
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000342
Bram Moolenaar551dbcc2006-04-25 22:13:59 +0000343 begin
Bram Moolenaar9964e462007-05-05 17:54:07 +0000344 eval classdef
345 rescue Exception
346 VIM::evaluate( "s:ErrMsg( 'Problem loading class \"%s\", was it already completed?' )" % name )
347 end
348 dprint "load_buffer_class(%s) END" % name
349 end
350
351 def load_buffer_module(name)
352 dprint "load_buffer_module(%s) START" % name
353 classdef = get_buffer_entity(name, 's:GetBufferRubyModule("%s")')
354 return if classdef == nil
355
356 begin
357 eval classdef
358 rescue Exception
359 VIM::evaluate( "s:ErrMsg( 'Problem loading module \"%s\", was it already completed?' )" % name )
360 end
361 dprint "load_buffer_module(%s) END" % name
362 end
363
364 def get_buffer_entity(name, vimfun)
365 loading_allowed = VIM::evaluate("exists('g:rubycomplete_buffer_loading') && g:rubycomplete_buffer_loading")
Bram Moolenaarc236c162008-07-13 17:41:49 +0000366 return nil if loading_allowed.to_i.zero?
Bram Moolenaar9964e462007-05-05 17:54:07 +0000367 return nil if /(\"|\')+/.match( name )
368 buf = VIM::Buffer.current
369 nums = eval( VIM::evaluate( vimfun % name ) )
370 return nil if nums == nil
371 return nil if nums.min == nums.max && nums.min == 0
372
373 dprint "get_buffer_entity START"
374 visited = []
375 clscnt = 0
376 bufname = VIM::Buffer.current.name
377 classdef = ""
378 cur_line = VIM::Buffer.current.line_number
379 while (nums != nil && !(nums.min == 0 && nums.max == 0) )
380 dprint "visited: %s" % visited.to_s
381 break if visited.index( nums )
382 visited << nums
383
384 nums.each do |x|
385 if x != cur_line
386 next if x == 0
387 ln = buf[x]
Bram Moolenaard09091d2019-01-17 16:07:22 +0100388 is_const = false
389 if /^\s*(module|class|def|include)\s+/.match(ln) || is_const = /^\s*?[A-Z]([A-z]|[1-9])*\s*?[|]{0,2}=\s*?.+\s*?/.match(ln)
390 clscnt += 1 if /class|module/.match($1)
391 # We must make sure to load each constant only once to avoid errors
392 if is_const
393 ln.gsub!(/\s*?[|]{0,2}=\s*?/, '||=')
394 end
Bram Moolenaar1d689522010-05-28 20:54:39 +0200395 #dprint "\$1$1
Bram Moolenaar9964e462007-05-05 17:54:07 +0000396 classdef += "%s\n" % ln
397 classdef += "end\n" if /def\s+/.match(ln)
398 dprint ln
399 end
400 end
401 end
402
403 nm = "%s(::.*)*\", %s, \"" % [ name, nums.last ]
404 nums = eval( VIM::evaluate( vimfun % nm ) )
405 dprint "nm: \"%s\"" % nm
406 dprint "vimfun: %s" % (vimfun % nm)
407 dprint "got nums: %s" % nums.to_s
408 end
409 if classdef.length > 1
410 classdef += "end\n"*clscnt
411 # classdef = "class %s\n%s\nend\n" % [ bufname.gsub( /\/|\\/, "_" ), classdef ]
412 end
413
414 dprint "get_buffer_entity END"
415 dprint "classdef====start"
416 lns = classdef.split( "\n" )
417 lns.each { |x| dprint x }
418 dprint "classdef====end"
419 return classdef
420 end
421
422 def get_var_type( receiver )
423 if /(\"|\')+/.match( receiver )
424 "String"
425 else
426 VIM::evaluate("s:GetRubyVarType('%s')" % receiver)
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000427 end
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000428 end
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000429
Bram Moolenaar9964e462007-05-05 17:54:07 +0000430 def dprint( txt )
431 print txt if @@debug
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000432 end
433
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200434 def escape_vim_singlequote_string(str)
435 str.to_s.gsub(/'/,"\\'")
436 end
437
Bram Moolenaar9964e462007-05-05 17:54:07 +0000438 def get_buffer_entity_list( type )
439 # this will be a little expensive.
440 loading_allowed = VIM::evaluate("exists('g:rubycomplete_buffer_loading') && g:rubycomplete_buffer_loading")
441 allow_aggressive_load = VIM::evaluate("exists('g:rubycomplete_classes_in_global') && g:rubycomplete_classes_in_global")
Bram Moolenaarc236c162008-07-13 17:41:49 +0000442 return [] if allow_aggressive_load.to_i.zero? || loading_allowed.to_i.zero?
Bram Moolenaar9964e462007-05-05 17:54:07 +0000443
444 buf = VIM::Buffer.current
445 eob = buf.length
446 ret = []
447 rg = 1..eob
448 re = eval( "/^\s*%s\s*([A-Za-z0-9_:-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*/" % type )
449
450 rg.each do |x|
451 if re.match( buf[x] )
452 next if type == "def" && eval( VIM::evaluate("s:IsPosInClassDef(%s)" % x) ) != nil
453 ret.push $1
454 end
455 end
456
457 return ret
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000458 end
459
Bram Moolenaar9964e462007-05-05 17:54:07 +0000460 def get_buffer_modules
461 return get_buffer_entity_list( "modules" )
462 end
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000463
Bram Moolenaar9964e462007-05-05 17:54:07 +0000464 def get_buffer_methods
465 return get_buffer_entity_list( "def" )
466 end
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000467
Bram Moolenaar9964e462007-05-05 17:54:07 +0000468 def get_buffer_classes
469 return get_buffer_entity_list( "class" )
470 end
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000471
Bram Moolenaar9964e462007-05-05 17:54:07 +0000472 def load_rails
473 allow_rails = VIM::evaluate("exists('g:rubycomplete_rails') && g:rubycomplete_rails")
Bram Moolenaarc236c162008-07-13 17:41:49 +0000474 return if allow_rails.to_i.zero?
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000475
Bram Moolenaar9964e462007-05-05 17:54:07 +0000476 buf_path = VIM::evaluate('expand("%:p")')
477 file_name = VIM::evaluate('expand("%:t")')
478 vim_dir = VIM::evaluate('getcwd()')
479 file_dir = buf_path.gsub( file_name, '' )
480 file_dir.gsub!( /\\/, "/" )
481 vim_dir.gsub!( /\\/, "/" )
482 vim_dir << "/"
483 dirs = [ vim_dir, file_dir ]
484 sdirs = [ "", "./", "../", "../../", "../../../", "../../../../" ]
485 rails_base = nil
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000486
Bram Moolenaar9964e462007-05-05 17:54:07 +0000487 dirs.each do |dir|
488 sdirs.each do |sub|
489 trail = "%s%s" % [ dir, sub ]
490 tcfg = "%sconfig" % trail
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000491
dkearnsd08059a2024-01-02 04:58:57 +1100492 if File.exist?( tcfg )
Bram Moolenaar9964e462007-05-05 17:54:07 +0000493 rails_base = trail
494 break
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000495 end
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000496 end
Bram Moolenaar9964e462007-05-05 17:54:07 +0000497 break if rails_base
498 end
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000499
Bram Moolenaar9964e462007-05-05 17:54:07 +0000500 return if rails_base == nil
501 $:.push rails_base unless $:.index( rails_base )
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000502
Bram Moolenaar4d8f4762021-06-27 15:18:56 +0200503 bootfile = rails_base + "config/boot.rb"
504 envfile = rails_base + "config/environment.rb"
dkearnsd08059a2024-01-02 04:58:57 +1100505 if File.exist?( bootfile ) && File.exist?( envfile )
Bram Moolenaar9964e462007-05-05 17:54:07 +0000506 begin
507 require bootfile
508 require envfile
509 begin
510 require 'console_app'
511 require 'console_with_helpers'
512 rescue Exception
513 dprint "Rails 1.1+ Error %s" % $!
514 # assume 1.0
515 end
516 #eval( "Rails::Initializer.run" ) #not necessary?
517 VIM::command('let s:rubycomplete_rails_loaded = 1')
518 dprint "rails loaded"
519 rescue Exception
520 dprint "Rails Error %s" % $!
521 VIM::evaluate( "s:ErrMsg('Error loading rails environment')" )
522 end
523 end
524 end
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000525
Bram Moolenaar9964e462007-05-05 17:54:07 +0000526 def get_rails_helpers
527 allow_rails = VIM::evaluate("exists('g:rubycomplete_rails') && g:rubycomplete_rails")
528 rails_loaded = VIM::evaluate('s:rubycomplete_rails_loaded')
Bram Moolenaarc236c162008-07-13 17:41:49 +0000529 return [] if allow_rails.to_i.zero? || rails_loaded.to_i.zero?
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000530
Bram Moolenaar9964e462007-05-05 17:54:07 +0000531 buf_path = VIM::evaluate('expand("%:p")')
532 buf_path.gsub!( /\\/, "/" )
533 path_elm = buf_path.split( "/" )
534 dprint "buf_path: %s" % buf_path
535 types = [ "app", "db", "lib", "test", "components", "script" ]
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000536
Bram Moolenaar9964e462007-05-05 17:54:07 +0000537 i = nil
538 ret = []
539 type = nil
540 types.each do |t|
541 i = path_elm.index( t )
542 break if i
543 end
544 type = path_elm[i]
545 type.downcase!
546
547 dprint "type: %s" % type
548 case type
549 when "app"
550 i += 1
551 subtype = path_elm[i]
552 subtype.downcase!
553
554 dprint "subtype: %s" % subtype
555 case subtype
556 when "views"
557 ret += ActionView::Base.instance_methods
558 ret += ActionView::Base.methods
559 when "controllers"
560 ret += ActionController::Base.instance_methods
561 ret += ActionController::Base.methods
562 when "models"
563 ret += ActiveRecord::Base.instance_methods
564 ret += ActiveRecord::Base.methods
565 end
566
567 when "db"
568 ret += ActiveRecord::ConnectionAdapters::SchemaStatements.instance_methods
569 ret += ActiveRecord::ConnectionAdapters::SchemaStatements.methods
570 end
571
Bram Moolenaar9964e462007-05-05 17:54:07 +0000572 return ret
573 end
574
575 def add_rails_columns( cls )
576 allow_rails = VIM::evaluate("exists('g:rubycomplete_rails') && g:rubycomplete_rails")
577 rails_loaded = VIM::evaluate('s:rubycomplete_rails_loaded')
Bram Moolenaarc236c162008-07-13 17:41:49 +0000578 return [] if allow_rails.to_i.zero? || rails_loaded.to_i.zero?
Bram Moolenaar9964e462007-05-05 17:54:07 +0000579
580 begin
581 eval( "#{cls}.establish_connection" )
582 return [] unless eval( "#{cls}.ancestors.include?(ActiveRecord::Base).to_s" )
583 col = eval( "#{cls}.column_names" )
584 return col if col
585 rescue
586 dprint "add_rails_columns err: (cls: %s) %s" % [ cls, $! ]
587 return []
588 end
589 return []
590 end
591
592 def clean_sel(sel, msg)
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200593 ret = sel.reject{|x|x.nil?}.uniq
594 ret = ret.grep(/^#{Regexp.quote(msg)}/) if msg != nil
595 ret
Bram Moolenaar9964e462007-05-05 17:54:07 +0000596 end
597
598 def get_rails_view_methods
599 allow_rails = VIM::evaluate("exists('g:rubycomplete_rails') && g:rubycomplete_rails")
600 rails_loaded = VIM::evaluate('s:rubycomplete_rails_loaded')
Bram Moolenaarc236c162008-07-13 17:41:49 +0000601 return [] if allow_rails.to_i.zero? || rails_loaded.to_i.zero?
Bram Moolenaar9964e462007-05-05 17:54:07 +0000602
603 buf_path = VIM::evaluate('expand("%:p")')
604 buf_path.gsub!( /\\/, "/" )
605 pelm = buf_path.split( "/" )
606 idx = pelm.index( "views" )
607
608 return [] unless idx
609 idx += 1
610
611 clspl = pelm[idx].camelize.pluralize
612 cls = clspl.singularize
613
614 ret = []
615 begin
616 ret += eval( "#{cls}.instance_methods" )
617 ret += eval( "#{clspl}Helper.instance_methods" )
618 rescue Exception
619 dprint "Error: Unable to load rails view helpers for %s: %s" % [ cls, $! ]
620 end
621
622 return ret
623 end
624# }}} buffer analysis magic
625
626# {{{ main completion code
627 def self.preload_rails
628 a = VimRubyCompletion.new
Bram Moolenaard09091d2019-01-17 16:07:22 +0100629 if VIM::evaluate("has('nvim')") == 0
630 require 'thread'
631 Thread.new(a) do |b|
632 begin
633 b.load_rails
634 rescue
635 end
Bram Moolenaar9964e462007-05-05 17:54:07 +0000636 end
637 end
638 a.load_rails
639 rescue
640 end
641
642 def self.get_completions(base)
643 b = VimRubyCompletion.new
644 b.get_completions base
645 end
646
647 def get_completions(base)
648 loading_allowed = VIM::evaluate("exists('g:rubycomplete_buffer_loading') && g:rubycomplete_buffer_loading")
Bram Moolenaarc236c162008-07-13 17:41:49 +0000649 if loading_allowed.to_i == 1
Bram Moolenaar9964e462007-05-05 17:54:07 +0000650 load_requires
651 load_rails
652 end
653
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200654 want_gems = VIM::evaluate("get(g:, 'rubycomplete_load_gemfile')")
655 load_gems unless want_gems.to_i.zero?
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200656
Bram Moolenaar9964e462007-05-05 17:54:07 +0000657 input = VIM::Buffer.current.line
658 cpos = VIM::Window.current.cursor[1] - 1
659 input = input[0..cpos]
660 input += base
661 input.sub!(/.*[ \t\n\"\\'`><=;|&{(]/, '') # Readline.basic_word_break_characters
662 input.sub!(/self\./, '')
663 input.sub!(/.*((\.\.[\[(]?)|([\[(]))/, '')
664
665 dprint 'input %s' % input
666 message = nil
667 receiver = nil
668 methods = []
669 variables = []
670 classes = []
671 constants = []
672
673 case input
674 when /^(\/[^\/]*\/)\.([^.]*)$/ # Regexp
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000675 receiver = $1
Bram Moolenaar9964e462007-05-05 17:54:07 +0000676 message = Regexp.quote($2)
677 methods = Regexp.instance_methods(true)
678
679 when /^([^\]]*\])\.([^.]*)$/ # Array
680 receiver = $1
681 message = Regexp.quote($2)
682 methods = Array.instance_methods(true)
683
684 when /^([^\}]*\})\.([^.]*)$/ # Proc or Hash
685 receiver = $1
686 message = Regexp.quote($2)
687 methods = Proc.instance_methods(true) | Hash.instance_methods(true)
688
689 when /^(:[^:.]*)$/ # Symbol
690 dprint "symbol"
691 if Symbol.respond_to?(:all_symbols)
692 receiver = $1
693 message = $1.sub( /:/, '' )
694 methods = Symbol.all_symbols.collect{|s| s.id2name}
695 methods.delete_if { |c| c.match( /'/ ) }
696 end
697
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100698 when /^::([A-Z][^:\.\(]*)?$/ # Absolute Constant or class methods
Bram Moolenaar9964e462007-05-05 17:54:07 +0000699 dprint "const or cls"
700 receiver = $1
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100701 methods = Object.constants.collect{ |c| c.to_s }.grep(/^#{receiver}/)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000702
Bram Moolenaar1d689522010-05-28 20:54:39 +0200703 when /^(((::)?[A-Z][^:.\(]*)+?)::?([^:.]*)$/ # Constant or class methods
Bram Moolenaar9964e462007-05-05 17:54:07 +0000704 receiver = $1
705 message = Regexp.quote($4)
706 dprint "const or cls 2 [recv: \'%s\', msg: \'%s\']" % [ receiver, message ]
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000707 load_buffer_class( receiver )
Bram Moolenaard09091d2019-01-17 16:07:22 +0100708 load_buffer_module( receiver )
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000709 begin
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100710 constants = eval("#{receiver}.constants").collect{ |c| c.to_s }.grep(/^#{message}/)
711 methods = eval("#{receiver}.methods").collect{ |m| m.to_s }.grep(/^#{message}/)
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000712 rescue Exception
Bram Moolenaar9964e462007-05-05 17:54:07 +0000713 dprint "exception: %s" % $!
Bram Moolenaar2ed639a2019-12-09 23:11:18 +0100714 constants = []
Bram Moolenaar9964e462007-05-05 17:54:07 +0000715 methods = []
716 end
Bram Moolenaar9964e462007-05-05 17:54:07 +0000717
718 when /^(:[^:.]+)\.([^.]*)$/ # Symbol
719 dprint "symbol"
720 receiver = $1
721 message = Regexp.quote($2)
722 methods = Symbol.instance_methods(true)
723
724 when /^([0-9_]+(\.[0-9_]+)?(e[0-9]+)?)\.([^.]*)$/ # Numeric
725 dprint "numeric"
726 receiver = $1
727 message = Regexp.quote($4)
728 begin
729 methods = eval(receiver).methods
730 rescue Exception
731 methods = []
732 end
733
734 when /^(\$[^.]*)$/ #global
735 dprint "global"
736 methods = global_variables.grep(Regexp.new(Regexp.quote($1)))
737
Bram Moolenaar1d689522010-05-28 20:54:39 +0200738 when /^((\.?[^.]+)+?)\.([^.]*)$/ # variable
Bram Moolenaar9964e462007-05-05 17:54:07 +0000739 dprint "variable"
740 receiver = $1
741 message = Regexp.quote($3)
742 load_buffer_class( receiver )
743
744 cv = eval("self.class.constants")
745 vartype = get_var_type( receiver )
746 dprint "vartype: %s" % vartype
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200747
748 invalid_vartype = ['', "gets"]
749 if !invalid_vartype.include?(vartype)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000750 load_buffer_class( vartype )
751
752 begin
753 methods = eval("#{vartype}.instance_methods")
754 variables = eval("#{vartype}.instance_variables")
755 rescue Exception
756 dprint "load_buffer_class err: %s" % $!
757 end
758 elsif (cv).include?(receiver)
759 # foo.func and foo is local var.
760 methods = eval("#{receiver}.methods")
761 vartype = receiver
762 elsif /^[A-Z]/ =~ receiver and /\./ !~ receiver
763 vartype = receiver
764 # Foo::Bar.func
765 begin
766 methods = eval("#{receiver}.methods")
767 rescue Exception
768 end
769 else
770 # func1.func2
771 ObjectSpace.each_object(Module){|m|
772 next if m.name != "IRB::Context" and
773 /^(IRB|SLex|RubyLex|RubyToken)/ =~ m.name
774 methods.concat m.instance_methods(false)
775 }
776 end
Bram Moolenaar89bcfda2016-08-30 23:26:57 +0200777 variables += add_rails_columns( "#{vartype}" ) if vartype && !invalid_vartype.include?(vartype)
Bram Moolenaar9964e462007-05-05 17:54:07 +0000778
779 when /^\(?\s*[A-Za-z0-9:^@.%\/+*\(\)]+\.\.\.?[A-Za-z0-9:^@.%\/+*\(\)]+\s*\)?\.([^.]*)/
780 message = $1
781 methods = Range.instance_methods(true)
782
783 when /^\.([^.]*)$/ # unknown(maybe String)
784 message = Regexp.quote($1)
785 methods = String.instance_methods(true)
786
787 else
788 dprint "default/other"
789 inclass = eval( VIM::evaluate("s:IsInClassDef()") )
790
791 if inclass != nil
792 dprint "inclass"
793 classdef = "%s\n" % VIM::Buffer.current[ inclass.min ]
794 found = /^\s*class\s*([A-Za-z0-9_-]*)(\s*<\s*([A-Za-z0-9_:-]*))?\s*\n$/.match( classdef )
795
796 if found != nil
797 receiver = $1
798 message = input
799 load_buffer_class( receiver )
800 begin
801 methods = eval( "#{receiver}.instance_methods" )
802 variables += add_rails_columns( "#{receiver}" )
803 rescue Exception
804 found = nil
805 end
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000806 end
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000807 end
Bram Moolenaar9964e462007-05-05 17:54:07 +0000808
809 if inclass == nil || found == nil
810 dprint "inclass == nil"
811 methods = get_buffer_methods
812 methods += get_rails_view_methods
813
814 cls_const = Class.constants
815 constants = cls_const.select { |c| /^[A-Z_-]+$/.match( c ) }
816 classes = eval("self.class.constants") - constants
817 classes += get_buffer_classes
818 classes += get_buffer_modules
819
820 include_objectspace = VIM::evaluate("exists('g:rubycomplete_include_objectspace') && g:rubycomplete_include_objectspace")
821 ObjectSpace.each_object(Class) { |cls| classes << cls.to_s } if include_objectspace == "1"
822 message = receiver = input
823 end
824
825 methods += get_rails_helpers
826 methods += Kernel.public_methods
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000827 end
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000828
Bram Moolenaar9964e462007-05-05 17:54:07 +0000829 include_object = VIM::evaluate("exists('g:rubycomplete_include_object') && g:rubycomplete_include_object")
830 methods = clean_sel( methods, message )
831 methods = (methods-Object.instance_methods) if include_object == "0"
832 rbcmeth = (VimRubyCompletion.instance_methods-Object.instance_methods) # lets remove those rubycomplete methods
833 methods = (methods-rbcmeth)
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000834
Bram Moolenaar9964e462007-05-05 17:54:07 +0000835 variables = clean_sel( variables, message )
836 classes = clean_sel( classes, message ) - ["VimRubyCompletion"]
837 constants = clean_sel( constants, message )
Bram Moolenaard8fc5c02006-04-29 21:55:22 +0000838
Bram Moolenaar9964e462007-05-05 17:54:07 +0000839 valid = []
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200840 valid += methods.collect { |m| { :name => m.to_s, :type => 'm' } }
841 valid += variables.collect { |v| { :name => v.to_s, :type => 'v' } }
842 valid += classes.collect { |c| { :name => c.to_s, :type => 't' } }
843 valid += constants.collect { |d| { :name => d.to_s, :type => 'd' } }
Bram Moolenaar9964e462007-05-05 17:54:07 +0000844 valid.sort! { |x,y| x[:name] <=> y[:name] }
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000845
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000846 outp = ""
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000847
Bram Moolenaar9964e462007-05-05 17:54:07 +0000848 rg = 0..valid.length
849 rg.step(150) do |x|
850 stpos = 0+x
851 enpos = 150+x
Bram Moolenaarec7944a2013-06-12 21:29:15 +0200852 valid[stpos..enpos].each { |c| outp += "{'word':'%s','item':'%s','kind':'%s'}," % [ c[:name], c[:name], c[:type] ].map{|x|escape_vim_singlequote_string(x)} }
Bram Moolenaar9964e462007-05-05 17:54:07 +0000853 outp.sub!(/,$/, '')
854
855 VIM::command("call extend(g:rubycomplete_completions, [%s])" % outp)
856 outp = ""
857 end
858 end
859# }}} main completion code
860
861end # VimRubyCompletion
Bram Moolenaareb3593b2006-04-22 22:33:57 +0000862# }}} ruby completion
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000863RUBYEOF
864endfunction
865
Bram Moolenaar9964e462007-05-05 17:54:07 +0000866let s:rubycomplete_rails_loaded = 0
Bram Moolenaarfc1421e2006-04-20 22:17:20 +0000867
Bram Moolenaarc6249bb2006-04-15 20:25:09 +0000868call s:DefRuby()
Bram Moolenaar9964e462007-05-05 17:54:07 +0000869"}}} ruby-side code
870
Bram Moolenaar9964e462007-05-05 17:54:07 +0000871" vim:tw=78:sw=4:ts=8:et:fdm=marker:ft=vim:norl: