diff --git a/autoload/pymatcher.py b/autoload/pymatcher.py index 57a2e3c..4b1e08c 100644 --- a/autoload/pymatcher.py +++ b/autoload/pymatcher.py @@ -1,23 +1,43 @@ -import vim, re +import re +import vim import heapq -_escape = dict((c , "\\" + c) for c in ['^','$','.','{','}','(',')','[',']','\\','/','+']) +_escape = dict((c, "\\" + c) for c in [ + '^', '$', '.', '{', '}', '(', ')', '[', ']', '\\', '/', '+']) + def CtrlPPyMatch(): - items = vim.eval('a:items') + try: + _doCtrlPPyMatch() + except Exception as ex: + import traceback + tb = traceback.format_exc() + vim.command( + 'let s:rez = ["Unknown error in matcher", "%s", "%s"]' % + (str(ex), tb)) + + +def _doCtrlPPyMatch(): + try: + items = vim.eval('a:items') + except UnicodeDecodeError: + _troubleshootUnicodeInputError() + return + 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') + ispath = vim.eval('a:ispath') crfile = vim.eval('a:crfile') - if not vim.eval('exists("g:ctrlp_match_current_file")') and ispath and crfile: + if (not vim.eval('exists("g:ctrlp_match_current_file")') and + ispath and crfile): items.remove(crfile) rez = vim.eval('s:rez') - + sep = vim.eval('s:slashsep') regex = '' if aregex == 1: @@ -30,11 +50,13 @@ def CtrlPPyMatch(): # 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]) + 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 + # 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() @@ -43,7 +65,7 @@ def CtrlPPyMatch(): def filename_score(line): # get filename via reverse find to improve performance - slashPos = line.rfind('/') + slashPos = line.rfind(sep) if slashPos != -1: line = line[slashPos + 1:] @@ -52,8 +74,8 @@ def filename_score(line): 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 + score = score + (len(lineLower) + 1) / 100.0 + score = score + (len(line) + 1) / 1000.0 return 1000.0 / score return 0 @@ -63,7 +85,7 @@ def path_score(line): result = prog.search(lineLower) if result: score = result.end() - result.start() + 1 - score = score + ( len(lineLower) + 1 ) / 100.0 + score = score + (len(lineLower) + 1) / 100.0 return 1000.0 / score return 0 @@ -80,11 +102,25 @@ def path_score(line): else: res = [(path_score(line), line) for line in items] - rez.extend([line for score, line in heapq.nlargest(limit, res) if score != 0]) + 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] + vimrez = ['"' + line.replace('\\', '\\\\').replace('"', '\\"') + '"' + for line in rez] vim.command("let s:regex = '%s'" % regex) vim.command('let s:rez = [%s]' % ','.join(vimrez)) + +def _troubleshootUnicodeInputError(): + count = vim.eval('len(a:items)') + for i in range(int(count)): + try: + vim.eval('a:items[%d]' % i) + except: + vim.command( + 'let s:rez = ["Unicode error at item %d: ".a:items[%d],' + '"Line contains invalid characters."]' % + (i, i)) + break diff --git a/autoload/pymatcher.vim b/autoload/pymatcher.vim index fc5f0ec..8eb68c3 100644 --- a/autoload/pymatcher.vim +++ b/autoload/pymatcher.vim @@ -1,7 +1,7 @@ " Python Matcher if !has('python') && !has('python3') - echo 'In order to use pymatcher plugin, you need +python or +python3 compiled vim' + echo 'In order to use pymatcher plugin, you need +python or +python3 compiled vim' endif let s:plugin_path = escape(expand(':p:h'), '\') @@ -12,32 +12,42 @@ else execute 'pyfile ' . s:plugin_path . '/pymatcher.py' endif -function! pymatcher#PyMatch(items, str, limit, mmode, ispath, crfile, regex) +let s:slashsep = '/' +if has('win32') || has('win64') + if !has('+shellslash') || !&shellslash + let s:slashsep = '\' + endif +endif - call clearmatches() +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 + 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 = '' + let s:rez = [] + let s:regex = '' + try execute 'python' . (has('python3') ? '3' : '') . ' CtrlPPyMatch()' + catch + return ['Error running matcher', str(v:exception)] + endtry - let s:matchregex = '\v\c' + let s:matchregex = '\v\c' - if a:mmode == 'filename-only' - let s:matchregex .= '[\^\/]*' - endif + if a:mmode == 'filename-only' + let s:matchregex .= '[\^\/\\]*' + endif - let s:matchregex .= s:regex + let s:matchregex .= s:regex - call matchadd('CtrlPMatch', s:matchregex) + call matchadd('CtrlPMatch', s:matchregex) - return s:rez + return s:rez endfunction