Browse Source

vim: ctrlp - pymatcher

raylu 8 years ago
parent
commit
9966d71dea
4 changed files with 203 additions and 0 deletions
  1. 90 0
      vim/autoload/pymatcher.py
  2. 43 0
      vim/autoload/pymatcher.vim
  3. 68 0
      vim/doc/pymatcher.txt
  4. 2 0
      vimrc

+ 90 - 0
vim/autoload/pymatcher.py

@@ -0,0 +1,90 @@
+import vim, re
+import heapq
+
+_escape = dict((c , "\\" + c) for c in ['^','$','.','{','}','(',')','[',']','\\','/','+'])
+
+def CtrlPPyMatch():
+    items = vim.eval('a:items')
+    astr = vim.eval('a:str')
+    lowAstr = astr.lower()
+    limit = int(vim.eval('a:limit'))
+    mmode = vim.eval('a:mmode')
+    aregex = int(vim.eval('a:regex'))
+    spath = vim.eval('a:ispath')
+    crfile = vim.eval('a:crfile')
+
+    if not vim.eval('exists("g:ctrlp_match_current_file")') and ispath and crfile:
+        items.remove(crfile)
+
+    rez = vim.eval('s:rez')
+
+
+    regex = ''
+    if aregex == 1:
+        regex = astr
+    else:
+        # Escape all of the characters as necessary
+        escaped = [_escape.get(c, c) for c in lowAstr]
+
+        # If the string is longer that one character, append a mismatch
+        # expression to each character (except the last).
+        if len(lowAstr) > 1:
+            mismatch = ["[^" + c + "]*" for c in escaped[:-1]]
+            regex = ''.join([c for pair in zip(escaped[:-1], mismatch) for c in pair])
+
+        # Append the last character in the string to the regex
+        regex += escaped[-1]
+    # because this IGNORECASE flag is extremely expensive we are converting everything to lower case
+    # see https://github.com/FelikZ/ctrlp-py-matcher/issues/29
+    regex = regex.lower()
+
+    res = []
+    prog = re.compile(regex)
+
+    def filename_score(line):
+        # get filename via reverse find to improve performance
+        slashPos = line.rfind('/')
+
+        if slashPos != -1:
+            line = line[slashPos + 1:]
+
+        lineLower = line.lower()
+        result = prog.search(lineLower)
+        if result:
+            score = result.end() - result.start() + 1
+            score = score + ( len(lineLower) + 1 ) / 100.0
+            score = score + ( len(line) + 1 ) / 1000.0
+            return 1000.0 / score
+
+        return 0
+
+    def path_score(line):
+        lineLower = line.lower()
+        result = prog.search(lineLower)
+        if result:
+            score = result.end() - result.start() + 1
+            score = score + ( len(lineLower) + 1 ) / 100.0
+            return 1000.0 / score
+
+        return 0
+
+    if mmode == 'filename-only':
+        res = [(filename_score(line), line) for line in items]
+
+    elif mmode == 'first-non-tab':
+        res = [(path_score(line.split('\t')[0]), line) for line in items]
+
+    elif mmode == 'until-last-tab':
+        res = [(path_score(line.rsplit('\t')[0]), line) for line in items]
+
+    else:
+        res = [(path_score(line), line) for line in items]
+
+    rez.extend([line for score, line in heapq.nlargest(limit, res) if score != 0])
+
+    # Use double quoted vim strings and escape \
+    vimrez = ['"' + line.replace('\\', '\\\\').replace('"', '\\"') + '"' for line in rez]
+
+    vim.command("let s:regex = '%s'" % regex)
+    vim.command('let s:rez = [%s]' % ','.join(vimrez))
+

+ 43 - 0
vim/autoload/pymatcher.vim

