Mark McGranaghan 8d31ec147c move to vendor
2012-11-17 08:21:42 -08:00

568 lines
16 KiB
VimL

" Vim completion script
" Language: PHP
" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
" Last Change: 2006 May 9
"
" TODO:
" - Class aware completion:
" a) caching?
" - Switching to HTML (XML?) completion (SQL) inside of phpStrings
" - allow also for XML completion <- better do html_flavor for HTML
" completion
" - outside of <?php?> getting parent tag may cause problems. Heh, even in
" perfect conditions GetLastOpenTag doesn't cooperate... Inside of
" phpStrings this can be even a bonus but outside of <?php?> it is not the
" best situation
function! phpcomplete#CompletePHP(findstart, base)
if a:findstart
unlet! b:php_menu
" Check if we are inside of PHP markup
let pos = getpos('.')
let phpbegin = searchpairpos('<?', '', '?>', 'bWn',
\ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string\|comment"')
let phpend = searchpairpos('<?', '', '?>', 'Wn',
\ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string\|comment"')
if phpbegin == [0,0] && phpend == [0,0]
" We are outside of any PHP markup. Complete HTML
let htmlbegin = htmlcomplete#CompleteTags(1, '')
let cursor_col = pos[2]
let base = getline('.')[htmlbegin : cursor_col]
let b:php_menu = htmlcomplete#CompleteTags(0, base)
return htmlbegin
else
" locate the start of the word
let line = getline('.')
let start = col('.') - 1
let curline = line('.')
let compl_begin = col('.') - 2
while start >= 0 && line[start - 1] =~ '[a-zA-Z_0-9\x7f-\xff$]'
let start -= 1
endwhile
let b:compl_context = getline('.')[0:compl_begin]
return start
" We can be also inside of phpString with HTML tags. Deal with
" it later (time, not lines).
endif
endif
" If exists b:php_menu it means completion was already constructed we
" don't need to do anything more
if exists("b:php_menu")
return b:php_menu
endif
" Initialize base return lists
let res = []
let res2 = []
" a:base is very short - we need context
if exists("b:compl_context")
let context = b:compl_context
unlet! b:compl_context
endif
if !exists('g:php_builtin_functions')
call phpcomplete#LoadData()
endif
let scontext = substitute(context, '\$\?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*$', '', '')
if scontext =~ '\(=\s*new\|extends\)\s\+$'
" Complete class name
" Internal solution for finding classes in current file.
let file = getline(1, '$')
call filter(file,
\ 'v:val =~ "class\\s\\+[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*("')
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let jfile = join(file, ' ')
let int_values = split(jfile, 'class\s\+')
let int_classes = {}
for i in int_values
let c_name = matchstr(i, '^[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*')
if c_name != ''
let int_classes[c_name] = ''
endif
endfor
" Prepare list of classes from tags file
let ext_classes = {}
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
if fnames != ''
exe 'silent! vimgrep /^'.a:base.'.*\tc\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
" [:space:] thing: we don't have to be so strict when
" dealing with tags files - entries there were already
" checked by ctags.
let item = matchstr(field['text'], '^[^[:space:]]\+')
let ext_classes[item] = ''
endfor
endif
endif
" Prepare list of built in classes from g:php_builtin_functions
if !exists("g:php_omni_bi_classes")
let g:php_omni_bi_classes = {}
for i in keys(g:php_builtin_object_functions)
let g:php_omni_bi_classes[substitute(i, '::.*$', '', '')] = ''
endfor
endif
let classes = sort(keys(int_classes))
let classes += sort(keys(ext_classes))
let classes += sort(keys(g:php_omni_bi_classes))
for m in classes
if m =~ '^'.a:base
call add(res, m)
endif
endfor
let final_menu = []
for i in res
let final_menu += [{'word':i, 'kind':'c'}]
endfor
return final_menu
elseif scontext =~ '\(->\|::\)$'
" Complete user functions and variables
" Internal solution for current file.
" That seems as unnecessary repeating of functions but there are
" few not so subtle differences as not appending of $ and addition
" of 'kind' tag (not necessary in regular completion)
if scontext =~ '->$' && scontext !~ '\$this->$'
" Get name of the class
let classname = phpcomplete#GetClassName(scontext)
" Get location of class definition, we have to iterate through all
" tags files separately because we need relative path from current
" file to the exact file (tags file can be in different dir)
if classname != ''
let classlocation = phpcomplete#GetClassLocation(classname)
else
let classlocation = ''
endif
if classlocation == 'VIMPHP_BUILTINOBJECT'
for object in keys(g:php_builtin_object_functions)
if object =~ '^'.classname
let res += [{'word':substitute(object, '.*::', '', ''),
\ 'info': g:php_builtin_object_functions[object]}]
endif
endfor
return res
endif
if filereadable(classlocation)
let classfile = readfile(classlocation)
let classcontent = ''
let classcontent .= "\n".phpcomplete#GetClassContents(classfile, classname)
let sccontent = split(classcontent, "\n")
" YES, YES, YES! - we have whole content including extends!
" Now we need to get two elements: public functions and public
" vars
" NO, NO, NO! - third separate filtering looking for content
" :(, but all of them have differences. To squeeze them into
" one implementation would require many additional arguments
" and ifs. No good solution
" Functions declared with public keyword or without any
" keyword are public
let functions = filter(deepcopy(sccontent),
\ 'v:val =~ "^\\s*\\(static\\s\\+\\|public\\s\\+\\)*function"')
let jfuncs = join(functions, ' ')
let sfuncs = split(jfuncs, 'function\s\+')
let c_functions = {}
for i in sfuncs
let f_name = matchstr(i,
\ '^&\?\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
let f_args = matchstr(i,
\ '^&\?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*(\zs.\{-}\ze)\_s*{')
if f_name != ''
let c_functions[f_name.'('] = f_args
endif
endfor
" Variables declared with var or with public keyword are
" public
let variables = filter(deepcopy(sccontent),
\ 'v:val =~ "^\\s*\\(public\\|var\\)\\s\\+\\$"')
let jvars = join(variables, ' ')
let svars = split(jvars, '\$')
let c_variables = {}
for i in svars
let c_var = matchstr(i,
\ '^\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
if c_var != ''
let c_variables[c_var] = ''
endif
endfor
let all_values = {}
call extend(all_values, c_functions)
call extend(all_values, c_variables)
for m in sort(keys(all_values))
if m =~ '^'.a:base && m !~ '::'
call add(res, m)
elseif m =~ '::'.a:base
call add(res2, m)
endif
endfor
let start_list = res + res2
let final_list = []
for i in start_list
if has_key(c_variables, i)
let class = ' '
if all_values[i] != ''
let class = i.' class '
endif
let final_list +=
\ [{'word':i,
\ 'info':class.all_values[i],
\ 'kind':'v'}]
else
let final_list +=
\ [{'word':substitute(i, '.*::', '', ''),
\ 'info':i.all_values[i].')',
\ 'kind':'f'}]
endif
endfor
return final_list
endif
endif
if a:base =~ '^\$'
let adddollar = '$'
else
let adddollar = ''
endif
let file = getline(1, '$')
let jfile = join(file, ' ')
let sfile = split(jfile, '\$')
let int_vars = {}
for i in sfile
if i =~ '^\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*=\s*new'
let val = matchstr(i, '^[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*').'->'
else
let val = matchstr(i, '^[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*')
endif
if val !~ ''
let int_vars[adddollar.val] = ''
endif
endfor
" ctags has good support for PHP, use tags file for external
" variables
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let ext_vars = {}
if fnames != ''
let sbase = substitute(a:base, '^\$', '', '')
exe 'silent! vimgrep /^'.sbase.'.*\tv\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
let item = matchstr(field['text'], '^[^[:space:]]\+')
" Add -> if it is possible object declaration
let classname = ''
if field['text'] =~ item.'\s*=\s*new\s\+'
let item = item.'->'
let classname = matchstr(field['text'],
\ '=\s*new\s\+\zs[a-zA-Z_0-9\x7f-\xff]\+\ze')
endif
let ext_vars[adddollar.item] = classname
endfor
endif
endif
" Now we have all variables in int_vars dictionary
call extend(int_vars, ext_vars)
" Internal solution for finding functions in current file.
let file = getline(1, '$')
call filter(file,
\ 'v:val =~ "function\\s\\+&\\?[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*("')
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let jfile = join(file, ' ')
let int_values = split(jfile, 'function\s\+')
let int_functions = {}
for i in int_values
let f_name = matchstr(i,
\ '^&\?\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
let f_args = matchstr(i,
\ '^&\?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*(\zs.\{-}\ze)\_s*{')
let int_functions[f_name.'('] = f_args.')'
endfor
" Prepare list of functions from tags file
let ext_functions = {}
if fnames != ''
exe 'silent! vimgrep /^'.a:base.'.*\tf\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
" File name
let item = matchstr(field['text'], '^[^[:space:]]\+')
let fname = matchstr(field['text'], '\t\zs\f\+\ze')
let prototype = matchstr(field['text'],
\ 'function\s\+&\?[^[:space:]]\+\s*(\s*\zs.\{-}\ze\s*)\s*{\?')
let ext_functions[item.'('] = prototype.') - '.fname
endfor
endif
endif
let all_values = {}
call extend(all_values, int_functions)
call extend(all_values, ext_functions)
call extend(all_values, int_vars) " external variables are already in
call extend(all_values, g:php_builtin_object_functions)
for m in sort(keys(all_values))
if m =~ '\(^\|::\)'.a:base
call add(res, m)
endif
endfor
let start_list = res
let final_list = []
for i in start_list
if has_key(int_vars, i)
let class = ' '
if all_values[i] != ''
let class = i.' class '
endif
let final_list += [{'word':i, 'info':class.all_values[i], 'kind':'v'}]
else
let final_list +=
\ [{'word':substitute(i, '.*::', '', ''),
\ 'info':i.all_values[i],
\ 'kind':'f'}]
endif
endfor
return final_list
endif
if a:base =~ '^\$'
" Complete variables
" Built-in variables {{{
let g:php_builtin_vars = {'$GLOBALS':'',
\ '$_SERVER':'',
\ '$_GET':'',
\ '$_POST':'',
\ '$_COOKIE':'',
\ '$_FILES':'',
\ '$_ENV':'',
\ '$_REQUEST':'',
\ '$_SESSION':'',
\ '$HTTP_SERVER_VARS':'',
\ '$HTTP_ENV_VARS':'',
\ '$HTTP_COOKIE_VARS':'',
\ '$HTTP_GET_VARS':'',
\ '$HTTP_POST_VARS':'',
\ '$HTTP_POST_FILES':'',
\ '$HTTP_SESSION_VARS':'',
\ '$php_errormsg':'',
\ '$this':''
\ }
" }}}
" Internal solution for current file.
let file = getline(1, '$')
let jfile = join(file, ' ')
let int_vals = split(jfile, '\ze\$')
let int_vars = {}
for i in int_vals
if i =~ '^\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*=\s*new'
let val = matchstr(i,
\ '^\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*').'->'
else
let val = matchstr(i,
\ '^\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*')
endif
if val != ''
let int_vars[val] = ''
endif
endfor
call extend(int_vars,g:php_builtin_vars)
" ctags has support for PHP, use tags file for external variables
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let ext_vars = {}
if fnames != ''
let sbase = substitute(a:base, '^\$', '', '')
exe 'silent! vimgrep /^'.sbase.'.*\tv\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
let item = '$'.matchstr(field['text'], '^[^[:space:]]\+')
let m_menu = ''
" Add -> if it is possible object declaration
if field['text'] =~ item.'\s*=\s*new\s\+'
let item = item.'->'
let m_menu = matchstr(field['text'],
\ '=\s*new\s\+\zs[a-zA-Z_0-9\x7f-\xff]\+\ze')
endif
let ext_vars[item] = m_menu
endfor
endif
endif
call extend(int_vars, ext_vars)
let g:a0 = keys(int_vars)
for m in sort(keys(int_vars))
if m =~ '^\'.a:base
call add(res, m)
endif
endfor
let int_list = res
let int_dict = []
for i in int_list
if int_vars[i] != ''
let class = ' '
if int_vars[i] != ''
let class = i.' class '
endif
let int_dict += [{'word':i, 'info':class.int_vars[i], 'kind':'v'}]
else
let int_dict += [{'word':i, 'kind':'v'}]
endif
endfor
return int_dict
else
" Complete everything else -
" + functions, DONE
" + keywords of language DONE
" + defines (constant definitions), DONE
" + extend keywords for predefined constants, DONE
" + classes (after new), DONE
" + limit choice after -> and :: to funcs and vars DONE
" Internal solution for finding functions in current file.
let file = getline(1, '$')
call filter(file,
\ 'v:val =~ "function\\s\\+&\\?[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*("')
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let jfile = join(file, ' ')
let int_values = split(jfile, 'function\s\+')
let int_functions = {}
for i in int_values
let f_name = matchstr(i,
\ '^&\?\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
let f_args = matchstr(i,
\ '^&\?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*(\s*\zs.\{-}\ze\s*)\_s*{')
let int_functions[f_name.'('] = f_args.')'
endfor
" Prepare list of functions from tags file
let ext_functions = {}
if fnames != ''
exe 'silent! vimgrep /^'.a:base.'.*\tf\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
" File name
let item = matchstr(field['text'], '^[^[:space:]]\+')
let fname = matchstr(field['text'], '\t\zs\f\+\ze')
let prototype = matchstr(field['text'],
\ 'function\s\+&\?[^[:space:]]\+\s*(\s*\zs.\{-}\ze\s*)\s*{\?')
let ext_functions[item.'('] = prototype.') - '.fname
endfor
endif
endif
" All functions
call extend(int_functions, ext_functions)
call extend(int_functions, g:php_builtin_functions)
" Internal solution for finding constants in current file
let file = getline(1, '$')
call filter(file, 'v:val =~ "define\\s*("')
let jfile = join(file, ' ')
let int_values = split(jfile, 'define\s*(\s*')
let int_constants = {}
for i in int_values
let c_name = matchstr(i, '\(["'']\)\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze\1')
" let c_value = matchstr(i,
" \ '\(["'']\)[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\1\s*,\s*\zs.\{-}\ze\s*)')
if c_name != ''
let int_constants[c_name] = '' " c_value
endif
endfor
" Prepare list of constants from tags file
let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
let ext_constants = {}
if fnames != ''
exe 'silent! vimgrep /^'.a:base.'.*\td\(\t\|$\)/j '.fnames
let qflist = getqflist()
if len(qflist) > 0
for field in qflist
let item = matchstr(field['text'], '^[^[:space:]]\+')
let ext_constants[item] = ''
endfor
endif
endif
" All constants
call extend(int_constants, ext_constants)
" Treat keywords as constants
let all_values = {}
" One big dictionary of functions
call extend(all_values, int_functions)
" Add constants
call extend(all_values, int_constants)
" Add keywords
call extend(all_values, g:php_keywords)
for m in sort(keys(all_values))
if m =~ '^'.a:base
call add(res, m)
endif
endfor
let int_list = res
let final_list = []
for i in int_list
if has_key(int_functions, i)
let final_list +=
\ [{'word':i,
\ 'info':i.int_functions[i],
\ 'kind':'f'}]
elseif has_key(int_constants, i)
let final_list += [{'word':i, 'kind':'d'}]
else
let final_list += [{'word':i}]
endif
endfor
return final_list
endif
endfunction
" vim:set foldmethod=marker: