| dmenu-vi_mode-20230416-0fe460d.diff - sites - public wiki contents of suckless.… | |
| git clone git://git.suckless.org/sites | |
| Log | |
| Files | |
| Refs | |
| --- | |
| 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… | |
| 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 u… | |
| 24 +static Key global_esc = { XK_n, Mod1Mask }; /* escape key when v… | |
| 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 }; /*… | |
| 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… | |
| 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 re… | |
| 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 == cur… | |
| 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… | |
| 206 + utf8, utf8, win… | |
| 207 + return; | |
| 208 + case XK_P: | |
| 209 + XConvertSelection(dpy, (ev->state & ControlMask) ? clip… | |
| 210 + utf8, utf8, win… | |
| 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 : te… | |
| 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) == qui… | |
| 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) == globa… | |
| 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)strl… | |
| 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 … | |
| 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 */ |