├── .gitignore ├── Makefile ├── README.adoc ├── autoload ├── fruzzy.vim └── fruzzy │ └── ctrlp.vim ├── nim.cfg ├── plugin └── fruzzy.vim ├── python3 ├── ctrlp.py └── fruzzy_installer.py ├── rplugin ├── fruzzy_test.py ├── neomru_file ├── python3 │ ├── denite │ │ └── filter │ │ │ └── matcher │ │ │ └── fruzzymatcher.py │ ├── fruzzy.py │ └── fruzzy_mod.nim ├── qc-fast.py ├── qc-single.py └── unitest.nim └── tests └── test_ctrlp.vim /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.pyd 3 | __pycache__/ 4 | nimcache/ 5 | .cache/ 6 | .benchmarks/ 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | ifdef WINDIR 3 | extn=pyd 4 | else 5 | extn=so 6 | endif 7 | 8 | src = rplugin/python3 9 | test = rplugin 10 | relopt = -d:release -d:removelogger 11 | infoopt = -d:release 12 | native = FUZZY_CMOD=1 13 | win = --os:windows --cpu:amd64 --out:fruzzy_mod.pyd 14 | macos = --os:macosx --cpu:amd64 --out:fruzzy_mod_mac.so 15 | binary=fruzzy_mod.$(extn) 16 | 17 | build-debug: 18 | cd $(src) && \ 19 | nim c --app:lib --out:$(binary) $(infoopt) fruzzy_mod 20 | 21 | build-profile: 22 | cd $(src) && \ 23 | nim c --app:lib --out:$(binary) $(relopt) -d:profile --profiler:on --stackTrace:on fruzzy_mod 24 | 25 | build: 26 | cd $(src) && \ 27 | nim c --app:lib --out:$(binary) $(relopt) fruzzy_mod 28 | 29 | debug-single: build-debug 30 | @echo Testing native mod with minimal file 31 | cd $(test) && \ 32 | python3 qc-single.py 33 | 34 | debug-native: build-debug 35 | cd $(test) && \ 36 | $(native) python3 qc-fast.py 37 | 38 | debug: 39 | cd $(test) && \ 40 | python3 qc-fast.py 41 | 42 | test-py: 43 | cd $(test) && \ 44 | pytest 45 | 46 | test-native: build 47 | cd $(test) && \ 48 | $(native) pytest 49 | 50 | 51 | test: test-py test-native 52 | 53 | macos: 54 | cd $(src) && \ 55 | nim c --app:lib $(macos) $(relopt) fruzzy_mod 56 | win: 57 | ifndef WINDIR 58 | cd $(src) && \ 59 | nim c --app:lib $(win) $(relopt) fruzzy_mod 60 | endif 61 | 62 | rel: 63 | @echo "container image: https://github.com/miyabisun/docker-nim-cross" 64 | @echo 65 | docker run -it --rm -v `pwd`:/usr/local/src nim-cross \ 66 | /bin/bash -c "nimble install -y binaryheap nimpy && make cross" 67 | 68 | all: test build 69 | 70 | # this goal should be run inside docker container 71 | cross: macos win build 72 | ls -al $(src)/fruzzy_mod* 73 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Freaky fast fuzzy Denite/CtrlP matcher for vim/neovim 2 | 3 | This is a matcher plugin for https://github.com/Shougo/denite.nvim[denite.nvim] 4 | and https://github.com/ctrlpvim/ctrlp.vim[CtrlP]. 5 | 6 | Read more at https://blog.rraghur.in/2018/09/27/fruzzy-a-freaky-fast-fuzzy-finder-for-vim/neovim/ 7 | 8 | ## Installation 9 | 10 | In your `.vimrc`: 11 | 12 | ```vim 13 | " vim-plug; for other plugin managers, use what's appropriate 14 | " if you don't want to trust a prebuilt binary, skip the 'do' part 15 | " and build the binaries yourself. Instructions are further down 16 | " and place them in the /path/to/plugin/rplugin/python3 folder 17 | 18 | Plug 'raghur/fruzzy', {'do': { -> fruzzy#install()}} 19 | 20 | " optional - but recommended - see below 21 | let g:fruzzy#usenative = 1 22 | 23 | " When there's no input, fruzzy can sort entries based on how similar they are to the current buffer 24 | " For ex: if you're on /path/to/somefile.h, then on opening denite, /path/to/somefile.cpp 25 | " would appear on the top of the list. 26 | " Useful if you're bouncing a lot between similar files. 27 | " To turn off this behavior, set the variable below to 0 28 | 29 | let g:fruzzy#sortonempty = 1 " default value 30 | 31 | " tell denite to use this matcher by default for all sources 32 | call denite#custom#source('_', 'matchers', ['matcher/fruzzy']) 33 | 34 | " tell CtrlP to use this matcher 35 | let g:ctrlp_match_func = {'match': 'fruzzy#ctrlp#matcher'} 36 | let g:ctrlp_match_current_file = 1 " to include current file in matches 37 | ``` 38 | 39 | ## Native modules 40 | 41 | Native module gives a 10 - 15x speedup over python - ~40-60μs!. Not that you'd notice 42 | it in usual operation (python impl is at 300 - 600μs) 43 | 44 | . Run command `:call fruzzy#install()` if you're not using vim-plug. 45 | . restart nvim 46 | 47 | ### Manual installation 48 | 49 | . Download the module for your platform. If on mac, rename to `fruzzy_mod.so` 50 | . place in `/path/to/fruzzy/rplugin/python3` 51 | . Make sure you set the pref to use native module - `g:fruzzy#usenative=1` 52 | . restart vim/nvim 53 | 54 | ## Troubleshooting/Support 55 | 56 | Raise a ticket - please include the following info: 57 | 58 | .Get the version 59 | . Start vim/nvim 60 | . Activate denite/ctrlp as the case may be. 61 | . Execute `:call fruzzy#version()` - this will print one of the following 62 | .. version number and branch - all is good 63 | .. `purepy` - you're using the pure python version. 64 | .. `modnotfound` - you requested the native mod with `let g:fruzzy#usenative=1` but it could not be loaded 65 | .. `outdated` - native mod was loaded but it's < v0.3. You can update the native mod from the releases. 66 | . include output of the above 67 | 68 | .Describe the issue 69 | . include the list of items 70 | . include your query 71 | . What it did vs what you expected it to do. 72 | 73 | ## Development 74 | 75 | .Build native module 76 | . install nim >= 0.19 77 | . dependencies 78 | .. nimble install binaryheap 79 | .. nimble install nimpy 80 | . `cd rplugin/python3` 81 | . [Windows] `nim c --app:lib --out:fruzzy_mod.pyd -d:release -d:removelogger fruzzy_mod` 82 | . [Linux] `nim c --app:lib --out:fruzzy_mod.so -d:release -d:removelogger fruzzy_mod` 83 | . `-d:removelogger` 84 | - removes all log statements from code. 85 | - When `removelogger` is not defined, only info level logs are emitted 86 | - Debug builds (ie: without `-d:release` flag) also turns on additional debug level logs 87 | 88 | .Running tests 89 | . cd `rplugin` 90 | . `pytest` - run tests with python implementation 91 | . `FUZZY_CMOD=1 pytest` - run tests with native module 92 | 93 | 94 | -------------------------------------------------------------------------------- /autoload/fruzzy.vim: -------------------------------------------------------------------------------- 1 | function! fruzzy#install() 2 | py3 import fruzzy_installer; fruzzy_installer.install() 3 | endfunction 4 | 5 | 6 | function! fruzzy#version() 7 | if !exists("g:fruzzy#version") 8 | echo "version not set. Depending on your usage:" 9 | echo "For denite: make sure that: " 10 | echo " fruzzy is set as the matcher " 11 | echo " Activate denite and filter list once to ensure it's loaded" 12 | echo "For Ctrlp: make sure that: " 13 | echo " matcher is set as fruzzy#ctrlp#matcher" 14 | echo " Ctrlp is activated once to ensure it's loaded" 15 | return 16 | endif 17 | echomsg g:fruzzy#version 18 | return g:fruzzy#version 19 | endfunction 20 | -------------------------------------------------------------------------------- /autoload/fruzzy/ctrlp.vim: -------------------------------------------------------------------------------- 1 | if !has('python3') 2 | echom 'fruzzy#ctrlp requires python3!' 3 | finish 4 | endif 5 | 6 | let s:root_dir = escape(expand(':p:h:h:h'), '\') 7 | unsilent execute 'py3file ' . s:root_dir . '/python3/ctrlp.py' 8 | 9 | function! fruzzy#ctrlp#matcher(items, str, limit, mmode, ispath, crfile, regex) abort 10 | call clearmatches() 11 | 12 | let input = { 13 | \ 'query': a:str, 14 | \ 'candidates': a:items, 15 | \ 'limit': a:limit, 16 | \ 'ispath': a:ispath, 17 | \ 'current': '', 18 | \} 19 | 20 | if a:ispath && !get(g:, 'ctrlp_match_current_file') 21 | if filereadable(expand(a:crfile)) 22 | let input.current = resolve(a:crfile) 23 | else 24 | let current = resolve(getcwd() . '/' . a:crfile) 25 | if getftype(current) ==# 'file' 26 | let input.current = resolve(a:crfile) 27 | endif 28 | endif 29 | endif 30 | 31 | if empty(a:str) 32 | let matches = a:items[0:(a:limit)] 33 | else 34 | call matchadd('CtrlPMatch', 35 | \ '\v' . substitute(a:str, '.', '\0[^\0]{-}', 'g')[:-8]) 36 | call matchadd('CtrlPLinePre', '^>') 37 | try 38 | let matches = py3eval('fruzzy_match()') 39 | catch /E688/ 40 | return [] 41 | endtry 42 | endif 43 | 44 | if !empty(input.current) 45 | call remove(matches, index(matches, input.current)) 46 | endif 47 | 48 | return matches 49 | endfunction 50 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | # mingw 2 | amd64.windows.gcc.path = "/usr/bin/" 3 | amd64.windows.gcc.exe = "x86_64-w64-mingw32-gcc" 4 | amd64.windows.gcc.linkerexe = "x86_64-w64-mingw32-gcc" 5 | amd64.windows.gcc.options.linker = "" 6 | 7 | # macos 8 | amd64.macosx.clang.path = "/opt/osxcross/target/bin/" 9 | amd64.macosx.clang.exe = "o64-clang" 10 | amd64.macosx.clang.linkerexe = "o64-clang" 11 | 12 | # linux (musl) 13 | @if musl: 14 | passL = "-static" 15 | gcc.exe = "/usr/bin/musl-gcc" 16 | gcc.linkerexe = "/usr/bin/musl-gcc" 17 | @end 18 | -------------------------------------------------------------------------------- /plugin/fruzzy.vim: -------------------------------------------------------------------------------- 1 | let s:is_win = has('win32') || has('win64') 2 | let s:root = expand(':h:h') 3 | 4 | if exists("g:loaded_fruzzy") 5 | finish 6 | endif 7 | 8 | let g:loaded_fruzzy = 1 9 | if !exists("g:fruzzy#usenative") 10 | let g:fruzzy#usenative = 0 11 | endif 12 | 13 | -------------------------------------------------------------------------------- /python3/ctrlp.py: -------------------------------------------------------------------------------- 1 | """A fruzzy wrapper for ctrlp support.""" 2 | from __future__ import print_function 3 | 4 | import sys 5 | 6 | # pylint: disable=import-error, wrong-import-position 7 | import vim 8 | sys.path.append(vim.eval('s:root_dir') + '/rplugin/python3') 9 | 10 | USE_NATIVE = vim.vars.get('fruzzy#usenative', 0) 11 | if USE_NATIVE: 12 | try: 13 | from fruzzy_mod import scoreMatchesStr 14 | import fruzzy_mod 15 | if 'version' in dir(fruzzy_mod): 16 | vim.vars["fruzzy#version"] = fruzzy_mod.version() 17 | else: 18 | vim.vars["fruzzy#version"] = "outdated" 19 | except ImportError: 20 | vim.vars["fruzzy#version"] = "modnotfound" 21 | from fruzzy import scoreMatches 22 | USE_NATIVE = False 23 | else: 24 | from fruzzy import scoreMatches 25 | vim.vars["fruzzy#version"] = "purepy" 26 | # pylint: enable=import-error, wrong-import-position 27 | 28 | 29 | def fruzzy_match(): 30 | """The wrapper for fruzzy matcher""" 31 | args = vim.eval('input') 32 | args['limit'] = int(args['limit']) 33 | args['ispath'] = int(args['ispath']) 34 | 35 | if USE_NATIVE: 36 | output = scoreMatchesStr(args['query'], 37 | args['candidates'], 38 | args['current'], 39 | args['limit'], 40 | args['ispath']) 41 | matches = [args['candidates'][i[0]] for i in output] 42 | else: 43 | matches = [c[0] for c in scoreMatches(**args)] 44 | 45 | return matches 46 | -------------------------------------------------------------------------------- /python3/fruzzy_installer.py: -------------------------------------------------------------------------------- 1 | import json 2 | import urllib.request 3 | import sys 4 | from os import path 5 | 6 | 7 | def install(): 8 | def get_asset(assets): 9 | def first(items): 10 | return next(items, None) 11 | 12 | if sys.platform == "win32": 13 | name = "fruzzy_mod.pyd" 14 | elif sys.platform == "linux": 15 | name = "fruzzy_mod.so" 16 | elif sys.platform == "darwin": 17 | name = "fruzzy_mod_mac.so" 18 | else: 19 | print("unknown platform %s" % sys.platform) 20 | 21 | return first(filter(lambda a: a["name"] == name, assets)) 22 | 23 | _json = json.loads(urllib.request.urlopen(urllib.request.Request( 24 | 'https://api.github.com/repos/raghur/fruzzy/releases/latest', 25 | headers={'Accept': 'application/vnd.github.v3+json'},)) 26 | .read().decode()) 27 | asset = get_asset(_json["assets"]) 28 | # print(__file__) 29 | dirname = path.normpath(path.join(path.dirname(__file__), "..")) 30 | outfile = path.join(dirname, "rplugin/python3/fruzzy_mod") 31 | extn = ".pyd" if sys.platform == "win32" else ".so" 32 | altfile = outfile + ".1" + extn 33 | outfile = outfile + extn 34 | try: 35 | print("fruzzy: downloading %s to %s" % (asset['browser_download_url'], 36 | asset['name'])) 37 | urllib.request.urlretrieve(asset['browser_download_url'], outfile) 38 | print("native mod %s installed - restart vim/nvim if needed" % outfile) 39 | except PermissionError as e: 40 | # happens on windows if the mod is loaded 41 | print("fruzzy: unable to write to file: ", e) 42 | print("fruzzy: saving as %s" % altfile) 43 | urllib.request.urlretrieve(asset['browser_download_url'], altfile) 44 | print("fruzzy: Exit vim/nvim & rename %s file to %s " % 45 | (altfile, outfile)) 46 | 47 | 48 | if __name__ == "__main__": 49 | install() 50 | 51 | -------------------------------------------------------------------------------- /rplugin/fruzzy_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from python3.fruzzy import fuzzyMatches, isMatch 4 | useNative = False 5 | if os.getenv("FUZZY_CMOD"): 6 | from python3.fruzzy_mod import scoreMatchesStr, baseline 7 | useNative = True 8 | else: 9 | from python3.fruzzy import scoreMatches 10 | 11 | 12 | def scoreMatchesProxy(q, c, limit=10, current="", key=None, ispath=True): 13 | def idfn(x): 14 | return x 15 | if key is None: 16 | key = idfn 17 | if useNative: 18 | idxArr = scoreMatchesStr(q, [key(x) for x in c], 19 | current, limit, ispath) 20 | results = [] 21 | for i in idxArr: 22 | results.append((c[i[0]], i[1])) 23 | return results 24 | else: 25 | return scoreMatches(q, c, current, limit, key, ispath) 26 | 27 | 28 | lines = [] 29 | with open("neomru_file") as fh: 30 | lines = [line.strip() for line in fh.readlines()] 31 | 32 | 33 | 34 | def test_is_match(): 35 | c = "D:/code/go/src/github.com/raghur/fuzzy-denite/lib/api.pb.go" 36 | match, positions, *rest = isMatch("api", c) 37 | assert match 38 | 39 | 40 | def test_match_should_find_at_end(): 41 | c = "D:/code/go/src/github.com/raghur/fuzzy-denite/lib/api.pb.go" 42 | match, positions, *rest = isMatch("api.pb.go", c) 43 | assert match 44 | 45 | def test_match_should_find_at_end1(): 46 | c = "D:/code/go/src/github.com/raghur/fuzzy-denite/lib/test_main.py" 47 | match, positions, *rest = isMatch("test", c) 48 | assert match 49 | 50 | 51 | def test_must_find_matches_should_work_correctly_when_query_has_char_repeated(): 52 | # in this case t-es-t - the t repeats 53 | # when looking right, we should not be bounded by the last found result 54 | # endpoint 55 | c = ["D:/code/go/src/github.com/raghur/fuzzy-denite/lib/test_main.py"] 56 | results = list(fuzzyMatches("test", c, "", 10)) 57 | assert len(results) == 1 58 | 59 | def test_must_find_matches_should_work_when_match_extends_to_the_right(): 60 | c = ["xxxxxxx_a_g_c_g_d_e_f"] 61 | results = list(fuzzyMatches("gcf", c, "", 10)) 62 | assert len(results) == 1 63 | 64 | def test_must_find_matches_after_failed_partial_matches(): 65 | lines = ["/this/a/is/pi/ap/andnomatchlater"] 66 | results = list(scoreMatchesProxy("api", lines, 10, ispath=True)) 67 | assert len(results) == 1 68 | 69 | def test_must_search_case_insensitively(): 70 | results = list(scoreMatchesProxy("ME", lines, 10, ispath=True)) 71 | assert results[0][0].endswith("README.md") 72 | 73 | def test_must_score_camel_case_higher(): 74 | c = ["/this/is/fileone.txt", "/this/is/FileOne.txt"] 75 | results = list(scoreMatchesProxy("fo", c, 10, ispath=True)) 76 | assert results[0][0].endswith("FileOne.txt") 77 | 78 | 79 | def test_must_score_camel_case_higher1(): 80 | results = list(scoreMatchesProxy("rv", lines, 10, ispath=True)) 81 | assert results[0][0].endswith("RelVer") 82 | 83 | 84 | def test_measure_baseline_native_call(benchmark): 85 | if useNative: 86 | results = benchmark(lambda: list(baseline(lines))) 87 | assert len(results) == len(lines) 88 | else: 89 | pytest.skip("baseline only measured when using native module") 90 | 91 | 92 | def test_must_prefer_match_at_end(benchmark): 93 | results = benchmark(lambda: list(scoreMatchesProxy("api", lines, 10, ispath=True))) 94 | assert results[0][0].endswith("api.pb.go") 95 | 96 | 97 | def test_must_prefer_match_after_separators(benchmark): 98 | results = benchmark(lambda: list(scoreMatchesProxy("rct", lines, 10, ispath=True))) 99 | assert results[0][0].endswith("root_cmd_test.go") 100 | 101 | 102 | def test_must_prefer_longer_match(benchmark): 103 | results = benchmark(lambda: list(scoreMatchesProxy("fuzz", lines, 10, ispath=True))) 104 | assert results[0][0].endswith("pyfuzzy.py") 105 | assert results[1][0].endswith("gofuzzy.py") 106 | 107 | 108 | def test_must_score_cluster_higher(benchmark): 109 | results = benchmark(lambda: list(scoreMatchesProxy("cli", lines, 10, ispath=True))) 110 | assert results[0][0].endswith("cli.go") 111 | assert results[1][0].endswith("client.go") 112 | 113 | 114 | def test_must_ignore_position_for_non_file_matching(): 115 | c = ["/this/is/fileone.txt", "/that/was/FileOne.txt"] 116 | results = list(scoreMatchesProxy("is", c, 10, ispath=False)) 117 | assert results[0][0].endswith("fileone.txt") 118 | 119 | 120 | def test_for_bug_08(): 121 | c = ['.gitignore', 'README.adoc', 'python3/ctrlp.py', 122 | 'plugin/fruzzy.vim', 'autoload/fruzzy.vim', 123 | 'autoload/fruzzy/ctrlp.vim', 'rplugin/python3/fruzzy.py', 124 | 'rplugin/python3/qc-fast.py', 'python3/fruzzy_installer.py', 125 | 'rplugin/python3/neomru_file', 'rplugin/python3/qc-single.py', 126 | 'rplugin/python3/fruzzy_mod.nim', 127 | 'rplugin/python3/fruzzy_test.py', 128 | 'rplugin/python3/denite/filter/matcher/fruzzymatcher.py'] 129 | results = list(scoreMatchesProxy("fmf", c, 10, ispath=True)) 130 | assert results[0][0] == \ 131 | 'rplugin/python3/denite/filter/matcher/fruzzymatcher.py' 132 | 133 | 134 | def test_must_return_topN_when_query_and_current_are_empty(): 135 | c = ["/this/is/fileone.txt", "/that/was/FileOne.txt"] 136 | results = list(scoreMatchesProxy("", c, 10, ispath=False)) 137 | assert len(results) == 2 138 | assert results[0][0] == c[0] 139 | assert results[1][0] == c[1] 140 | 141 | 142 | def test_must_return_match_when_query_match_char_is_to_right(): 143 | c = ["D:/code/go/src/github.com/raghur/fuzzy-denite/lib/api.pb.go"] 144 | results = list(scoreMatchesProxy("gbf", c, 10, ispath=False)) 145 | assert results[0][0] == c[0] 146 | -------------------------------------------------------------------------------- /rplugin/neomru_file: -------------------------------------------------------------------------------- 1 | 0.3.0 2 | d:/code/go/src/github.com/raghur/fuzzy-denite/scratch/pyfuzzy.py 3 | C:/users/raghuramanr/.vim/ginit.vim 4 | C:/Users/raghuramanr/.vim/bundle/fuzzy-denite/scratch/sender.py 5 | d:/code/go/src/github.com/raghur/fuzzy-denite/cmd/match.go 6 | C:/users/raghuramanr/.cache/neomru/file 7 | C:/users/raghuramanr/.vim/bundle/neomru.vim/plugin/neomru.vim 8 | d:/code/go/src/github.com/raghur/fuzzy-denite/exp/cli.go 9 | C:/users/raghuramanr/.vim/autoload/utils.vim 10 | C:/users/raghuramanr/.vim/hibaml60075.vim 11 | D:/code/go/src/github.com/raghur/fuzzy-denite/cmd/server.go 12 | d:/code/go/src/github.com/raghur/fuzzy-denite/main.go 13 | d:/code/go/src/github.com/raghur/fuzzy-denite/lib/server.go 14 | D:/code/go/src/github.com/bytesparadise/libasciidoc/cmd/libasciidoc/root_cmd_test.go 15 | C:/Users/raghuramanr/.gitignore 16 | C:/Users/raghuramanr/.gitconfig 17 | C:/Users/raghuramanr/.gitignore_global 18 | D:/code/vim/build-all.bat 19 | D:/code/proglang/PartC/hw6assignment.rb 20 | C:/Users/raghuramanr/ctags.cnf 21 | d:/code/go/src/github.com/raghur/fuzzy-denite/lib/api.pb.go 22 | C:/Users/raghuramanr/.vim/_vimrc 23 | D:/code/vim/vimcopy.cmd 24 | D:/code/vim/buildopts.cmd 25 | D:/code/cpsm/cmake/FindICU.cmake 26 | D:/code/cpsm/cmake/FindPythonConfig.cmake 27 | D:/code/cpsm/build/Makefile 28 | D:/code/cpsm/build/CMakeFiles/cpsm_py.dir/link.txt 29 | D:/code/easyblogger/blogger/main.py 30 | D:/code/easyblogger/blogger/blogger.py 31 | D:/code/easyblogger/setup.py 32 | d:/code/easyblogger/README.md 33 | D:/code/easyblogger/tox.ini 34 | D:/code/go/src/github.com/raghur/goblogger/.gitlab-ci.yml 35 | D:/code/easyblogger/tests/test_contentargparser.py 36 | D:/code/easyblogger/bug46.md 37 | D:/code/easyblogger/README.rst 38 | D:/code/easyblogger/tests/test_update_delete.py 39 | D:/code/easyblogger/bug.md 40 | D:/code/easyblogger/requirements.txt 41 | D:/code/easyblogger/tests/test_main.py 42 | d:/code/mermaid-filter/test.rst 43 | d:/code/mermaid-filter/test.md 44 | D:/code/mermaid-filter/package.json 45 | D:/code/mermaid-filter/index.js 46 | D:/code/go/src/github.com/raghur/goblogger/Taskfile.yml 47 | D:/code/go/src/github.com/raghur/goblogger/bloggerOps.go 48 | D:/code/go/src/github.com/raghur/goblogger/client_test.go 49 | d:/code/go/src/github.com/raghur/goblogger/.gitignore 50 | d:/code/go/src/github.com/raghur/goblogger/client.go 51 | d:/code/go/src/github.com/raghur/goblogger/cmd/file.go 52 | d:/code/go/src/github.com/raghur/goblogger/mock_bloggerOps_test.go 53 | d:/code/go/src/github.com/raghur/goblogger/testdata/file01.asciidoc 54 | D:/code/go/src/github.com/raghur/goblogger/auth.go 55 | d:/code/go/src/github.com/raghur/goblogger/vendor/google.golang.org/api/blogger/v3/blogger-gen.go 56 | D:/code/go/src/github.com/raghur/goblogger/transform_content_test.go 57 | D://code//go//src//github.com//raghur//goblogger//client_test.go 58 | D:/code/go/src/github.com/raghur/goblogger/cmd/listblogs.go 59 | D:/code/go/src/github.com/raghur/fuzzy-denite/rplugin/python3/denite/filter/matcher/gofuzzy.py 60 | D:/code/powertool/setup.py 61 | D:/code/go/src/github.com/raghur/goblogger/cmd/post.go 62 | D:/code/go/src/github.com/raghur/goblogger/cmd/delete.go 63 | D:/code/go/src/github.com/raghur/goblogger/cmd/get.go 64 | D:/code/go/src/github.com/raghur/goblogger/cmd/auth.go 65 | d:/code/go/src/github.com/raghur/goblogger/frontmatter_test.go 66 | D:/code/go/src/github.com/raghur/goblogger/frontmatter.go 67 | C:/Users/raghuramanr/AppData/Local/nvim/UltiSnips/go.snippets 68 | D:/code/go/src/github.com/raghur/goblogger/client_secret.json 69 | C:/Users/raghuramanr/blog/.gitlab-ci.yml 70 | C:/Users/raghuramanr/blog/.asciidoctor 71 | D:/code/go/src/github.com/raghur/fuzzy-denite/Taskfile.yml 72 | C:/Users/raghuramanr/.vim/bundle/fuzzy-denite/Taskfile.yml 73 | D:/code/go/src/github.com/raghur/fuzzy-denite/download.py 74 | D:/code/go/src/github.com/raghur/fuzzy-denite/RelVer 75 | C:/Users/raghuramanr/blog/assets/css/style_custom.css 76 | C:/Users/raghuramanr/blog/assets/css/asciidoctor.css 77 | C:/users/raghuramanr/blog/content/post/wireguard-vpn.adoc 78 | C:/users/raghuramanr/algorithms.adoc 79 | C:/Users/raghuramanr/.inputrc 80 | D:/code/hugo-asciidoctor/README.adoc 81 | D:/code/hugo-asciidoctor/Dockerfile 82 | C:/Users/raghuramanr/blog/layouts/partials/bloc/footer/credits_footline.html 83 | C:/Users/raghuramanr/blog/layouts/partials/base/footer.html 84 | C:/Users/raghuramanr/blog/build.sh 85 | d:/code/hugo-asciidoctor/asciidoctor 86 | C:/users/raghuramanr/girmiti.adoc 87 | C:/Users/raghuramanr/blog/assets/css/google-fonts.css 88 | D:/code/hugo-asciidoctor/make.bat 89 | C:/users/raghuramanr/blog/content/about.asciidoc 90 | C:/users/raghuramanr/blog/content/post/countdownevent-for-synchronization.adoc 91 | C:/users/raghuramanr/blog/content/post/blog-reboot.adoc 92 | C:/users/raghuramanr/blog/content/post/Micro.containers.with.iron.io.base.images.adoc 93 | C:/users/raghuramanr/blog/content/post/Dealing-with-carrier-grade-NAT.adoc 94 | C:/users/raghuramanr/blog/content/post/Eventstore.on.Azure.AKS.adoc 95 | C:/users/raghuramanr/blog/content/post/git.log.to.rule.them.all.adoc 96 | C:/users/raghuramanr/blog/content/post/introducing.vim-ghost.adoc 97 | C:/users/raghuramanr/blog/content/post/Firefox-awesomebar-tab-switching.adoc 98 | C:/users/raghuramanr/blog/content/post/program-mindstorms-ev3-on-linux.adoc 99 | C:/users/raghuramanr/blog/content/post/minikube.on.win10.hyperv.adoc 100 | C:/users/raghuramanr/blog/content/post/Moving-to-cert-manager.adoc 101 | C:/users/raghuramanr/blog/content/post/Frontend-development-in-2017.adoc 102 | -------------------------------------------------------------------------------- /rplugin/python3/denite/filter/matcher/fruzzymatcher.py: -------------------------------------------------------------------------------- 1 | from denite.filter.base import Base 2 | from denite.util import convert2fuzzy_pattern 3 | from denite.util import relpath 4 | import os 5 | import sys 6 | import logging 7 | from os import path 8 | 9 | logger = logging.getLogger() 10 | pkgPath = os.path.dirname(__file__).split(os.path.sep)[:-3] 11 | pkgPath = os.path.sep.join(pkgPath) 12 | if pkgPath not in sys.path: 13 | logger.debug("added %s to sys.path" % pkgPath) 14 | sys.path.insert(0, pkgPath) 15 | 16 | import fruzzy 17 | 18 | class Filter(Base): 19 | 20 | def __init__(self, vim): 21 | super().__init__(vim) 22 | 23 | self.name = 'matcher/fruzzy' 24 | self.description = 'fruzzy - freakishly fast fuzzy matcher' 25 | self.useNative = False 26 | if self.vim.vars.get("fruzzy#usenative", 0): 27 | try: 28 | import fruzzy_mod 29 | self.nativeMethod = fruzzy_mod.scoreMatchesStr 30 | self.useNative = True 31 | if 'version' not in dir(fruzzy_mod): 32 | self.debug("You have an old version of the native module") 33 | self.debug("please execute :call fruzzy#install()") 34 | self.vim.vars["fruzzy#version"] = "outdated" 35 | else: 36 | self.vim.vars["fruzzy#version"] = fruzzy_mod.version() 37 | except ImportError: 38 | self.debug("Native module requested but module was not found") 39 | self.debug("falling back to python implementation") 40 | self.debug("execute :call fruzzy#install() to install the native module") 41 | self.debug("and then check if you have fruzzy_mod.so or fruzzy_mod.pyd at %s" % 42 | pkgPath) 43 | self.vim.vars["fruzzy#version"] = "modnotfound" 44 | self.useNative = False 45 | else: 46 | self.vim.vars["fruzzy#version"] = "purepy" 47 | # self.debug("usenative: %s" % self.useNative) 48 | 49 | def filter(self, context): 50 | candidates = context['candidates'] 51 | qry = context['input'] 52 | # self.debug("source: %s" % candidates[0]['source_name']) 53 | # self.debug("context: %s" % context) 54 | ispath = candidates and 'action__path' in candidates[0] 55 | # self.debug("candidates %s %s" % (qry, len(candidates))) 56 | limit = context['winheight'] 57 | limit = int(limit) if isinstance(limit, str) else limit 58 | buffer = context['bufnr'] 59 | buffer = int(buffer) if isinstance(buffer, str) else buffer 60 | sortOnEmptyQuery = self.vim.vars.get("fruzzy#sortonempty", 1) 61 | results = self.scoreMatchesProxy(qry, candidates, 62 | limit, 63 | key=lambda x: x['word'], 64 | ispath=ispath, 65 | buffer=buffer, 66 | sortonempty=sortOnEmptyQuery) 67 | # self.debug("results %s" % results) 68 | rset = [w[0] for w in results] 69 | # self.debug("rset %s" % rset) 70 | return rset 71 | 72 | def scoreMatchesProxy(self, q, c, limit, key=None, ispath=True, buffer=0, 73 | sortonempty=True): 74 | relname = "" 75 | if sortonempty and ispath and buffer > 0 and q == "": 76 | fname = self.vim.buffers[buffer].name 77 | relname = relpath(self.vim, fname) 78 | # self.debug("sort on empty: %s, '%s'" % (sortonempty, relname)) 79 | if self.useNative: 80 | idxArr = self.nativeMethod(q, [key(d) for d in c], 81 | relname, limit, ispath) 82 | results = [] 83 | for i in idxArr: 84 | idx, score = i 85 | results.append((c[idx], score)) 86 | return results 87 | else: 88 | return fruzzy.scoreMatches(q, c, relname, limit, key, ispath) 89 | 90 | def convert_pattern(self, input_str): 91 | p = convert2fuzzy_pattern(input_str) 92 | # self.debug("pattern: %s : %s" % (input_str, p)) 93 | return p 94 | -------------------------------------------------------------------------------- /rplugin/python3/fruzzy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import heapq 3 | import itertools 4 | import logging 5 | 6 | 7 | sep = '-/\_. ' 8 | 9 | 10 | def idfn(x): 11 | return x 12 | 13 | 14 | def scorer(x, key, ispath=True): 15 | """ 16 | :x: - tuple of (item, positions, clusterScore, endScore, sepScore) 17 | - item - the item itself 18 | - positions - indices where each char matched 19 | - clusterScore - How closely are matched chars clustered - 0 if 20 | consecutive 21 | - sepScore - how many matches were after separators (count) 22 | - camelCaseScore - how many matched chars were camelcase 23 | :key: - key func that when applied to x[0] returns the search string 24 | """ 25 | candidate = key(x[0]) 26 | lqry = len(x[1]) 27 | lcan = len(candidate) 28 | 29 | position_boost, end_boost, filematchBoost = 0, 0, 0 30 | if ispath: 31 | # print("item is", candidate) 32 | # how close to the end of string as pct 33 | position_boost = 100 * (x[1][0]//lcan) 34 | # absolute value of how close it is to end 35 | end_boost = (100 - (lcan - x[1][0])) * 2 36 | 37 | lastPathSep = candidate.rfind("\\") 38 | if lastPathSep == -1: 39 | lastPathSep = candidate.rfind("/") 40 | fileMatchCount = sum(1 for i in itertools.filterfalse( 41 | lambda p: p < lastPathSep, x[1])) 42 | # print(candidate, lastPathSep, x[1], fileMatchCount) 43 | filematchBoost = 100 * fileMatchCount // lqry 44 | 45 | # how closely are matches clustered 46 | cluster_boost = 100 * (1 - x[2]//lcan) * 4 47 | 48 | # boost for matches after separators 49 | # weighted by length of query 50 | sep_boost = 100 * x[3]//lqry * 75//100 51 | 52 | # boost for camelCase matches 53 | # weighted by lenght of query 54 | camel_boost = 100 * x[4]//lqry 55 | 56 | return position_boost + end_boost + filematchBoost + \ 57 | cluster_boost + sep_boost + camel_boost 58 | # return position_boost + cluster_boost + sep_boost + camel_boost 59 | 60 | 61 | def scoreMatches(query, candidates, current, limit, key=None, ispath=True): 62 | """Find fuzzy matches among given candidates 63 | 64 | :query: the fuzzy string 65 | :candidates: list of items 66 | :limit: max number of resuls 67 | :key: func to extract string to match the query against. 68 | :returns: list of tuples 69 | (x, postions, clusterScore, sepScore, camelCaseScore, rank) where 70 | - x - the matched item in candidates 71 | - postions - array of ints of matched char positions 72 | - clusterScore - how closely are the positions clustered. 0 means 73 | consecutive. Lower is better. 74 | - sepScore - how many matches are after separators. higher is better. 75 | - camelCaseScore - how many matches were camelCase. higher is better 76 | - rank - cumulative rank calculated by scorer, higher is better 77 | list length is limited to limit 78 | """ 79 | # TODO: implement levenshtein but not at the cost of complicating the 80 | # installation 81 | if query == "": 82 | return ((c, 0) for c in candidates) 83 | key = idfn if not key else key 84 | matches = fuzzyMatches(query, candidates, current, limit * 5, key, ispath) 85 | return heapq.nlargest(limit, matches, key=lambda x: x[5]) 86 | 87 | 88 | def isMatch(query, candidate): 89 | """check if candidate matches query fuzzily 90 | 91 | :query: the fuzzy string 92 | :candidate: the string to search in 93 | :returns: tuple (didMatch, postions, clusterScore, sepScore, camelCaseScore) 94 | - Other values have relevance only if didMatch is true. 95 | """ 96 | def walkString(query, candidate, left, right): 97 | logging.debug("Call: %s, %d, %d", query, left, right) 98 | orig = candidate 99 | candidate = candidate.lower() 100 | query = query.lower() 101 | matchPos = [] 102 | first = True 103 | sepScore = 0 104 | clusterScore = 0 105 | camelCaseScore = 0 106 | for i, c in enumerate(query): 107 | logging.debug("Looking %d, %s, %d, %d", i, c, left, right) 108 | if first: 109 | pos = candidate.rfind(c, left, right) 110 | else: 111 | pos = candidate.find(c, left) 112 | logging.debug("Result %d, %d, %s", i, pos, c) 113 | if pos == -1: 114 | return (False, matchPos) 115 | else: 116 | if pos < len(orig) - 1: 117 | nextChar = orig[pos + 1] 118 | sepScore = sepScore + 1 if nextChar in sep else sepScore 119 | if pos > 0: 120 | prevChar = orig[pos -1] 121 | sepScore = sepScore + 1 if prevChar in sep else sepScore 122 | camelCaseScore = camelCaseScore + 1 if ord(orig[pos]) < 97 \ 123 | and ord(prevChar) >= 97 else camelCaseScore 124 | if pos == 0: 125 | sepScore = sepScore + 1 126 | camelCaseScore = camelCaseScore + 1 127 | matchPos.append(pos) 128 | if len(matchPos) > 1: 129 | clusterScore = clusterScore + matchPos[-1] - matchPos[-2] - 1 130 | left = pos + 1 131 | first = False 132 | return (True, matchPos, clusterScore, sepScore, camelCaseScore) 133 | 134 | didMatch = False 135 | l, r = 0, len(candidate) 136 | while not didMatch: 137 | didMatch, positions, *rest = walkString(query, candidate, l, r) 138 | if didMatch: 139 | break # all done 140 | if not positions: 141 | break # all done too - first char didn't match 142 | 143 | # resume search - start looking left from this position onwards 144 | r = positions[0] 145 | return (didMatch, positions, *rest) 146 | 147 | 148 | def fuzzyMatches(query, candidates, current, limit, key=None, ispath=True): 149 | """Find fuzzy matches among given candidates 150 | 151 | :query: the fuzzy string 152 | :candidates: list of items 153 | :limit: max number of resuls 154 | :key: func to extract string to match the query against. 155 | :returns: list of tuple of 156 | (x, postions, clusterScore, sepScore, camelCaseScore, rank) where 157 | - x - the matched item in candidates 158 | - postions - array of ints of matched char positions 159 | - clusterScore - how closely are the positions clustered. 0 means 160 | consecutive. Lower is better. 161 | - sepScore - how many matches are after separators. higher is better. 162 | - camelCaseScore - how many matches were camelCase. higher is better 163 | - rank - cumulative rank calculated by scorer, higher is better 164 | """ 165 | key = idfn if not key else key 166 | findFirstN = True 167 | count = 0 168 | logging.debug("query: %s", query) 169 | for x in candidates: 170 | s = key(x) 171 | if ispath and s == current: 172 | continue 173 | logging.debug("Candidate %s", x) 174 | didMatch, positions, *rest = isMatch(query, s) 175 | if didMatch: 176 | count = count + 1 177 | yield (x, positions, *rest, scorer((x, positions, *rest), key, 178 | ispath)) 179 | if findFirstN and count == limit: 180 | return 181 | 182 | 183 | def usage(): 184 | """TODO: Docstring for usage. 185 | :returns: TODO 186 | 187 | """ 188 | print("usage") 189 | 190 | 191 | if __name__ == "__main__": 192 | if len(sys.argv) == 1: 193 | usage() 194 | exit(0) 195 | 196 | file = "neomru_file" 197 | query = sys.argv[1] 198 | if len(sys.argv) == 3: 199 | file = sys.argv[1] 200 | query = sys.argv[2] 201 | 202 | with open(file) as fh: 203 | logging.basicConfig(level=logging.DEBUG) 204 | lines = (line.strip() for line in fh.readlines()) 205 | for x in scoreMatches(query, lines, "", 10): 206 | print(x) 207 | 208 | -------------------------------------------------------------------------------- /rplugin/python3/fruzzy_mod.nim: -------------------------------------------------------------------------------- 1 | import nimpy 2 | import strutils 3 | import binaryheap 4 | import logging 5 | import strformat 6 | import sequtils 7 | import tables 8 | import os 9 | import system 10 | import std/editdistance 11 | when defined(profile): 12 | import nimprof 13 | 14 | proc getVersion(): string {.compileTime.}= 15 | let ver = staticExec("git describe --tags --always --dirty").strip() 16 | # let cTime = format(times.now(), "yyyy-MM-dd hh:mm:ss") 17 | let branch = staticExec("git rev-parse --abbrev-ref HEAD").strip() 18 | var options:seq[string] = newSeq[string]() 19 | if not defined(removelogger): 20 | options.add("info") 21 | if defined(profile): 22 | options.add("profile") 23 | if not defined(release): 24 | options.add("debug") 25 | else: 26 | options.add("release") 27 | let optionsStr = options.join(",") 28 | 29 | return &"rev: {ver} on branch: {branch} with options: {optionsStr}" 30 | 31 | let L = newConsoleLogger(levelThreshold = logging.Level.lvlDebug) 32 | addHandler(L) 33 | 34 | const sep:string = "-/\\_. " 35 | const VERSION = getVersion() 36 | 37 | template info(args: varargs[string, `$`]) = 38 | when not defined(removelogger): 39 | info(args) 40 | 41 | template l(fmt: string) = 42 | when not defined(removelogger): 43 | debug(&fmt) 44 | type 45 | Match = object 46 | found:bool 47 | positions: seq[int] 48 | sepScore, clusterScore, camelCaseScore: int 49 | 50 | proc scorer(x: var Match, candidate:string, ispath:bool=true): int {.inline.}= 51 | let lqry = len(x.positions) 52 | let lcan = len(candidate) 53 | 54 | var 55 | position_boost = 0 56 | end_boost = 0 57 | filematchBoost = 0 58 | if ispath: 59 | # print("item is", candidate) 60 | # how close to the end of string as pct 61 | position_boost = 100 * (x.positions[0] div lcan) 62 | # absolute value of how close it is to end 63 | end_boost = (100 - (lcan - x.positions[0])) * 2 64 | 65 | var lastSep = candidate.rfind(os.DirSep) 66 | if lastSep == -1: 67 | lastSep = candidate.rfind(os.AltSep) 68 | let fileMatchCount = len(sequtils.filter(x.positions, proc(p: int):bool = p > lastSep)) 69 | info &"fileMatchCount: {candidate}, {lastSep}, {x.positions}, {fileMatchCount}" 70 | filematchBoost = 100 * fileMatchCount div lqry 71 | 72 | # how closely are matches clustered 73 | let cluster_boost = 100 * (1 - x.clusterScore div lcan) * 4 74 | 75 | # boost for matches after separators 76 | # weighted by length of query 77 | let sep_boost = (100 * x.sepScore div lqry) * 75 div 100 78 | 79 | # boost for camelCase matches 80 | # weighted by lenght of query 81 | let camel_boost = 100 * x.camelCaseScore div lqry 82 | 83 | return position_boost + end_boost + filematchBoost + cluster_boost + sep_boost + camel_boost 84 | 85 | proc walkString(q, orig: string, left, right: int, m: var Match) {.inline.}= 86 | l "CALL==> {q} {left} {right}, {orig[left..right]}" 87 | if left > right or right == 0: 88 | m.found = false 89 | return 90 | let candidate = strutils.toLowerAscii(orig) 91 | let query = strutils.toLowerAscii(q) 92 | var first = true 93 | var pos:int = -1 94 | var l = left 95 | for i, c in query: 96 | if first: 97 | l "ScanBack: {i}, {c}, candidate[{l}..{right}]: {candidate[l..right]}" 98 | pos = strutils.rfind(candidate, c, l, right) 99 | else: 100 | l "ScanFwd: {i}, {c}, candidate[{l}..{right}]: {candidate[l..right]}" 101 | pos = strutils.find(candidate, c, l) 102 | l "Result: {i}, {pos}, {c}" 103 | if pos == -1: 104 | return 105 | if pos < candidate.high: 106 | let nextChar = orig[pos + 1] 107 | if nextChar in sep: 108 | m.sepScore.inc 109 | if pos == 0: 110 | m.sepScore.inc 111 | m.camelCaseScore.inc 112 | else: 113 | var prevChar = orig[pos - 1] 114 | if prevChar in sep: 115 | m.sepScore.inc 116 | if ord(orig[pos]) < ord('Z') and ord(prevChar) >= ord('a'): 117 | m.camelCaseScore.inc 118 | m.positions[i] = pos 119 | if i == 0: 120 | let qlen = q.len 121 | m.clusterScore = -1 * ((pos * qlen) + (qlen * (qlen-1) div 2)) 122 | m.clusterScore.inc(pos) 123 | l = pos + 1 124 | first = false 125 | m.found = true 126 | return 127 | 128 | proc resetMatch(m: var Match, l:int) {.inline.} = 129 | m.found = false 130 | for j in 0 ..< l: 131 | m.positions[j] = 0 132 | m.clusterScore =0 133 | m.sepScore = 0 134 | m.camelCaseScore = 0 135 | 136 | proc isMatch(query, candidate: string, m: var Match) {.inline.}= 137 | var didMatch = false 138 | var r = candidate.high 139 | while not didMatch: 140 | resetMatch(m, query.len) 141 | walkString(query, candidate, 0, r, m) 142 | if m.found: 143 | break # all done 144 | # resume search - start looking left from this position onwards 145 | r = m.positions[0] - 1 146 | if r < 0: 147 | m.found = false 148 | break 149 | return 150 | 151 | iterator fuzzyMatches(query:string, candidates: openarray[string], current: string, limit: int, ispath: bool = true): tuple[i:int, r:int] {.inline.}= 152 | let findFirstN = true 153 | var count = 0 154 | var mtch:Match 155 | mtch.positions = newSeq[int](query.len) 156 | var heap = newHeap[tuple[i:int, r:int]]() do (a, b: tuple[i:int, r:int]) -> int: 157 | b.r - a.r 158 | if query != "": 159 | for i, x in candidates: 160 | if ispath and x == current: 161 | continue 162 | l "processing: {x}" 163 | isMatch(query, x, mtch) 164 | if mtch.found: 165 | count.inc 166 | l "ADDED: {x}" 167 | let rank = scorer(mtch, x, ispath) 168 | info &"{x} - {mtch} - {rank}" 169 | heap.push((i, rank)) 170 | if findFirstN and count == limit * 5: 171 | break 172 | elif query == "" and current != "" and ispath: # if query is empty just take N items based on levenshtien (rev) 173 | for i, x in candidates: 174 | if current != x: 175 | heap.push((i, 300 - editdistance.editDistanceAscii(current, x))) 176 | else: # just return top N items from candidates as is 177 | for j in 0 ..< min(limit, candidates.len): 178 | yield (j, 0) 179 | 180 | if heap.size > 0: 181 | count = 0 182 | while count < limit and heap.size > 0: 183 | let item = heap.pop 184 | yield item 185 | count.inc 186 | 187 | 188 | proc scoreMatchesStr(query: string, candidates: openarray[string], current: string, limit: int, ispath:bool=true): seq[tuple[i:int, r:int]] {.exportpy.} = 189 | result = newSeq[tuple[i:int, r:int]](limit) 190 | var idx = 0 191 | for m in fuzzyMatches(query, candidates, current, limit, ispath): 192 | result[idx] = m 193 | idx.inc 194 | 195 | result.setlen(idx) 196 | return 197 | 198 | proc baseline(candidates: openarray[string] ): seq[tuple[i:int, r:int]] {.exportpy.} = 199 | result = newSeq[tuple[i:int, r:int]](candidates.len) 200 | var idx = 0 201 | for m in candidates: 202 | result[idx] = (idx, m.len) 203 | idx.inc 204 | result.setlen(idx) 205 | return 206 | 207 | proc version():string {.exportpy.} = 208 | return VERSION 209 | -------------------------------------------------------------------------------- /rplugin/qc-fast.py: -------------------------------------------------------------------------------- 1 | from python3 import fruzzy 2 | import sys 3 | import os 4 | 5 | useNative = False 6 | if os.getenv("FUZZY_CMOD"): 7 | from python3.fruzzy_mod import scoreMatchesStr, baseline 8 | useNative = True 9 | 10 | 11 | def printResults(query, results): 12 | print() 13 | print("query: %s, results: " % query) 14 | for r in results: 15 | print(r) 16 | 17 | def scoreMatches(q, c, limit, ispath): 18 | if useNative: 19 | idxArr = scoreMatchesStr(q, c, "", limit, ispath) 20 | results = [] 21 | for i in idxArr: 22 | results.append((c[i[0]],i[1])) 23 | return results 24 | else: 25 | return fruzzy.scoreMatches(q, c, "", limit, ispath=ispath) 26 | 27 | check = True 28 | lines = [] 29 | 30 | def run(): 31 | results = scoreMatches("api", lines, 10, True) 32 | printResults("api", results) 33 | if check: 34 | assert results[0][0].endswith("api.pb.go") 35 | 36 | results = scoreMatches("rct", lines, 10, True) 37 | printResults("rct", results) 38 | if check: 39 | assert results[0][0].endswith("root_cmd_test.go") 40 | 41 | results = scoreMatches("fuzz", lines, 10, True) 42 | printResults("fuzz", results) 43 | if check: 44 | assert results[0][0].endswith("pyfuzzy.py") 45 | assert results[1][0].endswith("gofuzzy.py") 46 | 47 | results = scoreMatches("ME", lines, 10, True) 48 | printResults("ME", results) 49 | if check: 50 | assert results[0][0].endswith("README.md") 51 | 52 | results = scoreMatches("cli", lines, 10, True) 53 | printResults("cli", results) 54 | if check: 55 | assert results[0][0].endswith("cli.go") 56 | assert results[1][0].endswith("client.go") 57 | 58 | results = scoreMatches("testn", lines, 10, True) 59 | printResults("testn", results) 60 | if check: 61 | assert results[0][0].endswith("test_main.py") 62 | 63 | def main(): 64 | global check 65 | global lines 66 | file = "neomru_file" 67 | if len(sys.argv) > 1: 68 | check = False 69 | file = sys.argv[1] 70 | with open(file) as fh: 71 | lines = [line.strip() for line in fh.readlines()] 72 | print("Loaded %d lines from file: %s. Asserts are %s" % (len(lines), file, 73 | check)) 74 | run() 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /rplugin/qc-single.py: -------------------------------------------------------------------------------- 1 | from python3 import fruzzy_mod 2 | from python3 import fruzzy 3 | l = ["d:/code/go/src/github.com/raghur/fuzzy-denite/scratch/pyfuzzy.py"] 4 | print("Native: fuzz", l, fruzzy_mod.scoreMatchesStr("fuzz", l, "", 10, True)) 5 | 6 | print("Python: fuzz", l, fruzzy.scoreMatches("fuzz", l, "", 10, lambda x: x, True)) 7 | 8 | l = ["D:/code/go/src/github.com/raghur/fuzzy-denite/lib/api.pb.go"] 9 | print("Native: fuzz", l, fruzzy_mod.scoreMatchesStr("fuzz", l, "", 10, True)) 10 | print("Native: api", l, fruzzy_mod.scoreMatchesStr("api", l, "", 10, True)) 11 | print("Native: xxx", l, fruzzy_mod.scoreMatchesStr("xxx", l, "", 10, True)) 12 | print("Native: gbf", l, fruzzy_mod.scoreMatchesStr("gbf", l, "", 10, True)) 13 | 14 | 15 | # python 16 | print("Python: fuzz", l, fruzzy.scoreMatches("fuzz", l, "", 10, lambda x: x, True)) 17 | print("Python: api", l, fruzzy.scoreMatches("api", l, "", 10, lambda x: x, True)) 18 | print("Python: xxx", l, fruzzy.scoreMatches("xxx", l, "", 10, lambda x: x, True)) 19 | print("Python: gbf", l, fruzzy.scoreMatches("gbf", l, "", 10, lambda x: x, True)) 20 | -------------------------------------------------------------------------------- /rplugin/unitest.nim: -------------------------------------------------------------------------------- 1 | import unicode 2 | import strutils 3 | import logging 4 | 5 | let L = newConsoleLogger(levelThreshold = logging.Level.lvlDebug) 6 | addHandler(L) 7 | 8 | template l(fmt: varargs[string, `$`]) = 9 | when not defined(release): 10 | debug(fmt) 11 | 12 | var s = "стакло" 13 | echo s.len 14 | let runes = s.toRunes() 15 | echo s.toRunes().len 16 | for i in s.toRunes(): 17 | l "rune ", i, i.size 18 | #echo i 19 | echo s.rfind(runes[0]) 20 | -------------------------------------------------------------------------------- /tests/test_ctrlp.vim: -------------------------------------------------------------------------------- 1 | set nocompatible 2 | let &rtp = '~/.vim/bundle/fruzzy/,' . &rtp 3 | filetype plugin indent on 4 | syntax enable 5 | 6 | let g:fruzzy#usenative = 1 7 | 8 | nnoremap q :qall! 9 | 10 | highlight link CtrlPMatch Type 11 | highlight link CtrlPLinePre PreProc 12 | 13 | let s:items = [ 14 | \ '.gitignore', 15 | \ 'README.adoc', 16 | \ 'python3/ctrlp.py', 17 | \ 'plugin/fruzzy.vim', 18 | \ 'autoload/fruzzy.vim', 19 | \ 'autoload/fruzzy/ctrlp.vim', 20 | \ 'rplugin/python3/fruzzy.py', 21 | \ 'rplugin/python3/qc-fast.py', 22 | \ 'python3/fruzzy_installer.py', 23 | \ 'rplugin/python3/neomru_file', 24 | \ 'rplugin/python3/qc-single.py', 25 | \ 'rplugin/python3/fruzzy_mod.nim', 26 | \ 'rplugin/python3/fruzzy_test.py', 27 | \ 'rplugin/python3/denite/filter/matcher/fruzzymatcher.py', 28 | \] 29 | 30 | let s:items = [ 31 | \ '~/.vim/bundle/ctrlp-py-matcher/autoload/pymatcher.py', 32 | \ '~/.vim/bundle/ctrlp-py-matcher/autoload/pymatcher.vim', 33 | \ '~/.vim/bundle/fruzzy/rplugin/python3/fruzzy_mod.nim', 34 | \ '~/.vim/personal/syntax/dagbok.vim', 35 | \ '~/.vim/bundle/vimtex/rplugin/python3/denite/source/vimtex.py', 36 | \ '~/.vim/bundle/fruzzy/rplugin/python3/denite/filter/matcher/fruzzymatcher.py', 37 | \ '~/.vim/bundle/wiki.vim/pythonx/ncm2_subscope_detector/wiki.py', 38 | \] 39 | 40 | let s:qry = 'pymatc' 41 | let s:cur = '~/.vim/bundle/ctrlp-py-matcher/autoload/pymatcher.vim' 42 | echo 'Current:' s:cur . "\n" 43 | for s:len in range(4) 44 | echo 'Query:' strpart(s:qry, 0, s:len) 45 | for s:res in fruzzy#ctrlp#matcher(s:items, strpart(s:qry, 0, s:len), 5, '', 1, s:cur, '') 46 | echo ' ' . s:res 47 | endfor 48 | echon "\n" 49 | endfor 50 | 51 | quitall! 52 | --------------------------------------------------------------------------------