Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Use matcher plugin for CtrlP

This speeds up the indexing and has a different algorithm that is more 
like Command-T.

$ make
$ make install
  • Loading branch information...
commit 3833f5b8d19a1d9d757a51ffb71df79aa0f6f7f1 1 parent 2922c8d
Mark Mulder authored
22 vim/bundle/matcher/LICENSE
... ... @@ -0,0 +1,22 @@
  1 +opyright (c) 2012, Burke Libbey
  2 +All rights reserved.
  3 +
  4 +Redistribution and use in source and binary forms, with or without
  5 +modification, are permitted provided that the following conditions are met:
  6 +
  7 +1. Redistributions of source code must retain the above copyright notice, this
  8 + list of conditions and the following disclaimer.
  9 +2. Redistributions in binary form must reproduce the above copyright notice,
  10 + this list of conditions and the following disclaimer in the documentation
  11 + and/or other materials provided with the distribution.
  12 +
  13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  14 +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15 +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16 +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  17 +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  18 +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19 +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  20 +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  22 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 vim/bundle/matcher/Makefile
... ... @@ -0,0 +1,14 @@
  1 +EXENAME=matcher
  2 +PREFIX=/usr/local
  3 +BINDIR=$(PREFIX)/bin
  4 +
  5 +.PHONY: all
  6 +all: $(EXENAME)
  7 +
  8 +$(EXENAME): main.c matcher.c
  9 + $(CC) $(CFLAGS) -O3 -Wall $^ -o $@
  10 +
  11 +.PHONY: install
  12 +install: $(EXENAME)
  13 + install -d $(DESTDIR)$(PREFIX)
  14 + install -m 0755 $< $(DESTDIR)$(PREFIX)/bin
98 vim/bundle/matcher/README.md
Source Rendered
... ... @@ -0,0 +1,98 @@
  1 +# Matcher
  2 +
  3 +This is a standalone library that does the same fuzzy-find matching as Command-T.vim.
  4 +
  5 +# Installation
  6 +
  7 +```shell
  8 +$ make
  9 +# move `matcher` somewhere useful
  10 +$ make install
  11 +# make install will install it to /usr/local/bin.
  12 +```
  13 +
  14 +# Usage
  15 +
  16 +Matcher searches for a string in a list of filenames, and returns the
  17 +ones it thinks you are most likely referring to. It works exactly like
  18 +fuzzy-finder, Command-T, and so on.
  19 +
  20 +### Usage:
  21 +
  22 +```shell
  23 +$ matcher [options] <search>
  24 +```
  25 +
  26 +#### Options:
  27 +
  28 +* `--limit`: The number of matches to return (default 10)
  29 +* `--no-dotfiles`: Dotfiles will never be returned (by default, they may
  30 + be)
  31 +* `--manifest`: Specify a file containing the list of files to scan. If
  32 + none given, matcher will read the list from stdin.
  33 +
  34 +### Examples
  35 +
  36 +```shell
  37 +$ matcher --limit 20 --no-dotfiles --manifest filelist.txt customer.rb
  38 +$ find . | matcher order
  39 +```
  40 +
  41 +# Using with CtrlP.vim
  42 +
  43 +```viml
  44 +let g:path_to_matcher = "/path/to/matcher"
  45 +
  46 +let g:ctrlp_user_command = ['.git/', 'cd %s && git ls-files . -co --exclude-standard']
  47 +
  48 +let g:ctrlp_match_func = { 'match': 'GoodMatch' }
  49 +
  50 +function! GoodMatch(items, str, limit, mmode, ispath, crfile, regex)
  51 +
  52 + " Create a cache file if not yet exists
  53 + let cachefile = ctrlp#utils#cachedir().'/matcher.cache'
  54 + if !( filereadable(cachefile) && a:items == readfile(cachefile) )
  55 + call writefile(a:items, cachefile)
  56 + endif
  57 + if !filereadable(cachefile)
  58 + return []
  59 + endif
  60 +
  61 + " a:mmode is currently ignored. In the future, we should probably do
  62 + " something about that. the matcher behaves like "full-line".
  63 + let cmd = g:path_to_matcher.' --limit '.a:limit.' --manifest '.cachefile.' '
  64 + if !( exists('g:ctrlp_dotfiles') && g:ctrlp_dotfiles )
  65 + let cmd = cmd.'--no-dotfiles '
  66 + endif
  67 + let cmd = cmd.a:str
  68 +
  69 + return split(system(cmd), "\n")
  70 +
  71 +endfunction
  72 +```
  73 +
  74 +# Using with zsh
  75 +
  76 +```shell
  77 +_matcher_complete() {
  78 + git ls-files | /Users/burke/bin/matcher -l20 ${words[CURRENT]} | while read line; do
  79 + compadd -U "$line"
  80 + done
  81 + compstate[insert]=menu # no expand
  82 +}
  83 +
  84 +zle -C matcher-complete 'menu-select' _matcher_complete
  85 +
  86 +bindkey '^X^T' matcher-complete # C-x C-t to find matches for the search under the cursor
  87 +# bindkey '^T' matcher-complete # C-t to find matches for the search under the cursor
  88 +```
  89 +
  90 +
  91 +# Bugs
  92 +
  93 +* Probably
  94 +
  95 +# Contributing
  96 +
  97 +* Fork branch commit push pullrequest
  98 +* I'm bad at github notifications. Send me an email too at burke@burkelibbey.org
