diff options
author | Bram Moolenaar <Bram@vim.org> | 2014-03-22 21:02:50 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2014-03-22 21:02:50 +0100 |
commit | a68783751647e3243ca6f22df62907efbdbccb02 (patch) | |
tree | 372908fd79defec22190b6d2a48bbc6155bb22e7 /runtime/autoload/phpcomplete.vim | |
parent | ed287f9a4e3f4ed5528ad2af65b7b23bce14a688 (diff) |
Updated runtime files.
Diffstat (limited to 'runtime/autoload/phpcomplete.vim')
-rw-r--r-- | runtime/autoload/phpcomplete.vim | 7315 |
1 files changed, 2360 insertions, 4955 deletions
diff --git a/runtime/autoload/phpcomplete.vim b/runtime/autoload/phpcomplete.vim index 07565558fe..2ff94ced22 100644 --- a/runtime/autoload/phpcomplete.vim +++ b/runtime/autoload/phpcomplete.vim @@ -1,27 +1,99 @@ " Vim completion script " Language: PHP -" Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl ) -" Last Change: 2011 Dec 08 +" Maintainer: Dávid Szabó ( complex857 AT gmail DOT com ) +" Previous Maintainer: Mikolaj Machowski ( mikmach AT wp DOT pl ) " -" 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) +" 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', + let phpend = searchpairpos('<?', '', '?>', 'Wn', \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string\|comment"') if phpbegin == [0,0] && phpend == [0,0] @@ -37,5108 +109,2416 @@ function! phpcomplete#CompletePHP(findstart, base) 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$]' + 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 + let b:phpbegin = phpbegin + let b:compl_context = phpcomplete#GetCurrentInstruction(line('.'), 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 - " Initialize base return lists - let res = [] - let res2 = [] + + 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 - endif + " chop of the "base" from the end of the current instruction + if a:base != "" + let context = substitute(context, '\s*\$\?\([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*\)*$', '', '') + end + end - if !exists('g:php_builtin_functions') - call phpcomplete#LoadData() + let [current_namespace, imports] = phpcomplete#GetCurrentNameSpace(getline(0, line('.'))) + + if context =~? '^use\s' + return phpcomplete#CompleteUse(a:base) 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] = '' + 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 - endfor + let classlocation = phpcomplete#GetClassLocation(classname, namespace) + else + let classlocation = '' + endif - " 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 + if classlocation != '' + if classlocation == 'VIMPHP_BUILTINOBJECT' && has_key(g:php_builtin_classes, tolower(classname)) + return phpcomplete#CompleteBuiltInClass(context, classname, a:base) 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 + 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 - let classes = sort(keys(int_classes)) - let classes += sort(keys(ext_classes)) - let classes += sort(keys(g:php_omni_bi_classes)) + 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 - for m in classes - if m =~ '^'.a:base - call add(res, m) + 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 - let final_menu = [] - for i in res - let final_menu += [{'word':i, 'kind':'c'}] + 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': classname, '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': interfacename, 'kind': 'i'}) endfor + endif - return final_menu + return res +endfunction +" }}} - 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) +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 - if scontext =~ '->$' && scontext !~ '\$this->$' + " Internal solution for finding functions in current file. - " Get name of the class - let classname = phpcomplete#GetClassName(scontext) + if a:base =~? '^\' + let leading_slash = '\' + else + let leading_slash = '' + endif - " 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 + 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 - if classlocation == 'VIMPHP_BUILTINOBJECT' + " 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 - 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 + " 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 - return res + 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 filereadable(classlocation) - let classfile = readfile(classlocation) - let classcontent = '' - let classcontent .= "\n".phpcomplete#GetClassContents(classfile, classname) - let sccontent = split(classcontent, "\n") + 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 - " 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 + 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 - 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 + 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 - 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 + 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 - endfor - - let start_list = res + res2 + endif - 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'}] + if key != '' + if tag.kind ==? 'c' + let ext_classes[key] = info + elseif tag.kind ==? 'i' + let ext_interfaces[key] = info endif - endfor + 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.classname] = 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 - return final_list + " 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 - if a:base =~ '^\$' - let adddollar = '$' + " 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 adddollar = '' + 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 - 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]*').'->' + 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 val = matchstr(i, '^[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*') + let prototype = matchstr(tag.cmd, + \ 'function\s\+&\?[^[:space:]]\+\s*(\s*\zs.\{-}\ze\s*)\s*{\?') endif - if val !~ '' - let int_vars[adddollar.val] = '' - endif - endfor + let ext_functions[item.'('] = prototype.') - '.tag['filename'] + 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 + " 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 +" }}} - " Now we have all variables in int_vars dictionary - call extend(int_vars, ext_vars) +function! phpcomplete#CompleteVariable(base) " {{{ + let res = [] - " 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 + " 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 - " 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 + 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 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) + let int_list = res - for m in sort(keys(all_values)) - if m =~ '\(^\|::\)'.a:base - call add(res, m) + let int_dict = [] + for i in int_list + if int_vars[i] != '' + let class = ' ' + if int_vars[i] != '' + let class = i.' class ' endif - endfor + 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 - let start_list = res + return int_dict +endfunction +" }}} - 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'}] +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 f |