dmenu-fuzzymatch-20170603-f428f3e.diff (4561B)
1 Author: Jan Christoph Ebersbach <jceb@e-jc.de> 2 URL: http://tools.suckless.org/dmenu/patches/fuzzymatch 3 Add add fuzzy matching to dmenu 4 5 Index: dmenu-patches/dmenu/dmenu.c 6 =================================================================== 7 --- a/dmenu.c 8 +++ b/dmenu.c 9 @@ -1,6 +1,7 @@ 10 /* See LICENSE file for copyright and license details. */ 11 #include <ctype.h> 12 #include <locale.h> 13 +#include <math.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 @@ -31,6 +32,7 @@ struct item { 18 char *text; 19 struct item *left, *right; 20 int out; 21 + double distance; 22 }; 23 24 static char text[BUFSIZ] = ""; 25 @@ -209,9 +211,94 @@ grabkeyboard(void) 26 die("cannot grab keyboard"); 27 } 28 29 +int 30 +compare_distance(const void *a, const void *b) 31 +{ 32 + struct item *da = *(struct item **) a; 33 + struct item *db = *(struct item **) b; 34 + 35 + if (!db) 36 + return 1; 37 + if (!da) 38 + return -1; 39 + 40 + return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1; 41 +} 42 + 43 +void 44 +fuzzymatch(void) 45 +{ 46 + /* bang - we have so much memory */ 47 + struct item *it; 48 + struct item **fuzzymatches = NULL; 49 + char c; 50 + int number_of_matches = 0, i, pidx, sidx, eidx; 51 + int text_len = strlen(text), itext_len; 52 + 53 + matches = matchend = NULL; 54 + 55 + /* walk through all items */ 56 + for (it = items; it && it->text; it++) { 57 + if (text_len) { 58 + itext_len = strlen(it->text); 59 + pidx = 0; /* pointer */ 60 + sidx = eidx = -1; /* start of match, end of match */ 61 + /* walk through item text */ 62 + for (i = 0; i < itext_len && (c = it->text[i]); i++) { 63 + /* fuzzy match pattern */ 64 + if (!fstrncmp(&text[pidx], &c, 1)) { 65 + if(sidx == -1) 66 + sidx = i; 67 + pidx++; 68 + if (pidx == text_len) { 69 + eidx = i; 70 + break; 71 + } 72 + } 73 + } 74 + /* build list of matches */ 75 + if (eidx != -1) { 76 + /* compute distance */ 77 + /* add penalty if match starts late (log(sidx+2)) 78 + * add penalty for long a match without many matching characters */ 79 + it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len); 80 + /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */ 81 + appenditem(it, &matches, &matchend); 82 + number_of_matches++; 83 + } 84 + } else { 85 + appenditem(it, &matches, &matchend); 86 + } 87 + } 88 + 89 + if (number_of_matches) { 90 + /* initialize array with matches */ 91 + if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*)))) 92 + die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*)); 93 + for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) { 94 + fuzzymatches[i] = it; 95 + } 96 + /* sort matches according to distance */ 97 + qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance); 98 + /* rebuild list of matches */ 99 + matches = matchend = NULL; 100 + for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \ 101 + it->text; i++, it = fuzzymatches[i]) { 102 + appenditem(it, &matches, &matchend); 103 + } 104 + free(fuzzymatches); 105 + } 106 + curr = sel = matches; 107 + calcoffsets(); 108 +} 109 + 110 static void 111 match(void) 112 { 113 + if (fuzzy) { 114 + fuzzymatch(); 115 + return; 116 + } 117 static char **tokv = NULL; 118 static int tokn = 0; 119 120 @@ -656,6 +743,8 @@ main(int argc, char *argv[]) 121 topbar = 0; 122 else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ 123 fast = 1; 124 + else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */ 125 + fuzzy = 0; 126 else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 127 fstrncmp = strncasecmp; 128 fstrstr = cistrstr; 129 Index: dmenu-patches/dmenu/config.def.h 130 =================================================================== 131 --- a/config.def.h 132 +++ b/config.def.h 133 @@ -2,6 +2,7 @@ 134 /* Default settings; can be overriden by command line. */ 135 136 static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ 137 +static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */ 138 /* -fn option overrides fonts[0]; default X11 font or font set */ 139 static const char *fonts[] = { 140 "monospace:size=10" 141 Index: dmenu-patches/dmenu/config.mk 142 =================================================================== 143 --- a/config.mk 144 +++ b/config.mk 145 @@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2 146 147 # includes and libs 148 INCS = -I${X11INC} -I${FREETYPEINC} 149 -LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} 150 +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lm 151 152 # flags 153 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}