about summary refs log tree commit diff
diff options
context:
space:
mode:
authorManuel Palenzuela <manuelpalenzuelamerino@gmail.com>2019-08-05 18:49:59 +0200
committerManuel Palenzuela <manuelpalenzuelamerino@gmail.com>2019-08-05 18:49:59 +0200
commit4935902378d321c465f5f8ec18619b22da75527b (patch)
tree9dbd7f981691cfd57c8c58d19ae9030304995bd8
parentImproved screenshot script (diff)
downloaddmenu-4935902378d321c465f5f8ec18619b22da75527b.tar.gz
dmenu-4935902378d321c465f5f8ec18619b22da75527b.tar.bz2
dmenu-4935902378d321c465f5f8ec18619b22da75527b.zip
Added fuzzymatch sorting
-rw-r--r--config.h1
-rw-r--r--config.mk2
-rw-r--r--dmenu.13
-rw-r--r--dmenu.c89
-rw-r--r--patches/dmenu-fuzzymatch-4.9.diff167
-rwxr-xr-xstestbin17688 -> 17680 bytes
6 files changed, 261 insertions, 1 deletions
diff --git a/config.h b/config.h
index 704fb6f..625b1a4 100644
--- a/config.h
+++ b/config.h
@@ -2,6 +2,7 @@
 /* Default settings; can be overriden by command line. */
 
 static int topbar = 1;                      /* -b  option; if 0, dmenu appears at bottom     */
+static int fuzzy = 1;                      /* -F  option; if 0, dmenu doesn't use fuzzy matching     */
 /* -fn option overrides fonts[0]; default X11 font or font set */
 static const char *fonts[] = {
 	"Noto Sans Display Nerd Font:size=10"
diff --git a/config.mk b/config.mk
index 260eeae..74396bc 100644
--- a/config.mk
+++ b/config.mk
@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
 
 # includes and libs
 INCS = -I$(X11INC) -I$(FREETYPEINC)
-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender -lm
 
 # flags
 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
diff --git a/dmenu.1 b/dmenu.1
index fe988bc..35e164e 100644
--- a/dmenu.1
+++ b/dmenu.1
@@ -47,6 +47,9 @@ dmenu appears at the bottom of the screen.
 dmenu grabs the keyboard before reading stdin if not reading from a tty. This
 is faster, but will lock up X until stdin reaches end\-of\-file.
 .TP
+.B \-F
+dmenu sorts items using fuzzymatch
+.TP
 .B \-i
 dmenu matches menu items case insensitively.
 .TP
diff --git a/dmenu.c b/dmenu.c
index 95bcace..0f7d5dd 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -1,6 +1,7 @@
 /* See LICENSE file for copyright and license details. */
 #include <ctype.h>
 #include <locale.h>
+#include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -34,6 +35,7 @@ struct item {
 	char *text;
 	struct item *left, *right;
 	int out;
+  double distance;
 };
 static void xinitvisual();
 static char text[BUFSIZ] = "";
@@ -199,6 +201,87 @@ grabfocus(void)
 	die("cannot grab focus");
 }
 
