st-flexipatch

My st-flexipatch configuration
git clone git://git.ethandl.dev/st-flexipatch
Log | Files | Refs | README | LICENSE

keyboardselect_reflow_st.c (18521B)


      1 #include <wctype.h>
      2 
      3 enum keyboardselect_mode {
      4 	KBDS_MODE_MOVE    = 0,
      5 	KBDS_MODE_SELECT  = 1<<1,
      6 	KBDS_MODE_LSELECT = 1<<2,
      7 	KBDS_MODE_FIND    = 1<<3,
      8 	KBDS_MODE_SEARCH  = 1<<4,
      9 };
     10 
     11 enum cursor_wrap {
     12 	KBDS_WRAP_NONE    = 0,
     13 	KBDS_WRAP_LINE    = 1<<0,
     14 	KBDS_WRAP_EDGE    = 1<<1,
     15 };
     16 
     17 typedef struct {
     18 	int x;
     19 	int y;
     20 	Line line;
     21 	int len;
     22 } KCursor;
     23 
     24 static int kbds_in_use, kbds_quant;
     25 static int kbds_seltype = SEL_REGULAR;
     26 static int kbds_mode, kbds_directsearch;
     27 static int kbds_searchlen, kbds_searchdir, kbds_searchcase;
     28 static int kbds_finddir, kbds_findtill;
     29 static Glyph *kbds_searchstr;
     30 static Rune kbds_findchar;
     31 static KCursor kbds_c, kbds_oc;
     32 
     33 void
     34 kbds_drawstatusbar(int y)
     35 {
     36 	static char *modes[] = { " MOVE ", "", " SELECT ", " RSELECT ", " LSELECT ",
     37 	                         " SEARCH FW ", " SEARCH BW ", " FIND FW ", " FIND BW " };
     38 	static char quant[20] = { ' ' };
     39 	static Glyph g;
     40 	int i, n, m;
     41 	int mlen, qlen;
     42 
     43 	if (!kbds_in_use)
     44 		return;
     45 
     46 	g.mode = ATTR_REVERSE;
     47 	g.fg = defaultfg;
     48 	g.bg = defaultbg;
     49 
     50 	if (y == 0) {
     51 		if (kbds_issearchmode())
     52 			m = 5 + (kbds_searchdir < 0 ? 1 : 0);
     53 		else if (kbds_mode & KBDS_MODE_FIND)
     54 			m = 7 + (kbds_finddir < 0 ? 1 : 0);
     55 		else if (kbds_mode & KBDS_MODE_SELECT)
     56 			m = 2 + (kbds_seltype == SEL_RECTANGULAR ? 1 : 0);
     57 		else
     58 			m = kbds_mode;
     59 		mlen = strlen(modes[m]);
     60 		qlen = kbds_quant ? snprintf(quant+1, sizeof quant-1, "%i", kbds_quant) + 1 : 0;
     61 		if (kbds_c.y != y || kbds_c.x < term.col - qlen - mlen) {
     62 			for (n = mlen, i = term.col-1; i >= 0 && n > 0; i--) {
     63 				g.u = modes[m][--n];
     64 				xdrawglyph(g, i, y);
     65 			}
     66 			for (n = qlen; i >= 0 && n > 0; i--) {
     67 				g.u = quant[--n];
     68 				xdrawglyph(g, i, y);
     69 			}
     70 		}
     71 	}
     72 
     73 	if (y == term.row-1 && kbds_issearchmode()) {
     74 		for (g.u = ' ', i = 0; i < term.col; i++)
     75 			xdrawglyph(g, i, y);
     76 		g.u = (kbds_searchdir > 0) ? '/' : '?';
     77 		xdrawglyph(g, 0, y);
     78 		for (i = 0; i < kbds_searchlen; i++) {
     79 			g.u = kbds_searchstr[i].u;
     80 			g.mode = kbds_searchstr[i].mode | ATTR_WIDE | ATTR_REVERSE;
     81 			if (g.u == ' ' || g.mode & ATTR_WDUMMY)
     82 				continue;
     83 			xdrawglyph(g, i + 1, y);
     84 		}
     85 		g.u = ' ';
     86 		g.mode = ATTR_NULL;
     87 		xdrawglyph(g, i + 1, y);
     88 	}
     89 }
     90 
     91 void
     92 kbds_pasteintosearch(const char *data, int len, int append)
     93 {
     94 	static char buf[BUFSIZ];
     95 	static int buflen;
     96 	Rune u;
     97 	int l, n, charsize;
     98 
     99 	if (!append)
    100 		buflen = 0;
    101 
    102 	for (; len > 0; len -= l, data += l) {
    103 		l = MIN(sizeof(buf) - buflen, len);
    104 		memmove(buf + buflen, data, l);
    105 		buflen += l;
    106 		for (n = 0; n < buflen; n += charsize) {
    107 			if (IS_SET(MODE_UTF8)) {
    108 				/* process a complete utf8 char */
    109 				charsize = utf8decode(buf + n, &u, buflen - n);
    110 				if (charsize == 0)
    111 					break;
    112 			} else {
    113 				u = buf[n] & 0xFF;
    114 				charsize = 1;
    115 			}
    116 			if (u > 0x1f && kbds_searchlen < term.col-2) {
    117 				kbds_searchstr[kbds_searchlen].u = u;
    118 				kbds_searchstr[kbds_searchlen++].mode = ATTR_NULL;
    119 				if (wcwidth(u) > 1) {
    120 					kbds_searchstr[kbds_searchlen-1].mode = ATTR_WIDE;
    121 					if (kbds_searchlen < term.col-2) {
    122 						kbds_searchstr[kbds_searchlen].u = 0;
    123 						kbds_searchstr[kbds_searchlen++].mode = ATTR_WDUMMY;
    124 					}
    125 				}
    126 			}
    127 		}
    128 		buflen -= n;
    129 		/* keep any incomplete UTF-8 byte sequence for the next call */
    130 		if (buflen > 0)
    131 			memmove(buf, buf + n, buflen);
    132 	}
    133 	term.dirty[term.row-1] = 1;
    134 }
    135 
    136 int
    137 kbds_top(void)
    138 {
    139 	return IS_SET(MODE_ALTSCREEN) ? 0 : -term.histf + term.scr;
    140 }
    141 
    142 int
    143 kbds_bot(void)
    144 {
    145 	return IS_SET(MODE_ALTSCREEN) ? term.row-1 : term.row-1 + term.scr;
    146 }
    147 
    148 int
    149 kbds_iswrapped(KCursor *c)
    150 {
    151     return c->len > 0 && (c->line[c->len-1].mode & ATTR_WRAP);
    152 }
    153 
    154 int
    155 kbds_isselectmode(void)
    156 {
    157 	return kbds_in_use && (kbds_mode & (KBDS_MODE_SELECT | KBDS_MODE_LSELECT));
    158 }
    159 
    160 int
    161 kbds_issearchmode(void)
    162 {
    163 	return kbds_in_use && (kbds_mode & KBDS_MODE_SEARCH);
    164 }
    165 
    166 void
    167 kbds_setmode(int mode)
    168 {
    169 	kbds_mode = mode;
    170 	term.dirty[0] = 1;
    171 }
    172 
    173 void
    174 kbds_selecttext(void)
    175 {
    176 	if (kbds_isselectmode()) {
    177 		if (kbds_mode & KBDS_MODE_LSELECT)
    178 			selextend(term.col-1, kbds_c.y, SEL_RECTANGULAR, 0);
    179 		else
    180 			selextend(kbds_c.x, kbds_c.y, kbds_seltype, 0);
    181 		if (sel.mode == SEL_IDLE)
    182 			kbds_setmode(kbds_mode & ~(KBDS_MODE_SELECT | KBDS_MODE_LSELECT));
    183 	}
    184 }
    185 
    186 void
    187 kbds_copytoclipboard(void)
    188 {
    189 	if (kbds_mode & KBDS_MODE_LSELECT) {
    190 		selextend(term.col-1, kbds_c.y, SEL_RECTANGULAR, 1);
    191 		sel.type = SEL_REGULAR;
    192 	} else {
    193 		selextend(kbds_c.x, kbds_c.y, kbds_seltype, 1);
    194 	}
    195 	xsetsel(getsel());
    196 
    197 	#if !CLIPBOARD_PATCH
    198 	xclipcopy();
    199 	#endif // CLIPBOARD_PATCH
    200 }
    201 
    202 void
    203 kbds_clearhighlights(void)
    204 {
    205 	int x, y;
    206 	Line line;
    207 
    208 	for (y = (IS_SET(MODE_ALTSCREEN) ? 0 : -term.histf); y < term.row; y++) {
    209 		line = TLINEABS(y);
    210 		for (x = 0; x < term.col; x++)
    211 			line[x].mode &= ~ATTR_HIGHLIGHT;
    212 	}
    213 	tfulldirt();
    214 }
    215 
    216 int
    217 kbds_moveto(int x, int y)
    218 {
    219 	if (y < 0)
    220 		kscrollup(&((Arg){ .i = -y }));
    221 	else if (y >= term.row)
    222 		kscrolldown(&((Arg){ .i = y - term.row + 1 }));
    223 	kbds_c.x = (x < 0) ? 0 : (x > term.col-1) ? term.col-1 : x;
    224 	kbds_c.y = (y < 0) ? 0 : (y > term.row-1) ? term.row-1 : y;
    225 	kbds_c.line = TLINE(kbds_c.y);
    226 	kbds_c.len = tlinelen(kbds_c.line);
    227 	if (kbds_c.x > 0 && (kbds_c.line[kbds_c.x].mode & ATTR_WDUMMY))
    228 		kbds_c.x--;
    229 }
    230 
    231 int
    232 kbds_moveforward(KCursor *c, int dx, int wrap)
    233 {
    234 	KCursor n = *c;
    235 
    236 	n.x += dx;
    237 	if (n.x >= 0 && n.x < term.col && (n.line[n.x].mode & ATTR_WDUMMY))
    238 		n.x += dx;
    239 
    240 	if (n.x < 0) {
    241 		if (!wrap || --n.y < kbds_top())
    242 			return 0;
    243 		n.line = TLINE(n.y);
    244 		n.len = tlinelen(n.line);
    245 		if ((wrap & KBDS_WRAP_LINE) && kbds_iswrapped(&n))
    246 			n.x = n.len-1;
    247 		else if (wrap & KBDS_WRAP_EDGE)
    248 			n.x = term.col-1;
    249 		else
    250 			return 0;
    251 		n.x -= (n.x > 0 && (n.line[n.x].mode & ATTR_WDUMMY)) ? 1 : 0;
    252 	} else if (n.x >= term.col) {
    253 		if (((wrap & KBDS_WRAP_EDGE) ||
    254 		    ((wrap & KBDS_WRAP_LINE) && kbds_iswrapped(&n))) && ++n.y <= kbds_bot()) {
    255 			n.line = TLINE(n.y);
    256 			n.len = tlinelen(n.line);
    257 			n.x = 0;
    258 		} else {
    259 			return 0;
    260 		}
    261 	} else if (n.x >= n.len && dx > 0 && (wrap & KBDS_WRAP_LINE)) {
    262 		if (n.x == n.len && kbds_iswrapped(&n) && n.y < kbds_bot()) {
    263 			++n.y;
    264 			n.line = TLINE(n.y);
    265 			n.len = tlinelen(n.line);
    266 			n.x = 0;
    267 		} else if (!(wrap & KBDS_WRAP_EDGE)) {
    268 			return 0;
    269 		}
    270 	}
    271 	*c = n;
    272 	return 1;
    273 }
    274 
    275 int
    276 kbds_ismatch(KCursor c)
    277 {
    278 	KCursor m = c;
    279 	int i, next;
    280 
    281 	if (c.x + kbds_searchlen > c.len && (!kbds_iswrapped(&c) || c.y >= kbds_bot()))
    282 		return 0;
    283 
    284 	for (next = 0, i = 0; i < kbds_searchlen; i++) {
    285 		if (kbds_searchstr[i].mode & ATTR_WDUMMY)
    286 			continue;
    287 		if ((next++ && !kbds_moveforward(&c, 1, KBDS_WRAP_LINE)) ||
    288 		    (kbds_searchcase && kbds_searchstr[i].u != c.line[c.x].u) ||
    289 		    (!kbds_searchcase && kbds_searchstr[i].u != towlower(c.line[c.x].u)))
    290 			return 0;
    291 	}
    292 
    293 	for (i = 0; i < kbds_searchlen; i++) {
    294 		if (!(kbds_searchstr[i].mode & ATTR_WDUMMY)) {
    295 			m.line[m.x].mode |= ATTR_HIGHLIGHT;
    296 			kbds_moveforward(&m, 1, KBDS_WRAP_LINE);
    297 		}
    298 	}
    299 	return 1;
    300 }
    301 
    302 int
    303 kbds_searchall(void)
    304 {
    305 	KCursor c;
    306 	int count = 0;
    307 
    308 	if (!kbds_searchlen)
    309 		return 0;
    310 
    311 	for (c.y = kbds_top(); c.y <= kbds_bot(); c.y++) {
    312 		c.line = TLINE(c.y);
    313 		c.len = tlinelen(c.line);
    314 		for (c.x = 0; c.x < c.len; c.x++)
    315 			count += kbds_ismatch(c);
    316 	}
    317 	tfulldirt();
    318 
    319 	return count;
    320 }
    321 
    322 void
    323 kbds_searchnext(int dir)
    324 {
    325 	KCursor c = kbds_c, n = kbds_c;
    326 	int wrapped = 0;
    327 
    328 	if (!kbds_searchlen) {
    329 		kbds_quant = 0;
    330 		return;
    331 	}
    332 
    333 	if (dir < 0 && c.x > c.len)
    334 		c.x = c.len;
    335 
    336 	for (kbds_quant = MAX(kbds_quant, 1); kbds_quant > 0;) {
    337 		if (!kbds_moveforward(&c, dir, KBDS_WRAP_LINE)) {
    338 			c.y += dir;
    339 			if (c.y < kbds_top())
    340 				c.y = kbds_bot(), wrapped++;
    341 			else if (c.y > kbds_bot())
    342 				c.y = kbds_top(), wrapped++;
    343 			if (wrapped > 1)
    344 				break;;
    345 			c.line = TLINE(c.y);
    346 			c.len = tlinelen(c.line);
    347 			c.x = (dir < 0 && c.len > 0) ? c.len-1 : 0;
    348 			c.x -= (c.x > 0 && (c.line[c.x].mode & ATTR_WDUMMY)) ? 1 : 0;
    349 		}
    350 		if (kbds_ismatch(c)) {
    351 			n = c;
    352 			kbds_quant--;
    353 		}
    354 	}
    355 
    356 	kbds_moveto(n.x, n.y);
    357 	kbds_quant = 0;
    358 }
    359 
    360 void
    361 kbds_findnext(int dir, int repeat)
    362 {
    363 	KCursor prev, c = kbds_c, n = kbds_c;
    364 	int skipfirst, yoff = 0;
    365 
    366 	if (c.len <= 0 || kbds_findchar == 0) {
    367 		kbds_quant = 0;
    368 		return;
    369 	}
    370 
    371 	if (dir < 0 && c.x > c.len)
    372 		c.x = c.len;
    373 
    374 	kbds_quant = MAX(kbds_quant, 1);
    375 	skipfirst = (kbds_quant == 1 && repeat && kbds_findtill);
    376 
    377 	while (kbds_quant > 0) {
    378 		prev = c;
    379 		if (!kbds_moveforward(&c, dir, KBDS_WRAP_LINE))
    380 			break;
    381 		if (c.line[c.x].u == kbds_findchar) {
    382 			if (skipfirst && prev.x == kbds_c.x && prev.y == kbds_c.y) {
    383 				skipfirst = 0;
    384 				continue;
    385 			}
    386 			n.x = kbds_findtill ? prev.x : c.x;
    387 			n.y = c.y;
    388 			yoff = kbds_findtill ? prev.y - c.y : 0;
    389 			kbds_quant--;
    390 		}
    391 	}
    392 
    393 	kbds_moveto(n.x, n.y);
    394 	kbds_moveto(kbds_c.x, kbds_c.y + yoff);
    395 	kbds_quant = 0;
    396 }
    397 
    398 int
    399 kbds_isdelim(KCursor c, int xoff, wchar_t *delims)
    400 {
    401 	if (xoff && !kbds_moveforward(&c, xoff, KBDS_WRAP_LINE))
    402 		return 1;
    403 	return wcschr(delims, c.line[c.x].u) != NULL;
    404 }
    405 
    406 void
    407 kbds_nextword(int start, int dir, wchar_t *delims)
    408 {
    409 	KCursor c = kbds_c, n = kbds_c;
    410 	int xoff = start ? -1 : 1;
    411 
    412 	if (dir < 0 && c.x > c.len)
    413 		c.x = c.len;
    414 	else if (dir > 0 && c.x >= c.len && c.len > 0)
    415 		c.x = c.len-1;
    416 
    417 	for (kbds_quant = MAX(kbds_quant, 1); kbds_quant > 0;) {
    418 		if (!kbds_moveforward(&c, dir, KBDS_WRAP_LINE)) {
    419 			c.y += dir;
    420 			if (c.y < kbds_top() || c.y > kbds_bot())
    421 				break;
    422 			c.line = TLINE(c.y);
    423 			c.len = tlinelen(c.line);
    424 			c.x = (dir < 0 && c.len > 0) ? c.len-1 : 0;
    425 			c.x -= (c.x > 0 && (c.line[c.x].mode & ATTR_WDUMMY)) ? 1 : 0;
    426 		}
    427 		if (c.len > 0 &&
    428 		    !kbds_isdelim(c, 0, delims) && kbds_isdelim(c, xoff, delims)) {
    429 			n = c;
    430 			kbds_quant--;
    431 		}
    432 	}
    433 
    434 	kbds_moveto(n.x, n.y);
    435 	kbds_quant = 0;
    436 }
    437 
    438 int
    439 kbds_drawcursor(void)
    440 {
    441 	if (kbds_in_use && (!kbds_issearchmode() || kbds_c.y != term.row-1)) {
    442 		#if LIGATURES_PATCH
    443 		xdrawcursor(kbds_c.x, kbds_c.y, TLINE(kbds_c.y)[kbds_c.x],
    444 					kbds_oc.x, kbds_oc.y, TLINE(kbds_oc.y)[kbds_oc.x],
    445 					TLINE(kbds_oc.y), term.col);
    446 		#else
    447 		xdrawcursor(kbds_c.x, kbds_c.y, TLINE(kbds_c.y)[kbds_c.x],
    448 					kbds_oc.x, kbds_oc.y, TLINE(kbds_oc.y)[kbds_oc.x]);
    449 		#endif // LIGATURES_PATCH
    450 		kbds_moveto(kbds_c.x, kbds_c.y);
    451 		kbds_oc = kbds_c;
    452 	}
    453 	return term.scr != 0 || kbds_in_use;
    454 }
    455 
    456 int
    457 kbds_keyboardhandler(KeySym ksym, char *buf, int len, int forcequit)
    458 {
    459 	int i, q, dy, eol, islast, prevscr, count, wrap;
    460 	int alt = IS_SET(MODE_ALTSCREEN);
    461 	Line line;
    462 	Rune u;
    463 
    464 	if (kbds_issearchmode() && !forcequit) {
    465 		switch (ksym) {
    466 			case XK_Escape:
    467 				kbds_searchlen = 0;
    468 				/* FALLTHROUGH */
    469 			case XK_Return:
    470 				for (kbds_searchcase = 0, i = 0; i < kbds_searchlen; i++) {
    471 					if (kbds_searchstr[i].u != towlower(kbds_searchstr[i].u)) {
    472 						kbds_searchcase = 1;
    473 						break;
    474 					}
    475 				}
    476 				count = kbds_searchall();
    477 				kbds_searchnext(kbds_searchdir);
    478 				kbds_selecttext();
    479 				kbds_setmode(kbds_mode & ~KBDS_MODE_SEARCH);
    480 				if (count == 0 && kbds_directsearch)
    481 					ksym = XK_Escape;
    482 				break;
    483 			case XK_BackSpace:
    484 				if (kbds_searchlen) {
    485 					kbds_searchlen--;
    486 					if (kbds_searchlen && (kbds_searchstr[kbds_searchlen].mode & ATTR_WDUMMY))
    487 						kbds_searchlen--;
    488 				}
    489 				break;
    490 			default:
    491 				if (len < 1 || kbds_searchlen >= term.col-2)
    492 					return 0;
    493 				utf8decode(buf, &u, len);
    494 				kbds_searchstr[kbds_searchlen].u = u;
    495 				kbds_searchstr[kbds_searchlen++].mode = ATTR_NULL;
    496 				if (wcwidth(u) > 1) {
    497 					kbds_searchstr[kbds_searchlen-1].mode = ATTR_WIDE;
    498 					if (kbds_searchlen < term.col-2) {
    499 						kbds_searchstr[kbds_searchlen].u = 0;
    500 						kbds_searchstr[kbds_searchlen++].mode = ATTR_WDUMMY;
    501 					}
    502 				}
    503 				break;
    504 		}
    505 		/* If the direct search is aborted, we just go to the next switch
    506 		 * statement and exit the keyboard selection mode immediately */
    507 		if (!(ksym == XK_Escape && kbds_directsearch)) {
    508 			term.dirty[term.row-1] = 1;
    509 			return 0;
    510 		}
    511 	} else if ((kbds_mode & KBDS_MODE_FIND) && !forcequit) {
    512 		kbds_findchar = 0;
    513 		switch (ksym) {
    514 			case XK_Escape:
    515 			case XK_Return:
    516 				kbds_quant = 0;
    517 				break;
    518 			default:
    519 				if (len < 1)
    520 					return 0;
    521 				utf8decode(buf, &kbds_findchar, len);
    522 				kbds_findnext(kbds_finddir, 0);
    523 				kbds_selecttext();
    524 				break;
    525 		}
    526 		kbds_setmode(kbds_mode & ~KBDS_MODE_FIND);
    527 		return 0;
    528 	}
    529 
    530 	switch (ksym) {
    531 	case -1:
    532 		kbds_searchstr = xmalloc(term.col * sizeof(Glyph));
    533 		kbds_in_use = 1;
    534 		kbds_moveto(term.c.x, term.c.y);
    535 		kbds_oc = kbds_c;
    536 		kbds_setmode(KBDS_MODE_MOVE);
    537 		return MODE_KBDSELECT;
    538 	case XK_V:
    539 		if (kbds_mode & KBDS_MODE_LSELECT) {
    540 			selclear();
    541 			kbds_setmode(kbds_mode & ~(KBDS_MODE_SELECT | KBDS_MODE_LSELECT));
    542 		} else if (kbds_mode & KBDS_MODE_SELECT) {
    543 			selextend(term.col-1, kbds_c.y, SEL_RECTANGULAR, 0);
    544 			sel.ob.x = 0;
    545 			tfulldirt();
    546 			kbds_setmode((kbds_mode ^ KBDS_MODE_SELECT) | KBDS_MODE_LSELECT);
    547 		} else {
    548 			selstart(0, kbds_c.y, 0);
    549 			selextend(term.col-1, kbds_c.y, SEL_RECTANGULAR, 0);
    550 			kbds_setmode(kbds_mode | KBDS_MODE_LSELECT);
    551 		}
    552 		break;
    553 	case XK_v:
    554 		if (kbds_mode & KBDS_MODE_SELECT) {
    555 			selclear();
    556 			kbds_setmode(kbds_mode & ~(KBDS_MODE_SELECT | KBDS_MODE_LSELECT));
    557 		} else if (kbds_mode & KBDS_MODE_LSELECT) {
    558 			selextend(kbds_c.x, kbds_c.y, kbds_seltype, 0);
    559 			kbds_setmode((kbds_mode ^ KBDS_MODE_LSELECT) | KBDS_MODE_SELECT);
    560 		} else {
    561 			selstart(kbds_c.x, kbds_c.y, 0);
    562 			kbds_setmode(kbds_mode | KBDS_MODE_SELECT);
    563 		}
    564 		break;
    565 	case XK_s:
    566 		if (!(kbds_mode & KBDS_MODE_LSELECT)) {
    567 			kbds_seltype ^= (SEL_REGULAR | SEL_RECTANGULAR);
    568 			selextend(kbds_c.x, kbds_c.y, kbds_seltype, 0);
    569 		}
    570 		break;
    571 	case XK_y:
    572 	case XK_Y:
    573 		if (kbds_isselectmode()) {
    574 			kbds_copytoclipboard();
    575 			selclear();
    576 			kbds_setmode(kbds_mode & ~(KBDS_MODE_SELECT | KBDS_MODE_LSELECT));
    577 		}
    578 		break;
    579 	case -2:
    580 	case -3:
    581 	case XK_slash:
    582 	case XK_KP_Divide:
    583 	case XK_question:
    584 		kbds_directsearch = (ksym == -2 || ksym == -3);
    585 		kbds_searchdir = (ksym == XK_question || ksym == -3) ? -1 : 1;
    586 		kbds_searchlen = 0;
    587 		kbds_setmode(kbds_mode | KBDS_MODE_SEARCH);
    588 		kbds_clearhighlights();
    589 		return 0;
    590 	case XK_q:
    591 	case XK_Escape:
    592 		if (!kbds_in_use)
    593 			return 0;
    594 		if (kbds_quant && !forcequit) {
    595 			kbds_quant = 0;
    596 			break;
    597 		}
    598 		selclear();
    599 		if (kbds_isselectmode() && !forcequit) {
    600 			kbds_setmode(KBDS_MODE_MOVE);
    601 			break;
    602 		}
    603 		kbds_setmode(KBDS_MODE_MOVE);
    604 		/* FALLTHROUGH */
    605 	case XK_Return:
    606 		if (kbds_isselectmode())
    607 			kbds_copytoclipboard();
    608 		kbds_in_use = kbds_quant = 0;
    609 		free(kbds_searchstr);
    610 		kscrolldown(&((Arg){ .i = term.histf }));
    611 		kbds_clearhighlights();
    612 		return MODE_KBDSELECT;
    613 	case XK_n:
    614 	case XK_N:
    615 		kbds_searchnext(ksym == XK_n ? kbds_searchdir : -kbds_searchdir);
    616 		break;
    617 	case XK_BackSpace:
    618 		kbds_moveto(0, kbds_c.y);
    619 		break;
    620 	case XK_exclam:
    621 		kbds_moveto(term.col/2, kbds_c.y);
    622 		break;
    623 	case XK_underscore:
    624 		kbds_moveto(term.col-1, kbds_c.y);
    625 		break;
    626 	case XK_dollar:
    627 	case XK_A:
    628 		eol = kbds_c.len-1;
    629 		line = kbds_c.line;
    630 		islast = (kbds_c.x == eol || (kbds_c.x == eol-1 && (line[eol-1].mode & ATTR_WIDE)));
    631 		if (islast && kbds_iswrapped(&kbds_c) && kbds_c.y < kbds_bot())
    632 			kbds_moveto(tlinelen(TLINE(kbds_c.y+1))-1, kbds_c.y+1);
    633 		else
    634 			kbds_moveto(islast ? term.col-1 : eol, kbds_c.y);
    635 		break;
    636 	case XK_asciicircum:
    637 	case XK_I:
    638 		for (i = 0; i < kbds_c.len && kbds_c.line[i].u == ' '; i++)
    639 			;
    640 		kbds_moveto((i < kbds_c.len) ? i : 0, kbds_c.y);
    641 		break;
    642 	case XK_End:
    643 	case XK_KP_End:
    644 		kbds_moveto(kbds_c.x, term.row-1);
    645 		break;
    646 	case XK_Home:
    647 	case XK_KP_Home:
    648 	case XK_H:
    649 		kbds_moveto(kbds_c.x, 0);
    650 		break;
    651 	case XK_M:
    652 		kbds_moveto(kbds_c.x, alt ? (term.row-1) / 2
    653                                   : MIN(term.c.y + term.scr, term.row-1) / 2);
    654 		break;
    655 	case XK_L:
    656 		kbds_moveto(kbds_c.x, alt ? term.row-1
    657 		                          : MIN(term.c.y + term.scr, term.row-1));
    658 		break;
    659 	case XK_Page_Up:
    660 	case XK_KP_Page_Up:
    661 	case XK_K:
    662 		prevscr = term.scr;
    663 		kscrollup(&((Arg){ .i = term.row }));
    664 		kbds_moveto(kbds_c.x, alt ? 0
    665 		                          : MAX(0, kbds_c.y - term.row + term.scr - prevscr));
    666 		break;
    667 	case XK_Page_Down:
    668 	case XK_KP_Page_Down:
    669 	case XK_J:
    670 		prevscr = term.scr;
    671 		kscrolldown(&((Arg){ .i = term.row }));
    672 		kbds_moveto(kbds_c.x, alt ? term.row-1
    673 		                          : MIN(MIN(term.c.y + term.scr, term.row-1),
    674 		                                    kbds_c.y + term.row + term.scr - prevscr));
    675 		break;
    676 	case XK_asterisk:
    677 	case XK_KP_Multiply:
    678 		kbds_moveto(term.col/2, (term.row-1) / 2);
    679 		break;
    680 	case XK_g:
    681 		kscrollup(&((Arg){ .i = term.histf }));
    682 		kbds_moveto(kbds_c.x, 0);
    683 		break;
    684 	case XK_G:
    685 		kscrolldown(&((Arg){ .i = term.histf }));
    686 		kbds_moveto(kbds_c.x, alt ? term.row-1 : term.c.y);
    687 		break;
    688 	case XK_b:
    689 	case XK_B:
    690 		kbds_nextword(1, -1, (ksym == XK_b) ? kbds_sdelim : kbds_ldelim);
    691 		break;
    692 	case XK_w:
    693 	case XK_W:
    694 		kbds_nextword(1, +1, (ksym == XK_w) ? kbds_sdelim : kbds_ldelim);
    695 		break;
    696 	case XK_e:
    697 	case XK_E:
    698 		kbds_nextword(0, +1, (ksym == XK_e) ? kbds_sdelim : kbds_ldelim);
    699 		break;
    700 	case XK_z:
    701 		prevscr = term.scr;
    702 		dy = kbds_c.y - (term.row-1) / 2;
    703 		if (dy <= 0)
    704 			kscrollup(&((Arg){ .i = -dy }));
    705 		else
    706 			kscrolldown(&((Arg){ .i = dy }));
    707 		kbds_moveto(kbds_c.x, kbds_c.y + term.scr - prevscr);
    708 		break;
    709 	case XK_f:
    710 	case XK_F:
    711 	case XK_t:
    712 	case XK_T:
    713 		kbds_finddir = (ksym == XK_f || ksym == XK_t) ? 1 : -1;
    714 		kbds_findtill = (ksym == XK_t || ksym == XK_T) ? 1 : 0;
    715 		kbds_setmode(kbds_mode | KBDS_MODE_FIND);
    716 		return 0;
    717 	case XK_semicolon:
    718 	case XK_r:
    719 		kbds_findnext(kbds_finddir, 1);
    720 		break;
    721 	case XK_comma:
    722 	case XK_R:
    723 		kbds_findnext(-kbds_finddir, 1);
    724 		break;
    725 	case XK_0:
    726 	case XK_KP_0:
    727 		if (!kbds_quant) {
    728 			kbds_moveto(0, kbds_c.y);
    729 			break;
    730 		}
    731 		/* FALLTHROUGH */
    732 	default:
    733 		if (ksym >= XK_0 && ksym <= XK_9) {                 /* 0-9 keyboard */
    734 			q = (kbds_quant * 10) + (ksym ^ XK_0);
    735 			kbds_quant = q <= 99999999 ? q : kbds_quant;
    736 			term.dirty[0] = 1;
    737 			return 0;
    738 		} else if (ksym >= XK_KP_0 && ksym <= XK_KP_9) {    /* 0-9 numpad */
    739 			q = (kbds_quant * 10) + (ksym ^ XK_KP_0);
    740 			kbds_quant = q <= 99999999 ? q : kbds_quant;
    741 			term.dirty[0] = 1;
    742 			return 0;
    743 		} else if (ksym == XK_k || ksym == XK_h)
    744 			i = ksym & 1;
    745 		else if (ksym == XK_l || ksym == XK_j)
    746 			i = ((ksym & 6) | 4) >> 1;
    747 		else if (ksym >= XK_KP_Left && ksym <= XK_KP_Down)
    748 			i = ksym - XK_KP_Left;
    749 		else if ((XK_Home & ksym) != XK_Home || (i = (ksym ^ XK_Home) - 1) > 3)
    750 			return 0;
    751 
    752 		kbds_quant = (kbds_quant ? kbds_quant : 1);
    753 
    754 		if (i & 1) {
    755 			kbds_c.y += kbds_quant * (i & 2 ? 1 : -1);
    756 		} else {
    757 			for (;kbds_quant > 0; kbds_quant--) {
    758 				if (!kbds_moveforward(&kbds_c, (i & 2) ? 1 : -1,
    759 					    KBDS_WRAP_LINE | KBDS_WRAP_EDGE))
    760 					break;
    761 			}
    762 		}
    763 		kbds_moveto(kbds_c.x, kbds_c.y);
    764 	}
    765 	kbds_selecttext();
    766 	kbds_quant = 0;
    767 	term.dirty[0] = 1;
    768 	return 0;
    769 }