├── .github └── FUNDING.yml ├── LICENSE ├── autoload ├── pymatcher.vim └── pymatcher.py ├── README.md └── doc └── pymatcher.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [FelikZ] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014-2016 FelikZ. http://felikz.me 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /autoload/pymatcher.vim: -------------------------------------------------------------------------------- 1 | " Python Matcher 2 | 3 | if !has('python') && !has('python3') 4 | echo 'In order to use pymatcher plugin, you need +python or +python3 compiled vim' 5 | endif 6 | 7 | let s:plugin_path = escape(expand(':p:h'), '\') 8 | 9 | if has('python3') 10 | execute 'py3file ' . s:plugin_path . '/pymatcher.py' 11 | else 12 | execute 'pyfile ' . s:plugin_path . '/pymatcher.py' 13 | endif 14 | 15 | function! pymatcher#PyMatch(items, str, limit, mmode, ispath, crfile, regex) 16 | 17 | call clearmatches() 18 | 19 | if a:str == '' 20 | let arr = a:items[0:a:limit] 21 | if pymatcher#ShouldHideCurrentFile(a:ispath, a:crfile) 22 | call remove(arr, index(arr, a:crfile)) 23 | endif 24 | return arr 25 | endif 26 | 27 | let s:rez = [] 28 | let s:regex = '' 29 | 30 | execute 'python' . (has('python3') ? '3' : '') . ' CtrlPPyMatch()' 31 | 32 | let s:matchregex = '\v\c' 33 | 34 | if a:mmode == 'filename-only' 35 | let s:matchregex .= '[\^\/]*' 36 | endif 37 | 38 | let s:matchregex .= s:regex 39 | 40 | call matchadd('CtrlPMatch', s:matchregex) 41 | 42 | return s:rez 43 | endfunction 44 | 45 | function! pymatcher#ShouldHideCurrentFile(ispath, crfile) 46 | return !get(g:, 'ctrlp_match_current_file', 0) && a:ispath && getftype(a:crfile) == 'file' 47 | endfunction 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ctrlp-py-matcher 2 | ================ 3 | 4 | Fast CtrlP matcher based on python 5 | 6 | Performance difference is up to x22, look at this perf: 7 | 8 | Default matcher: 9 | ``` 10 | FUNCTIONS SORTED ON SELF TIME 11 | count total (s) self (s) function 12 | 3 17.768008 17.610161 102_MatchIt() 13 | ``` 14 | 15 | With Py Matcher: 16 | ``` 17 | FUNCTIONS SORTED ON SELF TIME 18 | count total (s) self (s) function 19 | 3 0.730215 pymatcher#PyMatch() 20 | ``` 21 | 22 | To achieve such results try to do **long** (5-10+ sym) text queries on a large amount of files (1kk+). 23 | 24 | To install this plugin you **need** Vim compiled with `+python` flag: 25 | ``` 26 | vim --version | grep python 27 | ``` 28 | 29 | This plugin should be compatible with vim **7.x** and [NeoVIM](http://neovim.io) as well. 30 | 31 | **If you still have performance issues, it can be caused by [bufferline](https://github.com/bling/vim-bufferline) or alike plugins. So if, for example, it caused by bufferline you can switch to [airline](https://github.com/bling/vim-airline) and setup this option:** 32 | ``` 33 | let g:airline#extensions#tabline#enabled = 1 34 | ``` 35 | 36 | Installation 37 | ------------ 38 | ### Pathogen (https://github.com/tpope/vim-pathogen) 39 | ``` 40 | git clone https://github.com/FelikZ/ctrlp-py-matcher ~/.vim/bundle/ctrlp-py-matcher 41 | ``` 42 | 43 | ### Vundle (https://github.com/gmarik/vundle) 44 | ``` 45 | Plugin 'FelikZ/ctrlp-py-matcher' 46 | ``` 47 | 48 | ### NeoBundle (https://github.com/Shougo/neobundle.vim) 49 | ``` 50 | NeoBundle 'FelikZ/ctrlp-py-matcher' 51 | ``` 52 | 53 | ### ~/.vimrc setup 54 | 55 | let g:ctrlp_match_func = { 'match': 'pymatcher#PyMatch' } 56 | 57 | Full documentation is available [here](https://github.com/FelikZ/ctrlp-py-matcher/blob/master/doc/pymatcher.txt) 58 | -------------------------------------------------------------------------------- /doc/pymatcher.txt: -------------------------------------------------------------------------------- 1 | *pymatcher.txt* 2 | 3 | Version: 1.0c 4 | Author : FelikZ 5 | License: {{{ 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 7 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 8 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 9 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 10 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 11 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 12 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | }}} 14 | 15 | CONTENTS *pymatcher-contents* 16 | 17 | Introduction |pymatcher-introduction| 18 | Install |pymatcher-install| 19 | 20 | ============================================================================== 21 | INTRODUCTION *pymatcher-introduction* 22 | 23 | *pymatcher* is a replacement for standard fuzzy matcher provided with CtrlP 24 | because of its performance issues on huge projects. Tested on 350k files. 25 | Some queries causes |CtrlP|'s standard matcher freezes up to 15 seconds. This 26 | matcher provides ~700ms performance on the same hardware with same queries. 27 | Pull requests to improve above performance are much appreciated. 28 | 29 | ============================================================================== 30 | INSTALL *pymatcher-install* 31 | 32 | NeoBundle installation: 33 | > 34 | NeoBundle 'FelikZ/ctrlp-py-matcher' 35 | < 36 | To enable |pymatcher| insert the following in your .vimrc: 37 | > 38 | " PyMatcher for CtrlP 39 | if !has('python') 40 | echo 'In order to use pymatcher plugin, you need +python compiled vim' 41 | else 42 | let g:ctrlp_match_func = { 'match': 'pymatcher#PyMatch' } 43 | endif 44 | < 45 | To improve |CtrlP| experience it is strongly recommended to install |AG| 46 | > 47 | https://github.com/ggreer/the_silver_searcher 48 | < 49 | and then use following |CtrlP| settings in your .vimrc: 50 | > 51 | " Set delay to prevent extra search 52 | let g:ctrlp_lazy_update = 350 53 | 54 | " Do not clear filenames cache, to improve CtrlP startup 55 | " You can manualy clear it by 56 | let g:ctrlp_clear_cache_on_exit = 0 57 | 58 | " Set no file limit, we are building a big project 59 | let g:ctrlp_max_files = 0 60 | 61 | " If ag is available use it as filename list generator instead of 'find' 62 | if executable("ag") 63 | set grepprg=ag\ --nogroup\ --nocolor 64 | let g:ctrlp_user_command = 'ag %s -i --nocolor --nogroup --ignore ''.git'' --ignore ''.DS_Store'' --ignore ''node_modules'' --hidden -g ""' 65 | endif 66 | < 67 | ============================================================================== 68 | vim:tw=78:ts=8:ft=help:norl:noet:fen: 69 | -------------------------------------------------------------------------------- /autoload/pymatcher.py: -------------------------------------------------------------------------------- 1 | import vim, re 2 | import heapq 3 | 4 | _escape = dict((c , "\\" + c) for c in ['^','$','.','{','}','(',')','[',']','\\','/','+']) 5 | 6 | def CtrlPPyMatch(): 7 | items = vim.eval('a:items') 8 | astr = vim.eval('a:str') 9 | lowAstr = astr.lower() 10 | limit = int(vim.eval('a:limit')) 11 | mmode = vim.eval('a:mmode') 12 | aregex = int(vim.eval('a:regex')) 13 | crfile = vim.eval('a:crfile') 14 | 15 | if crfile in items and int(vim.eval("pymatcher#ShouldHideCurrentFile(a:ispath, a:crfile)")): 16 | items.remove(crfile) 17 | 18 | rez = vim.eval('s:rez') 19 | 20 | regex = '' 21 | if aregex == 1: 22 | regex = astr 23 | else: 24 | # Escape all of the characters as necessary 25 | escaped = [_escape.get(c, c) for c in lowAstr] 26 | 27 | # If the string is longer that one character, append a mismatch 28 | # expression to each character (except the last). 29 | if len(lowAstr) > 1: 30 | regex = ''.join([c + "[^" + c + "]*" for c in escaped[:-1]]) 31 | 32 | # Append the last character in the string to the regex 33 | regex += escaped[-1] 34 | # because this IGNORECASE flag is extremely expensive we are converting everything to lower case 35 | # see https://github.com/FelikZ/ctrlp-py-matcher/issues/29 36 | regex = regex.lower() 37 | 38 | res = [] 39 | prog = re.compile(regex) 40 | 41 | def filename_score(line): 42 | # get filename via reverse find to improve performance 43 | slashPos = line.rfind('/') 44 | 45 | if slashPos != -1: 46 | line = line[slashPos + 1:] 47 | 48 | lineLower = line.lower() 49 | result = prog.search(lineLower) 50 | if result: 51 | score = result.end() - result.start() + 1 52 | score = score + ( len(lineLower) + 1 ) / 100.0 53 | score = score + ( len(line) + 1 ) / 1000.0 54 | return 1000.0 / score 55 | 56 | return 0 57 | 58 | def path_score(line): 59 | lineLower = line.lower() 60 | result = prog.search(lineLower) 61 | if result: 62 | score = result.end() - result.start() + 1 63 | score = score + ( len(lineLower) + 1 ) / 100.0 64 | return 1000.0 / score 65 | 66 | return 0 67 | 68 | if mmode == 'filename-only': 69 | res = [(filename_score(line), line) for line in items] 70 | 71 | elif mmode == 'first-non-tab': 72 | res = [(path_score(line.split('\t')[0]), line) for line in items] 73 | 74 | elif mmode == 'until-last-tab': 75 | res = [(path_score(line.rsplit('\t')[0]), line) for line in items] 76 | 77 | else: 78 | res = [(path_score(line), line) for line in items] 79 | 80 | rez.extend([line for score, line in heapq.nlargest(limit, res) if score != 0]) 81 | 82 | # Use double quoted vim strings and escape \ 83 | vimrez = ['"' + line.replace('\\', '\\\\').replace('"', '\\"') + '"' for line in rez] 84 | 85 | vim.command("let s:regex = '%s'" % regex) 86 | vim.command('let s:rez = [%s]' % ','.join(vimrez)) 87 | --------------------------------------------------------------------------------