commit d491991a22af459398f22c9a118d23db370c455a
parent a21f1409dc76f791623689c98f320fab7a4848ba
Author: Erik Letson <hmagellan@hmagellan.com>
Date: Wed, 26 Aug 2020 15:41:39 -0500
Added navhistory
Diffstat:
6 files changed, 1016 insertions(+), 1 deletion(-)
diff --git a/README b/README
@@ -42,7 +42,7 @@ patches!
(1). https://tools.suckless.org/dmenu/patches/
- CURRENT PROGRESS: 15/32 patches supported
+ CURRENT PROGRESS: 16/32 patches supported
3/32 patches unsupported
USUPPORTED PATCHES:
diff --git a/patches/navhistory/USAGE b/patches/navhistory/USAGE
@@ -0,0 +1,83 @@
+navhistory - Give dmenu bash-like history functionality
+Source: https://tools.suckless.org/dmenu/patches/navhistory/dmenu-navhistory-20200709.diff
+Original Authors: phi crispyfrog@163.com
+ Philip K. - philipk (at) posteo (dot) net
+
+Description from source:
+ """
+ This patch provides dmenu the ability for history navigation similar to that of bash. Press alt+p
+ for the previous history and alt+n for the next.
+
+ Configuration
+
+ Set the maximum number of histories with a new variable 'maxhist' in config.h. By default, it only
+ records a new history if it is not the same as the last one. To change this behaviour, set 'histnodup'
+ to 0 in config.h.
+ """
+
+== YOU MUST ==
+(1). Place the patch file in /etc/portage/patches/x11-misc/dmenu/
+(2). Add the following lines to your savedconfig file:
+
+ static unsigned int maxhist = 64;
+ static int histnodup = 1;
+
+ 'maxhist' represents the maximum number of history entries to record. 'histnodup' will not add duplicate
+ entries to the history file if set to 0, and will add duplicate entries if set to 1.
+
+(3). Run 'emerge dmenu'
+
+== YOU PROBABLY SHOULD ==
+(1). Run dmenu with the '-H X' option, where 'X' is the history file location.
+(2). Press Alt-P to go to the previous search item and Alt-N for the next.
+
+== PATCH MODIFICATIONS ==
+(1). Removed all changes made to 'config.def.h'
+(2). Modified chunk #7 of the dmenu.c diff to reflect the 4.8 code
+(3). Added a 'nodoc' patch version which strips out added documentation
+
+== INCOMPATIBILITIES ==
+THIS PATCH MAKES CHANGES TO DMENU'S BUILT-IN DOCUMENTATION, WHICH CAN CAUSE CONFLICTS WITH OTHER PATCHES!
+If you encounter such conflicts, try emerging with the 'navhistory-nodoc.patch' file instead. It is the
+same as the 'navhistory.patch' file except it makes no changes to the documentation.
+
+###################################################################################################
+###################################################################################################
+
+navhistorysearch - Extends navhistory.patch with search functionality
+Source: https://tools.suckless.org/dmenu/patches/navhistory/dmenu-navhistory+search-20200709.diff
+Original Authors: phi crispyfrog@163.com
+ Philip K. - philipk (at) posteo (dot) net
+
+Description from source:
+ """
+ This patch extends navhist with history-search functionality. Press ctrl-r, like in bash or ksh, and the
+ suggestions will be replaced with the history. Press ctrl-r again to revert.
+ """
+
+== YOU MUST ==
+(1). Place the patch file in /etc/portage/patches/x11-misc/dmenu/
+(2). Add the following lines to your savedconfig file:
+
+ static unsigned int maxhist = 64;
+ static int histnodup = 1;
+
+ 'maxhist' represents the maximum number of history entries to record. 'histnodup' will not add duplicate
+ entries to the history file if set to 0, and will add duplicate entries if set to 1.
+
+(3). Run 'emerge dmenu'
+
+== YOU PROBABLY SHOULD ==
+(1). Run dmenu with the '-H X' option, where 'X' is the history file location.
+(2). Press Alt-P to go to the previous search item and Alt-N for the next.
+(3). Press Ctrl-R to see search suggestions, and Ctrl-R again to revert.
+
+== PATCH MODIFICATIONS ==
+(1). Removed all changes made to 'config.def.h'
+(2). Modified chunks #3, #4, and #9 of the dmenu.c diff to reflect the 4.8 code
+(3). Added a 'nodoc' patch version which strips out added documentation
+
+== INCOMPATIBILITIES ==
+THIS PATCH MAKES CHANGES TO DMENU'S BUILT-IN DOCUMENTATION, WHICH CAN CAUSE CONFLICTS WITH OTHER PATCHES!
+If you encounter such conflicts, try emerging with the 'navhistorysearch-nodoc.patch' file instead. It is the
+same as the 'navhistorysearch.patch' file except it makes no changes to the documentation.
diff --git a/patches/navhistory/navhistory-nodoc.patch b/patches/navhistory/navhistory-nodoc.patch
@@ -0,0 +1,195 @@
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..9d15f78 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -53,6 +53,10 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++static char *histfile;
++static char **history;
++static size_t histsz, histpos;
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+@@ -304,6 +308,129 @@ movewordedge(int dir)
+ }
+ }
+
++static void
++loadhistory(void)
++{
++ FILE *fp = NULL;
++ static size_t cap = 0;
++ size_t llen;
++ char *line;
++
++ if (!histfile) {
++ return;
++ }
++
++ fp = fopen(histfile, "r");
++ if (!fp) {
++ return;
++ }
++
++ for (;;) {
++ line = NULL;
++ llen = 0;
++ if (-1 == getline(&line, &llen, fp)) {
++ if (ferror(fp)) {
++ die("failed to read history");
++ }
++ free(line);
++ break;
++ }
++
++ if (cap == histsz) {
++ cap += 64 * sizeof(char*);
++ history = realloc(history, cap);
++ if (!history) {
++ die("failed to realloc memory");
++ }
++ }
++ strtok(line, "\n");
++ history[histsz] = line;
++ histsz++;
++ }
++ histpos = histsz;
++
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++}
++
++static void
++navhistory(int dir)
++{
++ static char def[BUFSIZ];
++ char *p = NULL;
++ size_t len = 0;
++
++ if (!history || histpos + 1 == 0)
++ return;
++
++ if (histsz == histpos) {
++ strncpy(def, text, sizeof(def));
++ }
++
++ switch(dir) {
++ case 1:
++ if (histpos < histsz - 1) {
++ p = history[++histpos];
++ } else if (histpos == histsz - 1) {
++ p = def;
++ histpos++;
++ }
++ break;
++ case -1:
++ if (histpos > 0) {
++ p = history[--histpos];
++ }
++ break;
++ }
++ if (p == NULL) {
++ return;
++ }
++
++ len = MIN(strlen(p), BUFSIZ - 1);
++ strncpy(text, p, len);
++ text[len] = '\0';
++ cursor = len;
++ match();
++}
++
++static void
++savehistory(char *input)
++{
++ unsigned int i;
++ FILE *fp;
++
++ if (!histfile ||
++ 0 == maxhist ||
++ 0 == strlen(input)) {
++ goto out;
++ }
++
++ fp = fopen(histfile, "w");
++ if (!fp) {
++ die("failed to open %s", histfile);
++ }
++ for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) {
++ if (0 >= fprintf(fp, "%s\n", history[i])) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (!histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */
++ if (0 >= fputs(input, fp)) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++
++out:
++ for (i = 0; i < histsz; i++) {
++ free(history[i]);
++ }
++ free(history);
++}
++
+ static void
+ keypress(XKeyEvent *ev)
+ {
+@@ -388,6 +515,14 @@ keypress(XKeyEvent *ev)
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
++ case XK_p:
++ navhistory(-1);
++ buf[0]=0;
++ break;
++ case XK_n:
++ navhistory(1);
++ buf[0]=0;
++ break;
+ default:
+ return;
+ }
+@@ -466,6 +601,8 @@ insert:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
++ savehistory((sel && !(ev->state & ShiftMask))
++ ? sel->text : text);
+ cleanup();
+ exit(0);
+ }
+@@ -715,6 +853,8 @@ main(int argc, char *argv[])
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
++ else if (!strcmp(argv[i], "-H"))
++ histfile = argv[++i];
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+@@ -757,6 +897,8 @@ main(int argc, char *argv[])
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
++ loadhistory();
++
+ if (fast) {
+ grabkeyboard();
+ readstdin();
+diff --git a/dmenu_run b/dmenu_run
+index 834ede5..59ec622 100755
+--- a/dmenu_run
++++ b/dmenu_run
+@@ -1,2 +1,2 @@
+ #!/bin/sh
+-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
++dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} &
diff --git a/patches/navhistory/navhistory.patch b/patches/navhistory/navhistory.patch
@@ -0,0 +1,228 @@
+diff --git a/dmenu.1 b/dmenu.1
+index 323f93c..ff496dd 100644
+--- a/dmenu.1
++++ b/dmenu.1
+@@ -22,6 +22,8 @@ dmenu \- dynamic menu
+ .IR color ]
+ .RB [ \-w
+ .IR windowid ]
++.RB [ \-H
++.IR histfile ]
+ .P
+ .BR dmenu_run " ..."
+ .SH DESCRIPTION
+@@ -80,6 +82,9 @@ prints version information to stdout, then exits.
+ .TP
+ .BI \-w " windowid"
+ embed into windowid.
++.TP
++.BI \-H " histfile"
++save input in histfile and use it for history navigation.
+ .SH USAGE
+ dmenu is completely controlled by the keyboard. Items are selected using the
+ arrow keys, page up, page down, home, and end.
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..9d15f78 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -53,6 +53,10 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++static char *histfile;
++static char **history;
++static size_t histsz, histpos;
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+@@ -304,6 +308,129 @@ movewordedge(int dir)
+ }
+ }
+
++static void
++loadhistory(void)
++{
++ FILE *fp = NULL;
++ static size_t cap = 0;
++ size_t llen;
++ char *line;
++
++ if (!histfile) {
++ return;
++ }
++
++ fp = fopen(histfile, "r");
++ if (!fp) {
++ return;
++ }
++
++ for (;;) {
++ line = NULL;
++ llen = 0;
++ if (-1 == getline(&line, &llen, fp)) {
++ if (ferror(fp)) {
++ die("failed to read history");
++ }
++ free(line);
++ break;
++ }
++
++ if (cap == histsz) {
++ cap += 64 * sizeof(char*);
++ history = realloc(history, cap);
++ if (!history) {
++ die("failed to realloc memory");
++ }
++ }
++ strtok(line, "\n");
++ history[histsz] = line;
++ histsz++;
++ }
++ histpos = histsz;
++
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++}
++
++static void
++navhistory(int dir)
++{
++ static char def[BUFSIZ];
++ char *p = NULL;
++ size_t len = 0;
++
++ if (!history || histpos + 1 == 0)
++ return;
++
++ if (histsz == histpos) {
++ strncpy(def, text, sizeof(def));
++ }
++
++ switch(dir) {
++ case 1:
++ if (histpos < histsz - 1) {
++ p = history[++histpos];
++ } else if (histpos == histsz - 1) {
++ p = def;
++ histpos++;
++ }
++ break;
++ case -1:
++ if (histpos > 0) {
++ p = history[--histpos];
++ }
++ break;
++ }
++ if (p == NULL) {
++ return;
++ }
++
++ len = MIN(strlen(p), BUFSIZ - 1);
++ strncpy(text, p, len);
++ text[len] = '\0';
++ cursor = len;
++ match();
++}
++
++static void
++savehistory(char *input)
++{
++ unsigned int i;
++ FILE *fp;
++
++ if (!histfile ||
++ 0 == maxhist ||
++ 0 == strlen(input)) {
++ goto out;
++ }
++
++ fp = fopen(histfile, "w");
++ if (!fp) {
++ die("failed to open %s", histfile);
++ }
++ for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) {
++ if (0 >= fprintf(fp, "%s\n", history[i])) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (!histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */
++ if (0 >= fputs(input, fp)) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++
++out:
++ for (i = 0; i < histsz; i++) {
++ free(history[i]);
++ }
++ free(history);
++}
++
+ static void
+ keypress(XKeyEvent *ev)
+ {
+@@ -388,6 +515,14 @@ keypress(XKeyEvent *ev)
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
++ case XK_p:
++ navhistory(-1);
++ buf[0]=0;
++ break;
++ case XK_n:
++ navhistory(1);
++ buf[0]=0;
++ break;
+ default:
+ return;
+ }
+@@ -466,6 +601,8 @@ insert:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
++ savehistory((sel && !(ev->state & ShiftMask))
++ ? sel->text : text);
+ cleanup();
+ exit(0);
+ }
+@@ -690,7 +827,8 @@ static void
+ usage(void)
+ {
+ fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
+- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
++ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n"
++ " [-H histfile]", stderr);
+ exit(1);
+ }
+
+@@ -715,6 +853,8 @@ main(int argc, char *argv[])
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
++ else if (!strcmp(argv[i], "-H"))
++ histfile = argv[++i];
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+@@ -757,6 +897,8 @@ main(int argc, char *argv[])
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
++ loadhistory();
++
+ if (fast) {
+ grabkeyboard();
+ readstdin();
+diff --git a/dmenu_run b/dmenu_run
+index 834ede5..59ec622 100755
+--- a/dmenu_run
++++ b/dmenu_run
+@@ -1,2 +1,2 @@
+ #!/bin/sh
+-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
++dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} &
diff --git a/patches/navhistory/navhistorysearch-nodoc.patch b/patches/navhistory/navhistorysearch-nodoc.patch
@@ -0,0 +1,238 @@
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..4242eb4 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -40,7 +40,7 @@ static int bh, mw, mh;
+ static int inputw = 0, promptw;
+ static int lrpad; /* sum of left and right padding */
+ static size_t cursor;
+-static struct item *items = NULL;
++static struct item *items = NULL, *backup_items;
+ static struct item *matches, *matchend;
+ static struct item *prev, *curr, *next, *sel;
+ static int mon = -1, screen;
+@@ -53,6 +53,10 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++static char *histfile;
++static char **history;
++static size_t histsz, histpos;
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+@@ -304,11 +308,134 @@ movewordedge(int dir)
+ }
+ }
+
++static void
++loadhistory(void)
++{
++ FILE *fp = NULL;
++ static size_t cap = 0;
++ size_t llen;
++ char *line;
++
++ if (!histfile) {
++ return;
++ }
++
++ fp = fopen(histfile, "r");
++ if (!fp) {
++ return;
++ }
++
++ for (;;) {
++ line = NULL;
++ llen = 0;
++ if (-1 == getline(&line, &llen, fp)) {
++ if (ferror(fp)) {
++ die("failed to read history");
++ }
++ free(line);
++ break;
++ }
++
++ if (cap == histsz) {
++ cap += 64 * sizeof(char*);
++ history = realloc(history, cap);
++ if (!history) {
++ die("failed to realloc memory");
++ }
++ }
++ strtok(line, "\n");
++ history[histsz] = line;
++ histsz++;
++ }
++ histpos = histsz;
++
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++}
++
++static void
++navhistory(int dir)
++{
++ static char def[BUFSIZ];
++ char *p = NULL;
++ size_t len = 0;
++
++ if (!history || histpos + 1 == 0)
++ return;
++
++ if (histsz == histpos) {
++ strncpy(def, text, sizeof(def));
++ }
++
++ switch(dir) {
++ case 1:
++ if (histpos < histsz - 1) {
++ p = history[++histpos];
++ } else if (histpos == histsz - 1) {
++ p = def;
++ histpos++;
++ }
++ break;
++ case -1:
++ if (histpos > 0) {
++ p = history[--histpos];
++ }
++ break;
++ }
++ if (p == NULL) {
++ return;
++ }
++
++ len = MIN(strlen(p), BUFSIZ - 1);
++ strncpy(text, p, len);
++ text[len] = '\0';
++ cursor = len;
++ match();
++}
++
++static void
++savehistory(char *input)
++{
++ unsigned int i;
++ FILE *fp;
++
++ if (!histfile ||
++ 0 == maxhist ||
++ 0 == strlen(input)) {
++ goto out;
++ }
++
++ fp = fopen(histfile, "w");
++ if (!fp) {
++ die("failed to open %s", histfile);
++ }
++ for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) {
++ if (0 >= fprintf(fp, "%s\n", history[i])) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (!histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */
++ if (0 >= fputs(input, fp)) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++
++out:
++ for (i = 0; i < histsz; i++) {
++ free(history[i]);
++ }
++ free(history);
++}
++
+ static void
+ keypress(XKeyEvent *ev)
+ {
+ char buf[32];
+- int len;
++ int len, i;
+ KeySym ksym = NoSymbol;
+ Status status;
+
+@@ -359,6 +486,27 @@ keypress(XKeyEvent *ev)
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ return;
++ case XK_r:
++ if (histfile) {
++ if (!backup_items) {
++ backup_items = items;
++ items = calloc(histsz + 1, sizeof(struct item));
++ if (!items) {
++ die("cannot allocate memory");
++ }
++
++ for (i = 0; i < histsz; i++) {
++ items[i].text = history[i];
++ }
++ } else {
++ free(items);
++ items = backup_items;
++ backup_items = NULL;
++ }
++ }
++ match();
++ ksym = NoSymbol;
++ break;
+ case XK_Left:
+ movewordedge(-1);
+ ksym = NoSymbol;
+@@ -388,6 +535,14 @@ keypress(XKeyEvent *ev)
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
++ case XK_p:
++ navhistory(-1);
++ buf[0]=0;
++ break;
++ case XK_n:
++ navhistory(1);
++ buf[0]=0;
++ break;
+ default:
+ return;
+ }
+@@ -466,6 +621,8 @@ insert:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
++ savehistory((sel && !(ev->state & ShiftMask))
++ ? sel->text : text);
+ cleanup();
+ exit(0);
+ }
+@@ -715,6 +873,8 @@ main(int argc, char *argv[])
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
++ else if (!strcmp(argv[i], "-H"))
++ histfile = argv[++i];
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+@@ -757,6 +897,8 @@ main(int argc, char *argv[])
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
++ loadhistory();
++
+ if (fast) {
+ grabkeyboard();
+ readstdin();
+diff --git a/dmenu_run b/dmenu_run
+index 834ede5..59ec622 100755
+--- a/dmenu_run
++++ b/dmenu_run
+@@ -1,2 +1,2 @@
+ #!/bin/sh
+-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
++dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} &
diff --git a/patches/navhistory/navhistorysearch.patch b/patches/navhistory/navhistorysearch.patch
@@ -0,0 +1,271 @@
+diff --git a/dmenu.1 b/dmenu.1
+index 323f93c..ff496dd 100644
+--- a/dmenu.1
++++ b/dmenu.1
+@@ -22,6 +22,8 @@ dmenu \- dynamic menu
+ .IR color ]
+ .RB [ \-w
+ .IR windowid ]
++.RB [ \-H
++.IR histfile ]
+ .P
+ .BR dmenu_run " ..."
+ .SH DESCRIPTION
+@@ -80,6 +82,9 @@ prints version information to stdout, then exits.
+ .TP
+ .BI \-w " windowid"
+ embed into windowid.
++.TP
++.BI \-H " histfile"
++save input in histfile and use it for history navigation.
+ .SH USAGE
+ dmenu is completely controlled by the keyboard. Items are selected using the
+ arrow keys, page up, page down, home, and end.
+diff --git a/dmenu.c b/dmenu.c
+index 65f25ce..4242eb4 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -40,7 +40,7 @@ static int bh, mw, mh;
+ static int inputw = 0, promptw;
+ static int lrpad; /* sum of left and right padding */
+ static size_t cursor;
+-static struct item *items = NULL;
++static struct item *items = NULL, *backup_items;
+ static struct item *matches, *matchend;
+ static struct item *prev, *curr, *next, *sel;
+ static int mon = -1, screen;
+@@ -53,6 +53,10 @@ static XIC xic;
+ static Drw *drw;
+ static Clr *scheme[SchemeLast];
+
++static char *histfile;
++static char **history;
++static size_t histsz, histpos;
++
+ #include "config.h"
+
+ static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
+@@ -304,11 +308,134 @@ movewordedge(int dir)
+ }
+ }
+
++static void
++loadhistory(void)
++{
++ FILE *fp = NULL;
++ static size_t cap = 0;
++ size_t llen;
++ char *line;
++
++ if (!histfile) {
++ return;
++ }
++
++ fp = fopen(histfile, "r");
++ if (!fp) {
++ return;
++ }
++
++ for (;;) {
++ line = NULL;
++ llen = 0;
++ if (-1 == getline(&line, &llen, fp)) {
++ if (ferror(fp)) {
++ die("failed to read history");
++ }
++ free(line);
++ break;
++ }
++
++ if (cap == histsz) {
++ cap += 64 * sizeof(char*);
++ history = realloc(history, cap);
++ if (!history) {
++ die("failed to realloc memory");
++ }
++ }
++ strtok(line, "\n");
++ history[histsz] = line;
++ histsz++;
++ }
++ histpos = histsz;
++
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++}
++
++static void
++navhistory(int dir)
++{
++ static char def[BUFSIZ];
++ char *p = NULL;
++ size_t len = 0;
++
++ if (!history || histpos + 1 == 0)
++ return;
++
++ if (histsz == histpos) {
++ strncpy(def, text, sizeof(def));
++ }
++
++ switch(dir) {
++ case 1:
++ if (histpos < histsz - 1) {
++ p = history[++histpos];
++ } else if (histpos == histsz - 1) {
++ p = def;
++ histpos++;
++ }
++ break;
++ case -1:
++ if (histpos > 0) {
++ p = history[--histpos];
++ }
++ break;
++ }
++ if (p == NULL) {
++ return;
++ }
++
++ len = MIN(strlen(p), BUFSIZ - 1);
++ strncpy(text, p, len);
++ text[len] = '\0';
++ cursor = len;
++ match();
++}
++
++static void
++savehistory(char *input)
++{
++ unsigned int i;
++ FILE *fp;
++
++ if (!histfile ||
++ 0 == maxhist ||
++ 0 == strlen(input)) {
++ goto out;
++ }
++
++ fp = fopen(histfile, "w");
++ if (!fp) {
++ die("failed to open %s", histfile);
++ }
++ for (i = histsz < maxhist ? 0 : histsz - maxhist; i < histsz; i++) {
++ if (0 >= fprintf(fp, "%s\n", history[i])) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (!histnodup || (histsz > 0 && strcmp(input, history[histsz-1]) != 0)) { /* TODO */
++ if (0 >= fputs(input, fp)) {
++ die("failed to write to %s", histfile);
++ }
++ }
++ if (fclose(fp)) {
++ die("failed to close file %s", histfile);
++ }
++
++out:
++ for (i = 0; i < histsz; i++) {
++ free(history[i]);
++ }
++ free(history);
++}
++
+ static void
+ keypress(XKeyEvent *ev)
+ {
+ char buf[32];
+- int len;
++ int len, i;
+ KeySym ksym = NoSymbol;
+ Status status;
+
+@@ -359,6 +486,27 @@ keypress(XKeyEvent *ev)
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ return;
++ case XK_r:
++ if (histfile) {
++ if (!backup_items) {
++ backup_items = items;
++ items = calloc(histsz + 1, sizeof(struct item));
++ if (!items) {
++ die("cannot allocate memory");
++ }
++
++ for (i = 0; i < histsz; i++) {
++ items[i].text = history[i];
++ }
++ } else {
++ free(items);
++ items = backup_items;
++ backup_items = NULL;
++ }
++ }
++ match();
++ ksym = NoSymbol;
++ break;
+ case XK_Left:
+ movewordedge(-1);
+ ksym = NoSymbol;
+@@ -388,6 +535,14 @@ keypress(XKeyEvent *ev)
+ case XK_j: ksym = XK_Next; break;
+ case XK_k: ksym = XK_Prior; break;
+ case XK_l: ksym = XK_Down; break;
++ case XK_p:
++ navhistory(-1);
++ buf[0]=0;
++ break;
++ case XK_n:
++ navhistory(1);
++ buf[0]=0;
++ break;
+ default:
+ return;
+ }
+@@ -466,6 +621,8 @@ insert:
+ case XK_KP_Enter:
+ puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+ if (!(ev->state & ControlMask)) {
++ savehistory((sel && !(ev->state & ShiftMask))
++ ? sel->text : text);
+ cleanup();
+ exit(0);
+ }
+@@ -690,7 +847,8 @@ static void
+ usage(void)
+ {
+ fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
+- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
++ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n"
++ " [-H histfile]", stderr);
+ exit(1);
+ }
+
+@@ -715,6 +873,8 @@ main(int argc, char *argv[])
+ } else if (i + 1 == argc)
+ usage();
+ /* these options take one argument */
++ else if (!strcmp(argv[i], "-H"))
++ histfile = argv[++i];
+ else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
+ lines = atoi(argv[++i]);
+ else if (!strcmp(argv[i], "-m"))
+@@ -757,6 +897,8 @@ main(int argc, char *argv[])
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+
++ loadhistory();
++
+ if (fast) {
+ grabkeyboard();
+ readstdin();
+diff --git a/dmenu_run b/dmenu_run
+index 834ede5..59ec622 100755
+--- a/dmenu_run
++++ b/dmenu_run
+@@ -1,2 +1,2 @@
+ #!/bin/sh
+-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
++dmenu_path | dmenu -H "${XDG_CACHE_HOME:-$HOME/.cache/}/dmenu_run.hist" "$@" | ${SHELL:-"/bin/sh"} &