101 vim/bundle/matcher/main.c
... ... @@ -0,0 +1,101 @@
  1 +#include <stdio.h>
  2 +#include <stdlib.h>
  3 +#include <getopt.h>
  4 +#include <ctype.h>
  5 +
  6 +#include "matcher.h"
  7 +
  8 +struct globalArgs_t {
  9 + int dotfiles; // -d
  10 + int limit; // -l
  11 + char *manifest; // -m
  12 + char *search;
  13 +} globalArgs;
  14 +
  15 +static const char *optString = "dl:m:h?";
  16 +
  17 +static const struct option longOpts[] = {
  18 + { "no-dotfiles", no_argument, NULL, 'd' },
  19 + { "limit", required_argument, NULL, 'l' },
  20 + { "manifest", required_argument, NULL, 'm' },
  21 + { "help", no_argument, NULL, 'h' },
  22 + { NULL, no_argument, NULL, 0 }
  23 +};
  24 +
  25 +/* Display program usage, and exit. */
  26 +void display_usage(void)
  27 +{
  28 + puts("Usage: matcher [--no-dotfiles] [--limit num] [--manifest filename] <query>\n");
  29 + exit( EXIT_FAILURE );
  30 +}
  31 +
  32 +void parse_arguments(int argc, char *argv[])
  33 +{
  34 + int opt = 0;
  35 + int longIndex = 0;
  36 +
  37 + globalArgs.dotfiles = 1;
  38 + globalArgs.limit = 10;
  39 + globalArgs.manifest = NULL;
  40 + globalArgs.search = NULL;
  41 +
  42 + opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
  43 +
  44 + while (opt != -1) {
  45 + switch (opt) {
  46 + case 'l':
  47 + globalArgs.limit = atoi(optarg);
  48 + break;
  49 + case 'd':
  50 + globalArgs.dotfiles = 0;
  51 + break;
  52 + case 'm':
  53 + globalArgs.manifest = optarg;
  54 + break;
  55 + case 'h':
  56 + case '?':
  57 + display_usage();
  58 + break;
  59 + default:
  60 + break;
  61 + }
  62 + opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
  63 + }
  64 +
  65 +
  66 + globalArgs.search = argv[argc - 1];
  67 + int i = 0;
  68 + while(globalArgs.search[i] != '\0'){
  69 + globalArgs.search[i] = tolower(globalArgs.search[i]);
  70 + i++;
  71 + }
  72 +}
  73 +
  74 +int main(int argc, char *argv[])
  75 +{
  76 + parse_arguments(argc, argv);
  77 +
  78 + char *strings[20000];
  79 + int num_strings = 0;
  80 +
  81 + FILE *fp = stdin;
  82 + if (globalArgs.manifest) {
  83 + fp = fopen(globalArgs.manifest, "r");
  84 + }
  85 +
  86 + for (num_strings = 0; num_strings < 20000; num_strings++) {
  87 + strings[num_strings] = malloc(1024 * sizeof(char));
  88 + fgets(strings[num_strings], 1023, fp);
  89 + if (feof(fp)) break;
  90 + }
  91 +
  92 + fclose(fp);
  93 +
  94 + score_list(globalArgs.search,
  95 + strings,
  96 + num_strings,
  97 + globalArgs.dotfiles,
  98 + globalArgs.limit);
  99 + return 0;
  100 +}
  101 +
