aboutsummaryrefslogtreecommitdiff
path: root/runtime/autoload/phpcomplete.vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/autoload/phpcomplete.vim')
-rw-r--r--runtime/autoload/phpcomplete.vim2572
1 files changed, 2572 insertions, 0 deletions
diff --git a/runtime/autoload/phpcomplete.vim b/runtime/autoload/phpcomplete.vim
new file mode 100644
index 0000000000..0bdcd5d5fb
--- /dev/null
+++ b/runtime/autoload/phpcomplete.vim
@@ -0,0 +1,2572 @@
+" Vim completion script
+" Language: PHP
+" Maintainer: Dávid Szabó ( complex857 AT gmail DOT com )
+" Previous Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl )
+" URL: https://github.com/shawncplus/phpcomplete.vim
+" Last Change: 2014 May 30
+"
+" OPTIONS:
+"
+" let g:phpcomplete_relax_static_constraint = 1/0 [default 0]
+" Enables completion for non-static methods when completing for static context (::).
+" This generates E_STRICT level warning, but php calls these methods nontheless.
+"
+" let g:phpcomplete_complete_for_unknown_classes = 1/0 [default 0]
+" Enables completion of variables and functions in "everything under the sun" fashion
+" when completing for an instance or static class context but the code can't tell the class
+" or locate the file that it lives in.
+" The completion list generated this way is only filtered by the completion base
+" and generally not much more accurate then simple keyword completion.
+"
+" let g:phpcomplete_search_tags_for_variables = 1/0 [default 0]
+" Enables use of tags when the plugin tries to find variables.
+" When enabled the plugin will search for the variables in the tag files with kind 'v',
+" lines like $some_var = new Foo; but these usually yield highly inaccurate results and
+" can be fairly slow.
+"
+" let g:phpcomplete_min_num_of_chars_for_namespace_completion = n [default 1]
+" This option controls the number of characters the user needs to type before
+" the tags will be searched for namespaces and classes in typed out namespaces in
+" "use ..." context. Setting this to 0 is not recommended because that means the code
+" have to scan every tag, and vim's taglist() function runs extremly slow with a
+" "match everything" pattern.
+"
+" let g:phpcomplete_parse_docblock_comments = 1/0 [default 0]
+" When enabled the preview window's content will include information
+" extracted from docblock comments of the completions.
+" Enabling this option will add return types to the completion menu for functions too.
+"
+" let g:phpcomplete_cache_taglists = 1/0 [default 1]
+" When enabled the taglist() lookups will be cached and subsequent searches
+" for the same pattern will not check the tagfiles any more, thus making the
+" lookups faster. Cache expiration is based on the mtimes of the tag files.
+"
+" TODO:
+" - 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
+
+if !exists('g:phpcomplete_relax_static_constraint')
+ let g:phpcomplete_relax_static_constraint = 0
+endif
+
+if !exists('g:phpcomplete_complete_for_unknown_classes')
+ let g:phpcomplete_complete_for_unknown_classes = 0
+endif
+
+if !exists('g:phpcomplete_search_tags_for_variables')
+ let g:phpcomplete_search_tags_for_variables = 0
+endif
+
+if !exists('g:phpcomplete_min_num_of_chars_for_namespace_completion')
+ let g:phpcomplete_min_num_of_chars_for_namespace_completion = 1
+endif
+
+if !exists('g:phpcomplete_parse_docblock_comments')
+ let g:phpcomplete_parse_docblock_comments = 0
+endif
+
+if !exists('g:phpcomplete_cache_taglists')
+ let g:phpcomplete_cache_taglists = 1
+endif
+
+if !exists('s:cache_classstructures')
+ let s:cache_classstructures = {}
+endif
+
+if !exists('s:cache_tags')
+ let s:cache_tags = {}
+endif
+
+if !exists('s:cache_tags_checksum')
+ let s:cache_tags_checksum = ''
+endif
+
+let s:script_path = fnamemodify(resolve(expand('<sfile>:p')), ':h')
+
+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 compl_begin = col('.') - 2
+ while start >= 0 && line[start - 1] =~ '[\\a-zA-Z_0-9\x7f-\xff$]'
+ let start -= 1
+ endwhile
+ let b:phpbegin = phpbegin
+ let b:compl_context = phpcomplete#GetCurrentInstruction(line('.'), max([0, col('.') - 2]), phpbegin)
+
+ 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
+
+ if !exists('g:php_builtin_functions')
+ call phpcomplete#LoadData()
+ endif
+
+ " a:base is very short - we need context
+ if exists("b:compl_context")
+ let context = b:compl_context
+ unlet! b:compl_context
+ " chop of the "base" from the end of the current instruction
+ if a:base != ""
+ let context = substitute(context, '\s*[$a-zA-Z_0-9\x7f-\xff]*$', '', '')
+ end
+ end
+
+ let [current_namespace, imports] = phpcomplete#GetCurrentNameSpace(getline(0, line('.')))
+
+ if context =~? '^use\s'
+ return phpcomplete#CompleteUse(a:base)
+ endif
+
+ if context =~ '\(->\|::\)$'
+ " {{{
+ " Get name of the class
+ let classname = phpcomplete#GetClassName(line('.'), context, current_namespace, imports)
+
+ " Get location of class definition, we have to iterate through all
+ if classname != ''
+ if classname =~ '\'
+ " split the last \ segment as a classname, everything else is the namespace
+ let classname_parts = split(classname, '\')
+ let namespace = join(classname_parts[0:-2], '\')
+ let classname = classname_parts[-1]
+ else
+ let namespace = '\'
+ endif
+ let classlocation = phpcomplete#GetClassLocation(classname, namespace)
+ else
+ let classlocation = ''
+ endif
+
+ if classlocation != ''
+ if classlocation == 'VIMPHP_BUILTINOBJECT' && has_key(g:php_builtin_classes, tolower(classname))
+ return phpcomplete#CompleteBuiltInClass(context, classname, a:base)
+ endif
+
+ if filereadable(classlocation)
+ let classfile = readfile(classlocation)
+ let classcontent = ''
+ let classcontent .= "\n".phpcomplete#GetClassContents(classlocation, classname)
+ let sccontent = split(classcontent, "\n")
+ let visibility = expand('%:p') == fnamemodify(classlocation, ':p') ? 'private' : 'public'
+
+ return phpcomplete#CompleteUserClass(context, a:base, sccontent, visibility)
+ endif
+ endif
+
+ return phpcomplete#CompleteUnknownClass(a:base, context)
+ " }}}
+ elseif context =~? 'implements'
+ return phpcomplete#CompleteClassName(a:base, ['i'], current_namespace, imports)
+ elseif context =~? 'extends\s\+.\+$'
+ return ['implements']
+ elseif context =~? 'extends'
+ let kinds = context =~? 'class\s' ? ['c'] : ['i']
+ return phpcomplete#CompleteClassName(a:base, kinds, current_namespace, imports)
+ elseif context =~? 'class [a-zA-Z_\x7f-\xff\\][a-zA-Z_0-9\x7f-\xff\\]*'
+ " special case when you've typed the class keyword and the name too, only extends and implements allowed there
+ return filter(['extends', 'implements'], 'stridx(v:val, a:base) == 0')
+ elseif context =~? 'new'
+ return phpcomplete#CompleteClassName(a:base, ['c'], current_namespace, imports)
+ endif
+
+ if a:base =~ '^\$'
+ return phpcomplete#CompleteVariable(a:base)
+ else
+ return phpcomplete#CompleteGeneral(a:base, current_namespace, imports)
+ endif
+endfunction
+" }}}
+
+function! phpcomplete#CompleteUse(base) " {{{
+ " completes builtin class names regadless of g:phpcomplete_min_num_of_chars_for_namespace_completion
+ " completes namespaces from tags
+ " * requires patched ctags
+ " completes classnames from tags within the already typed out namespace using the "namespace" field of tags
+ " * requires patched ctags
+
+ let res = []
+
+ " class and namespace names are always considered absoltute in use ... expressions, leading slash is not recommended
+ " by the php manual, so we gonna get rid of that
+ if a:base =~? '^\'
+ let base = substitute(a:base, '^\', '', '')
+ else
+ let base = a:base
+ endif
+
+ let namespace_match_pattern = substitute(base, '\\', '\\\\', 'g')
+ let classname_match_pattern = matchstr(base, '[^\\]\+$')
+ let namespace_for_class = substitute(substitute(namespace_match_pattern, '\\\\', '\\', 'g'), '\\*'.classname_match_pattern.'$', '', '')
+
+ if len(namespace_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion
+ if len(classname_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion
+ let tags = phpcomplete#GetTaglist('^\('.namespace_match_pattern.'\|'.classname_match_pattern.'\)')
+ else
+ let tags = phpcomplete#GetTaglist('^'.namespace_match_pattern)
+ endif
+
+ let patched_ctags_detected = 0
+ let namespaced_matches = []
+ let no_namespace_matches = []
+ for tag in tags
+ if has_key(tag, 'namespace')
+ let patched_ctags_detected = 1
+ endif
+ if tag.kind ==? 'n' && tag.name =~? '^'.namespace_match_pattern
+ let patched_ctags_detected = 1
+ call add(namespaced_matches, {'word': tag.name, 'kind': 'n', 'menu': tag.filename, 'info': tag.filename })
+ elseif has_key(tag, 'namespace') && (tag.kind ==? 'c' || tag.kind ==? 'i') && tag.namespace ==? namespace_for_class
+ call add(namespaced_matches, {'word': namespace_for_class.'\'.tag.name, 'kind': tag.kind, 'menu': tag.filename, 'info': tag.filename })
+ elseif (tag.kind ==? 'c' || tag.kind ==? 'i')
+ call add(no_namespace_matches, {'word': namespace_for_class.'\'.tag.name, 'kind': tag.kind, 'menu': tag.filename, 'info': tag.filename })
+ endif
+ endfor
+ " if it seems that the tags file have namespace informations we can safely throw
+ " away namespaceless tag matches since we can be sure they are invalid
+ if patched_ctags_detected
+ no_namespace_matches = []
+ endif
+ let res += namespaced_matches + no_namespace_matches
+ endif
+
+ if base !~ '\'
+ let builtin_classnames = filter(keys(copy(g:php_builtin_classnames)), 'v:val =~? "^'.classname_match_pattern.'"')
+ for classname in builtin_classnames
+ call add(res, {'word': g:php_builtin_classes[tolower(classname)].name, 'kind': 'c'})
+ endfor
+ let builtin_interfacenames = filter(keys(copy(g:php_builtin_interfacenames)), 'v:val =~? "^'.classname_match_pattern.'"')
+ for interfacename in builtin_interfacenames
+ call add(res, {'word': g:php_builtin_interfaces[tolower(interfacename)].name, 'kind': 'i'})
+ endfor
+ endif
+
+ return res
+endfunction
+" }}}
+
+function! phpcomplete#CompleteGeneral(base, current_namespace, imports) " {{{
+ " 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.
+
+ if a:base =~? '^\'
+ let leading_slash = '\'
+ else
+ let leading_slash = ''
+ endif
+
+ 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 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')
+ if f_name =~? '^'.substitute(a:base, '\\', '\\\\', 'g')
+ 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.')'
+ endif
+ endfor
+
+ " 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')
+ if c_name != '' && c_name =~# '^'.substitute(a:base, '\\', '\\\\', 'g')
+ let int_constants[leading_slash.c_name] = ''
+ endif
+ endfor
+
+ " Prepare list of functions from tags file
+ let ext_functions = {}
+ let ext_constants = {}
+ let ext_classes = {}
+ let ext_interfaces = {}
+ let ext_namespaces = {}
+
+ let base = substitute(a:base, '^\\', '', '')
+ let [tag_match_pattern, namespace_for_tag] = phpcomplete#ExpandClassName(a:base, a:current_namespace, a:imports)
+ let namespace_match_pattern = substitute((namespace_for_tag == '' ? '' : namespace_for_tag.'\').tag_match_pattern, '\\', '\\\\', 'g')
+
+ let tags = []
+ if len(namespace_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion && len(tag_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion && tag_match_pattern != namespace_match_pattern
+ let tags = phpcomplete#GetTaglist('\c^\('.tag_match_pattern.'\|'.namespace_match_pattern.'\)')
+ elseif len(namespace_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion
+ let tags = phpcomplete#GetTaglist('\c^'.namespace_match_pattern)
+ elseif len(tag_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion
+ let tags = phpcomplete#GetTaglist('\c^'.tag_match_pattern)
+ endif
+
+ for tag in tags
+ if !has_key(tag, 'namespace') || tag.namespace ==? a:current_namespace || tag.namespace ==? namespace_for_tag
+ if has_key(tag, 'namespace')
+ let full_name = tag.namespace.'\'.tag.name " absolute namespaced name (without leading '\')
+
+ let base_parts = split(a:base, '\')
+ if len(base_parts) > 1
+ let namespace_part = join(base_parts[0:-2], '\')
+ else
+ let namespace_part = ''
+ endif
+ let relative_name = (namespace_part == '' ? '' : namespace_part.'\').tag.name
+ endif
+
+ if tag.kind ==? 'n' && tag.name =~? '^'.namespace_match_pattern
+ let info = tag.name.' - '.tag.filename
+ " patched ctag provides absolute namespace names as tag name, namespace tags dont have namespace fields
+ let full_name = tag.name
+
+ let base_parts = split(a:base, '\')
+ let full_name_parts = split(full_name, '\')
+ if len(base_parts) > 1
+ " the first segment could be a renamed import, take the first segment from the user provided input
+ " so if it's a sub namespace of a renamed namespace, just use the typed in segments in place of the absolute path
+ " for example:
+ " you have a namespace NS1\SUBNS as SUB
+ " you have a sub-sub-namespace NS1\SUBNS\SUBSUB
+ " typed in SUB\SU
+ " the tags will return NS1\SUBNS\SUBSUB
+ " the completion should be: SUB\SUBSUB by replacing the NS1\SUBSN to SUB as in the import
+ if has_key(a:imports, base_parts[0]) && a:imports[base_parts[0]].kind == 'n'
+ let import = a:imports[base_parts[0]]
+ let relative_name = substitute(full_name, '^'.substitute(import.name, '\\', '\\\\', 'g'), base_parts[0], '')
+ else
+ let relative_name = strpart(full_name, stridx(full_name, a:base))
+ endif
+ else
+ let relative_name = strpart(full_name, stridx(full_name, a:base))
+ endif
+
+ if leading_slash == ''
+ let ext_namespaces[relative_name.'\'] = info
+ else
+ let ext_namespaces['\'.full_name.'\'] = info
+ endif
+ elseif tag.kind ==? 'f' && !has_key(tag, 'class') " class related functions (methods) completed elsewhere, only works with patched ctags
+ if has_key(tag, 'signature')
+ let prototype = tag.signature[1:-2] " drop the ()s around the string
+ else
+ let prototype = matchstr(tag.cmd,
+ \ 'function\s\+&\?[^[:space:]]\+\s*(\s*\zs.\{-}\ze\s*)\s*{\?')
+ endif
+ let info = prototype.') - '.tag.filename
+
+ if !has_key(tag, 'namespace')
+ let ext_functions[tag.name.'('] = info
+ else
+ if tag.namespace ==? namespace_for_tag
+ if leading_slash == ''
+ let ext_functions[relative_name.'('] = info
+ else
+ let ext_functions['\'.full_name.'('] = info
+ endif
+ endif
+ endif
+ elseif tag.kind ==? 'd'
+ let info = ' - '.tag.filename
+ if !has_key(tag, 'namespace')
+ let ext_constants[tag.name] = info
+ else
+ if tag.namespace ==? namespace_for_tag
+ if leading_slash == ''
+ let ext_constants[relative_name] = info
+ else
+ let ext_constants['\'.full_name] = info
+ endif
+ endif
+ endif
+ elseif tag.kind ==? 'c' || tag.kind ==? 'i'
+ let info = ' - '.tag.filename
+
+ let key = ''
+ if !has_key(tag, 'namespace')
+ let key = tag.name
+ else
+ if tag.namespace ==? namespace_for_tag
+ if leading_slash == ''
+ let key = relative_name
+ else
+ let key = '\'.full_name
+ endif
+ endif
+ endif
+
+ if key != ''
+ if tag.kind ==? 'c'
+ let ext_classes[key] = info
+ elseif tag.kind ==? 'i'
+ let ext_interfaces[key] = info
+ endif
+ endif
+ endif
+ endif
+ endfor
+
+ let builtin_constants = {}
+ let builtin_classnames = {}
+ let builtin_interfaces = {}
+ let builtin_functions = {}
+ let builtin_keywords = {}
+ let base = substitute(a:base, '^\', '', '')
+ if a:current_namespace == '\' || (a:base =~ '^\\' && a:base =~ '^\\[^\\]*$')
+
+ " Add builtin class names
+ for [classname, info] in items(g:php_builtin_classnames)
+ if classname =~? '^'.base
+ let builtin_classnames[leading_slash.g:php_builtin_classes[tolower(classname)].name] = info
+ endif
+ endfor
+ for [interfacename, info] in items(g:php_builtin_interfacenames)
+ if interfacename =~? '^'.base
+ let builtin_interfaces[leading_slash.interfacename] = info
+ endif
+ endfor
+ endif
+
+ " Prepare list of constants from built-in constants
+ for [constant, info] in items(g:php_constants)
+ if constant =~# '^'.base
+ let builtin_constants[leading_slash.constant] = info
+ endif
+ endfor
+
+ if leading_slash == '' " keywords should not be completed when base starts with '\'
+ " Treat keywords as constants
+ for [constant, info] in items(g:php_keywords)
+ if constant =~? '^'.a:base
+ let builtin_keywords[constant] = info
+ endif
+ endfor
+ endif
+
+ for [function_name, info] in items(g:php_builtin_functions)
+ if function_name =~? '^'.base
+ let builtin_functions[leading_slash.function_name] = info
+ endif
+ endfor
+
+ " All constants
+ call extend(int_constants, ext_constants)
+
+ " All functions
+ call extend(int_functions, ext_functions)
+ call extend(int_functions, builtin_functions)
+
+ for [imported_name, import] in items(a:imports)
+ if imported_name =~? '^'.base
+ if import.kind ==? 'c'
+ if import.builtin
+ let builtin_classnames[imported_name] = ' '.import.name
+ else
+ let ext_classes[imported_name] = ' '.import.name.' - '.import.filename
+ endif
+ elseif import.kind ==? 'i'
+ if import.builtin
+ let builtin_interfaces[imported_name] = ' '.import.name
+ else
+ let ext_interfaces[imported_name] = ' '.import.name.' - '.import.filename
+ endif
+ endif
+
+ " no builtin interfaces
+ if import.kind == 'n'
+ let ext_namespaces[imported_name.'\'] = ' '.import.name.' - '.import.filename
+ endif
+ end
+ endfor
+
+ let all_values = {}
+
+ " Add functions found in this file
+ call extend(all_values, int_functions)
+
+ " Add namespaces from tags
+ call extend(all_values, ext_namespaces)
+
+ " Add constants from the current file
+ call extend(all_values, int_constants)
+
+ " Add built-in constants
+ call extend(all_values, builtin_constants)
+
+ " Add external classes
+ call extend(all_values, ext_classes)
+
+ " Add external interfaces
+ call extend(all_values, ext_interfaces)
+
+ " Add built-in classes
+ call extend(all_values, builtin_classnames)
+
+ " Add built-in interfaces
+ call extend(all_values, builtin_interfaces)
+
+ " Add php keywords
+ call extend(all_values, builtin_keywords)
+
+ let final_list = []
+ let int_list = sort(keys(all_values))
+ for i in int_list
+ if has_key(ext_namespaces, i)
+ let final_list += [{'word':i, 'kind':'n', 'menu': ext_namespaces[i], 'info': ext_namespaces[i]}]
+ elseif has_key(int_functions, i)
+ let final_list +=
+ \ [{'word':i,
+ \ 'info':i.int_functions[i],
+ \ 'menu':int_functions[i],
+ \ 'kind':'f'}]
+ elseif has_key(ext_classes, i) || has_key(builtin_classnames, i)
+ let info = has_key(ext_classes, i) ? ext_classes[i] : builtin_classnames[i].' - builtin'
+ let final_list += [{'word':i, 'kind': 'c', 'menu': info, 'info': i.info}]
+ elseif has_key(ext_interfaces, i) || has_key(builtin_interfaces, i)
+ let info = has_key(ext_interfaces, i) ? ext_interfaces[i] : builtin_interfaces[i].' - builtin'
+ let final_list += [{'word':i, 'kind': 'i', 'menu': info, 'info': i.info}]
+ elseif has_key(int_constants, i) || has_key(builtin_constants, i)
+ let info = has_key(int_constants, i) ? int_constants[i] : ' - builtin'
+ let final_list += [{'word':i, 'kind': 'd', 'menu': info, 'info': i.info}]
+ else
+ let final_list += [{'word':i}]
+ endif
+ endfor
+
+ return final_list
+endfunction
+" }}}
+
+function! phpcomplete#CompleteUnknownClass(base, context) " {{{
+ let res = []
+
+ if g:phpcomplete_complete_for_unknown_classes != 1
+ return []
+ endif
+
+ if a:base =~ '^\$'
+ let adddollar = '$'
+ else
+ let adddollar = ''
+ endif
+
+ let file = getline(1, '$')
+
+ " Internal solution for finding object properties in current file.
+ if a:context =~ '::'
+ let variables = filter(deepcopy(file),
+ \ 'v:val =~ "^\\s*\\(static\\|static\\s\\+\\(public\\|var\\)\\|\\(public\\|var\\)\\s\\+static\\)\\s\\+\\$"')
+ elseif a:context =~ '->'
+ let variables = filter(deepcopy(file),
+ \ 'v:val =~ "^\\s*\\(public\\|var\\)\\s\\+\\$"')
+ endif
+ let jvars = join(variables, ' ')
+ let svars = split(jvars, '\$')
+ let int_vars = {}
+ 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 int_vars[adddollar.c_var] = ''
+ endif
+ endfor
+
+ " Internal solution for finding functions in current file.
+ call filter(deepcopy(file),
+ \ 'v:val =~ "function\\s\\+&\\?[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*("')
+ 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
+
+ " collect external functions from tags
+ let ext_functions = {}
+ let tags = phpcomplete#GetTaglist('^'.substitute(a:base, '^\$', '', ''))
+ for tag in tags
+ if tag.kind ==? 'f'
+ let item = tag.name
+ if has_key(tag, 'signature')
+ let prototype = tag.signature[1:-2]
+ else
+ let prototype = matchstr(tag.cmd,
+ \ 'function\s\+&\?[^[:space:]]\+\s*(\s*\zs.\{-}\ze\s*)\s*{\?')
+ endif
+ let ext_functions[item.'('] = prototype.') - '.tag['filename']
+ endif
+ endfor
+
+ " All functions to one hash for later reference when deciding kind
+ call extend(int_functions, ext_functions)
+
+ let all_values = {}
+ call extend(all_values, int_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],
+ \ 'menu':all_values[i],
+ \ 'kind':'f'}]
+ endif
+ endfor
+ return final_list
+endfunction
+" }}}
+
+function! phpcomplete#CompleteVariable(base) " {{{
+ let res = []
+
+ " 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
+ if g:phpcomplete_search_tags_for_variables
+ let ext_vars = {}
+ let tags = phpcomplete#GetTaglist('\C^'.substitute(a:base, '^\$', '', ''))
+ for tag in tags
+ if tag.kind ==? 'v'
+ let item = tag.name
+ let m_menu = ''
+ if tag.cmd =~? tag['name'].'\s*=\s*new\s\+'
+ let m_menu = matchstr(tag.cmd,
+ \ '\c=\s*new\s\+\zs[a-zA-Z_0-9\x7f-\xff]\+\ze')
+ endif
+ let ext_vars['$'.item] = m_menu
+ endif
+ endfor
+ call extend(int_vars, ext_vars)
+ endif
+
+ 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], 'menu':int_vars[i], 'kind':'v'}]
+ else
+ let int_dict += [{'word':i, 'kind':'v'}]
+ endif
+ endfor
+
+ return int_dict
+endfunction
+" }}}
+
+function! phpcomplete#CompleteClassName(base, kinds, current_namespace, imports) " {{{
+ let kinds = sort(a:kinds)
+ " Complete class name
+ let res = []
+ if a:base =~? '^\'
+ let leading_slash = '\'
+ let base = substitute(a:base, '^\', '', '')
+ else
+ let leading_slash = ''
+ let base = a:base
+ endif
+
+ " Internal solution for finding classes in current file.
+ let file = getline(1, '$')
+ let filterstr = ''
+
+ if kinds == ['c', 'i']
+ let filterstr = 'v:val =~? "\\(class\\|interface\\)\\s\\+[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*"'
+ elseif kinds == ['c']
+ let filterstr = 'v:val =~? "class\\s\\+[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*"'
+ elseif kinds == ['i']
+ let filterstr = 'v:val =~? "interface\\s\\+[a-zA-Z_\\x7f-\\xff][a-zA-Z_0-9\\x7f-\\xff]*\\s*"'
+ endif
+
+ call filter(file, filterstr)
+
+ for line in file
+ let c_name = matchstr(line, '\c\(class\|interface\)\s*\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*')
+ let kind = (line =~? '^\s*class' ? 'c' : 'i')
+ if c_name != '' && c_name =~? '^'.base
+ call add(res, {'word': c_name, 'kind': kind})
+ endif
+ endfor
+
+ " resolve the typed in part with namespaces (if theres a \ in it)
+ let [tag_match_pattern, namespace_for_class] = phpcomplete#ExpandClassName(a:base, a:current_namespace, a:imports)
+
+ let tags = []
+ if len(tag_match_pattern) >= g:phpcomplete_min_num_of_chars_for_namespace_completion
+ let tags = phpcomplete#GetTaglist('^'.tag_match_pattern)
+ endif
+
+ if len(tags)
+ let base_parts = split(a:base, '\')
+ if len(base_parts) > 1
+ let namespace_part = join(base_parts[0:-2], '\').'\'
+ else
+ let namespace_part = ''
+ endif
+ let no_namespace_matches = []
+ let namespaced_matches = []
+ let seen_namespaced_tag = 0
+ for tag in tags
+ if has_key(tag, 'namespace')
+ let seen_namespaced_tag = 1
+ endif
+ let relative_name = namespace_part.tag.name
+ " match base without the namespace part for namespaced base but not namespaced tags, for tagfiles with old ctags
+ if !has_key(tag, 'namespace') && index(kinds, tag.kind) != -1 && stridx(tag.name, base[len(namespace_part):]) == 0
+ call add(no_namespace_matches, {'word': leading_slash.relative_name, 'kind': tag.kind, 'menu': tag.filename, 'info': tag.filename })
+ endif
+ if has_key(tag, 'namespace') && index(kinds, tag.kind) != -1 && tag.namespace ==? namespace_for_class
+ let full_name = tag.namespace.'\'.tag.name " absolute namespaced name (without leading '\')
+ call add(namespaced_matches, {'word': leading_slash == '\' ? leading_slash.full_name : relative_name, 'kind': tag.kind, 'menu': tag.filename, 'info': tag.filename })
+ endif
+ endfor
+ " if there was a tag with namespace field, assume tag files with namespace support, so the matches
+ " without a namespace field are in the global namespace so if there were namespace in the base
+ " we should not add them to the matches
+ if seen_namespaced_tag && namespace_part != ''
+ let no_namespace_matches = []
+ endif
+ let res += no_namespace_matches + namespaced_matches
+ endif
+
+ " look for built in classnames and interfaces
+ let base_parts = split(base, '\')
+ if a:current_namespace == '\' || (leading_slash == '\' && len(base_parts) < 2)
+ if index(kinds, 'c') != -1
+ let builtin_classnames = filter(keys(copy(g:php_builtin_classnames)), 'v:val =~? "^'.substitute(a:base, '\\', '', 'g').'"')
+ for classname in builtin_classnames
+ let menu = ''
+ " if we have a constructor for this class, add parameters as to the info
+ if has_key(g:php_builtin_classes[tolower(classname)].methods, '__construct')
+ let menu = g:php_builtin_classes[tolower(classname)]['methods']['__construct']['signature']
+ endif
+ call add(res, {'word': leading_slash.g:php_builtin_classes[tolower(classname)].name, 'kind': 'c', 'menu': menu})
+ endfor
+ endif
+
+ if index(kinds, 'i') != -1
+ let builtin_interfaces = filter(keys(copy(g:php_builtin_interfaces)), 'v:val =~? "^'.substitute(a:base, '\\', '', 'g').'"')
+ for interfacename in builtin_interfaces
+ call add(res, {'word': leading_slash.g:php_builtin_interfaces[interfacename]['name'], 'kind': 'i', 'menu': ''})
+ endfor
+ endif
+ endif
+
+ " add matching imported things
+ for [imported_name, import] in items(a:imports)
+ if imported_name =~? '^'.base && index(kinds, import.kind) != -1
+ let menu = import.name.(import.builtin ? ' - builtin' : '')
+ call add(res, {'word': imported_name, 'kind': import.kind, 'menu': menu})
+ endif
+ endfor
+
+ let res = sort(res, 'phpcomplete#CompareCompletionRow')
+ return res
+endfunction
+" }}}
+
+function! phpcomplete#CompareCompletionRow(i1, i2) " {{{
+ return a:i1.word == a:i2.word ? 0 : a:i1.word > a:i2.word ? 1 : -1
+endfunction
+" }}}
+
+function! phpcomplete#EvaluateModifiers(modifiers, required_modifiers, prohibited_modifiers) " {{{
+ " if theres no modifier, and no modifier is allowed and no modifier is required
+ if len(a:modifiers) == 0 && len(a:required_modifiers) == 0
+ return 1
+ else
+ " check if every requred modifier is present
+ for required_modifier in a:required_modifiers
+ if index(a:modifiers, required_modifier) == -1
+ return 0
+ endif
+ endfor
+
+ for modifier in a:modifiers
+ " if the modifier is prohibited its a no match
+ if index(a:prohibited_modifiers, modifier) != -1
+ return 0
+ endif
+ endfor
+
+ " anything that is not explicitly required or prohibited is allowed
+ return 1
+ endif
+endfunction
+" }}}
+
+function! phpcomplete#CompleteUserClass(context, base, sccontent, visibility) " {{{
+ let final_list = []
+ let res = []
+
+ let required_modifiers = []
+ let prohibited_modifiers = []
+
+ if a:visibility == 'public'
+ let prohibited_modifiers += ['private', 'protected']
+ endif
+
+ " limit based on context to static or normal methods
+ let static_con = ''
+ if a:context =~ '::$' && a:context !~? 'parent::$'
+ if g:phpcomplete_relax_static_constraint != 1
+ let required_modifiers += ['static']
+ endif
+ elseif a:context =~ '->$'
+ let prohibited_modifiers += ['static']
+ endif
+
+ let all_function = filter(deepcopy(a:sccontent),
+ \ 'v:val =~ "^\\s*\\(public\\s\\+\\|protected\\s\\+\\|private\\s\\+\\|final\\s\\+\\|abstract\\s\\+\\|static\\s\\+\\)*function"')
+
+ let functions = []
+ for i in all_function
+ let modifiers = split(matchstr(tolower(i), '\zs.\+\zefunction'), '\s\+')
+ if phpcomplete#EvaluateModifiers(modifiers, required_modifiers, prohibited_modifiers) == 1
+ call add(functions, i)
+ endif
+ endfor
+
+ let c_functions = {}
+ let c_doc = {}
+ for i in functions
+ let f_name = matchstr(i,
+ \ 'function\s*&\?\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
+ let f_args = matchstr(i,
+ \ 'function\s*&\?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\s*(\zs.\{-}\ze)\_s*\({\|\_$\)')
+ if f_name != '' && stridx(f_name, '__') != 0
+ let c_functions[f_name.'('] = f_args
+ if g:phpcomplete_parse_docblock_comments
+ let c_doc[f_name.'('] = phpcomplete#GetDocBlock(a:sccontent, 'function\s*&\?\<'.f_name.'\>')
+ endif
+ endif
+ endfor
+
+ " limit based on context to static or normal attributes
+ if a:context =~ '::$' && a:context !~? 'parent::$'
+ " variables must have static to be accessed as static unlike functions
+ let required_modifiers += ['static']
+ endif
+ let all_variable = filter(deepcopy(a:sccontent),
+ \ 'v:val =~ "^\\s*\\(var\\s\\+\\|public\\s\\+\\|protected\\s\\+\\|private\\s\\+\\|final\\s\\+\\|abstract\\s\\+\\|static\\s\\+\\)\\+\\$"')
+
+ let variables = []
+ for i in all_variable
+ let modifiers = split(matchstr(tolower(i), '\zs.\+\ze\$'), '\s\+')
+ if phpcomplete#EvaluateModifiers(modifiers, required_modifiers, prohibited_modifiers) == 1
+ call add(variables, i)
+ endif
+ endfor
+
+ let jvars = join(variables, ' ')
+ let svars = split(jvars, '\$')
+ let c_variables = {}
+
+ let var_index = 0
+ 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 != ''
+ if a:context =~ '::$'
+ let c_var = '$'.c_var
+ endif
+ let c_variables[c_var] = ''
+ if g:phpcomplete_parse_docblock_comments && len(get(variables, var_index)) > 0
+ let c_doc[c_var] = phpcomplete#GetDocBlock(a:sccontent, variables[var_index])
+ endif
+ let var_index += 1
+ endif
+ endfor
+
+ let constants = filter(deepcopy(a:sccontent),
+ \ 'v:val =~ "^\\s*const\\s\\+"')
+
+ let jcons = join(constants, ' ')
+ let scons = split(jcons, 'const')
+
+ let c_constants = {}
+ let const_index = 0
+ for i in scons
+ let c_con = matchstr(i,
+ \ '^\s*\zs[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\ze')
+ if c_con != ''
+ let c_constants[c_con] = ''
+ if g:phpcomplete_parse_docblock_comments && len(get(constants, const_index)) > 0
+ let c_doc[c_con] = phpcomplete#GetDocBlock(a:sccontent, constants[const_index])
+ endif
+ let const_index += 1
+ endif
+ endfor
+
+ let all_values = {}
+ call extend(all_values, c_functions)
+ call extend(all_values, c_variables)
+ call extend(all_values, c_constants)
+
+ for m in sort(keys(all_values))
+ if stridx(m, a:base) == 0
+ call add(res, m)
+ endif
+ endfor
+
+ let start_list = res
+
+ let final_list = []
+ for i in start_list
+ let docblock = phpcomplete#ParseDocBlock(get(c_doc, i, ''))
+ if has_key(c_variables, i)
+ let final_list +=
+ \ [{'word': i,
+ \ 'info':phpcomplete#FormatDocBlock(docblock),
+ \ 'menu':get(docblock.var, 'type', ''),
+ \ 'kind':'v'}]
+ elseif has_key(c_constants, i)
+ let info = phpcomplete#FormatDocBlock(docblock)
+ if info != ''
+ let info = "\n".info
+ endif
+ let final_list +=
+ \ [{'word':i,
+ \ 'info':i.info,
+ \ 'menu':all_values[i],
+ \ 'kind':'d'}]
+ else
+ let return_type = get(docblock.return, 'type', '')
+ if return_type != ''
+ let return_type = ' | '.return_type