summaryrefslogtreecommitdiff
path: root/lib/filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/filter.c')
-rw-r--r--lib/filter.c188
1 files changed, 188 insertions, 0 deletions
diff --git a/lib/filter.c b/lib/filter.c
new file mode 100644
index 0000000..04733c5
--- /dev/null
+++ b/lib/filter.c
@@ -0,0 +1,188 @@
+#include "internal.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+/**
+ * Shrink bmItem** list pointer.
+ *
+ * Useful helper function for filter functions.
+ *
+ * @param list Pointer to pointer to list of bmItem pointers.
+ * @param osize Current size of the list.
+ * @param nsize New size the list will be shrinked to.
+ * @return Pointer to list of bmItem pointers.
+ */
+static bmItem** _bmFilterShrinkList(bmItem ***inOutList, size_t osize, size_t nsize)
+{
+ assert(inOutList);
+
+ if (nsize == 0) {
+ free(*inOutList);
+ return (*inOutList = NULL);
+ }
+
+ if (nsize >= osize)
+ return *inOutList;
+
+ void *tmp = malloc(sizeof(bmItem*) * nsize);
+ if (!tmp)
+ return *inOutList;
+
+ memcpy(tmp, *inOutList, sizeof(bmItem*) * nsize);
+ free(*inOutList);
+ return (*inOutList = tmp);
+}
+
+/**
+ * Text filter tokenizer helper.
+ *
+ * @param menu bmMenu instance which filter to tokenize.
+ * @param outTokv char pointer reference to list of tokens, this should be freed after use.
+ * @param outTokc unsigned int reference to number of tokens.
+ * @return Pointer to buffer that contains tokenized string, this should be freed after use.
+ */
+static char* _bmFilterTokenize(bmMenu *menu, char ***outTokv, unsigned int *outTokc)
+{
+ assert(menu);
+ assert(outTokv);
+ assert(outTokc);
+ *outTokv = NULL;
+ *outTokc = 0;
+
+ char **tokv = NULL, *buffer = NULL;
+ if (!(buffer = _bmStrdup(menu->filter)))
+ goto fail;
+
+ char *s;
+ for (s = buffer; *s && *s == ' '; ++s);
+
+ char **tmp = NULL;
+ size_t pos = 0, next;
+ unsigned int tokc = 0, tokn = 0;
+ for (; (pos = _bmStripToken(s, " ", &next)) > 0; tokv = tmp) {
+ if (++tokc > tokn && !(tmp = realloc(tokv, ++tokn * sizeof(char*))))
+ goto fail;
+
+ tmp[tokc - 1] = s;
+ s += next;
+ }
+
+ *outTokv = tmp;
+ *outTokc = tokc;
+ return buffer;
+
+fail:
+ if (buffer)
+ free(buffer);
+ if (tokv)
+ free(tokv);
+ return NULL;
+}
+
+/**
+ * Dmenu filterer that accepts substring function.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param fstrstr Substring function used to match items.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenuFun(bmMenu *menu, char addition, char* (*fstrstr)(const char *a, const char *b), int (*fstrncmp)(const char *a, const char *b, size_t len), unsigned int *outNmemb)
+{
+ assert(menu);
+ assert(fstrstr);
+ assert(fstrncmp);
+ assert(outNmemb);
+ *outNmemb = 0;
+
+ unsigned int itemsCount;
+ bmItem **items;
+
+ if (addition) {
+ items = bmMenuGetFilteredItems(menu, &itemsCount);
+ } else {
+ items = bmMenuGetItems(menu, &itemsCount);
+ }
+
+ char *buffer = NULL;
+ bmItem **filtered = calloc(itemsCount, sizeof(bmItem*));
+ if (!filtered)
+ goto fail;
+
+ char **tokv;
+ unsigned int tokc;
+ if (!(buffer = _bmFilterTokenize(menu, &tokv, &tokc)))
+ goto fail;
+
+ size_t len = (tokc ? strlen(tokv[0]) : 0);
+ unsigned int i, f, e;
+ for (e = f = i = 0; i < itemsCount; ++i) {
+ bmItem *item = items[i];
+ if (!item->text && tokc != 0)
+ continue;
+
+ if (tokc && item->text) {
+ unsigned int t;
+ for (t = 0; t < tokc && fstrstr(item->text, tokv[t]); ++t);
+ if (t < tokc)
+ continue;
+ }
+
+ if (tokc && item->text && !fstrncmp(tokv[0], item->text, len + 1)) { /* exact matches */
+ memmove(&filtered[1], filtered, f * sizeof(bmItem*));
+ filtered[0] = item;
+ e++; /* where do exact matches end */
+ } else if (tokc && item->text && !fstrncmp(tokv[0], item->text, len)) { /* prefixes */
+ memmove(&filtered[e + 1], &filtered[e], (f - e) * sizeof(bmItem*));
+ filtered[e] = item;
+ e++; /* where do exact matches end */
+ } else {
+ filtered[f] = item;
+ }
+ f++; /* where do all matches end */
+ }
+
+ if (buffer)
+ free(buffer);
+ if (tokv)
+ free(tokv);
+
+ return _bmFilterShrinkList(&filtered, menu->items.count, (*outNmemb = f));
+
+fail:
+ if (filtered)
+ free(filtered);
+ if (buffer)
+ free(buffer);
+ return NULL;
+}
+
+/**
+ * Filter that mimics the vanilla dmenu filtering.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb)
+{
+ return _bmFilterDmenuFun(menu, addition, strstr, strncmp, outNmemb);
+}
+
+/**
+ * Filter that mimics the vanilla case-insensitive dmenu filtering.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb)
+{
+ return _bmFilterDmenuFun(menu, addition, _bmStrupstr, _bmStrnupcmp, outNmemb);
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/