Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
@bittersweet authored
View
22 vim/bundle/matcher/LICENSE
@@ -0,0 +1,22 @@
+opyright (c) 2012, Burke Libbey
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
14 vim/bundle/matcher/Makefile
@@ -0,0 +1,14 @@
+EXENAME=matcher
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+
+.PHONY: all
+all: $(EXENAME)
+
+$(EXENAME): main.c matcher.c
+ $(CC) $(CFLAGS) -O3 -Wall $^ -o $@
+
+.PHONY: install
+install: $(EXENAME)
+ install -d $(DESTDIR)$(PREFIX)
+ install -m 0755 $< $(DESTDIR)$(PREFIX)/bin
View
98 vim/bundle/matcher/README.md
@@ -0,0 +1,98 @@
+# Matcher
+
+This is a standalone library that does the same fuzzy-find matching as Command-T.vim.
+
+# Installation
+
+```shell
+$ make
+# move `matcher` somewhere useful
+$ make install
+# make install will install it to /usr/local/bin.
+```
+
+# Usage
+
+Matcher searches for a string in a list of filenames, and returns the
+ones it thinks you are most likely referring to. It works exactly like
+fuzzy-finder, Command-T, and so on.
+
+### Usage:
+
+```shell
+$ matcher [options] <search>
+```
+
+#### Options:
+
+* `--limit`: The number of matches to return (default 10)
+* `--no-dotfiles`: Dotfiles will never be returned (by default, they may
+ be)
+* `--manifest`: Specify a file containing the list of files to scan. If
+ none given, matcher will read the list from stdin.
+
+### Examples
+
+```shell
+$ matcher --limit 20 --no-dotfiles --manifest filelist.txt customer.rb
+$ find . | matcher order
+```
+
+# Using with CtrlP.vim
+
+```viml
+let g:path_to_matcher = "/path/to/matcher"
+
+let g:ctrlp_user_command = ['.git/', 'cd %s && git ls-files . -co --exclude-standard']
+
+let g:ctrlp_match_func = { 'match': 'GoodMatch' }
+
+function! GoodMatch(items, str, limit, mmode, ispath, crfile, regex)
+
+ " Create a cache file if not yet exists
+ let cachefile = ctrlp#utils#cachedir().'/matcher.cache'
+ if !( filereadable(cachefile) && a:items == readfile(cachefile) )
+ call writefile(a:items, cachefile)
+ endif
+ if !filereadable(cachefile)
+ return []
+ endif
+
+ " a:mmode is currently ignored. In the future, we should probably do
+ " something about that. the matcher behaves like "full-line".
+ let cmd = g:path_to_matcher.' --limit '.a:limit.' --manifest '.cachefile.' '
+ if !( exists('g:ctrlp_dotfiles') && g:ctrlp_dotfiles )
+ let cmd = cmd.'--no-dotfiles '
+ endif
+ let cmd = cmd.a:str
+
+ return split(system(cmd), "\n")
+
+endfunction
+```
+
+# Using with zsh
+
+```shell
+_matcher_complete() {
+ git ls-files | /Users/burke/bin/matcher -l20 ${words[CURRENT]} | while read line; do
+ compadd -U "$line"
+ done
+ compstate[insert]=menu # no expand
+}
+
+zle -C matcher-complete 'menu-select' _matcher_complete
+
+bindkey '^X^T' matcher-complete # C-x C-t to find matches for the search under the cursor
+# bindkey '^T' matcher-complete # C-t to find matches for the search under the cursor
+```
+
+
+# Bugs
+
+* Probably
+
+# Contributing
+
+* Fork branch commit push pullrequest
+* I'm bad at github notifications. Send me an email too at burke@burkelibbey.org
View
101 vim/bundle/matcher/main.c
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+
+#include "matcher.h"
+
+struct globalArgs_t {
+ int dotfiles; // -d
+ int limit; // -l
+ char *manifest; // -m
+ char *search;
+} globalArgs;
+
+static const char *optString = "dl:m:h?";
+
+static const struct option longOpts[] = {
+ { "no-dotfiles", no_argument, NULL, 'd' },
+ { "limit", required_argument, NULL, 'l' },
+ { "manifest", required_argument, NULL, 'm' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, no_argument, NULL, 0 }
+};
+
+/* Display program usage, and exit. */
+void display_usage(void)
+{
+ puts("Usage: matcher [--no-dotfiles] [--limit num] [--manifest filename] <query>\n");
+ exit( EXIT_FAILURE );
+}
+
+void parse_arguments(int argc, char *argv[])
+{
+ int opt = 0;
+ int longIndex = 0;
+
+ globalArgs.dotfiles = 1;
+ globalArgs.limit = 10;
+ globalArgs.manifest = NULL;
+ globalArgs.search = NULL;
+
+ opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
+
+ while (opt != -1) {
+ switch (opt) {
+ case 'l':
+ globalArgs.limit = atoi(optarg);
+ break;
+ case 'd':
+ globalArgs.dotfiles = 0;
+ break;
+ case 'm':
+ globalArgs.manifest = optarg;
+ break;
+ case 'h':
+ case '?':
+ display_usage();
+ break;
+ default:
+ break;
+ }
+ opt = getopt_long(argc, argv, optString, longOpts, &longIndex);
+ }
+
+
+ globalArgs.search = argv[argc - 1];
+ int i = 0;
+ while(globalArgs.search[i] != '\0'){
+ globalArgs.search[i] = tolower(globalArgs.search[i]);
+ i++;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ parse_arguments(argc, argv);
+
+ char *strings[20000];
+ int num_strings = 0;
+
+ FILE *fp = stdin;
+ if (globalArgs.manifest) {
+ fp = fopen(globalArgs.manifest, "r");
+ }
+
+ for (num_strings = 0; num_strings < 20000; num_strings++) {
+ strings[num_strings] = malloc(1024 * sizeof(char));
+ fgets(strings[num_strings], 1023, fp);
+ if (feof(fp)) break;
+ }
+
+ fclose(fp);
+
+ score_list(globalArgs.search,
+ strings,
+ num_strings,
+ globalArgs.dotfiles,
+ globalArgs.limit);
+ return 0;
+}
+
View
181 vim/bundle/matcher/matcher.c
@@ -0,0 +1,181 @@
+/*
+ * Most of this is borrowed from Command-T:
+ * https://github.com/wincent/Command-T/blob/4b2da2fb/ruby/command-t/match.c
+ * The rest is also under standard 2-clause BSD license, 2012 Burke Libbey
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// use a struct to make passing params during recursion easier
+typedef struct
+{
+ char *str_p; // pointer to string to be searched
+ long str_len; // length of same
+ char *abbrev_p; // pointer to search string (abbreviation)
+ long abbrev_len; // length of same
+ double max_score_per_char;
+ int dot_file; // boolean: true if str is a dot-file
+ int always_show_dot_files; // boolean
+ int never_show_dot_files; // boolean
+} matchinfo_t;
+
+double recursive_match(matchinfo_t *m, // sharable meta-data
+ long str_idx, // where in the path string to start
+ long abbrev_idx, // where in the search string to start
+ long last_idx, // location of last matched character
+ double score) // cumulative score so far
+{
+ double seen_score = 0; // remember best score seen via recursion
+ int dot_file_match = 0; // true if abbrev matches a dot-file
+ int dot_search = 0; // true if searching for a dot
+
+ long i, j;
+
+ for (i = abbrev_idx; i < m->abbrev_len; i++) {
+ char c = m->abbrev_p[i];
+ if (c == '.')
+ dot_search = 1;
+ int found = 0;
+ for (j = str_idx; j < m->str_len; j++, str_idx++) {
+ char d = m->str_p[j];
+ if (d == '.') {
+ if (j == 0 || m->str_p[j - 1] == '/') {
+ m->dot_file = 1; // this is a dot-file
+ if (dot_search) // and we are searching for a dot
+ dot_file_match = 1; // so this must be a match
+ }
+ }
+ else if (d >= 'A' && d <= 'Z')
+ d += 'a' - 'A'; // add 32 to downcase
+ if (c == d) {
+ found = 1;
+ dot_search = 0;
+
+ // calculate score
+ double score_for_char = m->max_score_per_char;
+ long distance = j - last_idx;
+ if (distance > 1) {
+ double factor = 1.0;
+ char last = m->str_p[j - 1];
+ char curr = m->str_p[j]; // case matters, so get again
+ if (last == '/')
+ factor = 0.9;
+ else if (last == '-' ||
+ last == '_' ||
+ last == ' ' ||
+ (last >= '0' && last <= '9'))
+ factor = 0.8;
+ else if (last >= 'a' && last <= 'z' && curr >= 'A' && curr <= 'Z')
+ factor = 0.8;
+ else if (last == '.')
+ factor = 0.7;
+ else
+ // if no "special" chars behind char, factor diminishes
+ // as distance from last matched char increases
+ factor = (1.0 / distance) * 0.75;
+ score_for_char *= factor;
+ }
+
+ if (++j < m->str_len) {
+ // bump cursor one char to the right and
+ // use recursion to try and find a better match
+ double sub_score = recursive_match(m, j, i, last_idx, score);
+ if (sub_score > seen_score)
+ seen_score = sub_score;
+ }
+
+ score += score_for_char;
+ last_idx = str_idx++;
+ break;
+ }
+ }
+ if (!found)
+ return 0.0;
+ }
+ if (m->dot_file) {
+ if (m->never_show_dot_files || (!dot_file_match && !m->always_show_dot_files))
+ return 0.0;
+ }
+ return (score > seen_score) ? score : seen_score;
+}
+
+double score(char *abbrev, // user input string to search for
+ char *str, // proposed potential match to calculate score against
+ int show_dotfiles) // bool
+{
+ long i;
+
+ matchinfo_t m;
+ m.str_p = str;
+ m.str_len = strlen(str);
+ m.abbrev_p = abbrev;
+ m.abbrev_len = strlen(abbrev);
+ m.max_score_per_char = (1.0 / m.str_len + 1.0 / m.abbrev_len) / 2;
+ m.dot_file = 0;
+ m.always_show_dot_files = show_dotfiles == 1;
+ m.never_show_dot_files = show_dotfiles == 0;
+
+ // calculate score
+ double score = 1.0;
+ if (m.abbrev_len == 0) { // special case for zero-length search string
+ // filter out dot files
+ if (!m.always_show_dot_files) {
+ for (i = 0; i < m.str_len; i++) {
+ char c = m.str_p[i];
+ if (c == '.' && (i == 0 || m.str_p[i - 1] == '/')) {
+ score = 0.0;
+ break;
+ }
+ }
+ }
+ } else { // normal case
+ score = recursive_match(&m, 0, 0, 0, 0.0);
+ }
+
+ return score;
+}
+
+typedef struct
+{
+ char *ptr;
+ double score;
+} item_t;
+
+int compare_items(const void *a, const void *b)
+{
+ item_t *at = (item_t *)a;
+ item_t *bt = (item_t *)b;
+ if (at->score > bt->score)
+ return -1;
+ else if (at->score < bt->score)
+ return 1;
+ else
+ return 0;
+}
+
+void score_list(char *abbrev,
+ char **strs,
+ int num_strs,
+ int show_dotfiles,
+ int limit)
+{
+ long i;
+
+ item_t items[num_strs];
+
+ for (i = 0; i < num_strs; i++) {
+ items[i].ptr = strs[i];
+ items[i].score = score(abbrev, strs[i], show_dotfiles);
+ }
+
+ qsort(items, num_strs, sizeof(item_t), compare_items);
+
+ if (num_strs < limit) limit = num_strs;
+ for (i = 0; i < limit; i++) {
+ printf("%s", items[i].ptr);
+ }
+
+}
+
View
5 vim/bundle/matcher/matcher.h
@@ -0,0 +1,5 @@
+void score_list(char *abbrev,
+ char **strs,
+ int num_strs,
+ int show_dotfiles,
+ int limit);
View
40 vimrc
@@ -97,10 +97,6 @@ map <leader>c :TComment<CR>
" Ragtag
let g:ragtag_global_maps = 1
-" Ctrl-P
-let g:ctrlp_map = '<leader>t'
-map <leader>p :CtrlPClearCache<CR>
-
" Shows what you are typing as a command
set showcmd
@@ -185,5 +181,37 @@ let g:Powerline_stl_path_style = 'short'
set shiftround " Round indent to multiples of shiftwidth
-" Use find on linux/osx to speed up file indexing
-let g:ctrlp_user_command = 'find %s -type f'
+" Tslime setup
+let g:slime_target = "tmux"
+
+" Ctrl-P setup
+" Uses matcher for the same fuzzy-find algorithm as Command-T
+" https://github.com/burke/matcher
+
+let g:ctrlp_map = '<leader>t'
+map <leader>p :CtrlPClearCache<CR>
+
+let g:path_to_matcher = "/usr/local/bin/matcher"
+let g:ctrlp_user_command = ['.git/', 'cd %s && git ls-files . -co --exclude-standard']
+let g:ctrlp_match_func = { 'match': 'GoodMatch' }
+
+function! GoodMatch(items, str, limit, mmode, ispath, crfile, regex)
+ " Create a cache file if not yet exists
+ let cachefile = ctrlp#utils#cachedir().'/matcher.cache'
+ if !( filereadable(cachefile) && a:items == readfile(cachefile) )
+ call writefile(a:items, cachefile)
+ endif
+ if !filereadable(cachefile)
+ return []
+ endif
+
+ " a:mmode is currently ignored. In the future, we should probably do
+ " something about that. the matcher behaves like "full-line".
+ let cmd = g:path_to_matcher.' --limit '.a:limit.' --manifest '.cachefile.' '
+ if !( exists('g:ctrlp_dotfiles') && g:ctrlp_dotfiles )
+ let cmd = cmd.'--no-dotfiles '
+ endif
+ let cmd = cmd.a:str
+
+ return split(system(cmd), "\n")
+endfunction
Please sign in to comment.
Something went wrong with that request. Please try again.