diff options
Diffstat (limited to 'runtime/autoload/phpcomplete.vim')
| -rw-r--r-- | runtime/autoload/phpcomplete.vim | 2572 |
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 |