dmenu-vi_mode-20230330-dfbbf7f.diff (7127B)
1 diff --git a/config.def.h b/config.def.h 2 index 1edb647..7bf5f4a 100644 3 --- a/config.def.h 4 +++ b/config.def.h 5 @@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = { 6 [SchemeNorm] = { "#bbbbbb", "#222222" }, 7 [SchemeSel] = { "#eeeeee", "#005577" }, 8 [SchemeOut] = { "#000000", "#00ffff" }, 9 + [SchemeCursor] = { "#222222", "#bbbbbb"}, 10 }; 11 /* -l option; if nonzero, dmenu uses vertical list with given number of lines */ 12 static unsigned int lines = 0; 13 @@ -21,3 +22,15 @@ static unsigned int lines = 0; 14 * for example: " /?\"&[]" 15 */ 16 static const char worddelimiters[] = " "; 17 + 18 +/* 19 + * -vi option; if nonzero, vi mode is always enabled and can be 20 + * accessed with the global_esc keysym + mod mask 21 + */ 22 +static unsigned int vi_mode = 1; 23 +static unsigned int start_mode = 0; /* mode to use when -vi is passed. 0 = insert mode, 1 = normal mode */ 24 +static Key global_esc = { XK_n, Mod1Mask }; /* escape key when vi mode is not enabled explicitly */ 25 +static Key quit_keys[] = { 26 + /* keysym modifier */ 27 + { XK_q, 0 } 28 +}; 29 diff --git a/dmenu.c b/dmenu.c 30 index 4e7df12..14fbde3 100644 31 --- a/dmenu.c 32 +++ b/dmenu.c 33 @@ -26,7 +26,7 @@ 34 #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 35 36 /* enums */ 37 -enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ 38 +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeCursor, SchemeLast }; /* color schemes */ 39 40 struct item { 41 char *text; 42 @@ -34,6 +34,11 @@ struct item { 43 int out; 44 }; 45 46 +typedef struct { 47 + KeySym ksym; 48 + unsigned int state; 49 +} Key; 50 + 51 static char text[BUFSIZ] = ""; 52 static char *embed; 53 static int bh, mw, mh; 54 @@ -44,6 +49,7 @@ static struct item *items = NULL; 55 static struct item *matches, *matchend; 56 static struct item *prev, *curr, *next, *sel; 57 static int mon = -1, screen; 58 +static unsigned int using_vi_mode = 0; 59 60 static Atom clip, utf8; 61 static Display *dpy; 62 @@ -163,7 +169,15 @@ drawmenu(void) 63 drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); 64 65 curpos = TEXTW(text) - TEXTW(&text[cursor]); 66 - if ((curpos += lrpad / 2 - 1) < w) { 67 + curpos += lrpad / 2 - 1; 68 + if (using_vi_mode && text[0] != '\0') { 69 + drw_setscheme(drw, scheme[SchemeCursor]); 70 + char vi_char[] = {text[cursor], '\0'}; 71 + drw_text(drw, x + curpos, 0, TEXTW(vi_char) - lrpad, bh, 0, vi_char, 0); 72 + } else if (using_vi_mode) { 73 + drw_setscheme(drw, scheme[SchemeNorm]); 74 + drw_rect(drw, x + curpos, 2, lrpad / 2, bh - 4, 1, 0); 75 + } else if (curpos < w) { 76 drw_setscheme(drw, scheme[SchemeNorm]); 77 drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); 78 } 79 @@ -321,6 +335,179 @@ movewordedge(int dir) 80 } 81 } 82 83 +static void 84 +vi_keypress(KeySym ksym, const XKeyEvent *ev) 85 +{ 86 + static const size_t quit_len = LENGTH(quit_keys); 87 + if (ev->state & ControlMask) { 88 + switch(ksym) { 89 + /* movement */ 90 + case XK_d: /* fallthrough */ 91 + if (next) { 92 + sel = curr = next; 93 + calcoffsets(); 94 + goto draw; 95 + } else 96 + ksym = XK_G; 97 + break; 98 + case XK_u: 99 + if (prev) { 100 + sel = curr = prev; 101 + calcoffsets(); 102 + goto draw; 103 + } else 104 + ksym = XK_g; 105 + break; 106 + case XK_p: /* fallthrough */ 107 + case XK_P: break; 108 + case XK_c: 109 + cleanup(); 110 + exit(1); 111 + default: return; 112 + } 113 + } 114 + 115 + switch(ksym) { 116 + /* movement */ 117 + case XK_0: 118 + cursor = 0; 119 + break; 120 + case XK_dollar: 121 + if (text[cursor + 1] != '\0') { 122 + cursor = strlen(text) - 1; 123 + break; 124 + } 125 + break; 126 + case XK_b: 127 + movewordedge(-1); 128 + break; 129 + case XK_e: 130 + cursor = nextrune(+1); 131 + movewordedge(+1); 132 + if (text[cursor] == '\0') 133 + --cursor; 134 + else 135 + cursor = nextrune(-1); 136 + break; 137 + case XK_g: 138 + if (sel == matches) { 139 + break; 140 + } 141 + sel = curr = matches; 142 + calcoffsets(); 143 + break; 144 + case XK_G: 145 + if (next) { 146 + /* jump to end of list and position items in reverse */ 147 + curr = matchend; 148 + calcoffsets(); 149 + curr = prev; 150 + calcoffsets(); 151 + while (next && (curr = curr->right)) 152 + calcoffsets(); 153 + } 154 + sel = matchend; 155 + break; 156 + case XK_h: 157 + if (cursor) 158 + cursor = nextrune(-1); 159 + break; 160 + case XK_j: 161 + if (sel && sel->right && (sel = sel->right) == next) { 162 + curr = next; 163 + calcoffsets(); 164 + } 165 + break; 166 + case XK_k: 167 + if (sel && sel->left && (sel = sel->left)->right == curr) { 168 + curr = prev; 169 + calcoffsets(); 170 + } 171 + break; 172 + case XK_l: 173 + if (text[cursor] != '\0' && text[cursor + 1] != '\0') 174 + cursor = nextrune(+1); 175 + else if (text[cursor] == '\0' && cursor) 176 + --cursor; 177 + break; 178 + case XK_w: 179 + movewordedge(+1); 180 + if (text[cursor] != '\0' && text[cursor + 1] != '\0') 181 + cursor = nextrune(+1); 182 + else if (cursor) 183 + --cursor; 184 + break; 185 + /* insertion */ 186 + case XK_a: 187 + cursor = nextrune(+1); 188 + /* fallthrough */ 189 + case XK_i: 190 + using_vi_mode = 0; 191 + break; 192 + case XK_A: 193 + if (text[cursor] != '\0') 194 + cursor = strlen(text); 195 + using_vi_mode = 0; 196 + break; 197 + case XK_I: 198 + cursor = using_vi_mode = 0; 199 + break; 200 + case XK_p: 201 + if (text[cursor] != '\0') 202 + cursor = nextrune(+1); 203 + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 204 + utf8, utf8, win, CurrentTime); 205 + return; 206 + case XK_P: 207 + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 208 + utf8, utf8, win, CurrentTime); 209 + return; 210 + /* deletion */ 211 + case XK_D: 212 + text[cursor] = '\0'; 213 + if (cursor) 214 + cursor = nextrune(-1); 215 + match(); 216 + break; 217 + case XK_x: 218 + cursor = nextrune(+1); 219 + insert(NULL, nextrune(-1) - cursor); 220 + if (text[cursor] == '\0' && text[0] != '\0') 221 + --cursor; 222 + match(); 223 + break; 224 + /* misc. */ 225 + case XK_Return: 226 + case XK_KP_Enter: 227 + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 228 + if (!(ev->state & ControlMask)) { 229 + cleanup(); 230 + exit(0); 231 + } 232 + if (sel) 233 + sel->out = 1; 234 + break; 235 + case XK_Tab: 236 + if (!sel) 237 + return; 238 + strncpy(text, sel->text, sizeof text - 1); 239 + text[sizeof text - 1] = '\0'; 240 + cursor = strlen(text) - 1; 241 + match(); 242 + break; 243 + default: 244 + for (size_t i = 0; i < quit_len; ++i) 245 + if (quit_keys[i].ksym == ksym && 246 + (quit_keys[i].state & ev->state) == quit_keys[i].state) { 247 + cleanup(); 248 + exit(1); 249 + } 250 + } 251 + 252 +draw: 253 + drawmenu(); 254 +} 255 + 256 static void 257 keypress(XKeyEvent *ev) 258 { 259 @@ -340,6 +527,18 @@ keypress(XKeyEvent *ev) 260 break; 261 } 262 263 + if (using_vi_mode) { 264 + vi_keypress(ksym, ev); 265 + return; 266 + } else if (vi_mode && 267 + (ksym == global_esc.ksym && 268 + (ev->state & global_esc.state) == global_esc.state)) { 269 + using_vi_mode = 1; 270 + if (cursor) 271 + cursor = nextrune(-1); 272 + goto draw; 273 + } 274 + 275 if (ev->state & ControlMask) { 276 switch(ksym) { 277 case XK_a: ksym = XK_Home; break; 278 @@ -543,6 +742,8 @@ paste(void) 279 insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 280 XFree(p); 281 } 282 + if (using_vi_mode && text[cursor] == '\0') 283 + --cursor; 284 drawmenu(); 285 } 286 287 @@ -737,6 +938,11 @@ main(int argc, char *argv[]) 288 else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 289 fstrncmp = strncasecmp; 290 fstrstr = cistrstr; 291 + } else if (!strcmp(argv[i], "-vi")) { 292 + vi_mode = 1; 293 + using_vi_mode = start_mode; 294 + global_esc.ksym = XK_Escape; 295 + global_esc.state = 0; 296 } else if (i + 1 == argc) 297 usage(); 298 /* these options take one argument */