Repository: m2mdas/phpcomplete-extended Branch: master Commit: 7cd1f9690a22 Files: 31 Total size: 23.5 MB Directory structure: gitextract_gva2ohpo/ ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── autoload/ │ ├── neocomplete/ │ │ └── sources/ │ │ └── php.vim │ ├── neosnippet/ │ │ └── snippets/ │ │ ├── php.snip │ │ └── vim.snip │ ├── phpcomplete_extended/ │ │ ├── parser.vim │ │ └── util.vim │ ├── phpcomplete_extended.vim │ ├── unite/ │ │ ├── kinds/ │ │ │ └── phpcomplete.vim │ │ └── sources/ │ │ └── phpcomplete.vim │ ├── vital/ │ │ ├── _62ad025/ │ │ │ ├── Data/ │ │ │ │ ├── List.vim │ │ │ │ └── String.vim │ │ │ ├── Prelude.vim │ │ │ ├── System/ │ │ │ │ ├── Cache.vim │ │ │ │ ├── File.vim │ │ │ │ └── Filepath.vim │ │ │ ├── Text/ │ │ │ │ ├── Lexer.vim │ │ │ │ └── Parser.vim │ │ │ └── Web/ │ │ │ └── JSON.vim │ │ ├── _62ad025.vim │ │ └── phpcomplete-extended.vital │ └── vital.vim ├── bin/ │ ├── CorePHPDocParser.php │ ├── IndexGenerator.php │ └── core_index ├── doc/ │ └── phpcomplete-extended.txt ├── plugin/ │ └── phpcomplete_extended.vim └── vest/ ├── test-fowrard-parse.vim └── test-reverse-parser.vim ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.vim text eol=lf *.php text eol=lf *.txt text eol=lf *.md text eol=lf bin/core_index binary ================================================ FILE: .gitignore ================================================ tests/ phpcompletePSR.rar ================================================ FILE: LICENSE ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ phpcomplete-extended ==================== phpcomplete-extended is a fast, extensible, context aware autocomplete plugin for PHP composer projects. Initially it reads autoload classmap of a composer project, parses doc-comments of each class and creates index from them. After that it auto updates index as you type thanks to [vimproc.vim](https://github.com/Shougo/vimproc.vim) plugin. Besides autocomplete this plugin have several code inspection features, * Includes full core PHP documentation * See documentation of current word, be it class name, method or property. It is context aware. * Go to definition of a symbol. Also context aware. * Automatically add use statement of current completed word. Also added plugin command of this action. * If [unite.vim](https://github.com/Shougo/unite.vim/) plugin installed following sources are available, * `phpcomplete/files` : Lists PHP files of the project. * `phpcomplete/vendors` : Lists vendor directories * `phpcomplete/extends` : Lists classes that extends the class guessed from the current cursor word. * `phpcomplete/implements` : Lists classes that implements the class guessed from the current cursor word. Demo videos (click on the image to goto youtube) ----------------------------------------------- ## Autocomplete demo video: [![ScreenShot](http://img.youtube.com/vi/yZYFKslqkC8/maxresdefault.jpg)](http://www.youtube.com/watch?v=yZYFKslqkC8) ## Unite sources demo video: [![ScreenShot](http://i1.ytimg.com/vi/Wd5G7QA3OFw/maxresdefault.jpg)](http://www.youtube.com/watch?v=Wd5G7QA3OFw) Installation ------------- The plugin depends on following plugins, * [vimproc.vim](https://github.com/Shougo/vimproc.vim) : Asynchronous library for vim. Required for auto-update index. C compiler is needed to build the plugiin. For windows install cygwin or msys to get the compiler. * [unite.vim](https://github.com/Shougo/unite.vim/): Optional, but enables some good features. Plugin managers are recommended for installing these plugins. Installation instructions for various plugin managers are given bellow. ## Pathogen Issue following commands. ```sh git clone https://github.com/Shougo/vimproc.vim.git ~/.vim/bundle/vimproc.vim cd ~/.vim/bundle/vimproc.vim make cd .. git clone https://github.com/Shougo/unite.vim.git ~/.vim/bundle/unit.vim git clone https://github.com/m2mdas/phpcomplete-extended.git ~/.vim/bundle/phpcomplete-extended ``` ## NeoBundle (preferred) Put these lines in `.vimrc` and issue `source %` command ```vim NeoBundle 'Shougo/vimproc', { \ 'build' : { \ 'windows' : 'make -f make_mingw32.mak', \ 'cygwin' : 'make -f make_cygwin.mak', \ 'mac' : 'make -f make_mac.mak', \ 'unix' : 'make -f make_unix.mak', \ }, \ } NeoBundle 'Shougo/unite.vim' NeoBundle 'm2mdas/phpcomplete-extended' "your other plugins NeoBundleCheck ``` If C compiler found for respective environment, `neobundle` will automatically compile the library. ## Vundle Put following lines in `.vimrc` and issue `:BundleInstall` command. ```vim Bundle 'Shougo/vimproc' Bundle 'Shougo/unite.vim' Bundle 'm2mdas/phpcomplete-extended' "your other plugins ".... ``` After installation goto `vimproc` folder and issue `make` command from command line. C compiler must be installed. Walk through of the plugin ------------------------- To enable omni complete add following line to `.vimrc`, autocmd FileType php setlocal omnifunc=phpcomplete_extended#CompletePHP Then issuing `` in insert mode will open the completion pop-up menu. This plugin is tested in Windows and Linux. It should work in OSX also. For now this plugin supports [Composer](http://getcomposer.org/) PHP projects. Upon detecting a composer project the plugin scans classmap generated by `php composer.phar dumpautoload --optimize` command. By default composer command is set to `php composer.phar`. The command can be changed by setting `g:phpcomplete_index_composer_command` global variable. The plugin is dependent on `@var`, `@param`, `@return` doc-comments to give proper context aware autocomplete. So documenting your class will help tremendously. It does not analyse full class. Just parses the variable declaration to get the relevant tokens. Does not provide autocomplete suggestion if there is error in code. Indexing may take some time depending on project size. To decrease index creation time I would recommend disabling xdebug extension in cli. To do that go to the location where `php.ini` resides and copy the `php.ini` file as `php-cli.ini` and comment out the xdebug line. To verify issue `php -m | grep xdebug` in commandline. For configuration reference see helpdoc. Auto complete plugin supports ----------------------------- ## [neocomplete](https://github.com/Shougo/neocomplete.vim) Neocomplete support is built-in and my preferred autocomplete plugin. It needs vim with lua bindings. To know more about installation refer to the plugin repository. ## [supertab](https://github.com/ervandew/supertab) Minimal configuration for supertab support, autocmd FileType php setlocal omnifunc=phpcomplete_extended#CompletePHP let g:SuperTabDefaultCompletionType = "" ## [YouCompleteMe](https://github.com/Valloric/YouCompleteMe) If `omnifunc` set the omni completer of `YouCompleteMe` should get the completion candidates. I haven't tested it though. Extensions --------- Phpcomplete Extended pluign exposes api hooks so that it can be possible to provide framework specific autocomplete suggestion. For example facades in laravel, DIC services in Symfony2 etc. The plugin api divided in two part, `PHP` and `vim`. PHP part is responsible for creating index related to the framework, and vim part is responsible for providing autocomplete menu entries based on the index. For reference see [phpcomplete-extended-symfony](https://github.com/m2mdas/phpcomplete-extended-symfony) and [phpcomplete-extended-laravlel](https://github.com/m2mdas/phpcomplete-extended-laravel) plugins. License ------- MIT licensed. Acknowledgement --------------- This plugin would not be possible without the works of [Shougo](https://github.com/Shougo), [shawncplus](https://github.com/shawncplus/), [tpope](https://github.com/tpope/), [vim-jp](https://github.com/vim-jp), [thinca](https://github.com/thinca) and many others. ================================================ FILE: autoload/neocomplete/sources/php.vim ================================================ "============================================================================= " AUTHOR: Mun Mun Das " FILE: php.vim " Last Modified: September 10, 2013 " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim let s:source = { \ 'name' : 'php', \ 'kind' : 'manual', \ 'mark' : '[P]', \ 'rank' : 6, \ 'hooks' : {}, \ 'filetypes' : { 'php' : 1}, \} let s:List = vital#of('neocomplete').import('Data.List') function! s:source.get_complete_position(context) "{{{ return s:get_complete_position(a:context) endfunction "}}} function! s:source.gather_candidates(context) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || &ft != 'php' return [] endif return s:gather_candidates(a:context) endfunction"}}} function! s:get_complete_position(context) "{{{ return phpcomplete_extended#CompletePHP(1, "") endfunction "}}} function! s:gather_candidates(context) "{{{ return phpcomplete_extended#CompletePHP(0, b:completeContext.base) endfunction "}}} function! neocomplete#sources#php#define() "{{{ return s:source endfunction"}}} function! s:set_neocomplete_sources() "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !g:loaded_neocomplete return endif let avail_sources = keys(neocomplete#available_sources()) if index(avail_sources, "omni") != -1 call remove(avail_sources, index(avail_sources, 'omni')) endif let b:neocomplete_sources = avail_sources endfunction "}}} augroup neocomplete_php autocmd! autocmd BufEnter *.php :call set_neocomplete_sources() augroup END let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker:expandtab:ts=4:sts=4:tw=78 ================================================ FILE: autoload/neosnippet/snippets/php.snip ================================================ snippet phpcomplete_extended_plugin index = array(); } /** * called to check that this plugin script is valid for current project * * @return bool */ public function isValidForProject() { ${2} //return is_file('projects/unique/file'); } /** * This hook is called before indexing started. Bootstraping code of the * framework goes here * @parem ClassLoader $loader the composer ClassLoader object */ public function init($loader) { } /** * Called after a class is processed * * @param string $fqcn FQCN of the class * @param string $file file name of the class * @param array $classData processed class info */ public function postProcess($fqcn, $file, $classData) { } /** * Called after main index is created * * @param mixed $fullIndex the main index * @param object $generatorObject the parant generator object */ public function postCreateIndex($fullIndex, $generatorObject) { } /** * Called after update script initialized * * @param mixed $prevIndex the previous index of this plugin */ public function preUpdateIndex($prevIndex) { } /** * Called after update index created * * @param mixed $classData class data of processed class * @param mixed $fullIndex the main index * @param object $generatorObject the parant generator object */ public function postUpdateIndex($classData, $fullIndex, $generatorObject) { } /** * Returns the plugin index * * @return mixed the index created by this plugin script */ public function getIndex() { return $this->index; } } ================================================ FILE: autoload/neosnippet/snippets/vim.snip ================================================ snippet phpcomplete_extended_plugin let s:${1:plugin_name}_plugin = { \ 'name': '$1' \} if !exists("s:$1_index") let s:$1_index = {} endif function! phpcomplete_extended#$1#define() "{{{ let is_$1_project = 0 "check this is a valid $1 project if is_$1_project return s:$1_plugin endif return {} endfunction "}}} function! s:$1_plugin.init() "{{{ ${2} endfunction "}}} function! s:$1_plugin.is_valid_for_project() "{{{ return true endfunction "}}} function! s:$1_plugin.get_fqcn(parentFQCN, token_data) "{{{ let fqcn = "" "get fqcn return fqcn endfunction "}}} function! s:$1_plugin.get_menu_entries(fqcn, base, is_this, is_static) "{{{ let menu_entries = [] " get menu entries return menu_entries endfunction "}}} function! s:$1_plugin.set_index(index) "{{{ let s:$1_index = a:index endfunction "}}} function! s:$1_plugin.resolve_fqcn(fqcn) "{{{ return a:fqcn endfunction "}}} function! s:$1_plugin.get_inside_quote_menu_entries(parentFQCN, token_data) "{{{ let menu_entries = [] //process menu entries return menu_entries endfunction "}}} ================================================ FILE: autoload/phpcomplete_extended/parser.vim ================================================ let s:save_cpo = &cpo set cpo&vim let s:lexer_symbols = [ \ ['new' , '\'] , \ ['identifier' , '\h\w*'] , ['whitespace' , '\s*'] , \ ['open_brace' , '('] , ['close_brace' , ')'] , \ ['dollar' , '\$'] , \ ['single_quote' , "'"] , ['double_quote' , '"'] , \ ["object_resolutor" , "->"] , ['static_resolutor' , "::"] , \ ['curly_brace_open' , '{'] , ['curly_brace_close' , '}'] , \ ['square_brace_open' , '['] , ['square_brace_close' , ']'] , \ ['ns_seperator' , "\\"] , ['equal' , '='] , \ ['front_slash' , "/"] , ['star' , '*'] , \ ['alpha' , "@"] , ['plus' , '+'] , \ ['OR' , "|"] , ['AND' , '&'] , \ ['negate' , "!"] , ['dash' , '-'] , \ ['semicolon' , ';'] , ['colon' , ':'] , \ ['dot' , '\.'] , \ ['left_arrow' , '>'] , ['right_arrow' , '<'] , \['comma' , ','], \['others' , '[^()\[\]""'',;:*/@|&+!-]\+'] , \] function! phpcomplete_extended#parser#reverseParse(line, parsedTokens) "{{{ let line = phpcomplete_extended#util#trim(a:line) if line =~ ';$' let line = phpcomplete_extended#util#getDataString().chop(line) endif if line == "" return [{'insideBraceText': '', 'methodPropertyText': '', 'nonClass': 1, 'start': 1}] endif let parsedTokens = a:parsedTokens let braceStack = [] let quoteStack = [] let L = phpcomplete_extended#util#getLexer() let P = phpcomplete_extended#util#getParser() let List = phpcomplete_extended#util#getDataList() let lexerTokens = reverse(L.lexer(s:lexer_symbols).exec(line)) let parser = P.parser().exec(lexerTokens) try catch return [] endtry let next = parser.next() let insideBraceText = "" let expectResolutor = 1 let methodPropertyText = "" let isMethod = 0 let isArray = 0 let isStatic = 0 let maybeNew = 0 let insideQuote = 0 let startSkipping = 0 let previousToken = "" while !parser.end() "PrettyPrint parser.next() "PrettyPrint braceStack "PrettyPrint parsedTokens "call parser.consume() "continue if parser.next_is('whitespace') call parser.consume() if empty(braceStack) && previousToken == "identifier" && !parser.next_is('new') let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, isMethod, 1) let parsedObject.nonClass = 1 call insert(parsedTokens, parsedObject) break endif continue elseif startSkipping && !parser.next_is('open_brace') call parser.consume() continue elseif parser.next_is('identifier') let identifier = parser.consume()['matched_text'] if empty(braceStack) && isArray \ && (parser.next_is('object_resolutor') || parser.next_is('static_resolutor') || parser.next_is('dollar')) \ && previousToken == 'square_brace_open' "'$foo->bar["baz"]->bzz' let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, 0, 0) let parsedObject.isArrayElement = 1 let insideBraceText = "" let methodPropertyText = identifier if insideQuote let parsedObject.insideQuote = 1 let insideQuote = 0 endif call insert(parsedTokens, parsedObject) let isArray = 0 elseif (empty(insideBraceText) && empty(methodPropertyText) && parser.end()) \ || (parser.end() && previousToken == "open_brace") \ || (parser.end() && previousToken == "static_resolutor") \ || parser.next_is('open_brace') \ || parser.next_is('square_brace_open') "foo_bar, foo(, Foo::, $this->get(foo_func('bar, $foo[Foo::Bar let methodPropertyText = identifier . methodPropertyText let start = 1 let isMethod = 0 let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, isMethod, start) let methodPropertyText = "" let insideBraceText = "" if insideQuote let parsedObject.insideQuote = 1 let insideQuote = 0 endif if previousToken == "static_resolutor" let parsedObject.isStatic = 1 let parsedObject.nonClass = 0 else let parsedObject.nonClass = 1 endif call insert(parsedTokens, parsedObject) break elseif empty(braceStack) && ( \ parser.next_is('dollar') \ || parser.next_is('whitespace') \ || parser.next_is("object_resolutor") \ || parser.next_is("static_resolutor") \ || parser.next_is("ns_seperator") \) let methodPropertyText = identifier .methodPropertyText else if(isArray) let isArray = 0 endif let insideBraceText = identifier .insideBraceText endif let previousToken = "identifier" elseif parser.next_is('open_brace') || parser.next_is("square_brace_open") let open_brace = parser.consume()['matched_text'] if !empty(braceStack) && open_brace == "(" && braceStack[-1] == ")" if startSkipping let startSkipping = 0 endif call List.pop(braceStack) let isMethod = 1 elseif !empty(braceStack) && open_brace == "[" && braceStack[-1] == "]" call List.pop(braceStack) let isArray = 1 elseif (previousToken == "single_quote" || previousToken == "double_quote") \ && parser.next_is("identifier") && open_brace == "[" if !empty(quoteStack) call List.pop(quoteStack) endif let isArray = 1 elseif len(insideBraceText) && insideBraceText[0] !~ '''\|"' let insideBraceText = open_brace . insideBraceText endif let previousToken = "open_brace" if open_brace == "[" let previousToken = "square_brace_open" endif elseif parser.next_is('close_brace') || parser.next_is("square_brace_close") let close_brace = parser.consume()['matched_text'] "if empty(braceStack) "\ && (previousToken == "static_resolutor" || previousToken == "object_resolutor") "\ && (parser.next_is('close_brace')) ""(new Foo())->bar "let maybeNew = 1 "continue if (empty(quoteStack) && ( \ (close_brace == ')' && parser.next_is('open_brace')) \ || (close_brace == ']' && parser.next_is('square_brace_open')) \ || (previousToken == 'object_resolutor' || previousToken == 'static_resolutor') \ || (parser.next_is('single_quote') || parser.next_is('double_quote')) \ || (parser.next_is('identifier')) \)) call List.push(braceStack, close_brace) if (close_brace == ")" && isArray && previousToken == "square_brace_open") "'$this->foo()[]->' let parsedObject = s:createParsedObject(insideBraceText, '', 0, 0) let parsedObject.isArrayElement = 1 let isArray = 0 let insideBraceText = "" let methodPropertyText = "" call insert(parsedTokens, parsedObject) endif else let insideBraceText = close_brace . insideBraceText endif let previousToken = "close_brace" if previousToken == "]" let previousToken = "square_brace_close" endif elseif parser.next_is('single_quote') || parser.next_is('double_quote') let quote = parser.consume()['matched_text'] if empty(quoteStack) && !parser.next_is('ns_seperator') call List.push(quoteStack, quote) let insideBraceText = quote . insideBraceText let insideQuote = 1 let insideBraceText .= methodPropertyText let methodPropertyText = "" else if empty(quoteStack) continue endif let qstack = copy(quoteStack) let q = List.pop(qstack) if q == quote && !parser.next_is('ns_seperator') call List.pop(quoteStack) let insideQuote = 0 let insideBraceText = quote . insideBraceText endif endif let previousToken = "single_quote" if quote == '"' let previousToken = "double_quote" endif elseif parser.next_is('static_resolutor') || parser.next_is('object_resolutor') let resolutor = parser.consume()['matched_text'] if empty(braceStack) && (previousToken == "identifier" || empty(previousToken)) \ && (parser.next_is('identifier') \ || parser.next_is('close_brace') \ || parser.next_is('square_brace_close')) let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, isMethod, 0) let isMethod = 0 if insideQuote let parsedObject.insideQuote = 1 let insideQuote = 0 endif if resolutor == "::" let parsedObject.isStatic = 1 let isStatic = 0 endif let methodPropertyText = "" let insideBraceText = "" call insert(parsedTokens, parsedObject) else let insideBraceText = resolutor .insideBraceText endif let previousToken = "object_resolutor" if resolutor == "::" let previousToken = "static_resolutor" endif elseif parser.next_is('new') let new = parser.consume()['matched_text'] if empty(braceStack) \ && (parser.next_is('whitespace') || parser.end() || parser.next_is('open_brace')) let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, 0, 1) let parsedObject.isNew = 1 call insert(parsedTokens, parsedObject) break else let insideBraceText = new .insideBraceText endif let previousToken = "new" elseif parser.next_is('dollar') let dollar = parser.consume()['matched_text'] if empty(braceStack) let methodPropertyText = dollar . methodPropertyText let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, 0, 1) let methodPropertyText = "" let insideBraceText = "" if isArray let parsedObject.isArrayElement = 1 endif call insert(parsedTokens, parsedObject) break else let insideBraceText = dollar . insideBraceText endif let previousToken = "dollar" elseif parser.next_is('ns_seperator') let ns_seperator = parser.consume()['matched_text'] if empty(braceStack) let methodPropertyText = ns_seperator . methodPropertyText else let insideBraceText = ns_seperator . insideBraceText endif let previousToken = "ns_seperator" elseif parser.end() let parsedObject = s:createparsedObject(insideBraceText, methodPropertyText, 0, 1) call insert(parsedTokens, parsedObject) break elseif parser.next_is("dot") let dot = parser.consume()['matched_text'] let insideBraceText = '\.' . insideBraceText elseif parser.next_is('comma') let comma = parser.consume()['matched_text'] "start skipping previous method arguments if len(insideBraceText) && (previousToken == 'single_quote' || parser.next_is('double_quote')) "TODO: have to refine skiping mechanism let startSkipping = 0 let insideBraceText = comma . insideBraceText else let insideBraceText = comma . insideBraceText endif else let extraData = parser.consume()['matched_text'] if !empty(braceStack) let insideBraceText = extraData . insideBraceText else let methodPropertyText = extraData . methodPropertyText endif let previousToken = "extradata" endif endwhile for token in parsedTokens if token['methodPropertyText'][0] == "\\" let token['methodPropertyText'] = matchstr(token['methodPropertyText'], '\\\zs.*') endif endfor return parsedTokens endfunction "}}} function! phpcomplete_extended#parser#forwardParse(line, parsedTokens) "{{{ let line = a:line let parsedTokens = a:parsedTokens let braceStack = [] let L = phpcomplete_extended#util#getLexer() let P = phpcomplete_extended#util#getParser() let List = phpcomplete_extended#util#getDataList() try let lexerTokens = L.lexer(s:lexer_symbols).exec(line) let parser = P.parser().exec(lexerTokens) catch return [] endtry let next = parser.next() let insideBraceText = "" let expectResolutor = 1 let methodPropertyText = "" let isMethod = 0 let isDollar = 0 let isNew = 0 let previousToken = "" let isArray = 0 while !parser.end() "PrettyPrint parser.next() "PrettyPrint braceStack "PrettyPrint parsedTokens if parser.next_is('whitespace') call parser.consume() let previousToken = "whitespace" continue elseif parser.next_is('dollar') let dollar = parser.consume()['matched_text'] if empty(braceStack) let methodPropertyText .= dollar else let insideBraceText .= dollar endif let previousToken = "dollar" elseif parser.next_is('identifier') let identifier = parser.consume()['matched_text'] if !empty(braceStack) let insideBraceText .= identifier elseif empty(previousToken) && parser.next_is('static_resolutor') let parsedObject = s:createParsedObject('', identifier, 0, 1) let parsedObject.isStatic = 1 let parsedObject.nonClass = 1 call add(parsedTokens, parsedObject) let methodPropertyText = "" let insideBraceText = "" elseif previousToken == "dollar" \ && (parser.next_is('object_resolutor') \ || parser.next_is('static_resolutor') \ || parser.next_is('square_brace_open') \ || parser.next_is('semicolon')) let methodPropertyText .= identifier elseif previousToken == "object_resolutor" && (parser.next_is('open_brace') \ || parser.next_is('square_brace_open') \ || parser.next_is('semicolon') \ || parser.next_is('object_resolutor')) let methodPropertyText .= identifier elseif previousToken == "whitespace" \ && isNew && (parser.next_is('open_brace') || parser.next_is('close_brace')) let methodPropertyText = identifier endif let previousToken = "identifier" elseif parser.next_is("object_resolutor") || parser.next_is('static_resolutor') let resolutor = parser.consume()['matched_text'] if empty(braceStack) \ && (previousToken == 'identifier' || previousToken == 'close_brace' || previousToken == 'square_brace_close') if resolutor == "::" continue endif let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, isMethod, 0) if methodPropertyText[0] == "$" let parsedObject.start = 1 endif if isNew let parsedObject.isNew = 1 let isNew = 0 endif if isArray let parsedObject.isArrayElement = 1 let isArray = 0 endif call add(parsedTokens, parsedObject) let insideBraceText = "" let methodPropertyText ="" elseif !empty(braceStack) let insideBraceText .= resolutor endif let previousToken = "object_resolutor" if resolutor == "static_resolutor" let previousToken = "static_resolutor" endif elseif parser.next_is('new') let new_operator = parser.consume()['matched_text'] if empty(braceStack) \ && (empty(previousToken) || previousToken == 'open_brace') let isNew = 1 elseif !empty(braceStack) let insideBraceText .= new_operator endif let previousToken = "new" elseif parser.next_is('open_brace') || parser.next_is('square_brace_open') let open_brace = parser.consume()['matched_text'] if (empty(previousToken)) && parser.next_is('new') continue elseif (previousToken == "close_brace" && open_brace == "[") \ || (previousToken == 'identifier') call List.push(braceStack, open_brace) if open_brace == "[" let isArray = 1 endif else let insideBraceText .= open_brace endif let previousToken = "open_brace" if open_brace == "[" let previousToken = "square_brace_open" endif elseif parser.next_is('close_brace') || parser.next_is('square_brace_close') let close_brace = parser.consume()['matched_text'] if empty(braceStack) && isNew && close_brace == ")" \ && (previousToken == "identifier" || previousToken == "close_brace") continue elseif !empty(braceStack) if (close_brace == ")" && braceStack[-1] == "(") \ || (close_brace == "]" && braceStack[-1] == "[") call List.pop(braceStack) if close_brace == ')' let isMethod = 1 endif if (close_brace == ")" && parser.next_is('square_brace_open')) let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, 0, 0) let parsedObject.isArrayElement = 1 let parsedObject.isMethod = 1 call add(parsedTokens, parsedObject) let methodPropertyText = "" let insideBraceText = "" endif endif else let insideBraceText .= close_brace endif let previousToken = "close_brace" if close_brace == "]" let previousToken = "square_brace_close" endif elseif parser.next_is('semicolon') let semicolon = parser.consume()['matched_text'] if empty(braceStack) \&& (previousToken == "close_brace" \ || previousToken == "square_brace_close" \ || previousToken == "identifier" \ || empty(previousToken) && methodPropertyText[0] == "$") let parsedObject = s:createParsedObject(insideBraceText, methodPropertyText, 1, 0) let parsedObject.pEnd = 1 if previousToken == "square_brace_close" let parsedObject.isMethod = 0 let parsedObject.isArrayElement = 1 elseif previousToken == "identifier" let parsedObject.isMethod = 0 endif call add(parsedTokens, parsedObject) break else let insideBraceText .= semicolon endif let previousToken = "semicolon" else let extraData = parser.consume()['matched_text'] if !empty(braceStack) let insideBraceText .= extraData endif let previousToken = "extra_data" endif endwhile for token in parsedTokens if token['methodPropertyText'][0] == "\\" let token['methodPropertyText'] = matchstr(token['methodPropertyText'], '\\\zs.*') endif endfor return parsedTokens endfunction "}}} function! ParserTest(line) "{{{ let line = a:line PrettyPrint line let tokens = phpcomplete_extended#parser#reverseParse(line, []) PrettyPrint tokens return tokens endfunction "}}} function! s:createParsedObject(insideBraceText, methodPropertyText, isMethod, start) let parsedObject = {} let parsedObject.insideBraceText = a:insideBraceText let parsedObject.methodPropertyText = a:methodPropertyText let parsedObject.isMethod = a:isMethod let parsedObject.start = a:start return parsedObject endfunction let &cpo = s:save_cpo unlet s:save_cpo ================================================ FILE: autoload/phpcomplete_extended/util.vim ================================================ "============================================================================= " AUTHOR: Mun Mun Das " FILE: util.vim " Last Modified: September 09, 2013 " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim function! phpcomplete_extended#util#getVital() if !exists("s:V") let s:V = vital#of('phpcomplete-extended') endif return s:V endfunction function! phpcomplete_extended#util#getFile() if !exists("s:File") let s:File = phpcomplete_extended#util#getVital().import('System.File') endif return s:File endfunction function! phpcomplete_extended#util#getLexer() if !exists("s:L") let s:L = phpcomplete_extended#util#getVital().import('Text.Lexer') endif return s:L endfunction function! phpcomplete_extended#util#getParser() if !exists("s:P") let s:P = phpcomplete_extended#util#getVital().import('Text.Parser') endif return s:P endfunction function! phpcomplete_extended#util#getDataList() if !exists("s:List") let s:List = phpcomplete_extended#util#getVital().import('Data.List') endif return s:List endfunction function! phpcomplete_extended#util#print_message(message) "{{{ echohl Comment | echo a:message | echohl none endfunction "}}} function! phpcomplete_extended#util#print_error_message(message) "{{{ echohl ErrorMsg | echo a:message | echohl none endfunction "}}} function! phpcomplete_extended#util#getDataString() if !exists("s:String") let s:String = phpcomplete_extended#util#getVital().import('Data.String') endif return s:String endfunction function! phpcomplete_extended#util#reverse(str) return join(reverse(split(a:str, '.\zs')), '') endfunction function! phpcomplete_extended#util#split_leftright(expr, pattern) return phpcomplete_extended#util#getDataString().split_leftright(a:expr, a:pattern) endfunction function! phpcomplete_extended#util#trim(str) return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') endfunction function! phpcomplete_extended#util#add_padding(list) "{{{ let list = a:list let max_len = 0 for e in a:list let max_len = len(e) > max_len ? len(e) : max_len endfor let format = '%-'.max_len. 's' return map(list, "printf(format, v:val)") endfunction "}}} let s:is_windows = has('win16') || has('win32') || has('win64') function! phpcomplete_extended#util#json_decode(...) return call(s:Json.decode, a:000) endfunction function! phpcomplete_extended#util#is_windows(...) return s:is_windows endfunction if phpcomplete_extended#util#is_windows() function! phpcomplete_extended#util#substitute_path_separator(...) let V = phpcomplete_extended#util#getVital() return call(V.substitute_path_separator, a:000) endfunction else function! phpcomplete_extended#util#substitute_path_separator(path) return a:path endfunction endif function! phpcomplete_extended#util#reverse(str) return join(reverse(split(a:str, '.\zs')), '') endfunction function! phpcomplete_extended#util#copy(...) return call(phpcomplete_extended#util#getFile().copy, a:000) endfunction function! phpcomplete_extended#util#has_vimproc(...) return call(phpcomplete_extended#util#getVital().has_vimproc, a:000) endfunction function! phpcomplete_extended#util#system(...) return call(phpcomplete_extended#util#getVital().system, a:000) endfunction function! phpcomplete_extended#util#input_yesno(message) "{{{ let yesno = input(a:message . ' [yes/no]: ') while yesno !~? '^\%(y\%[es]\|n\%[o]\)$' redraw if yesno == '' echo 'Canceled.' break endif " Retry. call phpcomplete_extended#print_error('Invalid input.') let yesno = input(a:message . ' [yes/no]: ') endwhile return yesno =~? 'y\%[es]' endfunction"}}} function! phpcomplete_extended#util#getLines(lineNum, lineCount, direction) if a:direction == "back" let lines = getline( a:lineNum - a:lineCount, a:lineNum) else let lines = getline(a:lineNum, a:lineNum + a:lineCount) endif let joinedLine = join(map(lines, 'phpcomplete_extended#util#trim(v:val)'), "") return joinedLine endfunction let &cpo = s:save_cpo unlet s:save_cpo ================================================ FILE: autoload/phpcomplete_extended.vim ================================================ "============================================================================= " AUTHOR: Mun Mun Das " FILE: phpcomplete_extended.vim " Last Modified: September 11, 2013 " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim if !exists("s:current_project_dir") let s:current_project_dir = "" endif if !exists("s:update_info") let s:update_info = {} endif if !exists("s:plugins") let s:plugins = {} endif if !exists("s:plugin_ftime") let s:plugin_ftime = -1 endif if !exists("s:plugin_index") let s:plugin_index = {} endif if !exists("s:plugin_php_files") let s:plugin_php_files = [] endif if !exists("s:psr_class_complete") let s:psr_class_complete = 0 endif if !exists("s:phpcomplete_enabled") let s:phpcomplete_enabled = 1 endif let s:disabled_projects = [] let s:T = { \ 'number': type(0), \ 'string': type(''), \ 'function': type(function('function')), \ 'list': type([]), \ 'dictionary': type({}), \ 'float': type(0.0), \ } function! phpcomplete_extended#CompletePHP(findstart, base) " {{{ if a:findstart let start = s:get_complete_start_pos() if !empty(b:completeContext) let b:completeContext.start = start let b:completeContext.base = getline('.')[start : col('.')-2] endif return start endif if !s:phpcomplete_enabled return [] endif if !phpcomplete_extended#is_phpcomplete_extended_project() return [] endif if empty(b:completeContext) return [] endif let s:psr_class_complete = 0 if b:completeContext.complete_type == "use" return s:getUseMenuEntries(a:base) elseif b:completeContext.complete_type == "nonclass" let s:psr_class_complete = 1 return s:getNonClassMenuEntries(a:base) elseif b:completeContext.complete_type == "insideQuote" return s:get_plugin_inside_qoute_menu_entries(b:completeContext.fqcn, b:completeContext.lastToken) elseif b:completeContext.complete_type == "new" let s:psr_class_complete = 1 return s:getClassMenuEntries(a:base) elseif b:completeContext.complete_type == "class" let is_static = 0 let is_this = 0 let lastResolutor = b:completeContext.last_resolutor let fqcn = b:completeContext.fqcn let sourceFile = phpcomplete_extended#util#substitute_path_separator(expand("%:.")) let sourceFQCN = s:getFQCNFromFileLocation(sourceFile) if sourceFQCN == fqcn let is_this = 1 endif if lastResolutor == '::' let is_static = 1 endif if b:completeContext.complete_type == 'class' \ && lastResolutor == "" return [] endif let menu_entries = [] let baseMenuEntries = phpcomplete_extended#getMenuEntries(fqcn, a:base, is_this, is_static) "plugin callback let plugin_menu_entries = s:get_plugin_menu_entries(fqcn, a:base, is_this, is_static) let menu_entries += plugin_menu_entries let menu_entries += baseMenuEntries return menu_entries endif endfunction "}}} function! s:get_complete_start_pos() "{{{ let line = getline('.') let start = col('.') - 1 while start >= 0 && line[start - 1] =~ '[\\a-zA-Z_0-9\x7f-\xff$]' let start -= 1 endwhile let b:completeContext = {} let completeContext = s:get_complete_context() if empty(completeContext) || !s:phpcomplete_enabled return start endif let b:completeContext = completeContext if b:completeContext.complete_type == "class" && getline('.')[start-2 : start-1] !~ '->\|::' let b:completeContext.last_resolutor = "" endif if !empty(completeContext) \ && has_key(completeContext, 'complete_type') \ && completeContext['complete_type'] == 'insideQuote' \ && match(completeContext.lastToken['insideBraceText'], '\.') != -1 "having some problem with dot charater let splits = split(completeContext.lastToken['insideBraceText'], '\\\.') let displace = len(splits) if match(completeContext.lastToken['insideBraceText'], '\\\.$') < 0 let displace += len(splits[-1]) -1 endif let start = start - len(completeContext.lastToken['insideBraceText']) + displace endif return start endfunction "}}} function! s:get_complete_context() "{{{ let cursorLine = getline('.')[0:col('.')-2] if !exists('phpcomplete_extended_context') let completeContext = {} endif let completeContext.complete_type = "class" if cursorLine =~? '^\s*use\s\+' " namespace completeion let completeContext.complete_type = "use" elseif cursorLine =~? '\(\s*new\|extends\)\s\+' \ && len(phpcomplete_extended#parsereverse(cursorLine, line('.'))) == 1 "new class completion let completeContext.complete_type = "new" else if !phpcomplete_extended#is_phpcomplete_extended_project() return {} endif let parsedTokens = phpcomplete_extended#parsereverse(cursorLine, line('.')) if empty(parsedTokens) let completeContext = {} return {} endif if has_key(parsedTokens[0], "nonClass") && parsedTokens[0]["nonClass"] let completeContext.complete_type = "nonclass" elseif has_key(parsedTokens[-1], "insideQuote") let lastToken = remove(parsedTokens, -1) let fqcn = s:guessTypeOfParsedTokens(deepcopy(parsedTokens)) let completeContext.lastToken = lastToken let completeContext.lastToken['insideBraceText'] = matchstr(lastToken['insideBraceText'], '[''"]\?\zs.*\ze[''"]\?') let completeContext.fqcn = fqcn let completeContext.complete_type = "insideQuote" else let lastToken = remove(parsedTokens, -1) let fqcn = s:guessTypeOfParsedTokens(deepcopy(parsedTokens)) let completeContext.complete_type = "class" let completeContext.last_resolutor = matchstr(cursorLine, '.*\zs\(->\|::\)\ze.*') let completeContext.lastToken = lastToken let completeContext.fqcn = fqcn endif endif return completeContext endfunction "}}} function! s:guessTypeOfParsedTokens(parsedTokens) "{{{ if !exists('g:phpcomplete_index') return "" endif let parsedTokens = a:parsedTokens let objectGraph = [] let sourceFile = phpcomplete_extended#util#substitute_path_separator(expand("%:.")) let sourceFQCN = s:getFQCNFromFileLocation(sourceFile) "if empty(sourceFQCN) "return "" "endif if empty(parsedTokens) return "" endif let sourceNamespace = matchstr(sourceFQCN, '\zs.*\ze\\') let firstToken = remove(parsedTokens, 0) let currentFQCN = "" let parentFQCN = "" let nonClass = has_key(firstToken, "nonClass")? firstToken['nonClass']: 0 if len(parsedTokens) == 1 && nonClass "normal class/function completion endif let isThis = 0 let pluginFQCN = s:get_plugin_fqcn(currentFQCN, firstToken) if firstToken['methodPropertyText'] == "$this" \ || firstToken['methodPropertyText'] == "self" \ || firstToken['methodPropertyText'] == "static" let currentFQCN = sourceFQCN let isThis = 1 elseif firstToken['methodPropertyText'] == "parent" "parent let currentClassData = phpcomplete_extended#getClassData(sourceFQCN) if !has_key(currentClassData, 'parentclass') return "" endif let currentFQCN = currentClassData['parentclass'] elseif pluginFQCN != "" let currentFQCN = pluginFQCN elseif has_key(firstToken, "isNew") let currentClassData = phpcomplete_extended#getClassData(sourceFQCN) let namespaces = {} if has_key(currentClassData, 'namespaces') let namespaces = currentClassData['namespaces'] endif let currentFQCN = s:getFQCNForLocalVar( \firstToken['methodPropertyText'], namespaces) elseif firstToken['methodPropertyText'][0] == "$" "local variable let linesTilFunc = s:getLinesTilFunc(line('.')) let currentFQCN = s:geussLocalVarType(firstToken['methodPropertyText'], \ sourceFQCN, linesTilFunc) elseif firstToken['methodPropertyText'][0] == "\\" let methodPropertyText = firstToken['methodPropertyText'] let currentFQCN = s:getFQCNForNsKeyword(methodPropertyText, sourceFQCN) return currentFQCN else let methodPropertyText = firstToken['methodPropertyText'] let sourceData = phpcomplete_extended#getClassData(sourceFQCN) let aliases = {} if !empty(sourceData) && has_key(sourceData['namespaces'], 'alias') && !empty(sourceData['namespaces']['alias']) let aliases = sourceData['namespaces']['alias'] endif if empty(sourceData) let currentFQCN = "" elseif has_key(g:phpcomplete_index['classes'], methodPropertyText) \ || has_key(g:phpcomplete_extended_core_index['classes'], methodPropertyText) let currentFQCN = methodPropertyText elseif has_key(sourceData, 'namespaces') \ && has_key(sourceData['namespaces'], 'uses') \ && !empty(sourceData['namespaces']['uses']) \ && has_key(sourceData['namespaces']['uses'], methodPropertyText) let currentFQCN = sourceData['namespaces']['uses'][methodPropertyText]. "\\" . methodPropertyText elseif !empty(aliases) && has_key(aliases, methodPropertyText) let uses_key = aliases[methodPropertyText] let uses_value = sourceData['namespaces']['uses'][uses_key] let currentFQCN = uses_value == uses_key? uses_key : uses_value. "\\" . uses_key endif endif if empty(currentFQCN) return "" endif return s:getFQCNFromTokens(parsedTokens, currentFQCN, isThis) endfunction "}}} function! s:getFQCNFromTokens(parsedTokens, currentFQCN, isThis) "{{{ let parsedTokens = a:parsedTokens let currentFQCN = a:currentFQCN let isThis = a:isThis let isPrevTokenArray = 0 if currentFQCN =~ '\[\]$' && len(parsedTokens) \ && has_key(parsedTokens[0], 'isArrayElement') let currentFQCN = matchstr(currentFQCN, '\zs.*\ze\[\]$') let isPrevTokenArray = 1 endif for token in parsedTokens let insideBraceText = token['insideBraceText'] let methodPropertyText = token['methodPropertyText'] let insideBraceText = token['insideBraceText'] let isArrayElement = has_key(token, 'isArrayElement')? 1 :0 let currentClassData = phpcomplete_extended#getClassData(currentFQCN) let pluginFQCN = s:get_plugin_fqcn(currentFQCN,token) if insideBraceText[0] == "(" let currentFQCN = "" elseif pluginFQCN != "" let currentFQCN = pluginFQCN elseif isArrayElement let isPrevTokenArray = 0 if phpcomplete_extended#isClassOfType(currentFQCN, 'ArrayAccess') \ && has_key(currentClassData['methods']['all'], 'offsetGet') let offsetType = currentClassData['methods']['all']['offsetGet']['return'] if empty(offsetType) return "" endif let currentFQCN = offsetType endif continue else let classPropertyType = token.isMethod == 1 ? 'method' : 'property' let [currentFQCN, isPrevTokenArray] = phpcomplete_extended#getFQCNForClassProperty( \ classPropertyType, methodPropertyText, currentFQCN, isThis) endif if isThis == 1 let isThis = 0 endif if empty(currentFQCN) return "" endif endfor if isPrevTokenArray return currentFQCN . '[]' endif return currentFQCN endfunction "}}} function! s:isScalar(type) "{{{ let scalars =['boolean', 'integer',"float", "string", 'array', 'object', \ 'resource', 'mixed', 'number', 'callback', 'null', 'void', \ 'bool', 'self', 'int', 'callable'] return index(scalars, a:type) endfunction "}}} function! s:getLinesTilFunc(currentLineNum) "{{{ let lineNum = a:currentLineNum let lines = [] "get the lines till function declaration while 1 let line = phpcomplete_extended#util#trim(getline(lineNum)) if !empty(line) call add(lines, line) endif if lineNum == 1 || match(line, 'function\s*\w\+\s*(.*)') > 1 break endif let lineNum = lineNum - 1 endwhile return lines endfunction "}}} function! s:geussLocalVarType(var, sourceFQCN, lines) "{{{ let lines = a:lines let sourceFQCN = a:sourceFQCN let var = a:var let fqcn = "" for line in lines "echoerr match(line, a:var.'\s*=.*') >= 0 if match(line, a:var.'\s*=.*') >= 0 "var = .* "echoerr line let compiedLines = deepcopy(lines) let parsedTokens = s:parseForward( \ reverse(compiedLines), \ line) if empty(parsedTokens) return "" endif let fqcn = phpcomplete_extended#util#trim(s:guessTypeOfParsedTokens(parsedTokens)) "echoerr fqcn elseif match(line, '@var\s*'.var.'\s*.*') >=0 "@var $var fqcn" let fqcn = phpcomplete_extended#util#trim(matchstr(line, '@var\s*'.var.'\s*\zs.*\ze\s*\*')) let fqcn = phpcomplete_extended#getFQCNFromWord(fqcn) endif if fqcn != "" break endif endfor "try to guess from method params if empty(fqcn) let localMethodName = matchstr(lines[-1], 'function\s*\zs.*\ze(.*)') let sourceClassData = phpcomplete_extended#getClassData(sourceFQCN) if empty(sourceClassData) return "" endif if !has_key(sourceClassData['methods']['all'], localMethodName) return "" endif let methodParams = sourceClassData['methods']['all'][localMethodName]['params'] if empty(methodParams) let fqcn = "" elseif has_key(methodParams, var) && s:isScalar(methodParams[var]) == -1 let fqcn = methodParams[var] endif endif return fqcn endfunction "}}} function! s:parseLocalVar(lines, varLine) "{{{ let parsedTokens = s:parseForward(a:lines, a:varLine) endfunction "}}} function! phpcomplete_extended#trackMenuChanges() "{{{ let g:complete_word = "" let current_char = getline('.')[col('.') -2] if !pumvisible() && s:psr_class_complete \ && (current_char == '(' || current_char == ':' || current_char == ' ') let s:psr_class_complete = 0 let cur_pos = getpos(".") let prev_pos = copy(cur_pos) let new_pos = copy(cur_pos) let new_pos[2] = new_pos[2] -2 call setpos(".", new_pos) let cur_word = expand("") if cur_word[0] =~ '\d' let keyword = matchstr(getline('.')[0:new_pos[2]-1], '\s*\zs.\{-}-\(\d\+\)\?\ze') if match(keyword, '-') > 0 let cur_word = keyword endif call feedkeys("\dF-") call feedkeys("\x") call feedkeys("\l") else call setpos(".", prev_pos) endif let fqcn = "" if match(cur_word, '-') > 0 let word = split(cur_word, '-')[0] let idx = split(cur_word, '-')[1] - 1 "echoerr word if has_key(g:phpcomplete_index['class_fqcn'], word) \ && string(g:phpcomplete_index['class_fqcn'][word])[0] == '[' let fqcn = g:phpcomplete_index['class_fqcn'][word][idx] endif elseif has_key(g:phpcomplete_index['class_fqcn'], cur_word) \ && string(g:phpcomplete_index['class_fqcn'][cur_word])[0] == "'" let fqcn = g:phpcomplete_index['class_fqcn'][cur_word] endif if fqcn != "" && g:phpcomplete_extended_auto_add_use call phpcomplete_extended#addUse(cur_word, fqcn) endif endif endfunction "}}} function! phpcomplete_extended#addUse(word, fqcn) "{{{ let word = a:word let fqcn = a:fqcn let cur_pos = getpos('.') if empty(fqcn) if !has_key(g:phpcomplete_index['class_fqcn'], word) return endif let fqcn_data = g:phpcomplete_index['class_fqcn'][word] if empty(fqcn) && string(fqcn_data)[0] == '[' let fqcn_data = deepcopy(g:phpcomplete_index['class_fqcn'][word]) let prompt_data = map(copy(fqcn_data), 'v:key+1.". " . v:val') call insert(prompt_data, "Select FQCN:") let selected = inputlist(prompt_data) let fqcn = fqcn_data[selected-1] elseif empty(fqcn) && string(fqcn_data)[0] == "'" let fqcn = fqcn_data endif if empty(fqcn) return endif endif let lines_to_class = getline(0, search('^\s*\(class\|interface\)')) call setpos('.', cur_pos) if empty(lines_to_class) || phpcomplete_extended#util#trim(lines_to_class[0]) != "= 0 return endif if match(line, '^\s*use\s*.*;') >=0 let last_use_pos = index(lines_to_class, line)+1 endif if match(line, '^\s*namespace\s*.*;') >=0 let namespace_pos = index(lines_to_class, line)+1 endif endfor if last_use_pos == -1 let last_use_pos = namespace_pos == -1? 1 : namespace_pos + 1 endif call append(last_use_pos, ['use '. fqcn. ';']) let cur_pos[1] = cur_pos[1] +1 call setpos('.', cur_pos) endfunction "}}} function! phpcomplete_extended#gotoSymbolORDoc(type) "{{{ let data = {} let type = a:type if match(expand('%'), 'phpcomplete_extended-Doc') == 0 let type = 'doc' endif let cur_word = expand("") let word_fqcn = s:get_plugin_resolve_fqcn(cur_word) if word_fqcn == cur_word let word_fqcn = phpcomplete_extended#getFQCNFromWord(cur_word) endif "messed up if word_fqcn != "" let classData = phpcomplete_extended#getClassData(word_fqcn) if !has_key(g:phpcomplete_index['fqcn_file'], word_fqcn) if type == "doc" let data = { \'doc': classData['docComment'], \ 'title': word_fqcn \} elseif type == "goto" let data = {} endif else let filename = g:phpcomplete_index['fqcn_file'][word_fqcn] let line = classData['startLine'] let data = {} let data.file = filename let data.command = '+' . line let data.title = word_fqcn let data.doc = classData['docComment'] endif else let data = s:getJumpDataOfCurrentWord(type) endif if empty(data) if type == 'doc' call feedkeys('K', 'n') elseif type == 'goto' call feedkeys("\", 'n') endif return endif if type == 'doc' call s:openDoc(data) elseif type == 'goto' call s:gotoLine(data) endif endfunction "}}} function! phpcomplete_extended#getFQCNFromWord(word) "{{{ let word = a:word let current_file_location = phpcomplete_extended#util#substitute_path_separator(fnamemodify(bufname('%'), ':p:.')) "current file let current_fqcn = s:getFQCNFromFileLocation(current_file_location) if current_fqcn == "" return "" endif let current_class_data = phpcomplete_extended#getClassData(current_fqcn) if empty(current_class_data) return "" endif let fqcn = s:getFQCNForLocalVar(word, current_class_data['namespaces']) if fqcn == "" return "" endif return fqcn endfunction "}}} function! s:gotoLine(data) "{{{ silent! execute "e ". a:data.command . ' ' . a:data.file silent! execute "normal! zt" normal! zv normal! zz endfunction "}}} function! s:openDoc(data) "{{{ let doc_buf_exists = 0 for i in range(1, winnr('$')) let bufname = bufname(winbufnr(i)) if match(bufname, 'phpcomplete_extended-Doc') != -1 let doc_buf_exists = 1 let winnr = i endif endfor "TODO: go to previous doc if doc_buf_exists silent! execute winnr."wincmd w" setlocal modifiable noreadonly normal ggdG else silent! execute "new" setlocal modifiable noreadonly setlocal nobuflisted setlocal buftype=nofile noswapfile setlocal bufhidden=delete setlocal nonumber let bufname = printf("%s: %s", 'phpcomplete_extended-Doc', a:data['title']) silent! file `=bufname` endif let doc = map(split(a:data['doc'], "\n"), "substitute(v:val, ' ', '', 'g')") call append(0, doc) "TODO: set ft=php "slow, have to do some thing about it normal! gg setlocal nomodifiable readonly endfunction "}}} function! s:getJumpDataOfCurrentWord(type) "{{{ let cur_word = expand("") let match_till_cur_word = matchstr(getline('.'), '\s*\zs\C.\{-}\<'.cur_word . '\>' .'(\?') if match_till_cur_word[len(match_till_cur_word)-1] == "(" let match_till_cur_word .= "'')" endif let parsedTokens = phpcomplete_extended#parsereverse(match_till_cur_word, line('.')) if empty(parsedTokens) return {} endif "very messed up, have to break it let static_or_ref = 0 if len(parsedTokens) == 1 && \ ( \ has_key(parsedTokens[0], 'isNew') \ || has_key(parsedTokens[0], 'nonClass') \ || parsedTokens[0]['methodPropertyText'][0] == "$" \) let lastToken = parsedTokens[0] "elseif len(parsedTokens) == 2 "\ && match(match_till_cur_word, "::") > 0 "let lastToken = remove(parsedTokens, 0) "let static_or_ref = 1 else let lastToken = remove(parsedTokens, -1) endif if has_key(lastToken, 'nonClass') let fqcn = lastToken['methodPropertyText'] let fqcn = s:get_plugin_resolve_fqcn(fqcn) else let fqcn = s:guessTypeOfParsedTokens(parsedTokens) let fqcn = s:get_plugin_resolve_fqcn(fqcn) if fqcn == "" return {} endif endif if static_or_ref let methodPropertyText = parsedTokens[0]['methodPropertyText'] else let methodPropertyText = lastToken['methodPropertyText'] endif let fqcn_data = {} let isInternal = 0 let isMethod = 0 let isClass = 0 let isProperty = 0 if has_key(lastToken, 'nonClass') && lastToken['nonClass'] let isInternal = 1 if static_or_ref let isMethod = 1 let cur_word = fqcn endif if has_key(g:phpcomplete_extended_core_index['functions'], cur_word) let fqcn_data = g:phpcomplete_extended_core_index['functions'][cur_word] elseif has_key(g:phpcomplete_extended_core_index['classes'], cur_word) let fqcn_data = g:phpcomplete_extended_core_index['classes'][cur_word] endif if static_or_ref \ && has_key(fqcn_data['methods']['all'], methodPropertyText) let fqcn_data = fqcn_data['methods']['all'][methodPropertyText] endif elseif has_key(lastToken, 'isMethod') let isProperty = !lastToken['isMethod'] let isMethod = lastToken['isMethod'] let fqcn_data = {} let method_property_key = lastToken['isMethod']? 'methods' : 'properties' if has_key(g:phpcomplete_index['classes'], fqcn) \ && has_key(g:phpcomplete_index['classes'][fqcn][method_property_key]['all'], methodPropertyText) let fqcn_data = g:phpcomplete_index['classes'][fqcn][method_property_key]['all'][methodPropertyText] elseif has_key(g:phpcomplete_extended_core_index['classes'], fqcn) \ && has_key(g:phpcomplete_extended_core_index['classes'][fqcn][method_property_key]['all'], methodPropertyText) let fqcn_data = g:phpcomplete_extended_core_index['classes'][fqcn][method_property_key]['all'][methodPropertyText] endif elseif has_key(lastToken, 'isNew') let isClass = 1 let fqcn_data = phpcomplete_extended#getClassData(fqcn) else let isClass = 1 let fqcn_data = phpcomplete_extended#getClassData(fqcn) endif if empty(fqcn_data) return {} endif if a:type == 'doc' let title = fqcn if isMethod let title = fqcn. "--". methodPropertyText . "()" endif if isProperty let title = fqcn. "--". methodPropertyText endif if isInternal let title = fqcn endif return { \'doc': fqcn_data['docComment'], \ 'title': title \} elseif a:type == 'goto' if isInternal return {} endif let classData = phpcomplete_extended#getClassData(fqcn) let gotoData = {} if isClass let gotoData.file = fqcn_data['file'] let gotoData.command = '+'.fqcn_data['startLine'] elseif isMethod let gotoData.file = fqcn_data['origin'] let gotoData.command = '+'.fqcn_data['startLine'] elseif isProperty let gotoData.file = fqcn_data['origin'] let gotoData.command ='+/^\\s*\\(private\\|public\\|protected\\|static\\)\\s*.*'.'$'.methodPropertyText endif return gotoData endif return {} endfunction "}}} function! s:parseForward(lines, varLine) "{{{ call remove(a:lines, 0, index(a:lines, a:varLine)) let lines = a:lines let varDecLine = phpcomplete_extended#util#trim(matchstr(a:varLine, '=\zs.*')) let joinedLine = varDecLine.join(lines, "") let parsedTokens = [] let parsedTokens = phpcomplete_extended#parser#forwardParse(joinedLine, parsedTokens) if len(parsedTokens) && !has_key(parsedTokens[-1], 'pEnd') return [] endif return parsedTokens endfunction "}}} function! phpcomplete_extended#parsereverse(cursorLine, cursorLineNumber) "{{{ if !exists('g:phpcomplete_index') return [] endif let cursorLine = phpcomplete_extended#util#trim(a:cursorLine) let parsedTokens = [] let parsedTokens = phpcomplete_extended#parser#reverseParse(cursorLine, []) if empty(parsedTokens) \ || (len(parsedTokens) && has_key(parsedTokens[0], 'start') && parsedTokens[0].start == 0) let linesTillFunc = s:getLinesTilFunc(a:cursorLineNumber) let joinedLines = join(reverse(linesTillFunc),"") let parsedTokens = phpcomplete_extended#parser#reverseParse(joinedLines, []) return parsedTokens endif return parsedTokens endfunction "}}} function! s:getFQCNForNsKeyword(keyword, sourceFQCN) "{{{ let sourceData = phpcomplete_extended#getClassData(a:sourceFQCN) let keyword = matchstr(a:keyword, '\\\zs.*') let fqcn = "" if empty(sourceData) return "" endif let sourceUses = sourceData['namespaces']['uses'] let splitedKeyword = split(keyword, "\\") if has_key(sourceUses, splitedKeyword[0]) let key = remove(splitedKeyword, 0) let fqcn = sourceUses[key] . "\\" .keyword else let keywordClassData = phpcomplete_extended#getClassData(keyword) if !empty(keywordClassData) let fqcn = keyword endif endif return fqcn endfunction "}}} function! s:getFQCNForLocalVar(classname, namespaces) " {{{ let classname = a:classname let namespaces = a:namespaces let aliases = {} if has_key(a:namespaces, 'alias') && !empty(a:namespaces['alias']) let aliases = a:namespaces['alias'] endif let fqcn = "" if has_key(g:phpcomplete_index['fqcn_file'], classname) return classname endif if has_key(namespaces, 'uses') && len(namespaces['uses']) != 0 && has_key(namespaces['uses'], classname) "if no data found json_encode make it to dictionary let fqcn = namespaces['uses'][classname]. "\\". classname return fqcn endif if !empty(aliases) && has_key(aliases, classname) let uses_key = aliases[classname] let uses_value = namespaces['uses'][uses_key] let fqcn = uses_value == uses_key? uses_key : uses_value. "\\" . uses_key return fqcn endif if has_key(g:phpcomplete_extended_core_index['classes'], classname) return classname endif let namespace_section = "" if has_key(namespaces, 'file') let namespace_section = namespaces['file'] . "\\" endif let full_fqcn = namespace_section . classname if has_key(g:phpcomplete_index['fqcn_file'], full_fqcn) return full_fqcn endif return "" endfunction " }}} function! phpcomplete_extended#getFQCNForClassProperty(type, property, parent_fqcn, is_this) " {{{ let type = a:type let is_this = a:is_this let property = a:property let classname = '' let isArray = 0 let this_fqcn = a:parent_fqcn let this_fqcn = s:get_plugin_resolve_fqcn(this_fqcn) let this_class_data = phpcomplete_extended#getClassData(this_fqcn) if empty(this_class_data) return ["", 0] endif "in same namespace folder. so not declared in use section if type == 'property' let this_properties = this_class_data['properties']['all'] if !has_key(this_properties, property) return ['', 0] endif let classname = this_properties[property]['type'] if has_key(this_properties[property], 'array_type') let isArray = this_properties[property]['array_type'] endif elseif type == 'method' let this_methods = this_class_data['methods']['all'] if !has_key(this_methods, property) return ['', 0] endif if has_key(this_methods[property], 'return') let classname = this_methods[property]['return'] if has_key(this_methods[property], 'array_return') let isArray = this_methods[property]['array_return'] endif endif endif return [classname, isArray] endfunction " }}} function! s:getClassMenuEntries(base) "{{{ let class_menu_entries = deepcopy(g:phpcomplete_index['class_func_menu_entries']) let class_menu_entries = filter(class_menu_entries, 'v:val.word =~ "^' . a:base .'" && v:val.kind == "c"') return class_menu_entries endfunction "}}} function! s:getUseMenuEntries(base) "{{{ let menu_list = [] let fqcns = deepcopy(keys(g:phpcomplete_index['fqcn_file'])) if a:base != '' "echoerr escape(a:base, ' \') "let fqcns = filter(fqcns, 'v:val =~ "^' . escape(a:base, ' \') .'"') let fqcns = filter(fqcns, 'v:val =~ "^' . escape(a:base, ' \\') .'"') endif for fqcn in fqcns let menu_list += [{'word': fqcn,'kind': 'v', 'menu': fqcn, 'info': fqcn}] endfor return menu_list endfunction "}}} function! s:getNonClassMenuEntries(base) "{{{ let menu_entries = [] let class_func_menu_entries = deepcopy(g:phpcomplete_index['class_func_menu_entries']) let class_func_menu_entries = filter(class_func_menu_entries, 'v:val.word =~# "^' . a:base .'"') let plugin_menu_entries = s:get_plugin_menu_entries("", a:base, 1, 0) let menu_entries += plugin_menu_entries let menu_entries += class_func_menu_entries return menu_entries endfunction "}}} function! phpcomplete_extended#getMenuEntries(fqcn, base, is_this, is_static) " {{{ let empty_dict = [] let fqcn = a:fqcn let is_this = a:is_this let is_static = a:is_static if fqcn == "" return [] endif let menu_list = [] let class_data = phpcomplete_extended#getClassData(fqcn) if len(class_data) == 0 return empty_dict endif "constants if is_static if len(class_data['constants']) != 0 let constants = deepcopy(keys(class_data['constants'])) if a:base != '' let constants = filter(constants, 'v:val =~ "^' . a:base .'"') endif for constant in constants let menu_list += [{'word': constant,'kind': 'v', 'menu': constant, 'info': constant}] endfor endif endif "properties if len(class_data['properties']['all']) != 0 if is_this let properties = deepcopy(keys(class_data['properties']['all'])) elseif is_static let properties = deepcopy(class_data['properties']['modifier']['static']) else let properties = deepcopy(class_data['properties']['modifier']['public']) endif if a:base != '' let properties = filter(properties, 'v:val =~ "^' . a:base .'"') endif for property in properties let property_data = class_data['properties']['all'][property] let menu_list += [{'word': property,'kind': 'v', 'menu': property, 'info': property_data['docComment']}] endfor endif "methods if len(class_data['methods']['all']) != 0 if is_this let methods = deepcopy(keys(class_data['methods']['all'])) elseif is_static let m = deepcopy(class_data['methods']['modifier']['static']) "sometime json_encode makes methods array as dictionary if type(m) == 4 let methods = values(m) else let methods = m endif else let m = deepcopy(class_data['methods']['modifier']['public']) "sometime json_encode makes methods array as dictionary if type(m) == 4 let methods = values(m) else let methods = m endif endif if a:base != '' let methods = filter(methods, 'v:val =~ "^' . a:base .'"') endif for method in methods if match(method, '__') == 0 continue endif let method_info = class_data['methods']['all'][method] let menu_list += [{'word': method,'kind': 'f', 'menu': method_info['signature'], 'info': method_info['docComment']}] endfor endif return menu_list endfunction " }}} function! s:getFQCNFromFileLocation(filelocation) " {{{ "TODO: add cache if !exists('g:phpcomplete_index') return "" endif let filelocation = a:filelocation if has('win32') || has('win64') let filelocation = substitute(filelocation,'\\', '/', 'g') endif let fqcn = "" if has_key(g:phpcomplete_index['file_fqcn'], filelocation) let fqcn = g:phpcomplete_index['file_fqcn'][filelocation] endif return fqcn endfunction " }}} function! phpcomplete_extended#getFileFromFQCN(fqcn) "{{{ let fqcn = a:fqcn let filelocation = "" if has_key(g:phpcomplete_index['fqcn_file'], fqcn) let filelocation = g:phpcomplete_index['fqcn_file'][fqcn] if has('win32') || has('win64') let filelocation = substitute(filelocation,'\\', '/', 'g') endif endif return filelocation endfunction "}}} function! phpcomplete_extended#getClassKeyFromFQCN(fqcn) "{{{ let fqcn = a:fqcn let classKey = "" if !g:phpcomplete_extended_cache_disable && has_key(g:phpcomplete_index_cache['fqcn_classkey_cache'], fqcn) return g:phpcomplete_index_cache['fqcn_classkey_cache'][fqcn] else if has_key(g:phpcomplete_index['fqcn_classkey'], fqcn) let classKey = g:phpcomplete_index['fqcn_classkey'][fqcn] let g:phpcomplete_index_cache['fqcn_classkey_cache'][fqcn] = classKey endif endif return classKey endfunction "}}} function! phpcomplete_extended#getClassData(fqcn) " {{{ let fqcn = a:fqcn let empty_dict = {} if g:phpcomplete_extended_cache_disable == 0 && has_key(g:phpcomplete_index_cache['classname_cache'], fqcn) let data = g:phpcomplete_index_cache['classname_cache'][fqcn] else if has_key(g:phpcomplete_index['classes'], fqcn) let data = g:phpcomplete_index['classes'][fqcn] elseif has_key(g:phpcomplete_extended_core_index['classes'], fqcn) let data = g:phpcomplete_extended_core_index['classes'][fqcn] else return empty_dict endif if has_key(data['methods']['all'], 'nnnnnnnn') call remove(data['methods']['all'], "nnnnnnnn") endif if has_key(data['properties']['all'], 'nnnnnnnn') call remove(data['properties']['all'], "nnnnnnnn") endif let g:phpcomplete_index_cache['classname_cache'][fqcn] = data return data endif return g:phpcomplete_index_cache['classname_cache'][fqcn] endfunction " }}} function! s:setClassData(fqcn, file, classData) "{{{ let fqcn = a:fqcn let file = a:file let className = a:classData['classname'] let classData = a:classData let g:phpcomplete_index['classes'][fqcn] = classData let g:phpcomplete_index['file_fqcn'][file] = fqcn let g:phpcomplete_index['fqcn_file'][fqcn] = file if index(g:phpcomplete_index['class_list'], className) == -1 call add(g:phpcomplete_index['class_list'], className) endif if has_key(g:phpcomplete_index['class_fqcn'], className) if string(g:phpcomplete_index['class_fqcn'][className])[0] == "'" \ && g:phpcomplete_index['class_fqcn'][className] != fqcn let tmp_class_fqcn = [] call add(tmp_class_fqcn, g:phpcomplete_index['class_fqcn'][className]) call add(tmp_class_fqcn, fqcn) call remove(g:phpcomplete_index['class_fqcn'], className) let g:phpcomplete_index['class_fqcn'][className] = tmp_class_fqcn elseif string(g:phpcomplete_index['class_fqcn'][className])[0] == "[" \ && index(g:phpcomplete_index['class_fqcn'][className], fqcn) == -1 call add(g:phpcomplete_index['class_fqcn'][className], fqcn) endif else let g:phpcomplete_index['class_fqcn'][className] = fqcn endif if index(g:phpcomplete_index['class_func_const_list'], className) == -1 call add(g:phpcomplete_index['class_func_const_list'], className) endif if has_key(g:phpcomplete_index_cache['classname_cache'], fqcn) call remove(g:phpcomplete_index_cache['classname_cache'], fqcn) endif endfunction "}}} function! s:makeCacheDir() "{{{ let cache_dir = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended') if !isdirectory(cache_dir) call mkdir(cache_dir) endif endfunction "}}} function! phpcomplete_extended#saveIndexCache() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let cache_dir = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended') let index_cache_file = phpcomplete_extended#util#substitute_path_separator(fnamemodify(cache_dir, ':p:h')."/index_cache") let content = [] if exists('g:phpcomplete_index_cache') call add(content, string(g:phpcomplete_index_cache)) call writefile(content, index_cache_file) endif endfunction " }}} function! s:getCacheFile() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let index_cache_file = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h')."/.phpcomplete_extended/index_cache") return index_cache_file endfunction " }}} function! s:loadIndexCache() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif if exists('g:phpcomplete_index_cache_loaded') && g:phpcomplete_index_cache_loaded return endif let index_cache_file = s:getCacheFile() if !filereadable(index_cache_file) let g:phpcomplete_index_cache = {} let g:phpcomplete_index_cache['classname_cache'] = {} let g:phpcomplete_index_cache['namespace_cache'] = {} let g:phpcomplete_index_cache['methods_cache'] = {} let g:phpcomplete_index_cache['fqcn_classkey_cache'] = {} else let content = readfile(index_cache_file) if len(content) == 0 let g:phpcomplete_index_cache = {} let g:phpcomplete_index_cache['classname_cache'] = {} let g:phpcomplete_index_cache['namespace_cache'] = {} let g:phpcomplete_index_cache['methods_cache'] = {} let g:phpcomplete_index_cache['fqcn_classkey_cache'] = {} else let true = 1 let false = 0 let null = 0 sandbox let g:phpcomplete_index_cache = eval(join(content, '\n')) endif endif let g:phpcomplete_index_cache_loaded = 1 endfunction " }}} function! s:loadIndex() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif if exists('g:phpcomplete_index_loaded') && g:phpcomplete_index_loaded return endif if index(s:disabled_projects, getcwd()) != -1 return endif let index_file = phpcomplete_extended#util#substitute_path_separator(getcwd().'/.phpcomplete_extended/phpcomplete_index') let plugin_index_file = s:getPluginIndexFileName() if !filereadable(index_file) let initial_message = "Composer project detected, Do you want to create index?" let ret = phpcomplete_extended#util#input_yesno(initial_message) if !ret call add(s:disabled_projects, getcwd()) return endif echo "\n\n" call phpcomplete_extended#generateIndex() endif if !g:phpcomplete_index_loaded call phpcomplete_extended#util#print_message("Loading Index") if filereadable(plugin_index_file) let s:plugin_index = s:readIndex(plugin_index_file) call s:set_plugin_indexes(plugin_index_file) let s:plugin_ftime = getftime(plugin_index_file) endif let g:phpcomplete_index = s:readIndex(index_file) call phpcomplete_extended#util#print_message("Index Loaded.") let g:phpcomplete_index_loaded = 1 endif endfunction " }}} " function! phpcomplete_extended#clear_disabled_project() "{{{ let s:disabled_projects = [] endfunction "}}} function! s:clearIndex() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let g:phpcomplete_index = {} let g:phpcomplete_index_loaded = 0 endfunction " }}} function! phpcomplete_extended#readDataForProject() "{{{ if !s:isCurrentProject() let s:current_project_dir = getcwd() let g:phpcomplete_index_loaded = 0 call phpcomplete_extended#loadProject() endif endfunction "}}} function! phpcomplete_extended#clearIndexCache() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let index_cache_file = s:getCacheFile() if filereadable(index_cache_file) call delete(index_cache_file) endif let g:phpcomplete_index_cache = {} let g:phpcomplete_index_cache['classname_cache'] = {} let g:phpcomplete_index_cache['namespace_cache'] = {} let g:phpcomplete_index_cache['methods_cache'] = {} let g:phpcomplete_index_cache_loaded = 0 endfunction " }}} function! s:isCurrentProject() "{{{ return s:current_project_dir == getcwd() endfunction "}}} function! phpcomplete_extended#is_phpcomplete_extended_project() " {{{ if filereadable(getcwd(). '/composer.json') && filereadable(getcwd(). "/vendor/autoload.php") return 1 endif return 0 endfunction " }}} function! phpcomplete_extended#loadProject() "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let s:update_info = {} call s:register_plugins() call s:makeCacheDir() call phpcomplete_extended#loadCoreIndex() call s:loadIndex() call s:loadIndexCache() endfunction "}}} function! phpcomplete_extended#reload() " {{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let s:update_info = {} call s:register_plugins() call s:makeCacheDir() call phpcomplete_extended#loadCoreIndex() call s:clearIndex() call phpcomplete_extended#clearIndexCache() call s:loadIndex() call s:loadIndexCache() endfunction " }}} function! s:copyCoreIndex() "{{{ let src = phpcomplete_extended#util#substitute_path_separator(g:phpcomplete_extended_root_dir . "/bin/core_index") let cache_dir = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended') let dest = phpcomplete_extended#util#substitute_path_separator(cache_dir. "/core_index") if empty(findfile(dest, cache_dir)) call phpcomplete_extended#util#copy(src, dest) endif endfunction "}}} function! phpcomplete_extended#loadCoreIndex() "{{{ if exists('g:core_index_loaded') && g:core_index_loaded return endif let cache_dir = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended') let location = phpcomplete_extended#util#substitute_path_separator(cache_dir. "/core_index") if !filereadable(location) call s:copyCoreIndex() endif let content = readfile(location) let true = 1 let false = 0 let null = 0 sandbox let g:phpcomplete_extended_core_index = eval(join(content, '\n')) let g:core_index_loaded = 1 endfunction "}}} function! phpcomplete_extended#generateIndex(...) "{{{ if !s:valid_composer_command() echoerr printf('The composer command "%s" is not a valid Composer command. Please set g:phpcomplete_index_composer_command in your .vimrc file', g:phpcomplete_index_composer_command) return endif call s:makeCacheDir() call s:copyCoreIndex() call s:register_plugins() let input = g:phpcomplete_extended_root_dir . "/bin/IndexGenerator.php generate" if len(a:000) == 1 && a:1 == '-verbose' let input .= ' -verbose' endif let input = phpcomplete_extended#util#substitute_path_separator(input) let plugin_php_file_command = join(map(copy(s:plugin_php_files), '" -u ".v:val')) let cmd = 'php ' . input . plugin_php_file_command "echoerr cmd "return let composer_command = g:phpcomplete_index_composer_command . " dumpautoload --optimize 1>/dev/null 2>&1" echomsg "Generating autoload classmap" call vimproc#system(composer_command) echomsg "Generating index..." let out = vimproc#system(cmd) if out == "success" echomsg "Index generated" endif echo out endfunction "}}} function! s:valid_composer_command() "{{{ let cmd = printf('%s --version', g:phpcomplete_index_composer_command) let output = system(cmd) return output =~ 'Composer version' endfunction "}}} function! phpcomplete_extended#updateIndex(background) "{{{ let s:update_info = {} if !phpcomplete_extended#is_phpcomplete_extended_project() || &ft != 'php' return endif let file_location = phpcomplete_extended#util#substitute_path_separator(fnamemodify(bufname('%'), ':p:.')) "current file let update_time = getftime(bufname('%')) let fileName = 'update_cache_'. update_time let plugin_php_file_command = join(map(copy(s:plugin_php_files), '" -u ".v:val')) let input = printf('%s %s %s %s', g:phpcomplete_extended_root_dir . "/bin/IndexGenerator.php update" , file_location, fileName, plugin_php_file_command) let input = phpcomplete_extended#util#substitute_path_separator(input) let cmd = 'php '. input if a:background let cmd .= ' 1>/dev/null 2>/dev/null' call vimproc#system_bg(cmd) else let out = vimproc#system(cmd) echo out endif let s:update_info['update_available'] = 1 let s:update_info['update_time'] = update_time let s:update_info['update_file_name'] = fileName let s:update_info['updated_file'] = file_location endfunction "}}} function! s:get_update_command() "{{{ return cmd endfunction "}}} function! phpcomplete_extended#checkUpdates() "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() return endif let timeout = 1000 "echoerr string(s:update_info) if has_key(s:update_info, 'update_available') && s:update_info['update_available'] if localtime() - s:update_info['update_time'] > timeout let s:update_info = {} return endif let update_file = phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended/'.s:update_info['update_file_name']) if filereadable(update_file) try let updateData = s:readIndex(update_file) catch echoerr "Error occured while reading update index" return endtry call s:updateLocalCache(updateData) call delete(update_file) let s:update_info = {} endif endif let plugin_index_file = s:getPluginIndexFileName() if !filereadable(plugin_index_file) return endif let plugin_file_time = getftime(plugin_index_file) if plugin_file_time > s:plugin_ftime call s:set_plugin_indexes(plugin_index_file) let s:plugin_ftime = plugin_file_time endif endfunction "}}} function! s:getPluginIndexFileName() "{{{ return phpcomplete_extended#util#substitute_path_separator(fnamemodify(getcwd(), ':p:h').'/.phpcomplete_extended/plugin_index') endfunction "}}} function! s:readIndex(filename) "{{{ if(!filereadable(a:filename)) echoerr printf('Could not read index file %s', fnamemodify(a:filename, ':.')) return endif let file_content = readfile(a:filename) let true = 1 let false = 0 let null = 0 sandbox let eval_data = eval(file_content[0]) return eval_data endfunction "}}} function! s:updateLocalCache(updateData) "{{{ let updateData = a:updateData let fqcn = updateData['classdata']['fqcn'] let file = updateData['classdata']['file'] let classData = updateData['classdata']['data'] let extendsData = updateData['extends'] let implementsData = updateData['interfaces'] call s:setClassData(fqcn, file, classData) call s:updateIntrospectionData('extends', fqcn, extendsData) call s:updateIntrospectionData('implements', fqcn, implementsData) call s:updateMenuEntries(classData['classname']) endfunction "}}} function! s:updateIntrospectionData(type,fqcn, data) "{{{ let type = a:type "type is extends/implements let fqcn = a:fqcn let data = a:data let collection = g:phpcomplete_index[type] if type(data) != type({}) return endif for added in data['added'] if !has_key(collection, added) let collection[added] = [] endif call add(collection[added], fqcn) endfor for removed in data['removed'] if !has_key(collection, removed) continue endif let index = index(collection[removed], fqcn) if index < 0 continue endif call remove(collection[removed], index(collection[removed], fqcn)) endfor endfunction "}}} function! s:updateMenuEntries(className) "{{{ let className = a:className let fqcns = g:phpcomplete_index['class_fqcn'][className] let class_menu_entries = g:phpcomplete_index['class_func_menu_entries'] let idx = 0 for class_menu_entry in class_menu_entries if class_menu_entry['word'] =~ '^'. className call remove(class_menu_entries, idx) else let idx = idx + 1 endif endfor let menu_entries = [] if type(fqcns) == type('') call add(menu_entries, { \ 'word': className, \ 'kind': 'c', \ 'menu': fqcns, \ 'info': fqcns \ } \) elseif type(fqcns) == type([]) let i = 1 for fqcn in fqcns call add(menu_entries , { \ 'word': className. '-'. i, \ 'kind': 'c', \ 'menu': fqcn, \ 'info': fqcn \} \) let i = i + 1 endfor endif for menu_entry in menu_entries " add at last for now call add(class_menu_entries, menu_entry) endfor endfunction "}}} function! phpcomplete_extended#isClassOfType(classFQCN, typeFQCN) "{{{ if a:classFQCN == a:typeFQCN return 1 endif if has_key(g:phpcomplete_index['extends'], a:typeFQCN) \ && index(g:phpcomplete_index['extends'][a:typeFQCN], a:classFQCN) >= 0 return 1 endif if has_key(g:phpcomplete_index['implements'], a:typeFQCN) \ && index(g:phpcomplete_index['implements'][a:typeFQCN], a:classFQCN) >= 0 return 1 endif return 0 endfunction "}}} " Blatantly copied form thinca/vim-ref :) let s:plugin_prototype = {} " {{{ plugin prototype let s:plugin_prototype.plugin_name = "" function! s:plugin_prototype.set_index(index) endfunction function! s:plugin_prototype.get_fqcn(parentFQCN, token_data) endfunction function! s:plugin_prototype.get_menu_entries(fqcn, base, is_this, is_static) endfunction "}}} function! s:register_plugins() "{{{ let list = split(globpath(&runtimepath, 'autoload/phpcomplete_extended/*.vim'), "\n") let s:plugins = {} let s:plugin_php_files = [] for script_file in list try let script_name = fnamemodify(script_file, ":t:r") call s:register(script_file, phpcomplete_extended#{script_name}#define()) catch /:E\%(117\|716\):/ endtry endfor endfunction "}}} function! s:register(script_file, plugin) "{{{ if empty(a:plugin) return endif let plugin = extend(copy(s:plugin_prototype), a:plugin) call plugin.init() let s:plugins[plugin.name] = plugin let script_name = fnamemodify(a:script_file, ":t:r") let plugin_dir = fnamemodify(a:script_file, ":p:h:h:h") let plugin_php_file = phpcomplete_extended#util#substitute_path_separator( \ plugin_dir . "/bin/".script_name.".php" \) call add(s:plugin_php_files, plugin_php_file) endfunction "}}} function! s:validate(plugin, key, type) "{{{ if !has_key(a:plugin, a:key) throw 'phpcomplete_extended: Invalid plugin: Without key ' . string(a:key) elseif type(a:plugin[a:key]) != s:T[a:type] throw 'phpcomplete_extended: Invalid plugin: Key ' . key . ' must be ' . a:type . ', ' . \ 'but given value is' string(a:plugin[a:key]) endif endfunction "}}} function! s:set_plugin_indexes(plugin_index_file) "{{{ let s:plugin_index = s:readIndex(a:plugin_index_file) for plugin_name in keys(s:plugins) if has_key(s:plugin_index, plugin_name) call s:plugins[plugin_name].set_index(deepcopy(s:plugin_index[plugin_name])) endif endfor endfunction "}}} function! s:get_plugin_php_files() "{{{ let php_files = [] for plugin in keys(s:plugins) let php_file = s:plugins[plugin].get_php_filename() call add(php_files, php_file) endfor return php_files endfunction "}}} function! s:get_plugin_fqcn(parentFQCN, token_data) "{{{ let fqcn = "" for plugin in keys(s:plugins) let fqcn = s:plugins[plugin].get_fqcn(a:parentFQCN, a:token_data) if !empty(fqcn) return fqcn endif endfor return fqcn endfunction "}}} function! s:get_plugin_resolve_fqcn(fqcn) "{{{ let fqcn = a:fqcn for plugin in keys(s:plugins) let fqcn = s:plugins[plugin].resolve_fqcn(a:fqcn) if !empty(fqcn) return fqcn endif endfor return fqcn endfunction "}}} function! s:get_plugin_menu_entries(fqcn, base, is_this, is_static) "{{{ let menu_entries = [] for plugin in keys(s:plugins) let plugin_menu_entries = s:plugins[plugin].get_menu_entries(a:fqcn, a:base, a:is_this, a:is_static) if !empty(plugin_menu_entries) let menu_entries += plugin_menu_entries return menu_entries endif endfor return menu_entries endfunction "}}} function! s:get_plugin_inside_qoute_menu_entries(fqcn, lastToken) "{{{ let menu_entries = [] for plugin in keys(s:plugins) let plugin_menu_entries = s:plugins[plugin].get_inside_quote_menu_entries(a:fqcn, a:lastToken) if !empty(plugin_menu_entries) let menu_entries += plugin_menu_entries return menu_entries endif endfor return menu_entries endfunction "}}} function! phpcomplete_extended#init_autocmd() "{{{ augroup phpcomplete-extended autocmd! "Todo add configuration option to load later autocmd BufWinEnter,BufEnter * call phpcomplete_extended#readDataForProject() autocmd VimLeave * call phpcomplete_extended#saveIndexCache() autocmd BufWritePost *.php call phpcomplete_extended#updateIndex(1) autocmd CursorHold * call phpcomplete_extended#saveIndexCache() autocmd CursorHold * call phpcomplete_extended#checkUpdates() autocmd CursorMoved,CursorMovedI *.php call phpcomplete_extended#checkUpdates() autocmd CursorMovedI *.php call phpcomplete_extended#trackMenuChanges() augroup END endfunction "}}} function! phpcomplete_extended#enable() "{{{ let s:phpcomplete_enabled = 1 call phpcomplete_extended#init_autocmd() command! -nargs=0 -bar PHPCompleteExtendedDisable \ call phpcomplete_extended#disable() endfunction "}}} function! phpcomplete_extended#disable() "{{{ let s:phpcomplete_enabled = 0 augroup phpcomplete-extended autocmd! augroup END silent! delcommand PHPCompleteExtendedDisable endfunction "}}} let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker:expandtab:ts=4:sts=4:tw=78 ================================================ FILE: autoload/unite/kinds/phpcomplete.vim ================================================ "let s:save_cpo = &cpo "set cpo&vim function! unite#kinds#phpcomplete_extended#define() "{{{ "return s:kind endfunction"}}} let s:kind = { \ 'name' : 'phpcomplete_extended', \ 'default_action' : 'insert', \ 'action_table': {}, \} let s:kind.action_table.insert_namespace = { \ 'description' : 'insert namespace', \ } " TBD "let &cpo = s:save_cpo "unlet s:save_cpo " vim: foldmethod=marker ================================================ FILE: autoload/unite/sources/phpcomplete.vim ================================================ "============================================================================= " AUTHOR: Mun Mun Das " FILE: phpcomplete.vim " Last Modified: September 10, 2013 " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim if !g:loaded_unite finish endif function! unite#sources#phpcomplete#define() "{{{ return [s:source, s:source_find_extends, s:source_find_implements, s:vendors_source] endfunction"}}} let s:source = { \ 'name' : 'phpcomplete/files', \ 'description' : 'File candidates read from the phpcomplete index', \ 'action_table' : {}, \ 'default_kind' : 'file', \} function! s:source.gather_candidates(args, context) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !exists("g:phpcomplete_index_loaded") || !g:phpcomplete_index_loaded call unite#print_error("Not a valid composer project") return [] endif let is_relative_path = 1 let files = keys(g:phpcomplete_index['file_fqcn']) let entries = [] for file in files let dict = { \ 'word' : file, \ 'action__path' : file, \ 'action__line' : 0, \ } call add(entries, dict) endfor return entries endfunction"}}} let s:source_find_extends = { \ 'name' : 'phpcomplete/extends', \ 'description' : 'File candidate class that extends form source argument word', \ 'action_table' : {}, \ 'default_kind' : 'file', \} function! s:source_find_extends.gather_candidates(args, context) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !g:phpcomplete_index_loaded call unite#print_error("Not a valid composer project") return [] endif let word = get(a:args, 0, expand("")) return s:getCandidates('extends', word) endfunction "}}} let s:source_find_implements = { \ 'name' : 'phpcomplete/implements', \ 'description' : 'File candidate class that implements source argument word', \ 'action_table' : {}, \ 'default_kind' : 'file', \} function! s:source_find_implements.gather_candidates(args, context) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !g:phpcomplete_index_loaded call unite#print_error("Not a valid composer project") return [] endif let word = get(a:args, 0, expand("")) return s:getCandidates('implements', word) endfunction "}}} function! s:getCandidates(type, word) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !g:phpcomplete_index_loaded call unite#print_error("Not a valid composer project") return [] endif let word_fqcn = phpcomplete_extended#getFQCNFromWord(a:word) if word_fqcn == "" return [] endif let fqcns = [] let candidates = [] if a:type == 'implements' let fqcns = get(g:phpcomplete_index['implements'], word_fqcn, []) elseif a:type == 'extends' let fqcns = get(g:phpcomplete_index['extends'], word_fqcn, []) endif if !len(fqcns) return [] endif for fqcn in fqcns let filelocation = phpcomplete_extended#getFileFromFQCN(fqcn) if filelocation == "" continue endif let dict = { \ 'word' : fqcn, \ 'action__path' : unite#util#substitute_path_separator( \ filelocation), \ 'action__line' : 0, \ } call add(candidates, dict) endfor return candidates endfunction "}}} let s:vendors_source = { \ 'name' : 'phpcomplete/vendors', \ 'description' : 'Vendor library candidates', \ 'action_table' : {}, \ } function! s:vendors_source.gather_candidates(args, context) "{{{ if !phpcomplete_extended#is_phpcomplete_extended_project() || !g:phpcomplete_index_loaded call unite#print_error("Not a valid composer project") return [] endif return s:get_vendors() endfunction "}}} function! s:get_vendors() "{{{ let vendor_list = deepcopy(g:phpcomplete_index['vendor_libs']) let vendor_names = keys(vendor_list) let padded_vendor_list = phpcomplete_extended#util#add_padding(copy(vendor_names)) let entries = [] for vendor in vendor_names let dir = unite#util#substitute_path_separator(vendor_list[vendor]) let vendor_index = index(vendor_names, vendor) let dict = {} let dict = { \ 'word' : dir, \ 'abbr' : printf('%s %s', padded_vendor_list[vendor_index], dir ), \ 'kind' : 'directory', \ 'action__path' : dir, \ 'action__directory' : dir, \ } "let entries += dict call add(entries, dict) endfor return entries endfunction "}}} let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker sw=4 ts=4 sts=4 expandtab ================================================ FILE: autoload/vital/_62ad025/Data/List.vim ================================================ " Utilities for list. let s:save_cpo = &cpo set cpo&vim function! s:pop(list) return remove(a:list, -1) endfunction function! s:push(list, val) call add(a:list, a:val) return a:list endfunction function! s:shift(list) return remove(a:list, 0) endfunction function! s:unshift(list, val) return insert(a:list, a:val) endfunction function! s:cons(x, xs) return [a:x] + a:xs endfunction " TODO spec function! s:conj(xs, x) return a:xs + [a:x] endfunction " Removes duplicates from a list. function! s:uniq(list, ...) let list = a:0 ? map(copy(a:list), printf('[v:val, %s]', a:1)) : copy(a:list) let i = 0 let seen = {} while i < len(list) let key = string(a:0 ? list[i][1] : list[i]) if has_key(seen, key) call remove(list, i) else let seen[key] = 1 let i += 1 endif endwhile return a:0 ? map(list, 'v:val[0]') : list endfunction function! s:clear(list) if !empty(a:list) unlet! a:list[0 : len(a:list) - 1] endif return a:list endfunction " Concatenates a list of lists. " XXX: Should we verify the input? function! s:concat(list) let list = [] for Value in a:list let list += Value endfor return list endfunction " Take each elements from lists to a new list. function! s:flatten(list, ...) let limit = a:0 > 0 ? a:1 : -1 let list = [] if limit == 0 return a:list endif let limit -= 1 for Value in a:list let list += \ type(Value) == type([]) ? \ s:flatten(Value, limit) : \ [Value] unlet! Value endfor return list endfunction " Sorts a list with expression to compare each two values. " a:a and a:b can be used in {expr}. function! s:sort(list, expr) if type(a:expr) == type(function('function')) return sort(a:list, a:expr) endif let s:expr = a:expr return sort(a:list, 's:_compare') endfunction function! s:_compare(a, b) return eval(s:expr) endfunction " Sorts a list using a set of keys generated by mapping the values in the list " through the given expr. " v:val is used in {expr} function! s:sort_by(list, expr) let pairs = map(a:list, printf('[v:val, %s]', a:expr)) return map(s:sort(pairs, \ 'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]') endfunction function! s:max(list, expr) echoerr 'Data.List.max() is obsolete. Use its max_by() instead.' return s:max_by(a:list, a:expr) endfunction " Returns a maximum value in {list} through given {expr}. " Returns 0 if {list} is empty. " v:val is used in {expr} function! s:max_by(list, expr) if empty(a:list) return 0 endif let list = map(copy(a:list), a:expr) return a:list[index(list, max(list))] endfunction function! s:min(list, expr) echoerr 'Data.List.min() is obsolete. Use its min_by() instead.' return s:min_by(a:list, a:expr) endfunction " Returns a minimum value in {list} through given {expr}. " Returns 0 if {list} is empty. " v:val is used in {expr} " FIXME: -0x80000000 == 0x80000000 function! s:min_by(list, expr) return s:max_by(a:list, '-(' . a:expr . ')') endfunction " Returns List of character sequence between [a:from, a:to] " e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] function! s:char_range(from, to) return map( \ range(char2nr(a:from), char2nr(a:to)), \ 'nr2char(v:val)' \) endfunction " Returns true if a:list has a:value. " Returns false otherwise. function! s:has(list, value) return index(a:list, a:value) isnot -1 endfunction " Returns true if a:list[a:index] exists. " Returns false otherwise. " NOTE: Returns false when a:index is negative number. function! s:has_index(list, index) " Return true when negative index? " let index = a:index >= 0 ? a:index : len(a:list) + a:index return 0 <= a:index && a:index < len(a:list) endfunction " similar to Haskell's Data.List.span function! s:span(f, xs) let border = len(a:xs) for i in range(len(a:xs)) if !eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) let border = i break endif endfor return border == 0 ? [[], copy(a:xs)] : [a:xs[: border - 1], a:xs[border :]] endfunction " similar to Haskell's Data.List.break function! s:break(f, xs) return s:span(printf('!(%s)', a:f), a:xs) endfunction " similar to Haskell's Data.List.takeWhile function! s:take_while(f, xs) return s:span(a:f, a:xs)[0] endfunction " similar to Haskell's Data.List.partition function! s:partition(f, xs) return [filter(copy(a:xs), a:f), filter(copy(a:xs), '!(' . a:f . ')')] endfunction " similar to Haskell's Prelude.all function! s:all(f, xs) return !s:any(printf('!(%s)', a:f), a:xs) endfunction " similar to Haskell's Prelude.any function! s:any(f, xs) return !empty(filter(map(copy(a:xs), a:f), 'v:val')) endfunction " similar to Haskell's Prelude.and function! s:and(xs) return s:all('v:val', a:xs) endfunction " similar to Haskell's Prelude.or function! s:or(xs) return s:any('v:val', a:xs) endfunction " similar to Haskell's Prelude.foldl function! s:foldl(f, init, xs) let memo = a:init for x in a:xs let expr = substitute(a:f, 'v:val', string(x), 'g') let expr = substitute(expr, 'v:memo', string(memo), 'g') unlet memo let memo = eval(expr) endfor return memo endfunction " similar to Haskell's Prelude.foldl1 function! s:foldl1(f, xs) if len(a:xs) == 0 throw 'foldl1' endif return s:foldl(a:f, a:xs[0], a:xs[1:]) endfunction " similar to Haskell's Prelude.foldr function! s:foldr(f, init, xs) return s:foldl(a:f, a:init, reverse(copy(a:xs))) endfunction " similar to Haskell's Prelude.fold11 function! s:foldr1(f, xs) if len(a:xs) == 0 throw 'foldr1' endif return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) endfunction " similar to python's zip() function! s:zip(...) return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") endfunction " similar to zip(), but goes until the longer one. function! s:zip_fill(xs, ys, filler) if empty(a:xs) && empty(a:ys) return [] elseif empty(a:ys) return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) elseif empty(a:xs) return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) else return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) endif endfunction " Inspired by Ruby's with_index method. function! s:with_index(list, ...) let base = a:0 > 0 ? a:1 : 0 return s:zip(a:list, range(base, len(a:list)+base-1)) endfunction " similar to Ruby's detect or Haskell's find. " TODO spec and doc function! s:find(list, default, f) for x in a:list if eval(substitute(a:f, 'v:val', string(x), 'g')) return x endif endfor return a:default endfunction " Return non-zero if a:list1 and a:list2 have any common item(s). " Return zero otherwise. function! s:has_common_items(list1, list2) return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/Data/String.vim ================================================ " Utilities for string. let s:save_cpo = &cpo set cpo&vim function! s:_vital_loaded(V) let s:V = a:V let s:L = s:V.import('Data.List') endfunction function! s:_vital_depends() return ['Data.List'] endfunction " Substitute a:from => a:to by string. " To substitute by pattern, use substitute() instead. function! s:replace(str, from, to) return s:_replace(a:str, a:from, a:to, 'g') endfunction " Substitute a:from => a:to only once. " cf. s:replace() function! s:replace_first(str, from, to) return s:_replace(a:str, a:from, a:to, '') endfunction " implement of replace() and replace_first() function! s:_replace(str, from, to, flags) return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags) endfunction function! s:scan(str, pattern) let list = [] call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g') return list endfunction function! s:reverse(str) return join(reverse(split(a:str, '.\zs')), '') endfunction function! s:common_head(strs) if empty(a:strs) return '' endif let len = len(a:strs) if len == 1 return a:strs[0] endif let strs = len == 2 ? a:strs : sort(copy(a:strs)) let pat = substitute(strs[0], '.', '[\0]', 'g') return pat == '' ? '' : matchstr(strs[-1], '^\%[' . pat . ']') endfunction " Split to two elements of List. ([left, right]) " e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache'] function! s:split_leftright(expr, pattern) let [left, _, right] = s:split3(a:expr, a:pattern) return [left, right] endfunction function! s:split3(expr, pattern) let ERROR = ['', '', ''] if a:expr ==# '' || a:pattern ==# '' return ERROR endif let begin = match(a:expr, a:pattern) if begin is -1 return ERROR endif let end = matchend(a:expr, a:pattern) let left = begin <=# 0 ? '' : a:expr[: begin - 1] let right = a:expr[end :] return [left, a:expr[begin : end-1], right] endfunction " Slices into strings determines the number of substrings. " e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache'] function! s:nsplit(expr, n, ...) let pattern = get(a:000, 0, '\s') let keepempty = get(a:000, 1, 1) let ret = [] let expr = a:expr if a:n <= 1 return [expr] endif while 1 let pos = match(expr, pattern) if pos == -1 if expr !~ pattern || keepempty call add(ret, expr) endif break elseif pos >= 0 let left = pos > 0 ? expr[:pos-1] : '' if pos > 0 || keepempty call add(ret, left) endif let ml = len(matchstr(expr, pattern)) if pos == 0 && ml == 0 let pos = 1 endif let expr = expr[pos+ml :] endif if len(expr) == 0 break endif if len(ret) == a:n - 1 call add(ret, expr) break endif endwhile return ret endfunction " Returns the number of character in a:str. " NOTE: This returns proper value " even if a:str contains multibyte character(s). " s:strchars(str) {{{ if exists('*strchars') function! s:strchars(str) return strchars(a:str) endfunction else function! s:strchars(str) return strlen(substitute(copy(a:str), '.', 'x', 'g')) endfunction endif "}}} " Returns the bool of contains any multibyte character in s:str function! s:contains_multibyte(str) "{{{ return strlen(a:str) != s:strchars(a:str) endfunction "}}} " Remove last character from a:str. " NOTE: This returns proper value " even if a:str contains multibyte character(s). function! s:chop(str) "{{{ return substitute(a:str, '.$', '', '') endfunction "}}} " Remove last \r,\n,\r\n from a:str. function! s:chomp(str) "{{{ return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '') endfunction "}}} " wrap() and its internal functions " * _split_by_wcswidth_once() " * _split_by_wcswidth() " * _concat() " * wrap() " " NOTE _concat() is just a copy of Data.List.concat(). " FIXME don't repeat yourself function! s:_split_by_wcswidth_once(body, x) let fst = s:V.strwidthpart(a:body, a:x) let snd = s:V.strwidthpart_reverse(a:body, s:V.wcswidth(a:body) - s:V.wcswidth(fst)) return [fst, snd] endfunction function! s:_split_by_wcswidth(body, x) let memo = [] let body = a:body while s:V.wcswidth(body) > a:x let [tmp, body] = s:_split_by_wcswidth_once(body, a:x) call add(memo, tmp) endwhile call add(memo, body) return memo endfunction function! s:trim(str) return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') endfunction function! s:wrap(str,...) let _columns = a:0 > 0 ? a:1 : &columns return s:L.concat( \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)')) endfunction function! s:nr2byte(nr) if a:nr < 0x80 return nr2char(a:nr) elseif a:nr < 0x800 return nr2char(a:nr/64+192).nr2char(a:nr%64+128) else return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128) endif endfunction function! s:nr2enc_char(charcode) if &encoding == 'utf-8' return nr2char(a:charcode) endif let char = s:nr2byte(a:charcode) if strlen(char) > 1 let char = strtrans(iconv(char, 'utf-8', &encoding)) endif return char endfunction function! s:nr2hex(nr) let n = a:nr let r = "" while n let r = '0123456789ABCDEF'[n % 16] . r let n = n / 16 endwhile return r endfunction " If a ==# b, returns -1. " If a !=# b, returns first index of diffrent character. function! s:diffidx(a, b) return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b])) endfunction function! s:substitute_last(expr, pat, sub) return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '') endfunction function! s:dstring(expr) let x = substitute(string(a:expr), "^'\\|'$", '', 'g') let x = substitute(x, "''", "'", 'g') return printf('"%s"', escape(x, '"')) endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/Prelude.vim ================================================ let s:save_cpo = &cpo set cpo&vim " glob() wrapper which returns List " and 'wildignore' does not affect " this function's return value. if v:version ># 703 || \ (v:version is 703 && has('patch465')) function! s:glob(expr) return glob(a:expr, 1, 1) endfunction else function! s:glob(expr) let R = glob(a:expr, 1) return split(R, '\n') endfunction endif " globpath() wrapper which returns List " and 'suffixes' and 'wildignore' does not affect " this function's return value. function! s:globpath(path, expr) let R = globpath(a:path, a:expr, 1) return split(R, '\n') endfunction " Wrapper functions for type(). let [ \ s:__TYPE_NUMBER, \ s:__TYPE_STRING, \ s:__TYPE_FUNCREF, \ s:__TYPE_LIST, \ s:__TYPE_DICT, \ s:__TYPE_FLOAT] = [ \ type(3), \ type(""), \ type(function('tr')), \ type([]), \ type({}), \ has('float') ? type(str2float('0')) : -1] " __TYPE_FLOAT = -1 when -float " This doesn't match to anything. " Number or Float function! s:is_numeric(Value) let _ = type(a:Value) return _ ==# s:__TYPE_NUMBER \ || _ ==# s:__TYPE_FLOAT endfunction " Number function! s:is_number(Value) return type(a:Value) ==# s:__TYPE_NUMBER endfunction " Float function! s:is_float(Value) return type(a:Value) ==# s:__TYPE_FLOAT endfunction " String function! s:is_string(Value) return type(a:Value) ==# s:__TYPE_STRING endfunction " Funcref function! s:is_funcref(Value) return type(a:Value) ==# s:__TYPE_FUNCREF endfunction " List function! s:is_list(Value) return type(a:Value) ==# s:__TYPE_LIST endfunction " Dictionary function! s:is_dict(Value) return type(a:Value) ==# s:__TYPE_DICT endfunction function! s:truncate_smart(str, max, footer_width, separator) echoerr 'Prelude.truncate_smart() is obsolete. Use its truncate_skipping() instead; they are equivalent.' return s:truncate_skipping(a:str, a:max, a:footer_width, a:separator) endfunction function! s:truncate_skipping(str, max, footer_width, separator) let width = s:wcswidth(a:str) if width <= a:max let ret = a:str else let header_width = a:max - s:wcswidth(a:separator) - a:footer_width let ret = s:strwidthpart(a:str, header_width) . a:separator \ . s:strwidthpart_reverse(a:str, a:footer_width) endif return s:truncate(ret, a:max) endfunction function! s:truncate(str, width) " Original function is from mattn. " http://github.com/mattn/googlereader-vim/tree/master if a:str =~# '^[\x00-\x7f]*$' return len(a:str) < a:width ? \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) endif let ret = a:str let width = s:wcswidth(a:str) if width > a:width let ret = s:strwidthpart(ret, a:width) let width = s:wcswidth(ret) endif if width < a:width let ret .= repeat(' ', a:width - width) endif return ret endfunction function! s:strwidthpart(str, width) if a:width <= 0 return '' endif let ret = a:str let width = s:wcswidth(a:str) while width > a:width let char = matchstr(ret, '.$') let ret = ret[: -1 - len(char)] let width -= s:wcswidth(char) endwhile return ret endfunction function! s:strwidthpart_reverse(str, width) if a:width <= 0 return '' endif let ret = a:str let width = s:wcswidth(a:str) while width > a:width let char = matchstr(ret, '^.') let ret = ret[len(char) :] let width -= s:wcswidth(char) endwhile return ret endfunction if v:version >= 703 " Use builtin function. function! s:wcswidth(str) return strwidth(a:str) endfunction else function! s:wcswidth(str) if a:str =~# '^[\x00-\x7f]*$' return strlen(a:str) end let mx_first = '^\(.\)' let str = a:str let width = 0 while 1 let ucs = char2nr(substitute(str, mx_first, '\1', '')) if ucs == 0 break endif let width += s:_wcwidth(ucs) let str = substitute(str, mx_first, '', '') endwhile return width endfunction " UTF-8 only. function! s:_wcwidth(ucs) let ucs = a:ucs if (ucs >= 0x1100 \ && (ucs <= 0x115f \ || ucs == 0x2329 \ || ucs == 0x232a \ || (ucs >= 0x2e80 && ucs <= 0xa4cf \ && ucs != 0x303f) \ || (ucs >= 0xac00 && ucs <= 0xd7a3) \ || (ucs >= 0xf900 && ucs <= 0xfaff) \ || (ucs >= 0xfe30 && ucs <= 0xfe6f) \ || (ucs >= 0xff00 && ucs <= 0xff60) \ || (ucs >= 0xffe0 && ucs <= 0xffe6) \ || (ucs >= 0x20000 && ucs <= 0x2fffd) \ || (ucs >= 0x30000 && ucs <= 0x3fffd) \ )) return 2 endif return 1 endfunction endif let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95') let s:is_cygwin = has('win32unix') let s:is_mac = !s:is_windows && !s:is_cygwin \ && (has('mac') || has('macunix') || has('gui_macvim') || \ (!isdirectory('/proc') && executable('sw_vers'))) let s:is_unix = has('unix') function! s:is_windows() return s:is_windows endfunction function! s:is_cygwin() return s:is_cygwin endfunction function! s:is_mac() return s:is_mac endfunction function! s:is_unix() return s:is_unix endfunction function! s:_deprecated(fname, newname) echomsg printf("Vital.Prelude.%s is deprecated! Please use %s instead.", \ a:fname, a:newname) endfunction function! s:print_error(message) call s:_deprecated('print_error', 'Vital.Vim.Message.error') echohl ErrorMsg for m in split(a:message, "\n") echomsg m endfor echohl None endfunction function! s:smart_execute_command(action, word) execute a:action . ' ' . (a:word == '' ? '' : '`=a:word`') endfunction function! s:escape_file_searching(buffer_name) return escape(a:buffer_name, '*[]?{}, ') endfunction function! s:escape_pattern(str) return escape(a:str, '~"\.^$[]*') endfunction " iconv() wrapper for safety. function! s:iconv(expr, from, to) if a:from == '' || a:to == '' || a:from ==? a:to return a:expr endif let result = iconv(a:expr, a:from, a:to) return result != '' ? result : a:expr endfunction " Like builtin getchar() but returns string always. function! s:getchar(...) let c = call('getchar', a:000) return type(c) == type(0) ? nr2char(c) : c endfunction " Like builtin getchar() but returns string always. " and do inputsave()/inputrestore() before/after getchar(). function! s:getchar_safe(...) let c = s:input_helper('getchar', a:000) return type(c) == type("") ? c : nr2char(c) endfunction " Like builtin getchar() but " do inputsave()/inputrestore() before/after input(). function! s:input_safe(...) return s:input_helper('input', a:000) endfunction " Do inputsave()/inputrestore() before/after calling a:funcname. function! s:input_helper(funcname, args) let success = 0 if inputsave() !=# success throw 'inputsave() failed' endif try return call(a:funcname, a:args) finally if inputrestore() !=# success throw 'inputrestore() failed' endif endtry endfunction function! s:set_default(var, val) if !exists(a:var) || type({a:var}) != type(a:val) let {a:var} = a:val endif endfunction function! s:set_dictionary_helper(variable, keys, pattern) for key in split(a:keys, '\s*,\s*') if !has_key(a:variable, key) let a:variable[key] = a:pattern endif endfor endfunction function! s:substitute_path_separator(path) return s:is_windows ? substitute(a:path, '\\', '/', 'g') : a:path endfunction function! s:path2directory(path) return s:substitute_path_separator(isdirectory(a:path) ? a:path : fnamemodify(a:path, ':p:h')) endfunction function! s:_path2project_directory_git(path) let parent = a:path while 1 let path = parent . '/.git' if isdirectory(path) || filereadable(path) return parent endif let next = fnamemodify(parent, ':h') if next == parent return '' endif let parent = next endwhile endfunction function! s:_path2project_directory_svn(path) let search_directory = a:path let directory = '' let find_directory = s:escape_file_searching(search_directory) let d = finddir('.svn', find_directory . ';') if d == '' return '' endif let directory = fnamemodify(d, ':p:h:h') " Search parent directories. let parent_directory = s:path2directory( \ fnamemodify(directory, ':h')) if parent_directory != '' let d = finddir('.svn', parent_directory . ';') if d != '' let directory = s:_path2project_directory_svn(parent_directory) endif endif return directory endfunction function! s:_path2project_directory_others(vcs, path) let vcs = a:vcs let search_directory = a:path let directory = '' let find_directory = s:escape_file_searching(search_directory) let d = finddir(vcs, find_directory . ';') if d == '' return '' endif return fnamemodify(d, ':p:h:h') endfunction function! s:path2project_directory(path, ...) let is_allow_empty = get(a:000, 0, 0) let search_directory = s:path2directory(a:path) let directory = '' " Search VCS directory. for vcs in ['.git', '.bzr', '.hg', '.svn'] if vcs ==# '.git' let directory = s:_path2project_directory_git(search_directory) elseif vcs ==# '.svn' let directory = s:_path2project_directory_svn(search_directory) else let directory = s:_path2project_directory_others(vcs, search_directory) endif if directory != '' break endif endfor " Search project file. if directory == '' for d in ['build.xml', 'prj.el', '.project', 'pom.xml', \ 'Makefile', 'configure', 'Rakefile', 'NAnt.build', 'tags', 'gtags'] let d = findfile(d, s:escape_file_searching(search_directory) . ';') if d != '' let directory = fnamemodify(d, ':p:h') break endif endfor endif if directory == '' " Search /src/ directory. let base = s:substitute_path_separator(search_directory) if base =~# '/src/' let directory = base[: strridx(base, '/src/') + 3] endif endif if directory == '' && !is_allow_empty " Use original path. let directory = search_directory endif return s:substitute_path_separator(directory) endfunction " Check vimproc. function! s:has_vimproc() if !exists('s:exists_vimproc') try call vimproc#version() let s:exists_vimproc = 1 catch let s:exists_vimproc = 0 endtry endif return s:exists_vimproc endfunction function! s:system(str, ...) let command = a:str let input = a:0 >= 1 ? a:1 : '' let command = s:iconv(command, &encoding, 'char') let input = s:iconv(input, &encoding, 'char') if a:0 == 0 let output = s:has_vimproc() ? \ vimproc#system(command) : system(command) elseif a:0 == 1 let output = s:has_vimproc() ? \ vimproc#system(command, input) : system(command, input) else " ignores 3rd argument unless you have vimproc. let output = s:has_vimproc() ? \ vimproc#system(command, input, a:2) : system(command, input) endif let output = s:iconv(output, 'char', &encoding) return output endfunction function! s:get_last_status() return s:has_vimproc() ? \ vimproc#get_last_status() : v:shell_error endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/System/Cache.vim ================================================ " Utilities for output cache. let s:save_cpo = &cpo set cpo&vim function! s:getfilename(cache_dir, filename) return s:_encode_name(a:cache_dir, a:filename) endfunction function! s:filereadable(cache_dir, filename) let cache_name = s:_encode_name(a:cache_dir, a:filename) return filereadable(cache_name) endfunction function! s:readfile(cache_dir, filename) let cache_name = s:_encode_name(a:cache_dir, a:filename) return filereadable(cache_name) ? readfile(cache_name) : [] endfunction function! s:writefile(cache_dir, filename, list) let cache_name = s:_encode_name(a:cache_dir, a:filename) call writefile(a:list, cache_name) endfunction function! s:delete(cache_dir, filename) echoerr 'System.Cache.delete() is obsolete. Use its deletefile() instead.' return call('s:deletefile', a:cache_dir, a:filename) endfunction function! s:deletefile(cache_dir, filename) let cache_name = s:_encode_name(a:cache_dir, a:filename) return delete(cache_name) endfunction function! s:_encode_name(cache_dir, filename) " Check cache directory. if !isdirectory(a:cache_dir) call mkdir(a:cache_dir, 'p') endif let cache_dir = a:cache_dir if cache_dir !~ '/$' let cache_dir .= '/' endif return cache_dir . s:_create_hash(cache_dir, a:filename) endfunction function! s:check_old_cache(cache_dir, filename) " Check old cache file. let cache_name = s:_encode_name(a:cache_dir, a:filename) let ret = getftime(cache_name) == -1 \ || getftime(cache_name) <= getftime(a:filename) if ret && filereadable(cache_name) " Delete old cache. call delete(cache_name) endif return ret endfunction " Check md5. try call md5#md5() let s:exists_md5 = 1 catch let s:exists_md5 = 0 endtry function! s:_create_hash(dir, str) if len(a:dir) + len(a:str) < 150 let hash = substitute(substitute( \ a:str, ':', '=-', 'g'), '[/\\]', '=+', 'g') elseif s:exists_md5 " Use md5.vim. let hash = md5#md5(a:str) else " Use simple hash. let sum = 0 for i in range(len(a:str)) let sum += char2nr(a:str[i]) * (i + 1) endfor let hash = printf('%x', sum) endif return hash endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/System/File.vim ================================================ " Utilities for file copy/move/mkdir/etc. let s:save_cpo = &cpo set cpo&vim let s:is_unix = has('unix') let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95') let s:is_cygwin = has('win32unix') let s:is_mac = !s:is_windows && !s:is_cygwin \ && (has('mac') || has('macunix') || has('gui_macvim') || \ (!isdirectory('/proc') && executable('sw_vers'))) " Open a file. function! s:open(filename) "{{{ let filename = fnamemodify(a:filename, ':p') " Detect desktop environment. if s:is_windows " For URI only. let filename = iconv(filename, &encoding, 'char') silent execute '!start rundll32 url.dll,FileProtocolHandler' filename elseif s:is_cygwin " Cygwin. call system(printf('%s %s', 'cygstart', \ shellescape(filename))) elseif executable('xdg-open') " Linux. call system(printf('%s %s &', 'xdg-open', \ shellescape(filename))) elseif exists('$KDE_FULL_SESSION') && $KDE_FULL_SESSION ==# 'true' " KDE. call system(printf('%s %s &', 'kioclient exec', \ shellescape(filename))) elseif exists('$GNOME_DESKTOP_SESSION_ID') " GNOME. call system(printf('%s %s &', 'gnome-open', \ shellescape(filename))) elseif executable('exo-open') " Xfce. call system(printf('%s %s &', 'exo-open', \ shellescape(filename))) elseif s:is_mac && executable('open') " Mac OS. call system(printf('%s %s &', 'open', \ shellescape(filename))) else " Give up. throw 'Not supported.' endif endfunction "}}} " Move a file. " Dispatch s:move_exe() or s:move_vim(). function! s:move(src, dest) "{{{ if s:_has_move_exe() return s:move_exe(a:src, a:dest) else return s:move_vim(a:src, a:dest) endif endfunction "}}} if s:is_unix function! s:_has_move_exe() return executable('mv') endfunction elseif s:is_windows function! s:_has_move_exe() return 1 endfunction else function! s:_has_move_exe() throw 'vital: System.File._has_move_exe(): your platform is not supported' endfunction endif " Move a file. " Implemented by external program. if s:is_unix function! s:move_exe(src, dest) if !s:_has_move_exe() | return 0 | endif let [src, dest] = [a:src, a:dest] silent execute '!mv' shellescape(src) shellescape(dest) return !v:shell_error endfunction elseif s:is_windows function! s:move_exe(src, dest) if !s:_has_move_exe() | return 0 | endif let [src, dest] = [a:src, a:dest] let src = substitute(src, '/', '\', 'g') let dest = substitute(dest, '/', '\', 'g') silent execute '!cmd /c move' src dest return !v:shell_error endfunction else function! s:move_exe() throw 'vital: System.File.move_exe(): your platform is not supported' endfunction endif " Move a file. " Implemented by pure Vim script. function! s:move_vim(src, dest) "{{{ return !rename(a:src, a:dest) endfunction "}}} " Copy a file. " Dispatch s:copy_exe() or s:copy_vim(). function! s:copy(src, dest) "{{{ if s:_has_copy_exe() return s:copy_exe(a:src, a:dest) else return s:copy_vim(a:src, a:dest) endif endfunction "}}} if s:is_unix function! s:_has_copy_exe() return executable('cp') endfunction elseif s:is_windows function! s:_has_copy_exe() return 1 endfunction else function! s:_has_copy_exe() throw 'vital: System.File._has_copy_exe(): your platform is not supported' endfunction endif " Copy a file. " Implemented by external program. if s:is_unix function! s:copy_exe(src, dest) if !s:_has_copy_exe() | return 0 | endif let [src, dest] = [a:src, a:dest] silent execute '!cp' shellescape(src) shellescape(dest) return !v:shell_error endfunction elseif s:is_windows function! s:copy_exe(src, dest) if !s:_has_copy_exe() | return 0 | endif let [src, dest] = [a:src, a:dest] let src = substitute(src, '/', '\', 'g') let dest = substitute(dest, '/', '\', 'g') silent execute '!cmd /c copy' src dest return !v:shell_error endfunction else function! s:copy_exe() throw 'vital: System.File.copy_exe(): your platform is not supported' endfunction endif " Copy a file. " Implemented by pure Vim script. function! s:copy_vim(src, dest) "{{{ let ret = writefile(readfile(a:src, "b"), a:dest, "b") if ret == -1 return 0 endif return 1 endfunction "}}} " mkdir() but does not throw an exception. " Returns true if success. " Returns false if failure. function! s:mkdir_nothrow(...) "{{{ silent! return call('mkdir', a:000) endfunction "}}} " Delete a file/directory. if s:is_unix function! s:rmdir(path, ...) let flags = a:0 ? a:1 : '' let cmd = flags =~# 'r' ? 'rm -r' : 'rmdir' let cmd .= flags =~# 'f' && cmd ==# 'rm -r' ? ' -f' : '' let ret = system(cmd . ' ' . shellescape(a:path)) if v:shell_error throw substitute(iconv(ret, 'char', &encoding), '\n', '', 'g') endif endfunction elseif s:is_windows function! s:rmdir(path, ...) let flags = a:0 ? a:1 : '' if &shell =~? "sh$" let cmd = flags =~# 'r' ? 'rm -r' : 'rmdir' let cmd .= flags =~# 'f' && cmd ==# 'rm -r' ? ' -f' : '' let ret = system(cmd . ' ' . shellescape(a:path)) else " 'f' flag does not make sense. let cmd = 'rmdir /Q' let cmd .= flags =~# 'r' ? ' /S' : '' let ret = system(cmd . ' "' . a:path . '"') endif if v:shell_error throw substitute(iconv(ret, 'char', &encoding), '\n', '', 'g') endif endfunction else function! s:rmdir(path, ...) throw 'vital: System.File.rmdir(): your platform is not supported' endfunction endif let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/System/Filepath.vim ================================================ " You should check the following related builtin functions. " fnamemodify() " resolve() " simplify() let s:save_cpo = &cpo set cpo&vim let s:path_sep_pattern = (exists('+shellslash') ? '[\\/]' : '/') . '\+' let s:is_windows = has('win16') || has('win32') || has('win64') || has('win95') let s:is_cygwin = has('win32unix') let s:is_mac = !s:is_windows && !s:is_cygwin \ && (has('mac') || has('macunix') || has('gui_macvim') || \ (!isdirectory('/proc') && executable('sw_vers'))) " Get the directory separator. function! s:separator() return fnamemodify('.', ':p')[-1 :] endfunction " Get the path separator. let s:path_separator = s:is_windows ? ';' : ':' function! s:path_separator() return s:path_separator endfunction " Get the path extensions function! s:path_extensions() if !exists('s:path_extensions') if s:is_windows if exists('$PATHEXT') let pathext = $PATHEXT else " get default PATHEXT let pathext = matchstr(system('set pathext'), '^pathext=\zs.*\ze\n', 'i') endif let s:path_extensions = map(split(pathext, s:path_separator), 'tolower(v:val)') elseif s:is_cygwin " cygwin is not use $PATHEXT let s:path_extensions = ['', '.exe'] else let s:path_extensions = [''] endif endif return s:path_extensions endfunction " Convert all directory separators to "/". function! s:unify_separator(path) return substitute(a:path, s:path_sep_pattern, '/', 'g') endfunction " Get the full path of command. function! s:which(command, ...) let pathlist = a:command =~# s:path_sep_pattern ? [''] : \ !a:0 ? split($PATH, s:path_separator) : \ type(a:1) == type([]) ? copy(a:1) : \ split(a:1, s:path_separator) let pathext = s:path_extensions() if index(pathext, '.' . tolower(fnamemodify(a:command, ':e'))) != -1 let pathext = [''] endif let dirsep = s:separator() for dir in pathlist let head = dir ==# '' ? '' : dir . dirsep for ext in pathext let full = fnamemodify(head . a:command . ext, ':p') if filereadable(full) if s:is_case_tolerant() let full = glob(substitute( \ toupper(full), '\u:\@!', '[\0\L\0]', 'g'), 1) endif if full != '' return full endif endif endfor endfor return '' endfunction " Split the path with directory separator. " Note that this includes the drive letter of MS Windows. function! s:split(path) return split(a:path, s:path_sep_pattern) endfunction " Join the paths. " join('foo', 'bar') => 'foo/bar' " join('foo/', 'bar') => 'foo/bar' " join('/foo/', ['bar', 'buz/']) => '/foo/bar/buz/' function! s:join(...) let sep = s:separator() let path = '' for part in a:000 let path .= sep . \ (type(part) is type([]) ? call('s:join', part) : \ part) unlet part endfor return substitute(path[1 :], s:path_sep_pattern, sep, 'g') endfunction " Check if the path is absolute path. if s:is_windows function! s:is_absolute(path) return a:path =~? '^[a-z]:[/\\]' endfunction else function! s:is_absolute(path) return a:path[0] ==# '/' endfunction endif function! s:is_relative(path) return !s:is_absolute(a:path) endfunction " Return the parent directory of the path. " NOTE: fnamemodify(path, ':h') does not return the parent directory " when path[-1] is the separator. function! s:dirname(path) let path = a:path let orig = a:path let path = s:remove_last_separator(path) if path == '' return orig " root directory endif let path = fnamemodify(path, ':h') return path endfunction " Return the basename of the path. " NOTE: fnamemodify(path, ':h') does not return basename " when path[-1] is the separator. function! s:basename(path) let path = a:path let orig = a:path let path = s:remove_last_separator(path) if path == '' return orig " root directory endif let path = fnamemodify(path, ':t') return path endfunction " Remove the separator at the end of a:path. function! s:remove_last_separator(path) let sep = s:separator() let pat = (sep == '\' ? '\\' : '/') . '\+$' return substitute(a:path, pat, '', '') endfunction " Return true if filesystem ignores alphabetic case of a filename. " Return false otherwise. let s:is_case_tolerant = filereadable(expand(':r') . '.VIM') function! s:is_case_tolerant() return s:is_case_tolerant endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/Text/Lexer.vim ================================================ let s:save_cpo = &cpo set cpo&vim function! s:_vital_loaded(V) let s:V = a:V let s:Prelude = s:V.import('Prelude') endfunction function! s:_list2dict(list) if s:Prelude.is_list(a:list) if len(a:list) < 2 | call s:_exception('too few arguments.') | endif if 2 < len(a:list) | call s:_exception('too many arguments.') | endif if ! s:Prelude.is_string(a:list[0]) | call s:_exception('element of list is not string.') | endif if ! s:Prelude.is_string(a:list[1]) | call s:_exception('element of list is not string.') | endif let tkn = { 'label' : a:list[0], 'regex' : a:list[1] } return tkn else call s:_exception('first argument is not list.') endif endfunction function! s:_exception(msg) throw printf('[Text.Lexer] %s', a:msg) endfunction let s:obj = { 'tokens' : [] } function! s:obj.exec(string) dict let match_tokens = [] let idx = 0 while idx < len(a:string) let best_tkn = {} for tkn in self.tokens let matched_text = matchstr(a:string[(idx):],'^' . tkn.regex) if ! empty(matched_text) let best_tkn = s:token(tkn.label,matched_text,idx) break endif endfor if best_tkn == {} call s:_exception(printf('cannot match. col:%d',idx)) else let idx += len(best_tkn.matched_text) let match_tokens += [best_tkn] endif endwhile return match_tokens endfunction function! s:lexer(patterns) let obj = deepcopy(s:obj) for e in a:patterns let obj.tokens += [(s:_list2dict(e))] endfor return obj endfunction function! s:token(label,matched_text,col) let obj = {} let obj['label'] = a:label let obj['matched_text'] = a:matched_text let obj['col'] = a:col return obj endfunction function! s:simple_parser(expr) echoerr 'Text.Lexer.simple_parser(expr) is obsolete. Use Text.Parser.parser() instead.' let obj = { 'expr' : a:expr, 'idx' : 0, 'tokens' : [] } function! obj.end() dict return len(self.expr) <= self.idx endfunction function! obj.next() dict if self.end() call s:_exception('Already end of tokens.') else return self.expr[self.idx] endif endfunction function! obj.next_is(label) dict return self.next().label ==# a:label endfunction function! obj.consume() dict if ! self.end() let next = self.next() let self.idx += 1 else call s:_exception('Already end of tokens.') endif return next endfunction return deepcopy(obj) endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/Text/Parser.vim ================================================ let s:save_cpo = &cpo set cpo&vim function! s:_exception(msg) throw printf('[Text.Parser] %s', a:msg) endfunction let s:obj = { '_idx' : 0, '_tokens' : [], '_ignore_labels' : [] } function! s:obj.config(dict) dict if has_key(a:dict,'ignore_labels') let self._ignore_labels = a:dict.ignore_labels endif return self endfunction function! s:obj.end() dict return len(self._tokens) <= self._idx endfunction function! s:obj.next() dict if self.end() call s:_exception('Already end of tokens.') else return self._tokens[self._idx] endif endfunction function! s:obj.next_is(labels) dict let labels = type([]) == type(a:labels) ? a:labels : [ a:labels ] if ! self.end() for lbl in labels if self.next().label ==# lbl return 1 endif endfor endif return 0 endfunction function! s:obj.ignore() dict while self.next_is(self._ignore_labels) call self.consume() endwhile return self endfunction function! s:obj.consume() dict if ! self.end() let next = self.next() let self._idx += 1 else call s:_exception('Already end of tokens.') endif return next endfunction function! s:obj.tostring() dict if ! self.end() return '' else return join( map(deepcopy(self._tokens[(self._idx):]),'v:val.matched_text'), '') endif endfunction function! s:parser() let o = {} function! o.exec(lexered_tokens) let obj = deepcopy(s:obj) let obj._tokens = deepcopy(a:lexered_tokens) return obj endfunction return o endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025/Web/JSON.vim ================================================ let s:save_cpo = &cpo set cpo&vim let s:V = vital#{expand(':h:h:t:r')}#new() function! s:_vital_depends() return ['Data.String'] endfunction let s:string = s:V.import('Data.String') function! s:decode(json) let json = iconv(a:json, "utf-8", &encoding) let json = substitute(json, '\n', '', 'g') let json = substitute(json, '\\u34;', '\\"', 'g') let json = substitute(json, '\\u\(\x\x\x\x\)', '\=s:string.nr2enc_char("0x".submatch(1))', 'g') let [null,true,false] = [0,1,0] sandbox let ret = eval(json) return ret endfunction function! s:encode(val) if type(a:val) == 0 return a:val elseif type(a:val) == 1 let json = '"' . escape(a:val, '"') . '"' let json = substitute(json, "\r", '\\r', 'g') let json = substitute(json, "\n", '\\n', 'g') let json = substitute(json, "\t", '\\t', 'g') return iconv(json, &encoding, "utf-8") elseif type(a:val) == 3 return '[' . join(map(copy(a:val), 's:encode(v:val)'), ',') . ']' elseif type(a:val) == 4 return '{' . join(map(keys(a:val), 's:encode(v:val).":".s:encode(a:val[v:val])'), ',') . '}' else return string(a:val) endif endfunction let &cpo = s:save_cpo unlet s:save_cpo " vim:set et ts=2 sts=2 sw=2 tw=0: ================================================ FILE: autoload/vital/_62ad025.vim ================================================ let s:self_version = expand(':t:r') " Note: The extra argument to globpath() was added in Patch 7.2.051. let s:globpath_third_arg = v:version > 702 || v:version == 702 && has('patch51') let s:loaded = {} function! s:import(name, ...) let target = {} let functions = [] for a in a:000 if type(a) == type({}) let target = a elseif type(a) == type([]) let functions = a endif unlet a endfor let module = s:_import(a:name) if empty(functions) call extend(target, module, 'keep') else for f in functions if has_key(module, f) && !has_key(target, f) let target[f] = module[f] endif endfor endif return target endfunction function! s:load(...) dict for arg in a:000 let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] let target = split(join(as, ''), '\W\+') let dict = self while 1 <= len(target) let ns = remove(target, 0) if !has_key(dict, ns) let dict[ns] = {} endif if type(dict[ns]) == type({}) let dict = dict[ns] else unlet dict break endif endwhile if exists('dict') call extend(dict, s:_import(name)) endif unlet arg endfor return self endfunction function! s:unload() let s:loaded = {} endfunction function! s:_import(name) if type(a:name) == type(0) return s:_build_module(a:name) endif let path = s:_get_module_path(a:name) if path ==# '' throw 'vital: module not found: ' . a:name endif let sid = get(s:_scripts(), path, 0) if !sid try execute 'source' fnameescape(path) catch /^Vim\%((\a\+)\)\?:E484/ throw 'vital: module not found: ' . a:name catch /^Vim\%((\a\+)\)\?:E127/ " Ignore. endtry let sid = s:_scripts()[path] endif return s:_build_module(sid) endfunction function! s:_get_module_path(name) if s:_is_absolute_path(a:name) && filereadable(a:name) return s:_unify_path(a:name) endif if a:name ==# '' let tailpath = printf('autoload/vital/%s.vim', s:self_version) elseif a:name =~# '\v^\u\w*%(\.\u\w*)*$' let target = '/' . substitute(a:name, '\W\+', '/', 'g') let tailpath = printf('autoload/vital/%s%s.vim', s:self_version, target) else throw 'vital: Invalid module name: ' . a:name endif if s:globpath_third_arg let paths = split(globpath(&runtimepath, tailpath, 1), "\n") else let paths = split(globpath(&runtimepath, tailpath), "\n") endif call filter(paths, 'filereadable(v:val)') return s:_unify_path(get(paths, 0, '')) endfunction function! s:_scripts() let scripts = {} for line in filter(split(s:_redir('scriptnames'), "\n"), \ 'stridx(v:val, s:self_version) > 0') let list = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') if !empty(list) let scripts[s:_unify_path(list[2])] = list[1] - 0 endif endfor return scripts endfunction if filereadable(expand(':r') . '.VIM') function! s:_unify_path(path) " Note: On windows, vim can't expand path names from 8.3 formats. " So if getting full path via and $HOME was set as 8.3 format, " vital load duplicated scripts. Below's :~ avoid this issue. return tolower(fnamemodify(resolve(fnamemodify( \ a:path, ':p:gs?[\\/]\+?/?')), ':~')) endfunction else function! s:_unify_path(path) return resolve(fnamemodify(a:path, ':p:gs?[\\/]\+?/?')) endfunction endif " Copy from System.Filepath if has('win16') || has('win32') || has('win64') function! s:_is_absolute_path(path) return a:path =~? '^[a-z]:[/\\]' endfunction else function! s:_is_absolute_path(path) return a:path[0] ==# '/' endfunction endif function! s:_build_module(sid) if has_key(s:loaded, a:sid) return copy(s:loaded[a:sid]) endif let functions = s:_get_functions(a:sid) let prefix = '' . a:sid . '_' let module = {} for func in functions let module[func] = function(prefix . func) endfor if has_key(module, '_vital_loaded') let V = vital#{s:self_version}#new() if has_key(module, '_vital_depends') call call(V.load, module._vital_depends(), V) endif try call module._vital_loaded(V) catch " FIXME: Show an error message for debug. endtry endif if !get(g:, 'vital_debug', 0) call filter(module, 'v:key =~# "^\\a"') endif let s:loaded[a:sid] = module return copy(module) endfunction if exists('+regexpengine') function! s:_get_functions(sid) let funcs = s:_redir(printf("function /\\%%#=2^\%d_", a:sid)) let map_pat = '' . a:sid . '_\zs\w\+' return map(split(funcs, "\n"), 'matchstr(v:val, map_pat)') endfunction else function! s:_get_functions(sid) let prefix = '' . a:sid . '_' let funcs = s:_redir('function') let filter_pat = '^\s*function ' . prefix let map_pat = prefix . '\zs\w\+' return map(filter(split(funcs, "\n"), \ 'stridx(v:val, prefix) > 0 && v:val =~# filter_pat'), \ 'matchstr(v:val, map_pat)') endfunction endif function! s:_redir(cmd) let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] set verbose=0 verbosefile= redir => res silent! execute a:cmd redir END let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] return res endfunction function! vital#{s:self_version}#new() return s:_import('').load(['Prelude', '']) endfunction ================================================ FILE: autoload/vital/phpcomplete-extended.vital ================================================ 62ad025 Data.List Data.String System.Cache System.File Web.JSON Text.Lexer Text.Parser System.Filepath ================================================ FILE: autoload/vital.vim ================================================ function! vital#of(name) let files = globpath(&runtimepath, 'autoload/vital/' . a:name . '.vital') let file = split(files, "\n") if empty(file) throw 'vital: version file not found: ' . a:name endif let ver = readfile(file[0], 'b') if empty(ver) throw 'vital: invalid version file: ' . a:name endif return vital#_{substitute(ver[0], '\W', '', 'g')}#new() endfunction ================================================ FILE: bin/CorePHPDocParser.php ================================================ * FILE: CorePHPDocParser.php * Last Modified: September 10, 2013 * License: MIT license {{{ * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * }}} *============================================================================= */ /** * generated index from offline php core documentation * It can be downloaded from http://www.php.net/download-docs.php * Download and unter the Many html files tar file * * Usage: * new CorePHPDocParser("/your/php/doc/location", "/filename/to/save/index")->parsePhpdoc(); */ class CorePhpDocParser { /** * undocumented class variable * * @var array */ private $coreIndex; /** * location of php doc folder * @var string */ private $docLocation; /** * processed function docs * * @var array */ public $functionDocs; /** * processed class docs * * @var array */ public $classDocs; /** * constants * * @var array */ public $constants; private $indexFileName; private $xmlParser; public function __construct($docLocation, $indexFileName) { $this->coreIndex = array(); $this->docLocation = $docLocation; $this->indexFileName = $indexFileName; $this->functionDocs = array(); $this->classDocs = array(); $this->constants = array(); $this->xmlParser = new XMLParser(); } /** * @return string */ public function setDocLocation($docLocation) { $this->docLocation = $docLocation; } /** * returns book links * * @return returns array of book links */ public function getBookLinks() { $books = array(); $dom = DomDocument::loadHTMLFile($this->docLocation . "/extensions.membership.html"); $query = '//*[@id="extensions.membership"]//a[@class="xref"]'; $xpath = new DOMXPath($dom); $entries = $xpath->query($query); foreach ($entries as $entry) { $books[] = $entry->attributes->getNamedItem('href')->nodeValue; } return $books; } public function parsePhpDoc() { $bookLinks = $this->getBookLinks(); foreach($bookLinks as $bookLink) { $this->parseSectionDocFile($bookLink); } //$extraPredefinedConstants = array('reserved.constants.php'); //foreach ($extraPredefinedConstants as $ep) { //$constants = $this->parsePredefinedConstants($ep); //$this->constants = array_merge($this->constants, $constants); //} $this->parsePredefinedClasses ("reserved.interfaces.html"); $this->parsePredefinedClasses ("reserved.exceptions.html"); $funcList = array_keys($this->functionDocs); sort($funcList); $classList = array_keys($this->classDocs); sort($classList); //ldd($indexedFuncList); sort($this->constants); $outData = array( 'functions' => $this->functionDocs, 'classes' => $this->classDocs, 'function_list' => $funcList, 'class_list' => $classList, 'predefined_constants' => $this->constants ); file_put_contents($this->indexFileName, json_encode($outData)); } public function getBookProperties($bookFile) { $excludedTypes = array( 'Requirements', 'Installation', 'Runtime Configuration', 'Resource Types', 'Introduction', 'Installing/Configuring', ); $ss = explode(".", $bookFile); $section = $ss[1]; $propertyLinks = array(); $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $bookFile); $xpath = new DOMXPath($dom); $query = '//*[@class="chunklist chunklist_book"]/li/a'; $entries = $xpath->query($query); $subSectionQuery = '//*[@class="chunklist chunklist_book chunklist_children"]/li/a'; $subSectionEntries = $xpath->query($subSectionQuery); $validLocations = array('class', 'ref', 'constants'); foreach ($entries as $entry) { $location = $entry->attributes->getNamedItem('href')->nodeValue; if(in_array(substr($location, 0, strpos($location, ".")), $validLocations) || strpos($location, 'constants') !== false) { $propertyValue = $entry->nodeValue; $propertyLinks[$propertyValue] = $location; } } foreach ($subSectionEntries as $subSectionEntry) { $location = $subSectionEntry->attributes->getNamedItem('href')->nodeValue; if(!in_array(substr($location, 0, strpos($location, ".")), $validLocations)) { continue; } $propertyValue = (string) $subSectionEntry->nodeValue; $propertyLinks[$propertyValue] = $location; } return $propertyLinks; } public function parseMethodDocFile($methodFile) { $methodInfo = array( 'params' => array(), 'docComment' => "", 'signature' => "", 'inheritdoc' => 0, 'modifier' => array(), 'return'=> "" ); $excludedFiles = array("swfbutton.setdown.html", "function.mysqli-report.html", "swfbutton.sethit.html", "swfbutton.setover.html", "swfbutton.setup.html", "function.xdiff-file-diff-binary.html", "function.xdiff-file-patch-binary.html", "function.xdiff-string-patch-binary.html", "function.xdiff-string-diff-binary.html", ); if(in_array($methodFile, $excludedFiles)) { return $methodInfo; } $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $methodFile); $descQuery = '//*[@class="refpurpose"]/span[@class="dc-title"]'; $fullDoc = utf8_encode(trim($this->parseFullDoc($methodFile))); $methodInfo['docComment'] = $fullDoc; $xpath = new DOMXPath($dom); $descEntries = $xpath->query($descQuery); foreach ($descEntries as $descEntry) { $desc = simplexml_import_dom($descEntry); if(strpos((string)$desc, "Alias") !== false && strpos((string)$desc, "Alias") == 0) { //ld($methodFile); $aliasFilaName = (string)$desc->span->a['href']; return $this->parseMethodDocFile($aliasFilaName); } } $signatureQuery = '//*[@class="refsect1 description"]/div[@class="methodsynopsis dc-description"]'; $signatureEntries = $xpath->query($signatureQuery); foreach ($signatureEntries as $signatureEntry) { $sxml = simplexml_import_dom($signatureEntry); $paramStrings = array(); //ld($sxml->asXML()); $modifier = ""; foreach ($sxml->children() as $children) { switch ($children['class']) { case 'type': $returns = isset($children->a)? (string) $children->a : (string) $children; $methodInfo['return'] = $this->isScalar($returns)? "" : $returns; break; case 'modifier': $modifier = (string) $children; $methodInfo['modifier'][] = $modifier; break; case 'methodname': $methodName = (string) $children->strong; break; case 'methodparam': if((string) $children == "void") { continue; } $paramType = isset($children->span->a)? (string) $children->span->a: (string) $children->span; $paramVar = (string) $children->code; $paramString = $paramType . " " . $paramVar; if(isset($children->span[1])) { $paramString = "[" . $paramString . "]"; } $paramStrings[] = $paramString; $methodInfo['params'][$paramVar] = $this->isScalar($paramType)? "" : $paramType; default: break; } } $methodInfo['signature'] = "(" . join(" ,", $paramStrings) . ") : ". (string)$returns; } return $methodInfo; } public function parseFullDoc($file) { $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $file); $descQuery = '//*[@class="refentry"]'; $descQuery2 = '//*[@class="reference"]'; //don't know the syntax of OR property text $xpath = new DOMXPath($dom); $descEntries = $xpath->query($descQuery); if($descEntries->length == 0) { $xpath = new DOMXPath($dom); $descEntries = $xpath->query($descQuery2); } foreach ($descEntries as $descEntry) { $simpleXML = simplexml_import_dom($descEntry); $xml = $simpleXML->asXML(); $this->xmlParser->reset(); $xml_parser = xml_parser_create(); xml_set_element_handler($xml_parser, array($this->xmlParser, 'startElementHandler'), array($this->xmlParser, 'endElementHandler')); xml_set_character_data_handler($xml_parser, array($this->xmlParser, 'dataHandler')); if (!xml_parse($xml_parser, $xml, true)) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } $content = (string)$this->xmlParser->getContent(); return $content; } public function parseRefDocoFile($refFile) { //ld($refFile); $functionDocs = array(); $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $refFile); $query = '//*[@class="chunklist chunklist_reference"]//a'; $xpath = new DOMXPath($dom); $entries = $xpath->query($query); foreach($entries as $methodEntry) { $methodLocation = $methodEntry->attributes->getNamedItem('href')->nodeValue; $methodName = $methodEntry->nodeValue; $functionDocs[$methodName] = $this->parseMethodDocFile($methodLocation); } return $functionDocs; } public function parsePredefinedConstants($constFile) { //ld($constFile); $constants = array(); $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $constFile); $dtQuery = '//*[@class="appendix"]//span[@class="term"]/strong/code'; $trQuery = '//*[@class="chapter"]//td/strong/code'; $xpath = new DOMXPath($dom); $entries = $xpath->query($dtQuery); if($entries->length == 0) { $entries = $xpath->query($trQuery); } foreach($entries as $constantEntry) { $constants[] = $constantEntry->nodeValue; } return $constants; } public function parseClassDocFile($classFile) { //ld($classFile); $className= ""; $dom = @DomDocument::loadHTMLFile($this->docLocation . "/" . $classFile); $propertyQuery = '//*[@class="fieldsynopsis"]'; $methodQuery = '//*[@class="methodsynopsis dc-description"]//a[@class="methodname"]'; $classNameQuery = '//*[@class="classsynopsisinfo"]//strong[@class="classname"]'; $xpath = new DOMXPath($dom); $propertyEntries = $xpath->query($propertyQuery); $methodEntries = $xpath->query($methodQuery); $classNameEntries = $xpath->query($classNameQuery); foreach ($classNameEntries as $cq) { $className = $cq->nodeValue; } $classData = $this->getEmptyClassData($className); $fullDoc = utf8_encode(trim($this->parseFullDoc($classFile))); $classData['docComment'] = $fullDoc; foreach ($propertyEntries as $propertyEntry) { $propertyData = array( 'type' => "", 'Inheritdoc' => 0, 'docComment' => $fullDoc, ); $propertyModifiers = array(); $propertyName = ""; $propertyType = ""; $propertyEntryXML = simplexml_import_dom($propertyEntry); //ld($propertyEntryXML->asXML()); foreach ($propertyEntryXML->children() as $propertyChild) { switch ($propertyChild['class']) { case 'modifier': if((string)$propertyChild == "readonly") { continue; } if((string) $propertyChild == "") { continue; } $propertyModifiers[] = (string) $propertyChild; break; case 'varname': case 'fieldsynopsis_varname': //ld($propertyEntryXML->asXML()); $propertyName = isset($propertyChild->a->var)?(string) $propertyChild->a->var : (string)$propertyChild->var; break; case 'type': $propertyType = isset($propertyChild->a)? (string) $propertyChild->a : (string) $propertyChild; default: break; } } $propertyData['type'] = $this->isScalar($propertyType)? "" : $propertyType; $propertyData['modifier'] = $propertyModifiers; foreach ($propertyModifiers as $propertyModifier) { $classData['properties']['modifier'][$propertyModifier][] = $propertyName; } if($propertyName != "") { $classData['properties']['all'][$propertyName] = $propertyData; } } foreach ($methodEntries as $methodEntry) { $methodData = array( 'params' => array(), 'docComment' => "", 'signature' => "", 'Inheritdoc' => 0 ); $methodXml = simplexml_import_dom($methodEntry); $methodDocFile = (string) $methodXml['href']; $methodName = (string) $methodXml; if(strpos($methodName, "::")) { $methodName = substr($methodName, strpos($methodName, "::")+2); } $methodData = $this->parseMethodDocFile($methodDocFile); $methodModifiers = $methodData['modifier']; if(empty($methodModifiers)) { $classData['methods']['modifier']['public'][] = $methodName; } foreach ($methodModifiers as $methodModifier) { $classData['methods']['modifier'][$methodModifier][] = $methodName; } $classData['methods']['all'][$methodName] = $methodData; } return $classData; } public function parsePredefinedClasses($fileLocation) { $dom = DomDocument::loadHTMLFile($this->docLocation . "/" . $fileLocation); $query = '//*[@class="chunklist chunklist_part"]/li/a'; $xpath = new DOMXPath($dom); $entries = $xpath->query($query); foreach ($entries as $entry) { $sxml = simplexml_import_dom($entry); $className = (string) $sxml; $classLocation = (string) $sxml['href']; $this->classDocs[$className] = $this->parseClassDocFile($classLocation); } } public function parseSectionDocFile($sectionFile) { $classes = array(); $functionDocs = array(); $bookPropertyLinks = $this->getBookProperties($sectionFile); foreach ($bookPropertyLinks as $key => $location) { $locationType = substr($location, 0, strpos($location, ".")); if($locationType == "class") { $this->classDocs[$key] = $this->parseClassDocFile($location); } else if($locationType == "ref") { $this->functionDocs = array_merge($this->functionDocs, $this->parseRefDocoFile($location)); } elseif(strpos($location, 'constants') !== false) { $this->constants = array_merge($this->constants, $this->parsePredefinedConstants($location)); } } } private function isScalar($type) { $scalarsTypes = array('boolean', 'integer','float', 'string', 'array', 'object', 'resource', 'mixed', 'number', 'callback', 'null', 'void', 'bool', 'self', 'int', 'callable'); return in_array(strtolower($type), $scalarsTypes); } public function createDictFile() { $signatures = array(); foreach ($this->functionDocs as $function) { $signatures[] = $function['signature']; } file_put_contents('php.dict', join("\n", $signatures)); } private function getEmptyClassData($className) { $classData = array(); $classData['classname'] = $className; $classData['docComment'] = ""; $classData['methods'] = array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn") ); $classData['properties'] = array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn") ); $classData['parentclass'] = ""; $classData['parentclasses'] = array(); $classData['interfaces'] = array(); $classData['file'] = ""; $classData['namespaces'] = array( 'uses' => array() ); $classData['constants'] = array(); return $classData; } } ================================================ FILE: bin/IndexGenerator.php ================================================ * FILE: IndexGenerator.php * Last Modified: October 04, 2013 * License: MIT license {{{ * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * }}} *============================================================================= */ set_time_limit(0); ini_set('memory_limit','1000M'); ini_set('display_errors', 'stderr'); if(php_sapi_name() == 'cli') { if(count($argv) < 2) { echo "error: not enough arguments"; } elseif ($argv[1] == 'generate') { array_shift($argv); array_shift($argv); $verbose = false; if(count($argv) > 0 && $argv[0] == '-verbose') { array_shift($argv); $verbose = true; } //$time = microtime(true); $phpCompletePsr = new IndexGenerator($verbose); $plugins = explode("-u", implode("", $argv)); foreach ($plugins as $pluginFile) { if(empty($pluginFile)) { continue; } $phpCompletePsr->addPlugin(trim($pluginFile)); } $index = $phpCompletePsr->generateIndex(); $jsonIndex = json_encode($index); $lastJsonError = json_last_error(); if($lastJsonError != JSON_ERROR_NONE) { printJsonError($lastJsonError); exit; } $phpCompletePsr->writeToFile($phpCompletePsr->getIndexFileName(), $jsonIndex); $phpCompletePsr->writeToFile($phpCompletePsr->getReportFileName(), implode("\n", $phpCompletePsr->getInvalidClasses())); } else if($argv[1] == 'update') { array_shift($argv); array_shift($argv); $file = array_shift($argv); $cacheFileName = array_shift($argv); $verbose = false; $p = new IndexGenerator($verbose); $plugins = explode("-u", implode("", $argv)); foreach ($plugins as $pluginFile) { if(empty($pluginFile)) { continue; } $p->addPlugin($pluginFile); } $p->writeUpdatedClassInfo($file, $cacheFileName); //echo "Time Elapsed: ".(microtime(true) - $time)."s\n"; //echo "highest memory ". memory_get_peak_usage(); } else { echo "not a valid argument"; exit; } } else { exit; } function printJsonError($errorCode) { switch (json_last_error()) { case JSON_ERROR_NONE: echo ' - No errors'; break; case JSON_ERROR_DEPTH: echo ' - Maximum stack depth exceeded'; break; case JSON_ERROR_STATE_MISMATCH: echo ' - Underflow or the modes mismatch'; break; case JSON_ERROR_CTRL_CHAR: echo ' - Unexpected control character found'; break; case JSON_ERROR_SYNTAX: echo ' - Syntax error, malformed JSON'; break; case JSON_ERROR_UTF8: echo ' - Malformed UTF-8 characters, possibly incorrectly encoded'; break; default: echo ' - Unknown error'; break; } echo "\n"; } class IndexGenerator { /** * * @var array */ private $file_fqcn; /** * * * @var array */ private $fqcn_file; /** * @var array */ private $classes; /** * @var array */ private $class_fqcn; /** * List of valid files * @var array */ private $validFiles; /** * * * @var array */ private $invalidClasses; /** * Array of processed classes * * @var string */ private $processedClasses; /** * index file name * * @var string */ private $indexFileName; /** * report filename * * @var string */ private $reportFileName; /** * list of parsed classes * * @var array */ private $parsedClasses; /** * php core doc index * @var array */ private $coreIndex; /** * php core doc index file * @var string */ private $coreIndexFile; /** * * * @var string */ private $pluginIndexFile; private $loader; /** * Array of plugin classes * @var array */ public $plugins; /** * Verbosity * * @var bool */ private $verbose; public function __construct($verbose) { $this->file_fqcn = array(); $this->fqcn_file = array(); $this->class_fqcn = array(); $this->classes = array(); $this->validFiles = array(); $this->invalidClasses = array(); $this->processedClasses = array(); $this->indexFileName = './.phpcomplete_extended/phpcomplete_index'; $this->reportFileName = './.phpcomplete_extended/report.txt'; $this->coreIndexFile = './.phpcomplete_extended/core_index'; $this->pluginIndexFile = './.phpcomplete_extended/plugin_index'; $this->parsedClasses = array(); $this->plugins = array(); $this->loader = require 'vendor/autoload.php'; $this->verbose = $verbose; } public function addPlugin($pluginFile) { if(!is_file($pluginFile)) { echo "not a valid file \n"; return; } include $pluginFile; $className = basename($pluginFile, ".php"); $plugin = new $className; if($plugin->isValidForProject()){ $this->plugins[] = $plugin; } } /** * calls plugin hooks * @param string $hookName name of the plugin hook * @param bool $return expect return date */ public function execHook($hookName, $return) { $args = func_get_args(); $out = array(); array_shift($args); $return = array_shift($args); $extraArgs = $args; foreach ($this->plugins as $plugin) { if(method_exists($plugin, $hookName)) { $pluginArgs = $extraArgs; if($hookName == "preUpdateIndex") { $pluginIndex = $extraArgs[0]; $indexForPlugin = $pluginIndex[strtolower(get_class($plugin))]; $pluginArgs = array($indexForPlugin); } $ret = call_user_func_array(array($plugin, $hookName), $pluginArgs); if($return) { $out[strtolower(get_class($plugin))] = $ret; } } } if($return) { return $out; } } public function writeUpdatedClassInfo($fileName, $cacheFileName) { $time = microtime(true); $this->processCoreIndexFile(); $fileName = $this->normalizePath($fileName); $classCache = json_decode(file_get_contents($this->indexFileName), true); $extends = $classCache['extends']; $implements = $classCache['implements']; $this->fqcn_file = $classCache['fqcn_file']; $this->file_fqcn = $classCache['file_fqcn']; $this->class_fqcn = $classCache['class_fqcn']; $fileData = array(); if(!is_file($this->pluginIndexFile)) { $pluginIndex = array(); } else{ $pluginIndex = json_decode(file_get_contents($this->pluginIndexFile), true); } $this->execHook("init", false, $this->loader); $this->execHook("preUpdateIndex", false, $pluginIndex); $fqcn = $this->validateClass($fileName); if(empty($fqcn)) { return; } if(array_key_exists($fileName, $classCache['file_fqcn'])) { $prevData = $classCache['classes'][$fqcn]; } else { $prevData = array( 'parentclasses' => array(), 'interfaces' => array() ); } $classData = $this->processClass($fqcn); $classCache['classes'][$fqcn] = $classData; $classCache['class_fqcn'] = $this->class_fqcn; $classCache['class_func_menu_entries'] = $this->createMenuEntries($this->class_fqcn, $this->coreIndex['function_list']); $fileData['classdata']['file'] = $fileName; $fileData['classdata']['fqcn'] = $fqcn; $fileData['classdata']['data'] = $classData; $fileData['extends'] = $this->getUpdatedExtraData($fqcn, $prevData, $classData, $classCache, $extends, 'parentclasses', 'extends'); $fileData['interfaces'] = $this->getUpdatedExtraData($fqcn, $prevData, $classData, $classCache, $implements, 'interfaces', 'implements'); $classCache['file_fqcn'][$fileName] = $fqcn; $classCache['fqcn_file'][$fqcn] = $fileName; file_put_contents('.phpcomplete_extended/'. $cacheFileName, json_encode($fileData)); file_put_contents('.phpcomplete_extended/phpcomplete_index', json_encode($classCache)); $this->execHook("postUpdateIndex", false, $classData, $classCache, $this); $this->writePluginIndexes(); return array($classCache, $fileData); } private function getUpdatedExtraData($fqcn, &$prevData, &$classData, &$classCache, $extraDataList, $extraClassDataKey, $extraClassCacheKey) { $extraDataDiff = array( 'added' => array(), 'removed' => array() ); $parentCountValues = array_count_values( array_merge($prevData[$extraClassDataKey], $classData[$extraClassDataKey]) ); foreach($parentCountValues as $value => $count) { if($count == 1) { if(in_array($value, $prevData[$extraClassDataKey])) { //removed $extraDataDiff['removed'][] = $value; } else { $extraDataDiff['added'][] = $value; } } } foreach($extraDataDiff['removed'] as $removed) { $removedExtendData = $extraDataList[$removed]; array_splice($removedExtendData, array_search($fqcn, $removedExtendData), 1); $classCache[$extraClassCacheKey][$removed] = $removedExtendData; } foreach($extraDataDiff['added'] as $added) { $classCache[$extraClassCacheKey][$added][] = $fqcn; } return $extraDataDiff; } private function listVendorLibraries() { $vendorLibs = array(); $autoloadNamespaces = require 'vendor/composer/autoload_namespaces.php'; foreach ($autoloadNamespaces as $namespace => $directory) { if($namespace == "") { continue; } $vendorLibs[$namespace] = $this->normalizePath($directory[0]); } return $vendorLibs; } private function normalizePath($path) { if($path == "") { return ""; } $cwd = str_replace('\\','/',getcwd())."/"; if(strpos($cwd, ':') == 1) { $drive = strtolower(substr($cwd, 0, 2)); $cwd = substr_replace($cwd, $drive, 0, 2); } $path = str_replace("\\", '/', $path); if(strpos($path, ':') == 1) { $drive = strtolower(substr($path, 0, 2)); $path = substr_replace($path, $drive, 0, 2); } $path = str_replace($cwd, '', $path); return $path; } public function generateIndex() { $this->processCoreIndexFile(); $time = microtime(true); // Gets microseconds //TODO: pasre constructor for doctype $classMap = require 'vendor/composer/autoload_classmap.php'; $cwd = str_replace('\\','/',getcwd())."/"; if(strpos($cwd, ':') == 1) { $drive = strtolower(substr($cwd, 0, 2)); $cwd = substr_replace($cwd, $drive, 0, 2); } array_walk($classMap, function(&$item, $key) use ($cwd){ $item = str_replace("\\", '/', $item); if(strpos($item, ':') == 1) { $drive = strtolower(substr($item, 0, 2)); $item = substr_replace($item, $drive, 0, 2); } $item = str_replace($cwd, '', $item); }); $out = array(); $out['namespaces'] = array(); $out['interface'] = array(); $out['fqcn_file'] = $classMap; $out['file_fqcn'] = array_flip($classMap); $out['extends'] = array(); $out['implements'] = array(); $out['vendor_libs'] = $this->listVendorLibraries(); //$this->file_fqcn = $classMap; $this->file_fqcn = $out['file_fqcn']; $this->fqcn_file = $out['fqcn_file']; $this->execHook("init", false, $this->loader); $regex = '/(.*)(?=\\\\(\w+)$)|(.*)/'; $count = 0; foreach($classMap as $fqcn => $file) { if( preg_match('/DateSelect/', $file) //zend //|| preg_match('/DateTime/', $file) //zend || preg_match('/DateTimeSelect/', $file) //zend || preg_match('/MonthSelect/', $file) //zend //|| preg_match('/PropelDataCollector/', $file) //zend ){ continue; } if( !array_key_exists('PHPUnit_Framework_TestCase', $this->fqcn_file) && (preg_match('/Test/',$file) || preg_match('/TestCase/' , $file) || preg_match('/0/' , $fqcn) || preg_match('/Fixtures/' , $file) || preg_match('/Test/' , $file) //|| preg_match('/Command/' , $file) || preg_match('/DataFixtures/' , $file) ) ){ continue; } if($this->verbose) { echo "processing $file\n"; } if(!$this->validateClass($file)) { $this->invalidClasses[] = $file; continue; } $classData = array(); $ret = preg_match($regex, $fqcn, $matches); if(!$ret) { continue; } $className = count($matches) == 3? $matches[2] :$matches[3]; $namespace = count($matches) == 3? $matches[1] : ""; if(!empty($namespace)) { $out['namespaces'][] = $namespace; } $classData = $this->processClass($fqcn); $out['classes'][$fqcn] = $classData; if(!empty($classData['parentclasses'])) { $parentClasses = $classData['parentclasses']; foreach ($parentClasses as $parentClass) { if(empty($parentClass)) { continue; } $out['extends'][$parentClass][] = $fqcn; } } if(!empty($classData['interfaces'])) { $interfaces = $classData['interfaces']; foreach ($interfaces as $interface) { if(empty($interface)) { continue; } $out['implements'][$interface][] = $fqcn; } } $this->execHook("postProcess", false, $fqcn, $file, $classData); $count++; } foreach ($this->coreIndex['class_list'] as $coreClass) { $this->classes[] = $coreClass; $this->class_fqcn[$coreClass] = $coreClass; } $classFuncConstList = array_merge($this->classes, $this->coreIndex['function_list']); sort($classFuncConstList); sort($this->classes); ksort($this->class_fqcn); $out['class_list'] = $this->classes; $out['class_fqcn'] = $this->class_fqcn; $out['class_func_const_list'] = $classFuncConstList; $fqcns = array_merge($this->classes, array_keys($this->fqcn_file)); sort($fqcns); $classFuncMenuEntries = $this->createMenuEntries($this->class_fqcn, $this->coreIndex['function_list']); $out['class_func_menu_entries'] = $classFuncMenuEntries; $this->execHook("postCreateIndex", false, $out, $this); $this->writePluginIndexes(); return $out; } private function createMenuEntries($class_fqcn, $functions) { $dict = array(); asort($functions); foreach ($functions as $func) { if($func == "Constants for PDO_4D" || $func == "Examples with PDO_4D") { continue; } $signature = $this->coreIndex['functions'][$func]['signature']; $dict[] = array( 'word' => $func, 'kind' => 'f', 'menu' => $signature, 'info' => $signature, ); } foreach ($class_fqcn as $keyword => $fqcns) { if(is_array($fqcns)) { $i = 0; foreach ($fqcns as $fqcn) { $dict[] = array( 'word' => $keyword . "-" . ($i + 1), 'kind' => 'c', 'menu' => $fqcn, 'info' => $fqcn, ); $i++; } } elseif(is_string($fqcns)) { $dict[] = array( 'word' => $keyword, 'kind' => 'c', 'menu' => $fqcns, 'info' => $fqcns, ); } } return $dict; } public function writePluginIndexes() { $indexes = $this->execHook('getIndex', true); if(empty($indexes)) { return; } $this->writeToFile($this->pluginIndexFile, json_encode($indexes)); } public function processClass($fqcn) { if(array_key_exists($fqcn, $this->processedClasses)) { return $this->processedClasses[$fqcn]; } $out = array(); $classData = $this->getClassInfo($fqcn); $baseProperties = $classData; $parentClass = $classData['parentclass']; $parentProperties = array(); if(!empty($parentClass)) { $parentProperties = $this->processClass($parentClass); } foreach($classData['interfaces'] as $interface) { $interfaceProperties = $this->processClass($interface); $baseProperties = $this->mergeClassProperties($baseProperties, $interfaceProperties); } $merge = $this->mergeClassProperties($baseProperties, $parentProperties); $this->processedClasses[$fqcn] = $merge; return $merge; } private function mergeClassProperties($baseProperties, $parentProperties) { if(empty($parentProperties)) { return $baseProperties; } if(empty($baseProperties)) { return $parentProperties; } $mergedProperties = $this->getEmptyMergeProperty(); foreach($baseProperties['methods']['modifier'] as $modifier => $methods) { $mergedProperties['methods']['modifier'][$modifier] = array_unique(array_merge($baseProperties['methods']['modifier'][$modifier], $parentProperties['methods']['modifier'][$modifier])); } $basePropertiesMethods = array_keys($baseProperties['methods']['all']); $parentPropertiesMethods = array_keys($parentProperties['methods']['all']); $combinedMethods = array_merge($basePropertiesMethods, $parentPropertiesMethods); $methodSummary = array_count_values($combinedMethods); foreach($methodSummary as $method => $count) { if($count == 1) { if(in_array($method, $basePropertiesMethods)) { $mergedProperties['methods']['all'][$method] = $baseProperties['methods']['all'][$method]; } if(in_array($method, $parentPropertiesMethods)) { $mergedProperties['methods']['all'][$method] = $parentProperties['methods']['all'][$method]; } } else if($count == 2) { $basePropertiesMethod = $baseProperties['methods']['all'][$method]; $parentPropertiesMethod = $parentProperties['methods']['all'][$method]; $mergedProperties['methods']['all'][$method] = $parentPropertiesMethod; if(is_array($parentPropertiesMethod) && !$parentPropertiesMethod['inheritdoc']) { $mergedProperties['methods']['all'][$method] = $parentPropertiesMethod; } if(is_array($parentPropertiesMethod) && !$basePropertiesMethod['inheritdoc']) { $mergedProperties['methods']['all'][$method] = $basePropertiesMethod; } } } if(!array_key_exists('alias', $parentProperties['namespaces'])) { $parentProperties['namespaces']['alias'] = array(); } if(!array_key_exists('alias', $baseProperties['namespaces'])) { $parentProperties['namespaces']['alias'] = array(); } $mergedProperties['namespaces']['uses'] = array_merge( $parentProperties['namespaces']['uses'] , $baseProperties['namespaces']['uses']); $mergedProperties['namespaces']['alias'] = array_merge( $parentProperties['namespaces']['alias'] , $baseProperties['namespaces']['alias']); if(array_key_exists('file', $baseProperties['namespaces'])) { $mergedProperties['namespaces']['file'] = $baseProperties['namespaces']['file']; } foreach($baseProperties['properties']['modifier'] as $modifier => $properties) { $mergedProperties['properties']['modifier'][$modifier] = array_unique(array_merge($baseProperties['properties']['modifier'][$modifier], $parentProperties['properties']['modifier'][$modifier])); } $mergedProperties['properties']['all'] = array_merge($baseProperties['properties']['all'], $parentProperties['properties']['all']); $mergedProperties['parentclass'] = isset($baseProperties['parentclass'])? $baseProperties['parentclass']: ""; $mergedProperties['parentclasses'] = array(); array_push($mergedProperties['parentclasses'], $baseProperties['parentclass'], $parentProperties['parentclass']); $mergedProperties['parentclasses'] = array_unique($mergedProperties['parentclasses']); $mergedProperties['interfaces'] = array_unique(array_merge($baseProperties['interfaces'], $parentProperties['interfaces'])); $mergedProperties['parentclasses'] = array_filter($mergedProperties['parentclasses'], function($var){ return !empty($var); }); $mergedProperties['interfaces'] = array_filter($mergedProperties['interfaces'], function($var){ return !empty($var); }); $mergedProperties['constants'] = array_unique(array_merge($baseProperties['constants'], $parentProperties['constants'])); $mergedProperties['file'] = $baseProperties['file']; $mergedProperties['startLine'] = $baseProperties['startLine']; $mergedProperties['docComment'] = $baseProperties['docComment']; $mergedProperties['classname'] = $baseProperties['classname']; return $mergedProperties; } public function validateClass($fileName) { if(!is_file($fileName)) { return false; } if(array_key_exists($fileName, $this->validFiles)) { return $this->validFiles[$fileName]; } $parsedClassData = $this->parseClass($fileName); $namespaces = $parsedClassData['namespaces']; $classLineData = $parsedClassData['class_line_data']; $classTokens = array(); $classTokens[] = $classLineData['extends']; $classTokens = array_merge($classTokens, $classLineData['implements']); $className = $classLineData['classname']; $tokens = array(); //print_r($namespaces);exit; foreach($classTokens as $classToken) { if(empty($classToken)) { continue; } $fqcn = $this->guessClass($classToken, $namespaces); if(empty($fqcn)) { return false; } if(!array_key_exists($fqcn, $this->fqcn_file)) { //it may be an internal interface try { $reflectionClass = new \ReflectionClass($fqcn); continue; } catch (\Exception $e) { return false; } } $isValidFqcnProperties = $this->validateClass($this->fqcn_file[$fqcn]); if(!$isValidFqcnProperties) { return false; } } foreach($parsedClassData['constructor_arguments'] as $classToken) { if(empty($classToken)) { continue; } if($classToken == $className) { continue; } $fqcn = $this->guessClass($classToken, $namespaces); if(empty($fqcn)) { return false; } if(!array_key_exists($fqcn, $this->fqcn_file)) { //it may be an internal interface try { $reflectionClass = new \ReflectionClass($fqcn); continue; } catch (\Exception $e) { return false; } } $isValidFqcnProperties = $this->validateClass($this->fqcn_file[$fqcn]); if(!$isValidFqcnProperties) { return false; } } if(array_key_exists($className, $this->fqcn_file) && $this->fqcn_file[$className] == $fileName) { $classFqcn = $className; } else{ $classFqcn = $this->guessClass($className, $namespaces); } if(empty($classFqcn)) { return false; } $this->validFiles[$fileName] = $classFqcn; return $classFqcn; } private function getClassInfo($fqcn) { $classData = $this->getEmptyClassData($fqcn); if(empty($fqcn)) { return $classData; } try { $reflectionClass = new \ReflectionClass($fqcn); } catch (\Exception $e) { return $classData; } if($reflectionClass->isInternal()) { $fqcn = $reflectionClass->getName(); if(array_key_exists($fqcn, $this->coreIndex['classes'])) { return $this->coreIndex['classes'][$reflectionClass->getName()]; } else { return $classData; } } $classContent = array(); $classData['file'] = $reflectionClass->getFileName(); $classData['startLine'] = $reflectionClass->getStartLine(); $docComment = $reflectionClass->getDocComment(); if(!$docComment) { $docComment = ""; } preg_match("/(\\\\)?(\w+)$/", $reflectionClass->name, $classNameMatches); $classData['docComment'] = $this->trimDocComment($docComment); $classContent = $this->getClassContent($reflectionClass->getFileName(), $fqcn); $classData['namespaces']['file'] = $reflectionClass->getNamespaceName(); $parsedClassData = $this->parseClass($classData['file']); $className = $classNameMatches[2]; $classData['namespaces'] = $parsedClassData['namespaces']; $classData['className'] = $className; $this->classes[] = $className; if(array_key_exists($className, $this->class_fqcn)) { if(is_array($this->class_fqcn[$className]) && !in_array($fqcn, $this->class_fqcn[$className]) ) { $this->class_fqcn[$className][] = $fqcn; } elseif(is_string($this->class_fqcn[$className]) && $this->class_fqcn[$className] != $fqcn ) { $fqcns = array(); $fqcns[] = $this->class_fqcn[$className]; $fqcns[] = $fqcn; $this->class_fqcn[$className] = $fqcns; } } else { $this->class_fqcn[$className] = $fqcn; } $this->getConstantData($reflectionClass, $classData); $classMethods = $reflectionClass->getMethods(); foreach($classMethods as $reflectionMethod) { if($reflectionMethod->class === $fqcn) { $this->getMethodData($reflectionMethod, $classContent, $classData); } } $classProperties = $reflectionClass->getProperties(); foreach($classProperties as $classProperty) { if($classProperty->class == $fqcn){ $this->getPropertyData($classProperty, $classData); } } $classData['parentclass'] = ""; $parentClass = $reflectionClass->getParentClass(); //if($parentClass && !$parentClass->isInternal() ){ if($parentClass){ $classData['parentclass'] = $parentClass->getName(); } $classData['interfaces'] = $reflectionClass->getInterfaceNames(); $classData['classname'] = $className; return $classData; } private function getMethodData(ReflectionMethod $reflectionMethod, $classContent, &$classData) { //TODO: parse local variable $modifiers = Reflection::getModifierNames($reflectionMethod->getModifiers()); $startLine = $reflectionMethod->getStartLine(); $endLine = $reflectionMethod->getEndline(); $parameters = $reflectionMethod->getParameters(); $docComment = $reflectionMethod->getDocComment(); if(!$docComment) { $docComment = ""; } $parsedComment = $this->parseDocComment($docComment, 'method'); $out = array(); $out['params'] = array(); $out['docComment'] = $this->trimDocComment($docComment); $out['inheritdoc'] = $parsedComment['inheritdoc']; $out['startLine'] = $startLine; $out['endLine'] = $endLine; $origin = $reflectionMethod->getDeclaringClass()->getFileName() == false? "": $reflectionMethod->getDeclaringClass()->getFileName(); $origin = $this->normalizePath($origin); $out['origin'] = $origin; $params = array(); foreach ($parameters as $parameter) { $parameterName = '$'.$parameter->getName(); try { $parameterClass = $parameter->getClass(); } catch (\Exception $e) { $parameterClass = ""; } $parameterType = ""; //not defined try to find in the doccomment if(empty($parameterClass)) { if(array_key_exists($parameterName, $parsedComment['params'])) { $parameterType = $parsedComment['params'][$parameterName]; } } else { $parameterType = $parameter->getClass()->getName(); } $params[] = $parameterType. ' '. $parameterName; $out['params'][$parameterName] = $parameterType; } if(array_key_exists('return', $parsedComment) && $parsedComment['return'] != "") { $returnType = ""; $arrayReturn = 0; $returnTypes = explode('|', trim($parsedComment['return'])); foreach ($returnTypes as $rType) { if(preg_match('/\[\]$/', $rType)) { $arrayReturn = 1; $rType = trim($rType, "[]"); } if(!$this->isScalar($rType)) { $returnType = $rType; if($returnType[0] == '\\') { $returnType = substr($returnType, 1); } break; } } if(empty($returnType)) { $out['return'] = ""; } else { $out['return'] = $this->guessClass($returnType, $classData['namespaces']); } $out['array_return'] = $arrayReturn; } $return = empty($parsedComment['return'])? "none" : $parsedComment['return']; $out['signature'] = '('. join(', ', $params) . ') : '. $return; foreach($modifiers as $modifier) { $classData['methods']['modifier'][$modifier][] = $reflectionMethod->name; } $classData['methods']['all'][$reflectionMethod->name] = $out; } private function getConstantData(ReflectionClass $reflectionClass, &$classData) { $constants = array(); try { $constants = $reflectionClass->getConstants(); } catch (\Exception $e) { //echo $e->getMessage(); } foreach ($constants as $key => $value) { if(is_string($value)) { $constants[$key] = $this->fixUTF8($value); } } $classData['constants'] = $constants; } private function parseDocComment($docComment, $type) { $out = array(); $comments = explode('*', $docComment); if(count($comments) > 1) { //var_dump($comments); } if($type == 'method') { $out['params'] = array(); $out['return'] = ""; $out['inheritdoc'] = 0; } else if($type == 'property') { $out['var'] = ""; $out['inheritdoc'] = 0; } foreach ($comments as $comment) { $comment = trim(trim($comment), "{}/"); $splits = preg_split('/\s+/', $comment); if(empty($splits)) { continue; } if($type=='method'){ switch ($splits[0]) { case '@param': if(count($splits) == 1) { continue; } elseif(count($splits) == 2) { $splits[2] = $splits[1]; } $out['params'][$splits[2]] = $splits[1]; break; case '@return': if(count($splits) < 2){ continue; } $out['return'] = $splits[1]; break; case '@inheritdoc': $out['inheritdoc'] = 1; break; } } else if($type == 'property') { if($splits[0] == '@var') { if(count($splits) < 2) { continue; } $out['var'] = $splits[1]; } } } return $out; } private function getPropertyData(ReflectionProperty $reflectionProperty, &$classData) { $sclarTypes = array('boolean', 'integer','float', 'string', 'array', 'object', 'resource', 'mixed', 'number', 'callback'); $modifiers = Reflection::getModifierNames($reflectionProperty->getModifiers()); $docComment = $reflectionProperty->getDocComment(); if(!$docComment) { $docComment = ""; } $parsedComment = $this->parseDocComment($docComment, 'property'); $type = $parsedComment['var']; $out = array(); $out['type'] = ""; $out['array_type'] = 0; if(preg_match('/\[\]$/', $type)) { $out['array_type'] = 1; } if(!$this->isScalar($type) && !empty($type)) { if($type[0] == '\\') { $type = substr($type, 1); } if(preg_match('/\[\]$/', $type)) { $type = trim($type, "[]"); } $out['type'] = $this->guessClass($type, $classData['namespaces']); } $out['inheritdoc'] = $parsedComment['inheritdoc']; $out['docComment'] = $this->trimDocComment($docComment); $origin = $reflectionProperty->getDeclaringClass()->getFileName() == false? "": $reflectionProperty->getDeclaringClass()->getFileName(); $origin = $this->normalizePath($origin); $out['origin'] = $origin; $classData['properties']['all'][$reflectionProperty->name] = $out; foreach ($modifiers as $modifier) { $classData['properties']['modifier'][$modifier][] = $reflectionProperty->name; } } private function guessClass($classToken, $namespaces) { if(empty($classToken)) { return ""; } if($classToken[0] == '\\') { return $classToken; //assuming it is a internal clas } //replace alias namespaces foreach ($namespaces['alias'] as $aliasName => $usesKey) { $useValue = $namespaces['uses'][$usesKey]; if($usesKey == $useValue) { $namespaces['uses'][$aliasName] = $useValue; } else { $namespaces['uses'][$aliasName] = $useValue. "\\". $usesKey; } } $aliasReverse = array_flip($namespaces['alias']); if(array_key_exists($classToken, $namespaces['uses']) && !array_key_exists($classToken, $aliasReverse)){ //in uses if(array_key_exists($classToken, $namespaces['alias'])) { $fqcn = $namespaces['uses'][$classToken]; } else { $fqcn = $namespaces['uses'][$classToken]. "\\". $classToken; } } elseif(!empty($namespaces['alias']) && array_key_exists($classToken, $namespaces['alias'])) { //in alias $classToken = $namespaces['alias'][$classToken]; $fqcn = $namespaces['uses'][$classToken]. "\\". $classToken; } else { //append with file namespace $fileNameSpace = ""; if(array_key_exists('file', $namespaces)) { //has namespace $fileNameSpace = $namespaces['file'] . "\\"; } $fqcn = trim($fileNameSpace. $classToken); } if($fqcn == "" && array_key_exists($classToken, $this->fqcn_file)){ $fqcn = $classToken; } if(array_key_exists($fqcn, $this->fqcn_file)) { return $fqcn; } else{ try { $reflectionClass = new \ReflectionClass($fqcn); return $fqcn; } catch (\Exception $e) { //ldd($e->getMessage()); $fqcn = ""; } if(empty($fqcn)){ try { $reflectionClass = new \ReflectionClass($classToken); return $classToken; } catch (\Exception $e) { $fqcn = ""; } } } return $fqcn; } private function getClassContent($fileLocation, $className) { $fp = fopen($fileLocation, 'r'); if(!$fp) { throw new \Exception("read file of class ". $className." failed"); } $classContent = array(); while (($line = fgets($fp, 4096)) !== false) { $classContent[] = $line; } if (!feof($fp)) { echo "Error: unexpected fgets() fail\n"; } fclose($fp); return $classContent; } private function trimDocComment($docComment) { $comments = explode("\n", $docComment); $newComments = array(); $count = 1; foreach ($comments as $comment) { $comment = trim($comment); $comment = str_replace("/**", '', $comment, $count); $comment = str_replace("*", '', $comment, $count); $comment = str_replace("*/", '', $comment, $count); $comment = str_replace("/", '', $comment, $count); $newComments[] = $comment; } $docComment = join("\n", $newComments); $docComment = $this->fixUTF8($docComment); return $docComment; } private function fixUTF8($content) { return iconv('UTF-8', 'UTF-8//IGNORE', utf8_encode($content)); } private function getEmptyMergeProperty() { return array( 'methods' => array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), //'all' => array(), 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn") ), 'namespaces' => array( 'uses' => array(), 'alias' => array() ), 'properties' => array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), //'all' => array() 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn"), "origin" => "", ), 'constants' => array(), 'parentclasses' => array(), 'interfaces' => array(), 'classname' => "", 'startLine' => 0, 'docComment' => "", 'file' => '' ); } private function getEmptyClassData($className) { $classData = array(); $classData['classname'] = $className; $classData['startLine'] = 1; $classData['docComment'] = ""; $classData['methods'] = array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn"), 'origin' => "" ); $classData['properties'] = array( 'modifier' => array( 'public' => array(), 'private' => array(), 'protected' => array(), 'final' => array(), 'static' => array(), 'interface' => array(), 'abstract' => array(), ), 'all' => array("nnnnnnnn" => "nnnnnnnnnnnn") ); $classData['parentclass'] = ""; $classData['parentclasses'] = array(); $classData['interfaces'] = array(); $classData['file'] = ""; $classData['namespaces'] = array( 'uses' => array(), 'alias' => array(), ); $classData['constants'] = array(); return $classData; } private function isScalar($type) { $scalarsTypes = array('boolean', 'integer','float', 'string', 'array', 'object', 'resource', 'mixed', 'number', 'callback', 'null', 'void', 'bool', 'self', 'int', 'callable'); return in_array(strtolower($type), $scalarsTypes); } private function getFQCNfromFile($file, $file_fqcn) { if(array_key_exists($file, $this->file_fqcn)) { return $this->file_fqcn[$file]; } else{ $parsedData = parseClass($file); $classLineData = $parsedData['class_line_data']; $className = $classLineData['classname']; if(array_key_exists('file', $parsedData['namespaces'])) { $fqcn = $parsedData['namespaces']['file'] . "\\" . $className; } else { $fqcn = $className; } return $fqcn; } } /** * Gets the value of indexFileName * * @return indexFileName */ public function getIndexFileName() { return $this->indexFileName; } /** * Sets the value of indexFileName * * @param string $indexFileName name of the file * * @return $this */ public function setIndexFileName($indexFileName ="./.phpcomplete_extended/phpcomplete_index") { $this->indexFileName = $indexFileName; return $this; } /** * Gets the value of reportFileName * * @return reportFileName */ public function getReportFileName() { return $this->reportFileName; } /** * Sets the value of reportFileName * * @param string $reportFileName report file name * * @return $this */ public function setReportFileName($reportFileName="./.phpcomplete_extended/report.txt") { $this->reportFileName = $reportFileName; return $this; } public function writeToFile($fileName, $data) { file_put_contents($fileName, $data); } /** * Gets the value of invalidClasses * * @return array */ public function getInvalidClasses() { return $this->invalidClasses; } /** * Sets the value of invalidClasses * * @param string $invalidClasses invalid class list * * @return */ public function setInvalidClasses($invalidClasses) { $this->invalidClasses = $invalidClasses; return $this; } /** * Splits multple use, declarations in one line * Easier for parsing later * @param array $classContent */ public function formatClassContent($classContent) { $namespace = ""; $uses = array(); $aliases = array(); $className = ""; $extends = array(); $implements = array(); $isMultiLine = false; $multiLine = ""; $currentSection = ""; $formattedClassContent = array(); $fullLine = ""; $isClassLine = false; $classLine = ""; $useEnded = false; $isConstructorLine = false; $constructorLine = ""; foreach ($classContent as $line) { $line = trim(str_replace(" 2) { $formattedClassContent = array_merge($formattedClassContent, explode(';', $line)); continue; } if($isClassLine) { $classLine .= " " . $line; if(strpos($classLine, "{")) { $isClassLine = false; $useEnded = true; $formattedClassContent[] = trim($classLine, "{}"); } } if($isConstructorLine) { $constructorLine .= " " . $line; if(strpos($constructorLine, "{")) { $isConstructorLine = false; $formattedClassContent[] = trim($constructorLine, "{}"); break; } } if(!$isMultiLine && !$isClassLine && !$isConstructorLine && ( preg_match("/^\s*namespace/", $line) || preg_match("/^\s*use/", $line) || preg_match("/^\s*(abstract|final)?\s*(class|interface)/", $line) || preg_match("/^\s*interface/", $line) || preg_match("/^\s*public\s+function\s+__construct/", $line) ) ){ if(preg_match("/^\s*(abstract|final)?\s*(class|interface)/", $line )) { if(strpos($line, "{") === false) { $isClassLine = true; $classLine .= trim($line, ";"); continue; } else { $isClassLine = false; $useEnded = false; $formattedClassContent[] = trim($line, ";{}"); continue; } } if(preg_match("/^\s*public\s+function\s+__construct/", $line)) { if(strpos($line, ")") === false) { $isConstructorLine = true; $constructorLine .= trim($line, ";"); continue; } else { $isConstructorLine = false; $formattedClassContent[] = trim($line, ";{}"); break; } } if(strpos($line, ";") === false && strpos($line, ",") >= 0) { $isMultiLine = true; $multiLine .= $line; } else { $formattedClassContent[] = trim($line, ";{"); } continue; } if($isMultiLine) { $multiLine .= $line; if(strpos($multiLine, ";") !== false) { $isMultiLine = false; if(explode(";", $multiLine) >2) { $formattedClassContent = array_merge($formattedClassContent, explode(";", $multiLine)); } else { $formattedClassContent[] = trim($multiLine, ";{"); } } } } array_walk($formattedClassContent, function(&$item){ $item = trim($item); }); return $formattedClassContent; } public function parseClass($fileName) { if(array_key_exists($fileName, $this->parsedClasses)) { return $this->parsedClasses[$fileName]; } $namespace = ""; $uses = array(); $aliases = array(); $className = ""; $extends = array(); $implements = array(); $constructorArguments = array(); $classNameregex = "/^\s*(abstract|final)?(\s+)?(class)\s+([\\\\,\w]+)(\s+extends\s+([\\\\\w]+))?(\s+implements\s+([^{]*))?/"; $interfaceRegex = "/^\s*interface\s+([\\\\,\w]+)(\s+extends(.*))?/"; $constructorRegex = "/^\s*public\s+function\s+__construct\((.*)\)/"; $classContent = file($fileName); $formattedClassContent = $this->formatClassContent($classContent); $parseEnded = false; foreach ($formattedClassContent as $line) { if(preg_match("/\s*namespace\s+(.*)(;)?/", $line, $matches)) { $namespace = trim($matches[1], ";"); } if(preg_match("/\s*use\s+(.*)/", $line, $matches)) { $classUses = explode(",", $matches[1]); foreach($classUses as $use) { // use statement $alias = ""; if(empty($use)) { continue; } if(strpos($use, " as ")) { $ases = explode(" as ", $use); $use = trim($ases[0]); $alias = trim($ases[1], ";"); } $useTokens = explode("\\", $use); if(count($useTokens) == 1) { $lastToken = trim($useTokens[0], "; "); $uses[$useTokens[0]] = $useTokens[0]; } else { $lastToken = trim(array_pop($useTokens), "; "); if(array_key_exists($lastToken, $uses)) { $lastToken = $use; $uses[$lastToken] = $use; } else { $uses[$lastToken] = join("\\", $useTokens); } } if(!empty($alias)) { $aliases[$alias] = $lastToken; } } } if(preg_match($classNameregex, $line, $matches)) { if(strlen($className) > 0){ continue; } $className = $matches[4]; if(!empty($matches[6])) { //extends $extends = trim($matches[6], " \n\r"); if(strpos($extends, "\\")) { $useTokens = explode("\\", $extends); $firstToken = $useTokens[0]; $lastToken = array_pop($useTokens); $extends = $lastToken; if(array_key_exists($firstToken, $uses)) { $uses[$lastToken] = $uses[$firstToken]. "\\" . join("\\", $useTokens); } else { $uses[$lastToken] = $namespace . "\\" . join("\\", $useTokens); //$uses[$lastToken] = join("\\", $useTokens); } } } if(!empty($matches[8])) { $classImplements = explode(",", $matches[8]); foreach($classImplements as $implement) { //implements if(empty($implement)) { continue; } $implement = trim($implement, " \n\r{"); if(strpos($implement, "\\")) { $useTokens = explode("\\", $implement); $firstToken = $useTokens[0]; $lastToken = array_pop($useTokens); $implements[] = $lastToken; if(array_key_exists($firstToken, $uses)) { $uses[$lastToken] = $uses[$firstToken]. "\\" . join("\\", $useTokens); } else { $uses[$lastToken] = $namespace . "\\" . join("\\", $useTokens); //print_r($useTokens); //$uses[$lastToken] = join("\\", $useTokens); } } else { $implements[] = $implement; } } } } if(preg_match($constructorRegex, $line, $matches)) { $arguments = explode(",", $matches[1]); foreach ($arguments as $argument) { $argument = trim($argument); if(isset($argument[0]) && $argument[0] == '$') { continue; } $segments = preg_split("/\s+/", $argument); if(count($segments) == 1) { continue; } if($this->isScalar($segments[0])) { continue; } $argumentFQCN = $segments[0]; if(strpos($segments[0], "\\")) { $useTokens = explode("\\", $segments[0]); $firstToken = $useTokens[0]; $lastToken = array_pop($useTokens); $constructorArguments[] = $lastToken; if(array_key_exists($firstToken, $uses)) { $uses[$lastToken] = $uses[$firstToken]. "\\" . join("\\", $useTokens); } else { $uses[$lastToken] = $namespace . "\\" . join("\\", $useTokens); } } else { $constructorArguments[] = $argumentFQCN; } } } if(preg_match($interfaceRegex, $line, $matches)) { $className = $matches[1]; if(!empty($matches[3])) { foreach (explode(",", $matches[3]) as $implement) { //triplicate, will extract later $implement = trim($implement, " \n\r{"); if(strpos($implement, "\\")) { $useTokens = explode("\\", $implement); $firstToken = $useTokens[0]; $lastToken = array_pop($useTokens); $implements[] = $lastToken; if(array_key_exists($firstToken, $uses)) { $uses[$lastToken] = $uses[$firstToken]. "\\" . join("\\", $useTokens); } else { $uses[$lastToken] = $namespace . "\\" . join("\\", $useTokens); //print_r($useTokens); //$uses[$lastToken] = join("\\", $useTokens); } } else { $implements[] = $implement; } } } $parseEnded = true; } } $parsedData = array( 'namespaces' => array( 'uses' => $uses, 'alias' => $aliases, 'file' => $namespace, ), 'class_line_data' => array( 'implements' => $implements, 'extends' => $extends, 'classname' => $className ), 'constructor_arguments' => $constructorArguments ); //ldd($parsedData); $this->parsedClasses[$fileName] = $parsedData; return $parsedData; } /** * Gets the value of coreIndex * * @return array */ public function getCoreIndex() { return $this->coreIndex; } /** * Sets the value of coreIndex * * @param array $coreIndex * * @return this */ public function setCoreIndex($coreIndex) { $this->coreIndex = $coreIndex; return $this; } /** * Gets the value of coreIndexFile * * @return string */ public function getCoreIndexFile() { return $this->coreIndexFile; } /** * Sets the value of coreIndexFile * * @param string $coreIndexFile * * @return this */ public function setCoreIndexFile($coreIndexFile) { $this->coreIndexFile = $coreIndexFile; return $this; } public function processCoreIndexFile() { $this->coreIndex = json_decode(file_get_contents($this->coreIndexFile), true); } } ================================================ FILE: bin/core_index ================================================ [File too large to display: 23.2 MB] ================================================ FILE: doc/phpcomplete-extended.txt ================================================ *phpcomplete-extended.txt* Fast autocomplete plugin for PHP composer projects License: MIT license {{{ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}} Introduction |phpcomplete-extended-introduction| Installation |phpcomplete-extended-install| Variables |phpcomplete-extended-variables| Key Mappings |phpcomplete-extended-key-mappings| Commands |phpcomplete-extended-commands| Functions |phpcomplete-extended-functions| Writing Extension |phpcomplete-extended-write-extension| ============================================================================== INTRODUCTION *phpcomplete-extended-introduction* phpcomplete-extended is a fast, extensible, context aware autocomplete plugin for PHP composer projects. Initially it reads autoload classmap of a composer project, parses doc-comments of each class and creates index from them. After that it auto updates index as you type thanks to [vimproc.vim](https://github.com/Shougo/vimproc.vim) plugin. Besides autocomplete this plugin have several code inspection features, * Includes full core PHP documentation * See documentation of current word, be it class name, method or property. It is context aware. * Go to definition of a symbol. Also context aware. * Automatically add use statement of current completed word. Also added plugin command of this action. * If (Unite) plugin installed following sources are available, * `phpcomplete/files`: Lists PHP files of the project. * `phpcomplete/vendors`: Lists vendor directories * `phpcomplete/extends`: Lists classes that extends the class guessed from the current cursor word. * `phpcomplete/implements`: Lists classes that implements the class guessed from the current cursor word. ============================================================================== Installation *phpcomplete-extended-install* The plugin depends on following plugins, * [vimproc.vim](https://github.com/Shougo/vimproc.vim) : Asynchronous library for vim. Required for auto-update index. C compiler is needed to build the plugiin. For windows install cygwin or msys to get the compiler. * [unite.vim](https://github.com/Shougo/unite.vim/): Optional, but enables some good features. Plugin managers are recommended for installing these plugins. Installation instructions for various plugin managers are given bellow. ## Pathogen Issue following commands. ```sh git clone https://github.com/Shougo/vimproc.vim.git ~/.vim/bundle cd ~/.vim/bundle/vimproc.vim make cd .. git clone https://github.com/Shougo/unite.vim.git ~/.vim/bundle git clone https://github.com/m2mdas/phpcomplete-extended.git ~/.vim/bundle ``` ## NeoBundle (prefered) Put these lines in `.vimrc` and issue `source %` command ```vim NeoBundle 'Shougo/vimproc', { \ 'build' : { \ 'windows' : 'make -f make_mingw32.mak', \ 'cygwin' : 'make -f make_cygwin.mak', \ 'mac' : 'make -f make_mac.mak', \ 'unix' : 'make -f make_unix.mak', \ }, \ } NeoBundle 'Shougo/unite.vim' NeoBundle 'm2mdas/phpcomplete-extended' "your other plugins NeoBundleCheck ``` If C compiler found for respective environment, `neobundle` will automatically compile the library. ## Vundle Put following lines in `.vimrc` and issue `:BundleInstall` command. ```vim Bundle 'Shougo/vimproc' Bundle 'Shougo/unite.vim' Bundle 'm2mdas/phpcomplete-extended' "your other plugins ".... ``` After installation goto `vimproc` folder and issue `make` command from command line. C compiler must be installed. ============================================================================== Variables *phpcomplete-extended-variables* g:phpcomplete_index_composer_command *g:phpcomplete_index_composer_command* Sets composer command. Default value "php composer.phar" g:phpcomplete_extended_auto_add_use *g:phpcomplete_extended_auto_add_use* Determine if the plugin will add use statement after class selection Permitted values 1 or 0 Default value 1 g:phpcomplete_extended_use_default_mapping *g:phpcomplete_extended_use_default_mapping* Sets whether default mappings for plugin enabled or not. Permitted value: 1 or 0 Default value 1 ============================================================================== Key Mappings *phpcomplete-extended-key-mappings* (phpcomplete-extended-goto) *(phpcomplete-extended-goto)* Goto definition of current word. By default mapped to `K` in normal mode (phpcomplete-extended-doc) *(phpcomplete-extended-doc)* If found opens documentation of the word under the cursor. If not found use default action is done. By default mapped to `` in normal mode (phpcomplete-extended-add-use) *(phpcomplete-extended-add-use)* Adds use statement of the current word. By default mapped to u in normal mode ============================================================================== Commands *phpcomplete-extended-commands* PHPCompleteExtendedReload *PHPCompleteExtendedReload* Reload index from disk PHPCompleteExtendedClearIndexCache *PHPCompleteExtendedClearIndexCache* Clears intermediate index cache PHPCompleteExtendedGenerateIndex *PHPCompleteExtendedGenerateIndex* Generate index from scratch. Previous index discarded. To get verbose output append " -verbose" to the command. For example, > :PHPCompleteExtendedGenerateIndex -verbose < To put large output in a scratch buffer you can use "tyru/capture.vim"(https://github.com/tyru/capture.vim). The command would be > :Capture PHPCompleteExtendedGenerateIndex -verbose < PHPCompleteExtendedUpdateIndex *PHPCompleteExtendedUpdateIndex* Manually update index of the class of current buffer. Used for debug purpose PHPCompleteExtendedCheckUpdate *PHPCompleteExtendedCheckUpdate* Manually issue check update command. Used for debug purpose. PHPCompleteExtendedDisable *PHPCompleteExtendedDisable* Disables PHP autocomplete PHPCompleteExtendedEnable *PHPCompleteExtendedEnable* Enables PHP autocomplete ============================================================================== Functions *phpcomplete-extended-functions* TBD Writing Extensions *phpcomplete-extended-write-extension* TBD vim:tw=78:ts=8:ft=help:norl:noet:fen: ================================================ FILE: plugin/phpcomplete_extended.vim ================================================ "============================================================================= " AUTHOR: Mun Mun Das " FILE: phpcomplete_extended.vim " Last Modified: September 10, 2013 " License: MIT license {{{ " Permission is hereby granted, free of charge, to any person obtaining " a copy of this software and associated documentation files (the " "Software"), to deal in the Software without restriction, including " without limitation the rights to use, copy, modify, merge, publish, " distribute, sublicense, and/or sell copies of the Software, and to " permit persons to whom the Software is furnished to do so, subject to " the following conditions: " " The above copyright notice and this permission notice shall be included " in all copies or substantial portions of the Software. " " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} "============================================================================= let s:save_cpo = &cpo set cpo&vim if !phpcomplete_extended#util#has_vimproc() echoerr "Vimproc is a requirement for phpcomplete-extended plugin" endif if !executable("php") echoerr "php executable not found. Put the directory containing php executable in your $PATH environment variable" endif let g:phpcomplete_index_composer_command = \ get(g:, 'phpcomplete_index_composer_command', 'php composer.phar') if exists('g:loaded_phpcomplete_extended') finish endif let g:loaded_phpcomplete_extended = 1 let g:phpcomplete_extended_tags_cache_dir = \ get(g:, 'phpcomplete_extended_tags_cache_dir', expand('~/.phpcomplete_extended')) let g:phpcomplete_extended_cache_disable = \ get(g:, 'phpcomplete_extended_cache_disable', 0) let g:phpcomplete_extended_load_cache_at_buf_enter = \ get(g:, 'phpcomplete_extended_load_cache_at_buf_enter', 1) let g:phpcomplete_extended_auto_add_use = \ get(g:, 'phpcomplete_extended_auto_add_use', 1) let g:phpcomplete_extended_root_dir = fnamemodify(expand(""), ':p:h:h') let g:phpcomplete_extended_use_default_mapping = \ get(g:, 'phpcomplete_extended_use_default_mapping', 1) command! -nargs=0 -bar PHPCompleteExtendedReload \ call phpcomplete_extended#reload() command! -nargs=0 -bar PHPCompleteExtendedClearIndexCache \ call phpcomplete_extended#clearIndexCache() command! -nargs=? -bar PHPCompleteExtendedGenerateIndex \ call phpcomplete_extended#generateIndex("") command! -nargs=0 -bar PHPCompleteExtendedUpdateIndex \ call phpcomplete_extended#updateIndex(0) command! -nargs=0 -bar PHPCompleteExtendedCheckUpdate \ call phpcomplete_extended#checkUpdates() command! -nargs=0 -bar PHPCompleteExtendedEnable \ call phpcomplete_extended#enable() command! -nargs=0 -bar PHPCompleteExtendedDisable \ call phpcomplete_extended#disable() nnoremap (phpcomplete-extended-goto) :call phpcomplete_extended#gotoSymbolORDoc('goto') nnoremap (phpcomplete-extended-doc) :call phpcomplete_extended#gotoSymbolORDoc('doc') nnoremap (phpcomplete-extended-add-use) :call phpcomplete_extended#addUse(expand(''), "") if g:phpcomplete_extended_use_default_mapping silent! nmap K (phpcomplete-extended-doc) silent! nmap (phpcomplete-extended-goto) silent! nmap u (phpcomplete-extended-add-use) endif call phpcomplete_extended#enable() let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker:expandtab:ts=4:sts=4:tw=78 ================================================ FILE: vest/test-fowrard-parse.vim ================================================ scriptencoding utf-8 let s:save_cpo = &cpo scriptencoding utf-8 set cpo&vim Context forward_parser It tests reverse parser ShouldEqual phpcomplete_extended#parser#forwardParse( \ '$var;' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$var', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ "$this->get('fdsa')->get2();" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': "'fdsa'", 'isMethod': 1, 'methodPropertyText': 'get', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'get2', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ 'Foo::Bar()->Baz();' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'isStatic': 1, 'methodPropertyText': 'Foo', 'nonClass': 1, 'start': 1}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': '', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'Baz', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ '(new Foo)->bar();' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'isNew': 1, 'methodPropertyText': 'Foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'bar', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ '(new Foo())->bar();' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 1, 'isNew': 1, 'methodPropertyText': 'Foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'bar', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ "$foo['bar']->get();" \ ,[]), \ [{'insideBraceText': "'bar'", 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'get', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ "$foo->bar()['baz'];" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, \ {'insideBraceText': '', 'isArrayElement': 1, 'isMethod': 1, 'methodPropertyText': 'bar', 'start': 0}, \ {'insideBraceText': "'baz'", 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ '$foo->bar->baz();' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'bar', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'baz', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ "$foo->bar()['baz']->bzz;" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, \ {'insideBraceText': '', 'isArrayElement': 1, 'isMethod': 1, 'methodPropertyText': 'bar', 'start': 0}, \ {'insideBraceText': "'baz'", 'isArrayElement': 1, 'isMethod': 1, 'methodPropertyText': '', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'bzz', 'pEnd': 1, 'start': 0}] ShouldEqual phpcomplete_extended#parser#forwardParse( \ '$this->foo($bar->baz());' \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': '$bar->baz', 'isMethod': 1, 'methodPropertyText': 'foo', 'pEnd': 1, 'start': 0}] End End Fin let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker:expandtab:ts=4:sts=4:tw=78 ================================================ FILE: vest/test-reverse-parser.vim ================================================ scriptencoding utf-8 let s:save_cpo = &cpo scriptencoding utf-8 set cpo&vim Context reverse_parser It tests reverse parser ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$var->" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$var', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$foo = $var->" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$var', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$var" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$var', 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "method_name" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'method_name', 'nonClass': 1, 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "new Test" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'isNew': 1, 'methodPropertyText': 'Test', 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->get" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'get', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->get('insidequote" \ ,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': "'insidequote", 'insideQuote': 1, 'isMethod': 0, 'methodPropertyText': 'get', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "Class('insidequote" \ ,[]), \ [{'insideBraceText': "'insidequote", 'insideQuote': 1, 'isMethod': 0, 'methodPropertyText': 'Class', 'nonClass': 1, 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "Class('insidequote')" \ ,[]), \ [{'insideBraceText': "'insidequote'", 'isMethod': 0, 'methodPropertyText': 'Class', 'nonClass': 1, 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "Class('dotted.service.text')" \ ,[]), \ [{'isMethod': 0, 'insideBraceText': '''dotted\.service\.text''', 'methodPropertyText': 'Class', 'nonClass': 1, 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->method('insidequote')->property" \ ,[]), \ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$this', 'start': 1}, {'isMethod': 1, 'insideBraceText': '''insidequote''', 'methodPropertyText': 'method', 'start': 0}, {'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': 'property', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->method('insidequote')->property->method1()" \ ,[]), \ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$this', 'start': 1}, {'isMethod': 1, 'insideBraceText': '''insidequote''', 'methodPropertyText': 'method', 'start': 0}, {'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': 'property', 'start': 0}, {'isMethod': 1, 'insideBraceText': '', 'methodPropertyText': 'method1', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->property->method()" \ ,[]), \ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$this', 'start': 1}, {'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': 'property', 'start': 0}, {'isMethod': 1, 'insideBraceText': '', 'methodPropertyText': 'method', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ 'Class("escaped string\"")' \, []), \ [{'isMethod': 0, 'insideBraceText': '"escapedstring\"', 'methodPropertyText': 'Class', 'nonClass': 1, 'start': 1}] "ShouldEqual phpcomplete_extended#parser#reverseParse( "\ '$this->get(''escaped string\''' "\ ,[]), "\ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$this', 'start': 1}, {'isMethod': 0, 'insideBraceText': '''escapedstring\', 'insideQuote': 1, 'methodPropertyText': 'get', 'start': 0}] "ShouldEqual phpcomplete_extended#parser#reverseParse( "\ "Class($skippedTokens, 'in quote')" "\, []), "\ [{'insideBraceText': "'inquote'", 'isMethod': 0, 'methodPropertyText': 'Class', 'nonClass': 1, 'start': 1}] "ShouldEqual phpcomplete_extended#parser#reverseParse( "\ '$this->get($skippedTokens, ''in quote'')' "\ ,[]), "\ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': "'inquote'", 'isMethod': 1, 'methodPropertyText': 'get', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ 'new \Namespace\Expansion("inside quote")' \, []), \ [{'isMethod': 0, 'insideBraceText': '"insidequote"', 'isNew': 1, 'methodPropertyText': 'Namespace\Expansion', 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->get($inside->brace("inside quote")' \, []), \ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$inside', 'start': 1}, {'isMethod': 1, 'insideBraceText': '"insidequote"', 'methodPropertyText': 'brace', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->get(normal_function("quote' \, []), \ [{'isMethod': 0, 'insideBraceText': '"quote', 'insideQuote': 1, 'methodPropertyText': 'normal_function', 'nonClass': 1, 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->get("string_not_alnum:' \,[]), \ [{'isMethod': 0, 'insideBraceText': '', 'methodPropertyText': '$this', 'start': 1}, {'isMethod': 0, 'insideBraceText': '"string_not_alnum:', 'insideQuote': 1, 'methodPropertyText': 'get', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$foo['bar']" \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, {'insideBraceText': "'bar'", 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$foo['bar" \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, {'insideBraceText': "'bar", 'insideQuote': 1, 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$foo['bar']->" \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, {'insideBraceText': "'bar'", 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->foo()["bar"]->' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, {'insideBraceText': '"bar"', 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$foo->bar["baz"]->bzz' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$foo', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'bar', 'start': 0}, {'insideBraceText': '"baz"', 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'bzz', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->foo['bar" \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': 'foo', 'start': 0}, {'insideBraceText': "'bar", 'insideQuote': 1, 'isArrayElement': 1, 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] "ShouldEqual phpcomplete_extended#parser#reverseParse( "\ '(new Foo)->bar()' "\,[]), "\ [{'isMethod': 0, 'insideBraceText': '', 'isNew': 1, 'methodPropertyText': 'Foo', 'start': 1}, {'isMethod': 1, 'insideBraceText': '', 'methodPropertyText': 'bar', 'start': 0}] "ShouldEqual phpcomplete_extended#parser#reverseParse( "\ "(new Foo('bar'))->baz('bzzz')" "\,[]), "\ [{'isMethod': 0, 'insideBraceText': '''bar''', 'isNew': 1, 'methodPropertyText': 'Foo', 'start': 1}, {'isMethod': 1, 'insideBraceText': '''bzzz''', 'methodPropertyText': 'baz', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->foo($bar->baz())->' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': '$bar->baz', 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->foo($bar->baz("bzz"))->' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': '$bar->baz"bzz"', 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->foo($bar->baz[])->' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': '$bar->baz', 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ '$this->foo($bar->baz["dfdfa"])->' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': '$bar->baz"dfdfa"', 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '', 'start': 0}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ 'return new Foo' \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'isNew': 1, 'methodPropertyText': 'Foo', 'start': 1}] ShouldEqual phpcomplete_extended#parser#reverseParse( \ "$this->foo('session')->bar()->baz($bzz, $zzzz)" \,[]), \ [{'insideBraceText': '', 'isMethod': 0, 'methodPropertyText': '$this', 'start': 1}, \ {'insideBraceText': "'session'", 'isMethod': 1, 'methodPropertyText': 'foo', 'start': 0}, \ {'insideBraceText': '', 'isMethod': 1, 'methodPropertyText': 'bar', 'start': 0}, \ {'insideBraceText': '$bzz,$zzzz', 'isMethod': 1, 'methodPropertyText': 'baz', 'start': 0}] End End Fin let &cpo = s:save_cpo unlet s:save_cpo " vim: foldmethod=marker:expandtab:ts=4:sts=4:tw=78