sites

public wiki contents of suckless.org
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 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 */