181 vim/bundle/matcher/matcher.c
... ... @@ -0,0 +1,181 @@
  1 +/*
  2 + * Most of this is borrowed from Command-T:
  3 + * https://github.com/wincent/Command-T/blob/4b2da2fb/ruby/command-t/match.c
  4 + * The rest is also under standard 2-clause BSD license, 2012 Burke Libbey
  5 + */
  6 +
  7 +#include <string.h>
  8 +#include <stdio.h>
  9 +#include <stdlib.h>
  10 +
  11 +// use a struct to make passing params during recursion easier
  12 +typedef struct
  13 +{
  14 + char *str_p; // pointer to string to be searched
  15 + long str_len; // length of same
  16 + char *abbrev_p; // pointer to search string (abbreviation)
  17 + long abbrev_len; // length of same
  18 + double max_score_per_char;
  19 + int dot_file; // boolean: true if str is a dot-file
  20 + int always_show_dot_files; // boolean
  21 + int never_show_dot_files; // boolean
  22 +} matchinfo_t;
  23 +
  24 +double recursive_match(matchinfo_t *m, // sharable meta-data
  25 + long str_idx, // where in the path string to start
  26 + long abbrev_idx, // where in the search string to start
  27 + long last_idx, // location of last matched character
  28 + double score) // cumulative score so far
  29 +{
  30 + double seen_score = 0; // remember best score seen via recursion
  31 + int dot_file_match = 0; // true if abbrev matches a dot-file
  32 + int dot_search = 0; // true if searching for a dot
  33 +
  34 + long i, j;
  35 +
  36 + for (i = abbrev_idx; i < m->abbrev_len; i++) {
  37 + char c = m->abbrev_p[i];
  38 + if (c == '.')
  39 + dot_search = 1;
  40 + int found = 0;
  41 + for (j = str_idx; j < m->str_len; j++, str_idx++) {
  42 + char d = m->str_p[j];
  43 + if (d == '.') {
  44 + if (j == 0 || m->str_p[j - 1] == '/') {
  45 + m->dot_file = 1; // this is a dot-file
  46 + if (dot_search) // and we are searching for a dot
  47 + dot_file_match = 1; // so this must be a match
  48 + }
  49 + }
  50 + else if (d >= 'A' && d <= 'Z')
  51 + d += 'a' - 'A'; // add 32 to downcase
  52 + if (c == d) {
  53 + found = 1;
  54 + dot_search = 0;
  55 +
  56 + // calculate score
  57 + double score_for_char = m->max_score_per_char;
  58 + long distance = j - last_idx;
  59 + if (distance > 1) {
  60 + double factor = 1.0;
  61 + char last = m->str_p[j - 1];
  62 + char curr = m->str_p[j]; // case matters, so get again
  63 + if (last == '/')
  64 + factor = 0.9;
  65 + else if (last == '-' ||
  66 + last == '_' ||
  67 + last == ' ' ||
  68 + (last >= '0' && last <= '9'))
  69 + factor = 0.8;
  70 + else if (last >= 'a' && last <= 'z' && curr >= 'A' && curr <= 'Z')
  71 + factor = 0.8;
  72 + else if (last == '.')
  73 + factor = 0.7;
  74 + else
  75 + // if no "special" chars behind char, factor diminishes
  76 + // as distance from last matched char increases
  77 + factor = (1.0 / distance) * 0.75;
  78 + score_for_char *= factor;
  79 + }
  80 +
  81 + if (++j < m->str_len) {
  82 + // bump cursor one char to the right and
  83 + // use recursion to try and find a better match
  84 + double sub_score = recursive_match(m, j, i, last_idx, score);
  85 + if (sub_score > seen_score)
  86 + seen_score = sub_score;
  87 + }
  88 +
  89 + score += score_for_char;
  90 + last_idx = str_idx++;
  91 + break;
  92 + }
  93 + }
  94 + if (!found)
  95 + return 0.0;
  96 + }
  97 + if (m->dot_file) {
  98 + if (m->never_show_dot_files || (!dot_file_match && !m->always_show_dot_files))
  99 + return 0.0;
  100 + }
  101 + return (score > seen_score) ? score : seen_score;
  102 +}
  103 +
  104 +double score(char *abbrev, // user input string to search for
  105 + char *str, // proposed potential match to calculate score against
  106 + int show_dotfiles) // bool
  107 +{
  108 + long i;
  109 +
  110 + matchinfo_t m;
  111 + m.str_p = str;
  112 + m.str_len = strlen(str);
  113 + m.abbrev_p = abbrev;
  114 + m.abbrev_len = strlen(abbrev);
  115 + m.max_score_per_char = (1.0 / m.str_len + 1.0 / m.abbrev_len) / 2;
  116 + m.dot_file = 0;
  117 + m.always_show_dot_files = show_dotfiles == 1;
  118 + m.never_show_dot_files = show_dotfiles == 0;
  119 +
  120 + // calculate score
  121 + double score = 1.0;
  122 + if (m.abbrev_len == 0) { // special case for zero-length search string
  123 + // filter out dot files
  124 + if (!m.always_show_dot_files) {
  125 + for (i = 0; i < m.str_len; i++) {
  126 + char c = m.str_p[i];
  127 + if (c == '.' && (i == 0 || m.str_p[i - 1] == '/')) {
  128 + score = 0.0;
  129 + break;
  130 + }
  131 + }
  132 + }
  133 + } else { // normal case
  134 + score = recursive_match(&m, 0, 0, 0, 0.0);
  135 + }
  136 +
  137 + return score;
  138 +}
  139 +
  140 +typedef struct
  141 +{
  142 + char *ptr;
  143 + double score;
  144 +} item_t;
  145 +
  146 +int compare_items(const void *a, const void *b)
  147 +{
  148 + item_t *at = (item_t *)a;
  149 + item_t *bt = (item_t *)b;
  150 + if (at->score > bt->score)
  151 + return -1;
  152 + else if (at->score < bt->score)
  153 + return 1;
  154 + else
  155 + return 0;
  156 +}
  157 +
  158 +void score_list(char *abbrev,
  159 + char **strs,
  160 + int num_strs,
  161 + int show_dotfiles,
  162 + int limit)
  163 +{
  164 + long i;
  165 +
  166 + item_t items[num_strs];
  167 +
  168 + for (i = 0; i < num_strs; i++) {
  169 + items[i].ptr = strs[i];
  170 + items[i].score = score(abbrev, strs[i], show_dotfiles);
  171 + }
  172 +
  173 + qsort(items, num_strs, sizeof(item_t), compare_items);
  174 +
  175 + if (num_strs < limit) limit = num_strs;
  176 + for (i = 0; i < limit; i++) {
  177 + printf("%s", items[i].ptr);
  178 + }
  179 +
  180 +}
  181 +
