dmenu-vi_mode-20230416-0fe460d.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) 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 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 */