dmenu-vi_mode-20230416-0fe460d.diff (7192B)
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 62f1089..8066271 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,181 @@ 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 + case XK_Return: /* fallthrough */ 112 + case XK_KP_Enter: break; 113 + default: return; 114 + } 115 + } 116 + 117 + switch(ksym) { 118 + /* movement */ 119 + case XK_0: 120 + cursor = 0; 121 + break; 122 + case XK_dollar: 123 + if (text[cursor + 1] != '\0') { 124 + cursor = strlen(text) - 1; 125 + break; 126 + } 127 + break; 128 + case XK_b: 129 + movewordedge(-1); 130 + break; 131 + case XK_e: 132 + cursor = nextrune(+1); 133 + movewordedge(+1); 134 + if (text[cursor] == '\0') 135 + --cursor; 136 + else 137 + cursor = nextrune(-1); 138 + break; 139 + case XK_g: 140 + if (sel == matches) { 141 + break; 142 + } 143 + sel = curr = matches; 144 + calcoffsets(); 145 + break; 146 + case XK_G: 147 + if (next) { 148 + /* jump to end of list and position items in reverse */ 149 + curr = matchend; 150 + calcoffsets(); 151 + curr = prev; 152 + calcoffsets(); 153 + while (next && (curr = curr->right)) 154 + calcoffsets(); 155 + } 156 + sel = matchend; 157 + break; 158 + case XK_h: 159 + if (cursor) 160 + cursor = nextrune(-1); 161 + break; 162 + case XK_j: 163 + if (sel && sel->right && (sel = sel->right) == next) { 164 + curr = next; 165 + calcoffsets(); 166 + } 167 + break; 168 + case XK_k: 169 + if (sel && sel->left && (sel = sel->left)->right == curr) { 170 + curr = prev; 171 + calcoffsets(); 172 + } 173 + break; 174 + case XK_l: 175 + if (text[cursor] != '\0' && text[cursor + 1] != '\0') 176 + cursor = nextrune(+1); 177 + else if (text[cursor] == '\0' && cursor) 178 + --cursor; 179 + break; 180 + case XK_w: 181 + movewordedge(+1); 182 + if (text[cursor] != '\0' && text[cursor + 1] != '\0') 183 + cursor = nextrune(+1); 184 + else if (cursor) 185 + --cursor; 186 + break; 187 + /* insertion */ 188 + case XK_a: 189 + cursor = nextrune(+1); 190 + /* fallthrough */ 191 + case XK_i: 192 + using_vi_mode = 0; 193 + break; 194 + case XK_A: 195 + if (text[cursor] != '\0') 196 + cursor = strlen(text); 197 + using_vi_mode = 0; 198 + break; 199 + case XK_I: 200 + cursor = using_vi_mode = 0; 201 + break; 202 + case XK_p: 203 + if (text[cursor] != '\0') 204 + cursor = nextrune(+1); 205 + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 206 + utf8, utf8, win, CurrentTime); 207 + return; 208 + case XK_P: 209 + XConvertSelection(dpy, (ev->state & ControlMask) ? clip : XA_PRIMARY, 210 + utf8, utf8, win, CurrentTime); 211 + return; 212 + /* deletion */ 213 + case XK_D: 214 + text[cursor] = '\0'; 215 + if (cursor) 216 + cursor = nextrune(-1); 217 + match(); 218 + break; 219 + case XK_x: 220 + cursor = nextrune(+1); 221 + insert(NULL, nextrune(-1) - cursor); 222 + if (text[cursor] == '\0' && text[0] != '\0') 223 + --cursor; 224 + match(); 225 + break; 226 + /* misc. */ 227 + case XK_Return: 228 + case XK_KP_Enter: 229 + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 230 + if (!(ev->state & ControlMask)) { 231 + cleanup(); 232 + exit(0); 233 + } 234 + if (sel) 235 + sel->out = 1; 236 + break; 237 + case XK_Tab: 238 + if (!sel) 239 + return; 240 + strncpy(text, sel->text, sizeof text - 1); 241 + text[sizeof text - 1] = '\0'; 242 + cursor = strlen(text) - 1; 243 + match(); 244 + break; 245 + default: 246 + for (size_t i = 0; i < quit_len; ++i) 247 + if (quit_keys[i].ksym == ksym && 248 + (quit_keys[i].state & ev->state) == quit_keys[i].state) { 249 + cleanup(); 250 + exit(1); 251 + } 252 + } 253 + 254 +draw: 255 + drawmenu(); 256 +} 257 + 258 static void 259 keypress(XKeyEvent *ev) 260 { 261 @@ -340,6 +529,18 @@ keypress(XKeyEvent *ev) 262 break; 263 } 264 265 + if (using_vi_mode) { 266 + vi_keypress(ksym, ev); 267 + return; 268 + } else if (vi_mode && 269 + (ksym == global_esc.ksym && 270 + (ev->state & global_esc.state) == global_esc.state)) { 271 + using_vi_mode = 1; 272 + if (cursor) 273 + cursor = nextrune(-1); 274 + goto draw; 275 + } 276 + 277 if (ev->state & ControlMask) { 278 switch(ksym) { 279 case XK_a: ksym = XK_Home; break; 280 @@ -543,6 +744,8 @@ paste(void) 281 insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 282 XFree(p); 283 } 284 + if (using_vi_mode && text[cursor] == '\0') 285 + --cursor; 286 drawmenu(); 287 } 288 289 @@ -738,6 +941,11 @@ main(int argc, char *argv[]) 290 else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ 291 fstrncmp = strncasecmp; 292 fstrstr = cistrstr; 293 + } else if (!strcmp(argv[i], "-vi")) { 294 + vi_mode = 1; 295 + using_vi_mode = start_mode; 296 + global_esc.ksym = XK_Escape; 297 + global_esc.state = 0; 298 } else if (i + 1 == argc) 299 usage(); 300 /* these options take one argument */