commit 4f334b4d0bdeb807e4d363667f9f3f48dcf151d9
Author: zerg <[email protected]>
Date:   Thu Mar 30 17:02:39 2023 -0700

    Adding vi_mode patch to dmenu
    
    This patch brings (limited) vi mode capabilities to dmenu via the -vi flag.
    Implementation has been done in such a way that existing dmenu scripts
    do not have to include the -vi flag to make use of the features.

diff --git 
a/tools.suckless.org/dmenu/patches/vi-mode/dmenu-vi_mode-20230330-dfbbf7f.diff 
b/tools.suckless.org/dmenu/patches/vi-mode/dmenu-vi_mode-20230330-dfbbf7f.diff
new file mode 100644
index 00000000..d23d0936
--- /dev/null
+++ 
b/tools.suckless.org/dmenu/patches/vi-mode/dmenu-vi_mode-20230330-dfbbf7f.diff
@@ -0,0 +1,280 @@
+diff --git a/config.def.h b/config.def.h
+index 1edb647..7bf5f4a 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -12,6 +12,7 @@ static const char *colors[SchemeLast][2] = {
+       [SchemeNorm] = { "#bbbbbb", "#222222" },
+       [SchemeSel] = { "#eeeeee", "#005577" },
+       [SchemeOut] = { "#000000", "#00ffff" },
++      [SchemeCursor] = { "#222222", "#bbbbbb"},
+ };
+ /* -l option; if nonzero, dmenu uses vertical list with given number of lines 
*/
+ static unsigned int lines      = 0;
+@@ -21,3 +22,15 @@ static unsigned int lines      = 0;
+  * for example: " /?\"&[]"
+  */
+ static const char worddelimiters[] = " ";
++
++/*
++ * -vi option; if nonzero, vi mode is always enabled and can be
++ * accessed with the global_esc keysym + mod mask
++ */
++static unsigned int vi_mode = 1;
++static unsigned int start_mode = 0;                   /* mode to use when -vi 
is passed. 0 = insert mode, 1 = normal mode */
++static Key global_esc = { XK_n, Mod1Mask };   /* escape key when vi mode is 
not enabled explicitly */
++static Key quit_keys[] = {
++      /* keysym       modifier */
++      { XK_q,         0 }
++};
+diff --git a/dmenu.c b/dmenu.c
+index 4e7df12..902ad4e 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -26,7 +26,7 @@
+ #define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)
+ 
+ /* enums */
+-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
++enum { SchemeNorm, SchemeSel, SchemeOut, SchemeCursor, SchemeLast }; /* color 
schemes */
+ 
+ struct item {
+       char *text;
+@@ -34,6 +34,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;
+@@ -44,6 +49,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;
+@@ -163,7 +169,15 @@ drawmenu(void)
+       drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
+ 
+       curpos = TEXTW(text) - TEXTW(&text[cursor]);
+-      if ((curpos += lrpad / 2 - 1) < w) {
++      curpos += lrpad / 2 - 1;
++      if (using_vi_mode && text[0] != '++             drw_setscheme(drw, 
scheme[SchemeCursor]);
++              char vi_char[] = {text[cursor], '++             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, 2, 2, bh - 4, 1, 0);
+       }
+@@ -321,6 +335,161 @@ movewordedge(int dir)
+       }
+ }
+ 
++static void
++vi_keypress(const KeySym ksym, const XKeyEvent *ev)
++{
++      static const size_t quit_len = LENGTH(quit_keys);
++      if (ev->state & ControlMask) {
++              switch(ksym) {
++              case XK_p: /* fallthrough */
++              case XK_P: break;
++              case XK_c:
++                      cleanup();
++                      exit(1);
++              default: return;
++              }
++      }
++
++      switch(ksym) {
++      /* movement */
++      case XK_0:
++              cursor = 0;
++              break;
++      case XK_dollar:
++              if (text[cursor + 1] != '++                     cursor = 
strlen(text) - 1;
++                      break;
++              }
++              break;
++      case XK_b:
++              movewordedge(-1);
++              break;
++      case XK_e:
++              cursor = nextrune(+1);
++              movewordedge(+1);
++              if (text[cursor] == '++                 --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] != '++                 cursor = nextrune(+1);
++              else if (text[cursor] == '++                    --cursor;
++              break;
++      case XK_w:
++              movewordedge(+1);
++              if (text[cursor] != '++                 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] != '++                 cursor = strlen(text);
++              using_vi_mode = 0;
++              break;
++      case XK_I:
++              cursor = using_vi_mode = 0;
++              break;
++      case XK_p:
++              if (text[cursor] != '++                 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] = '++              if (cursor)
++                      cursor = nextrune(-1);
++              match();
++              break;
++      case XK_x:
++              cursor = nextrune(+1);
++              insert(NULL, nextrune(-1) - cursor);
++              if (text[cursor] == '++                 --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] = '++             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);
++                      }
++      }
++
++      drawmenu();
++}
++
+ static void
+ keypress(XKeyEvent *ev)
+ {
+@@ -340,6 +509,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;
+@@ -543,6 +724,8 @@ paste(void)
+               insert(p, (q = strchr(p, '
')) ? q - p : (ssize_t)strlen(p));
+               XFree(p);
+       }
++      if (using_vi_mode && text[cursor] == '++                --cursor;
+       drawmenu();
+ }
+ 
+@@ -737,6 +920,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 */
diff --git a/tools.suckless.org/dmenu/patches/vi-mode/index.md 
b/tools.suckless.org/dmenu/patches/vi-mode/index.md
new file mode 100644
index 00000000..8780a2c2
--- /dev/null
+++ b/tools.suckless.org/dmenu/patches/vi-mode/index.md
@@ -0,0 +1,50 @@
+vi-mode
+=======
+Description
+-----------
+With this patch dmenu will have basic vi mode capabilities. 
+
+Implemented actions
+-------------------
+* movements inside typed text with `[h|l|w|b|e|0|$]`
+* movements through list with `[j|k|g|G]`
+* standard insertions with `[a|A|i|I]`
+* paste after|before cursor with `[p|P]`, use `ctrl` to use clipboard
+* delete from cursor to eol with `D`
+* delete the character under cursor with `x`
+
+*Note* `Enter` and `Tab` work like normal
+
+Flags
+-----
+When `-vi` is passed `Esc` will now put you into `normal mode` instead of
+quitting `dmenu`.
+
+Customization
+-------------
+This patch defines two `Key` structs made up of a `KeySym` and `ModMask`.
+
+The first is a single `Key` variable `global_esc` set to `{ XK_n, Mod1Mask }`. 
+
+The second is an array variable `quit_keys`. These keys will be used to quit
+`dmenu` when using `vi_mode`. If `-vi` was passed this will be the only way to
+quit `dmenu` besides using `C-c`.
+
+Set `vi_mode` in `config[.def].h` to `1` to enable `vi_mode` wherever you use
+`dmenu`. This will not change the behavior of `Esc`. Instead  your key defined
+under `global_esc` will be respected. This is useful as you don't have to 
change
+all your scripts to include the `-vi` flag.  `vi_mode` is set to `1` by 
default.
+
+Set `start_mode` to `1` if you want to start in `normal mode` when the `-vi`
+flag is passed.  Set to `0` by default so that you start in `insert mode`.
+
+There is a new color scheme included in this patch.  It is an inversion of 
+`SchemeNorm` called `SchemeCursor`
+
+Download
+--------
+* [dmenu-vi_mode-20230330-dfbbf7f.diff](dmenu-vi_mode-20230330-dfbbf7f.diff)
+
+Author
+------
+* zerg <zergrusherncrusher at disroot.org>


Reply via email to