From 7a9346c5d3e998a5b2a27ecb93faaf567f9438eb Mon Sep 17 00:00:00 2001 From: Chris Pride Date: Mon, 10 Aug 2015 11:55:31 -0700 Subject: [PATCH 1/5] Add new enum for mode. Using the enum rather than string comparisons as the match progresses should save cycles. --- autoload/fuzzycomt.c | 39 +++++++++++++++++++++++++++++++-------- autoload/fuzzycomt.h | 11 ++++++++++- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/autoload/fuzzycomt.c b/autoload/fuzzycomt.c index ec48124..36544e9 100644 --- a/autoload/fuzzycomt.c +++ b/autoload/fuzzycomt.c @@ -26,7 +26,26 @@ #include "fuzzycomt.h" // Forward declaration for ctrlp_get_line_matches -matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, char *mmode); +matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, mmode_t mmode); + +mmode_t getMMode(char *mmode) { + mmode_t result = fullLine; + if (mmode[0] == 'f') { + if (mmode[1] == 'i') { + if (mmode[2] == 'l') { + result = filenameOnly; + } else { + result = firstNonTab; + } + } else { + result = fullLine; + } + } else { + result = untilLastTab; + } + + return result; +} void ctrlp_get_line_matches(PyObject* paths, PyObject* abbrev, @@ -35,11 +54,14 @@ void ctrlp_get_line_matches(PyObject* paths, { int i; int max; + + mmode_t mmodeEnum = getMMode(mmode); + // iterate over lines and get match score for every line for (i = 0, max = PyList_Size(paths); i < max; i++) { PyObject* path = PyList_GetItem(paths, i); matchobj_t match; - match = ctrlp_find_match(path, abbrev, mmode); + match = ctrlp_find_match(path, abbrev, mmodeEnum); matches[i] = match; } } @@ -123,7 +145,8 @@ double ctrlp_recursive_match(matchinfo_t *m, // sharable meta-data long haystack_idx, // where in the path string to start long needle_idx, // where in the needle string to start long last_idx, // location of last matched character - double score) // cumulative score so far + double score, // cumulative score so far + mmode_t mmode) { double seen_score = 0; // remember best score seen via recursion long i, j, distance; @@ -194,7 +217,7 @@ double ctrlp_recursive_match(matchinfo_t *m, // sharable meta-data if (++j < m->haystack_len) { // bump cursor one char to the right and // use recursion to try and find a better match - double sub_score = ctrlp_recursive_match(m, j, i, last_idx, score); + double sub_score = ctrlp_recursive_match(m, j, i, last_idx, score, mmode); if (sub_score > seen_score) seen_score = sub_score; } @@ -350,7 +373,7 @@ PyObject* ctrlp_fuzzycomt_sorted_match_list(PyObject* self, PyObject* args) { } -matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, char *mmode) +matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, mmode_t mmode) { long i, max; double score; @@ -371,7 +394,7 @@ matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, char *mmode) } matchinfo_t m; - if (strcmp(mmode, "filename-only") == 0) { + if (mmode == filenameOnly) { // get file name by splitting string on slashes m.haystack_p = slashsplit(temp_string); m.haystack_len = strlen(m.haystack_p); @@ -407,12 +430,12 @@ matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, char *mmode) memo[i] = DBL_MAX; m.memo = memo; - score = ctrlp_recursive_match(&m, 0, 0, 0, 0.0); + score = ctrlp_recursive_match(&m, 0, 0, 0, 0.0, mmode); } // need to free memory because strdump() function in slashsplit() uses // malloc to allocate memory, otherwise memory will leak - if (strcmp(mmode, "filename-only") == 0) { + if (mmode == filenameOnly) { free(m.haystack_p); } diff --git a/autoload/fuzzycomt.h b/autoload/fuzzycomt.h index 55d4ded..941e6ee 100644 --- a/autoload/fuzzycomt.h +++ b/autoload/fuzzycomt.h @@ -28,6 +28,15 @@ #include #include + + +typedef enum mmode { + fullLine, + filenameOnly, + firstNonTab, + untilLastTab +} mmode_t; + typedef struct { PyObject *str; // Python object with file path double score; // score of string @@ -43,7 +52,7 @@ typedef struct { double *memo; // memoization } matchinfo_t; -matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, char *mmode); +matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, mmode_t mmode); void ctrlp_get_line_matches(PyObject* paths, PyObject* abbrev, matchobj_t matches[], char *mode); From 5ea13499a973cdfea390f67c42186336454e114e Mon Sep 17 00:00:00 2001 From: Chris Pride Date: Mon, 10 Aug 2015 12:24:05 -0700 Subject: [PATCH 2/5] Avoid extra copying. --- autoload/fuzzycomt.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/autoload/fuzzycomt.c b/autoload/fuzzycomt.c index 36544e9..1e24b5f 100644 --- a/autoload/fuzzycomt.c +++ b/autoload/fuzzycomt.c @@ -60,9 +60,7 @@ void ctrlp_get_line_matches(PyObject* paths, // iterate over lines and get match score for every line for (i = 0, max = PyList_Size(paths); i < max; i++) { PyObject* path = PyList_GetItem(paths, i); - matchobj_t match; - match = ctrlp_find_match(path, abbrev, mmodeEnum); - matches[i] = match; + matches[i] = ctrlp_find_match(path, abbrev, mmodeEnum); } } From 889a0e8c14e2e49cb915b091be646123d54b7dd4 Mon Sep 17 00:00:00 2001 From: Chris Pride Date: Mon, 10 Aug 2015 12:24:43 -0700 Subject: [PATCH 3/5] Avoid significant memory allocation. - Don't use strtok -- we only care about the last token anyway which means we can just find where that token begins. - If we are scanning rather than tokenizing we can look for both '\\' and '/' so we don't need to modify the string. - If we don't modify the string we don't need to make a copy. --- autoload/fuzzycomt.c | 58 +++++++++----------------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/autoload/fuzzycomt.c b/autoload/fuzzycomt.c index 1e24b5f..223b723 100644 --- a/autoload/fuzzycomt.c +++ b/autoload/fuzzycomt.c @@ -73,29 +73,18 @@ char *strduplicate(const char *s) { } char *slashsplit(char *line) { - char *pch, *linedup; - char *fname = ""; - - // we need to create a copy of input string because strtok() changes string - // while splitting. Need to call free() when linedup is not needed. - linedup = strduplicate(line); - - pch = strtok(linedup, "/"); - - while (pch != NULL) - { - fname = pch; - pch = strtok(NULL, "/"); + char *fname = line; + char *scan = fname; + while (scan != '\0') + { + if (*scan == '/' || *scan == '\\') { + fname = ++scan; + } else { + ++scan; + } } - // We need to get a copy of a filename because fname is a pointer to the - // start of filename in linedup string which will be free'd. We need to - // call free() when return value of func will not be needed. - char *retval = strduplicate(fname); - - free(linedup); - - return retval; + return fname; } // comparison function for use with qsort @@ -377,28 +366,14 @@ matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, mmode_t mmode) double score; matchobj_t returnobj; - // Make a copy of input string to replace all backslashes. - // We need to create a copy because PyString_AsString returns - // string that must not be changed. - // We will free() it later - char *temp_string; - temp_string = strduplicate(PyString_AsString(str)); - - // Replace all backslashes - for (i = 0; i < strlen(temp_string); i++) { - if (temp_string[i] == '\\') { - temp_string[i] = '/'; - } - } - matchinfo_t m; if (mmode == filenameOnly) { // get file name by splitting string on slashes - m.haystack_p = slashsplit(temp_string); + m.haystack_p = slashsplit(PyString_AsString(str)); m.haystack_len = strlen(m.haystack_p); } else { - m.haystack_p = temp_string; + m.haystack_p = PyString_AsString(str); m.haystack_len = PyString_Size(str); } m.needle_p = PyString_AsString(abbrev); @@ -431,15 +406,6 @@ matchobj_t ctrlp_find_match(PyObject* str, PyObject* abbrev, mmode_t mmode) score = ctrlp_recursive_match(&m, 0, 0, 0, 0.0, mmode); } - // need to free memory because strdump() function in slashsplit() uses - // malloc to allocate memory, otherwise memory will leak - if (mmode == filenameOnly) { - free(m.haystack_p); - } - - // Free memory after strdup() - free(temp_string); - returnobj.str = str; returnobj.score = score; From 902e0460b40916db4e4d50cec03c12d9436d25bc Mon Sep 17 00:00:00 2001 From: Chris Pride Date: Mon, 10 Aug 2015 11:54:03 -0700 Subject: [PATCH 4/5] Add comments delineating python code. --- autoload/matcher.vim | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/autoload/matcher.vim b/autoload/matcher.vim index a55bd34..c510524 100644 --- a/autoload/matcher.vim +++ b/autoload/matcher.vim @@ -23,12 +23,18 @@ else endif let s:script_folder_path = escape( expand( ':p:h' ), '\' ) +" ----- +" PYTHON UNINDETED CODE BEGIN +" ----- python << ImportEOF import sys, os, vim sys.path.insert( 0, os.path.abspath( vim.eval('s:script_folder_path' ) ) ) import fuzzycomt sys.path.pop(0) ImportEOF +" ----- +" PYTHON UNINDETED CODE END +" ----- fu! s:matchtabs(item, pat) return match(split(a:item, '\t\+')[0], a:pat) @@ -40,6 +46,9 @@ fu! s:matchfname(item, pat) endf fu! s:cmatcher(lines, input, limit, mmode, ispath, crfile) + " ----- + " PYTHON UNINDETED CODE BEGIN + " ----- python << EOF lines = vim.eval('a:lines') searchinp = vim.eval('a:input') @@ -60,7 +69,10 @@ try: except: matchlist = [] EOF -return s:pyeval("matchlist") + " ----- + " PYTHON UNINDETED CODE END + " ----- + return s:pyeval("matchlist") endf fu! s:escapechars(chars) From 97fb3f65c6b62cb2a1f4c3f737ce31f7a7ba8051 Mon Sep 17 00:00:00 2001 From: Chris Pride Date: Mon, 10 Aug 2015 11:57:08 -0700 Subject: [PATCH 5/5] We can handle firstNonTab in C I think. --- autoload/fuzzycomt.c | 4 +++- autoload/matcher.vim | 20 +++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/autoload/fuzzycomt.c b/autoload/fuzzycomt.c index 223b723..4ae5f9d 100644 --- a/autoload/fuzzycomt.c +++ b/autoload/fuzzycomt.c @@ -163,7 +163,9 @@ double ctrlp_recursive_match(matchinfo_t *m, // sharable meta-data j++, haystack_idx++) { char d = m->haystack_p[j]; - if (d == '.') { + if (d == '\t' && mmode == firstNonTab) { + break; + } else if (d == '.') { if (j == 0 || m->haystack_p[j - 1] == '/') { m->dot_file = 1; // this is a dot-file } diff --git a/autoload/matcher.vim b/autoload/matcher.vim index c510524..af63afe 100644 --- a/autoload/matcher.vim +++ b/autoload/matcher.vim @@ -114,6 +114,11 @@ fu! s:highlight(input, mmode, regex) let beginning = beginning.'\([^\/]*$\)\@=' end + if a:mmode == "first-non-tab" + " Make sure we stop at the tab + let ending = ending.'\t' + end + for i in range(len(a:input)) " Surround our current target letter with \zs and \ze so it only " actually matches that one letter, but has all preceding and trailing @@ -169,21 +174,6 @@ fu! matcher#cmatch(lines, input, limit, mmode, ispath, crfile, regex) cal s:highlight(a:input, a:mmode, a:regex) return array endif - " use built-in matcher if mmode set to match until first tab ( in other case - " tag.vim doesnt work - if a:mmode == "first-non-tab" - let array = [] - " call ctrlp.vim function to get proper input pattern - let pat = ctrlp#call('s:SplitPattern', a:input) - for item in a:lines - if call('s:matchtabs', [item, pat]) >= 0 - cal add(array, item) - en - endfo - "TODO add highlight - cal sort(array, ctrlp#call('s:mixedsort')) - return array - en let matchlist = s:cmatcher(a:lines, a:input, a:limit, a:mmode, a:ispath, a:crfile) en