sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

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 */