@@ -0,0 +1,43 @@
+" Python Matcher
+
+if !has('python') && !has('python3')
+    echo 'In order to use pymatcher plugin, you need +python or +python3 compiled vim'
+endif
+
+let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
+
+if has('python3')
+  execute 'py3file ' . s:plugin_path . '/pymatcher.py'
+else
+  execute 'pyfile ' . s:plugin_path . '/pymatcher.py'
+endif
+
+function! pymatcher#PyMatch(items, str, limit, mmode, ispath, crfile, regex)
+
+    call clearmatches()
+
+    if a:str == ''
+        let arr = a:items[0:a:limit]
+        if !exists('g:ctrlp_match_current_file') && a:ispath && !empty(a:crfile)
+            call remove(arr, index(arr, a:crfile))
+        endif
+        return arr
+    endif
+
+    let s:rez = []
+    let s:regex = ''
+
+    execute 'python' . (has('python3') ? '3' : '') . ' CtrlPPyMatch()'
+
+    let s:matchregex = '\v\c'
+
+    if a:mmode == 'filename-only'
+        let s:matchregex .= '[\^\/]*'
+    endif
+
+    let s:matchregex .= s:regex
+
+    call matchadd('CtrlPMatch', s:matchregex)
+
+    return s:rez
+endfunction

+ 68 - 0
vim/doc/pymatcher.txt

@@ -0,0 +1,68 @@
+*pymatcher.txt* 
+
+Version: 1.0c
+Author : FelikZ <thefelikz gmailcom>
+License: {{{
+    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.
+}}}
+
+CONTENTS                    *pymatcher-contents*
+
+Introduction        |pymatcher-introduction|
+Install             |pymatcher-install|
+
+==============================================================================
+INTRODUCTION                    *pymatcher-introduction*
+
+*pymatcher* is a replacement for standard fuzzy matcher provided with CtrlP
+because of its performance issues on huge projects. Tested on 350k files.
+Some queries causes |CtrlP|'s standard matcher freezes up to 15 seconds. This
+matcher provides ~700ms performance on the same hardware with same queries.
+Pull requests to improve above performance are much appreciated.
+
+==============================================================================
+INSTALL                         *pymatcher-install*
+
+    NeoBundle installation:
+>
+    NeoBundle 'FelikZ/ctrlp-py-matcher'
+<
+    To enable |pymatcher| insert the following in your .vimrc:
+>
+    " PyMatcher for CtrlP
+    if !has('python')
+        echo 'In order to use pymatcher plugin, you need +python compiled vim'
+    else
+        let g:ctrlp_match_func = { 'match': 'pymatcher#PyMatch' }
+    endif
+<
+    To improve |CtrlP| experience it is strongly recommended to install |AG|
+>
+        https://github.com/ggreer/the_silver_searcher
+<
+    and then use following |CtrlP| settings in your .vimrc:
+>
+    " Set delay to prevent extra search
+    let g:ctrlp_lazy_update = 350
+
+    " Do not clear filenames cache, to improve CtrlP startup
+    " You can manualy clear it by <F5>
+    let g:ctrlp_clear_cache_on_exit = 0
+
+    " Set no file limit, we are building a big project
+    let g:ctrlp_max_files = 0
+
+    " If ag is available use it as filename list generator instead of 'find'
+    if executable("ag")
+        set grepprg=ag\ --nogroup\ --nocolor
+        let g:ctrlp_user_command = 'ag %s -i --nocolor --nogroup --ignore ''.git'' --ignore ''.DS_Store'' --ignore ''node_modules'' --hidden -g ""'
+    endif
+<
+==============================================================================
+vim:tw=78:ts=8:ft=help:norl:noet:fen:

+ 2 - 0
vimrc

@@ -65,6 +65,8 @@ let g:syntastic_mode_map = {'mode': 'passive', 'active_filetypes': [], 'passive_
 let g:syntastic_enable_signs = 0
 nmap <F11> :SyntasticCheck<cr>
 
+let g:ctrlp_match_func = {'match': 'pymatcher#PyMatch'}
+
 filetype plugin indent on
 syntax on
 highlight Folded ctermbg=black