5 vim/bundle/matcher/matcher.h
... ... @@ -0,0 +1,5 @@
  1 +void score_list(char *abbrev,
  2 + char **strs,
  3 + int num_strs,
  4 + int show_dotfiles,
  5 + int limit);
40 vimrc
@@ -97,10 +97,6 @@ map <leader>c :TComment<CR>
97 97 " Ragtag
98 98 let g:ragtag_global_maps = 1
99 99
100   -" Ctrl-P
101   -let g:ctrlp_map = '<leader>t'
102   -map <leader>p :CtrlPClearCache<CR>
103   -
104 100 " Shows what you are typing as a command
105 101 set showcmd
106 102
@@ -185,5 +181,37 @@ let g:Powerline_stl_path_style = 'short'
185 181
186 182 set shiftround " Round indent to multiples of shiftwidth
187 183
188   -" Use find on linux/osx to speed up file indexing
189   -let g:ctrlp_user_command = 'find %s -type f'
  184 +" Tslime setup
  185 +let g:slime_target = "tmux"
  186 +
  187 +" Ctrl-P setup
  188 +" Uses matcher for the same fuzzy-find algorithm as Command-T
  189 +" https://github.com/burke/matcher
  190 +
  191 +let g:ctrlp_map = '<leader>t'
  192 +map <leader>p :CtrlPClearCache<CR>
  193 +
  194 +let g:path_to_matcher = "/usr/local/bin/matcher"
  195 +let g:ctrlp_user_command = ['.git/', 'cd %s && git ls-files . -co --exclude-standard']
  196 +let g:ctrlp_match_func = { 'match': 'GoodMatch' }
  197 +
  198 +function! GoodMatch(items, str, limit, mmode, ispath, crfile, regex)
  199 + " Create a cache file if not yet exists
  200 + let cachefile = ctrlp#utils#cachedir().'/matcher.cache'
  201 + if !( filereadable(cachefile) && a:items == readfile(cachefile) )
  202 + call writefile(a:items, cachefile)
  203 + endif
  204 + if !filereadable(cachefile)
  205 + return []
  206 + endif
  207 +
  208 + " a:mmode is currently ignored. In the future, we should probably do
  209 + " something about that. the matcher behaves like "full-line".
  210 + let cmd = g:path_to_matcher.' --limit '.a:limit.' --manifest '.cachefile.' '
  211 + if !( exists('g:ctrlp_dotfiles') && g:ctrlp_dotfiles )
  212 + let cmd = cmd.'--no-dotfiles '
  213 + endif
  214 + let cmd = cmd.a:str
  215 +
  216 + return split(system(cmd), "\n")
  217 +endfunction

0 comments on commit 3833f5b

Please sign in to comment.
Something went wrong with that request. Please try again.