+int
+compare_distance(const void *a, const void *b)
+{
+	struct item *da = *(struct item **) a;
+	struct item *db = *(struct item **) b;
+
+	if (!db)
+		return 1;
+	if (!da)
+		return -1;
+
+	return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
+}
+
+void
+fuzzymatch(void)
+{
+	/* bang - we have so much memory */
+	struct item *it;
+	struct item **fuzzymatches = NULL;
+	char c;
+	int number_of_matches = 0, i, pidx, sidx, eidx;
+	int text_len = strlen(text), itext_len;
+
+	matches = matchend = NULL;
+
+	/* walk through all items */
+	for (it = items; it && it->text; it++) {
+		if (text_len) {
+			itext_len = strlen(it->text);
+			pidx = 0; /* pointer */
+			sidx = eidx = -1; /* start of match, end of match */
+			/* walk through item text */
+			for (i = 0; i < itext_len && (c = it->text[i]); i++) {
+				/* fuzzy match pattern */
+				if (!fstrncmp(&text[pidx], &c, 1)) {
+					if(sidx == -1)
+						sidx = i;
+					pidx++;
+					if (pidx == text_len) {
+						eidx = i;
+						break;
+					}
+				}
+			}
+			/* build list of matches */
+			if (eidx != -1) {
+				/* compute distance */
+				/* add penalty if match starts late (log(sidx+2))
+				 * add penalty for long a match without many matching characters */
+				it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
+				/* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
+				appenditem(it, &matches, &matchend);
+				number_of_matches++;
+			}
+		} else {
+			appenditem(it, &matches, &matchend);
+		}
+	}
+
+	if (number_of_matches) {
+		/* initialize array with matches */
+		if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
+			die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
+		for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
+			fuzzymatches[i] = it;
+		}
+		/* sort matches according to distance */
+		qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
+		/* rebuild list of matches */
+		matches = matchend = NULL;
+		for (i = 0, it = fuzzymatches[i];  i < number_of_matches && it && \
+				it->text; i++, it = fuzzymatches[i]) {
+			appenditem(it, &matches, &matchend);
+		}
+		free(fuzzymatches);
+	}
+	curr = sel = matches;
+	calcoffsets();
+}
+
 static void
 grabkeyboard(void)
 {
@@ -220,6 +303,10 @@ grabkeyboard(void)
 static void
 match(void)
 {
+  if (fuzzy) {
+  	fuzzymatch();
+  	return;
+  }
 	static char **tokv = NULL;
 	static int tokn = 0;
 
@@ -887,6 +974,8 @@ main(int argc, char *argv[])
 			topbar = 0;
 		else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
 			fast = 1;
+    else if (!strcmp(argv[i], "-F"))   /* grabs keyboard before reading stdin */
+      fuzzy = 0;
 		else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
 			fstrncmp = strncasecmp;
 			fstrstr = cistrstr;
diff --git a/patches/dmenu-fuzzymatch-4.9.diff b/patches/dmenu-fuzzymatch-4.9.diff
new file mode 100644
index 0000000..97712fa
--- /dev/null
+++ b/patches/dmenu-fuzzymatch-4.9.diff
@@ -0,0 +1,167 @@
+diff --git a/config.h b/config.h
+index 704fb6f..625b1a4 100644
+--- a/config.h
++++ b/config.h
+@@ -2,6 +2,7 @@
+ /* Default settings; can be overriden by command line. */
+ 
+ static int topbar = 1;                      /* -b  option; if 0, dmenu appears at bottom     */
++static int fuzzy = 1;                      /* -F  option; if 0, dmenu doesn't use fuzzy matching     */
+ /* -fn option overrides fonts[0]; default X11 font or font set */
+ static const char *fonts[] = {
+ 	"Noto Sans Display Nerd Font:size=10"
+diff --git a/config.mk b/config.mk
+index 260eeae..74396bc 100644
+--- a/config.mk
++++ b/config.mk
+@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
+ 
+ # includes and libs
+ INCS = -I$(X11INC) -I$(FREETYPEINC)
+-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender
++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lXrender -lm
+ 
+ # flags
+ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
+diff --git a/dmenu.1 b/dmenu.1
+index fe988bc..35e164e 100644
+--- a/dmenu.1
++++ b/dmenu.1
+@@ -47,6 +47,9 @@ dmenu appears at the bottom of the screen.
+ dmenu grabs the keyboard before reading stdin if not reading from a tty. This
+ is faster, but will lock up X until stdin reaches end\-of\-file.
+ .TP
++.B \-F
++dmenu sorts items using fuzzymatch
++.TP
+ .B \-i
+ dmenu matches menu items case insensitively.
+ .TP
+diff --git a/dmenu.c b/dmenu.c
+index 95bcace..0f7d5dd 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -1,6 +1,7 @@
+ /* See LICENSE file for copyright and license details. */
+ #include <ctype.h>
+ #include <locale.h>
++#include <math.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -34,6 +35,7 @@ struct item {
+ 	char *text;
+ 	struct item *left, *right;
+ 	int out;
++  double distance;
+ };
+ static void xinitvisual();
+ static char text[BUFSIZ] = "";
+@@ -199,6 +201,87 @@ grabfocus(void)
+ 	die("cannot grab focus");
+ }
+ 
++int
++compare_distance(const void *a, const void *b)
++{
++	struct item *da = *(struct item **) a;
++	struct item *db = *(struct item **) b;
++
++	if (!db)
++		return 1;
++	if (!da)
++		return -1;
++
++	return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
++}
++
++void
++fuzzymatch(void)
++{
++	/* bang - we have so much memory */
++	struct item *it;
++	struct item **fuzzymatches = NULL;
++	char c;
++	int number_of_matches = 0, i, pidx, sidx, eidx;
++	int text_len = strlen(text), itext_len;
++
++	matches = matchend = NULL;
++
++	/* walk through all items */
++	for (it = items; it && it->text; it++) {
++		if (text_len) {
++			itext_len = strlen(it->text);
++			pidx = 0; /* pointer */
++			sidx = eidx = -1; /* start of match, end of match */
++			/* walk through item text */
++			for (i = 0; i < itext_len && (c = it->text[i]); i++) {
++				/* fuzzy match pattern */
++				if (!fstrncmp(&text[pidx], &c, 1)) {
++					if(sidx == -1)
++						sidx = i;
++					pidx++;
++					if (pidx == text_len) {
++						eidx = i;
++						break;
++					}
++				}
++			}
++			/* build list of matches */
++			if (eidx != -1) {
++				/* compute distance */
++				/* add penalty if match starts late (log(sidx+2))
++				 * add penalty for long a match without many matching characters */
++				it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
++				/* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
++				appenditem(it, &matches, &matchend);
++				number_of_matches++;
++			}
++		} else {
++			appenditem(it, &matches, &matchend);
++		}
++	}
++
++	if (number_of_matches) {
++		/* initialize array with matches */
++		if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
++			die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
++		for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
++			fuzzymatches[i] = it;
++		}
++		/* sort matches according to distance */
++		qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
++		/* rebuild list of matches */
++		matches = matchend = NULL;
++		for (i = 0, it = fuzzymatches[i];  i < number_of_matches && it && \
++				it->text; i++, it = fuzzymatches[i]) {
++			appenditem(it, &matches, &matchend);
++		}
++		free(fuzzymatches);
++	}
++	curr = sel = matches;
++	calcoffsets();
++}
++
+ static void
+ grabkeyboard(void)
+ {
+@@ -220,6 +303,10 @@ grabkeyboard(void)
+ static void
+ match(void)
+ {
++  if (fuzzy) {
++  	fuzzymatch();
++  	return;
++  }
+ 	static char **tokv = NULL;
+ 	static int tokn = 0;
+ 
+@@ -887,6 +974,8 @@ main(int argc, char *argv[])
+ 			topbar = 0;
+ 		else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
+ 			fast = 1;
++    else if (!strcmp(argv[i], "-F"))   /* grabs keyboard before reading stdin */
++      fuzzy = 0;
+ 		else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+ 			fstrncmp = strncasecmp;
+ 			fstrstr = cistrstr;
diff --git a/stest b/stest
index c8e01b7..b28c06f 100755
--- a/stest
+++ b/stest
Binary files differ