From 188dde6ca27ef29460daffaf754bd9352545ce7a Mon Sep 17 00:00:00 2001 From: ProsperousPotato Date: Fri, 12 Dec 2025 23:27:14 +0000 Subject: add vi mode --- dmenu.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 2 deletions(-) mode change 100755 => 100644 dmenu.c (limited to 'dmenu.c') diff --git a/dmenu.c b/dmenu.c old mode 100755 new mode 100644 index a58a28b..f7257c3 --- a/dmenu.c +++ b/dmenu.c @@ -25,7 +25,7 @@ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) /* enums */ -enum { SchemeNorm, SchemeSel, SchemeOut, SchemePrompt, SchemeLast }; /* color schemes */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemePrompt, SchemeCursor, SchemeLast }; /* color schemes */ struct item { char *text; @@ -33,6 +33,11 @@ struct item { int out; }; +typedef struct { + KeySym ksym; + unsigned int state; +} Key; + static char text[BUFSIZ] = ""; static char *embed; static int bh, mw, mh; @@ -43,6 +48,7 @@ static struct item *items = NULL; static struct item *matches, *matchend; static struct item *prev, *curr, *next, *sel; static int mon = -1, screen; +static unsigned int using_vi_mode = 0; static Atom clip, utf8; static Display *dpy; @@ -165,7 +171,15 @@ drawmenu(void) if (text[0] != '\0') { curpos = TEXTW(text) - TEXTW(&text[cursor]); - if ((curpos += lrpad / 2 - 1) < w) { + curpos += lrpad / 2 - 1; + if (using_vi_mode && text[0] != '\0') { + drw_setscheme(drw, scheme[SchemeCursor]); + char vi_char[] = {text[cursor], '\0'}; + drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0); + } else if (using_vi_mode) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); + } else if (curpos < w) { drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, x + curpos, 1, 2, bh - 4, 1, 0); } @@ -323,6 +337,181 @@ movewordedge(int dir) } } +static void +vi_keypress(KeySym ksym, const XKeyEvent *ev) +{ + static const size_t quit_len = LENGTH(quit_keys); + if (ev->state & ControlMask) { + switch(ksym) { + /* movement */ + case XK_d: /* fallthrough */ + if (next) { + sel = curr = next; + calcoffsets(); + goto draw; + } else + ksym = XK_G; + break; + case XK_u: + if (prev) { + sel = curr = prev; + calcoffsets(); + goto draw; + } else + ksym = XK_g; + break; + case XK_p: /* fallthrough */ + case XK_P: break; + case XK_c: + cleanup(); + exit(1); + case XK_Return: /* fallthrough */ + case XK_KP_Enter: break; + default: return; + } + } + + switch(ksym) { + /* movement */ + case XK_0: + cursor = 0; + break; + case XK_dollar: + if (text[cursor + 1] != '\0') { + cursor = strlen(text) - 1; + break; + } + break; + case XK_b: + movewordedge(-1); + break; + case XK_e: + cursor = nextrune(+1); + movewordedge(+1); + if (text[cursor] == '\0') + --cursor; + else + cursor = nextrune(-1); + break; + case XK_g: + if (sel == matches) { + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_G: + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_h: + if (cursor) + cursor = nextrune(-1); + break; + case XK_j: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_k: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_l: + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (text[cursor] == '\0' && cursor) + --cursor; + break; + case XK_w: + movewordedge(+1); + if (text[cursor] != '\0' && text[cursor + 1] != '\0') + cursor = nextrune(+1); + else if (cursor) + --cursor; + break; + /* insertion */ + case XK_a: + cursor = nextrune(+1); + /* fallthrough */ + case XK_i: + using_vi_mode = 0; + break; + case XK_A: + if (text[cursor] != '\0') + cursor = strlen(text); + using_vi_mode = 0; + break; + case XK_I: + cursor = using_vi_mode = 0; + break; + case XK_p: + if (text[cursor] != '\0') + cursor = nextrune(+1); + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_P: + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + /* deletion */ + case XK_D: + text[cursor] = '\0'; + if (cursor) + cursor = nextrune(-1); + match(); + break; + case XK_x: + cursor = nextrune(+1); + insert(NULL, nextrune(-1) - cursor); + if (text[cursor] == '\0' && text[0] != '\0') + --cursor; + match(); + break; + /* misc. */ + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text) - 1; + match(); + break; + default: + for (size_t i = 0; i < quit_len; ++i) + if (quit_keys[i].ksym == ksym && + (quit_keys[i].state & ev->state) == quit_keys[i].state) { + cleanup(); + exit(1); + } + } + +draw: + drawmenu(); +} + static void keypress(XKeyEvent *ev) { @@ -342,6 +531,18 @@ keypress(XKeyEvent *ev) break; } + if (using_vi_mode) { + vi_keypress(ksym, ev); + return; + } else if (vi_mode && + (ksym == global_esc.ksym && + (ev->state & global_esc.state) == global_esc.state)) { + using_vi_mode = 1; + if (cursor) + cursor = nextrune(-1); + goto draw; + } + if (ev->state & ControlMask) { switch(ksym) { case XK_a: ksym = XK_Home; break; @@ -545,6 +746,8 @@ paste(void) insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); XFree(p); } + if (using_vi_mode && text[cursor] == '\0') + --cursor; drawmenu(); } @@ -739,6 +942,11 @@ main(int argc, char *argv[]) else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ fstrncmp = strncasecmp; fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-vi")) { + vi_mode = 1; + using_vi_mode = start_mode; + global_esc.ksym = XK_Escape; + global_esc.state = 0; } else if (i + 1 == argc) usage(); /* these options take one argument */ -- cgit v1.2.3