lchat

A line oriented chat front end for ii.
git clone git://git.suckless.org/lchat
Log | Files | Refs | README

slackline.c (6196B)


      1 /*
      2  * Copyright (c) 2015-2023 Jan Klemkow <j.klemkow@wemelug.de>
      3  * Copyright (c) 2022-2023 Tom Schwindl <schwindl@posteo.de>
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 
     18 #include <ctype.h>
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 
     23 #include <grapheme.h>
     24 
     25 #include "slackline_internals.h"
     26 #include "slackline.h"
     27 #include "util.h"
     28 
     29 /* CTRL+W: stop erasing if certain characters are reached. */
     30 #define IS_WORD_BREAK "\f\n\r\t\v (){}[]\\/#,.=-+|%$!@^&*"
     31 
     32 struct slackline *
     33 sl_init(void)
     34 {
     35 	char *mode = getenv("EDITOR");
     36 	struct slackline *sl = malloc(sizeof *sl);
     37 
     38 	if (sl == NULL)
     39 		return NULL;
     40 
     41 	sl->bufsize = BUFSIZ;
     42 	if ((sl->buf = malloc(sl->bufsize)) == NULL) {
     43 		free(sl);
     44 		return NULL;
     45 	}
     46 
     47 	memset(sl->ubuf, 0, sizeof(sl->ubuf));
     48 	sl->ubuf_len = 0;
     49 
     50 	sl_reset(sl);
     51 
     52 	sl->mode = SL_DEFAULT;
     53 	if (mode != NULL) {
     54 		if (strcmp(mode, "emacs") == 0)
     55 			sl->mode = SL_EMACS;
     56 		else if (strcmp(mode, "vi") == 0)
     57 			sl->mode = SL_VI;
     58 	}
     59 
     60 	return sl;
     61 }
     62 
     63 void
     64 sl_free(struct slackline *sl)
     65 {
     66 	free(sl->buf);
     67 	free(sl);
     68 }
     69 
     70 void
     71 sl_reset(struct slackline *sl)
     72 {
     73 	sl->buf[0] = '\0';
     74 	sl->ptr = sl->buf;
     75 	sl->last = sl->buf;
     76 
     77 	sl->bcur = 0;
     78 	sl->blen = 0;
     79 	sl->rcur = 0;
     80 	sl->rlen = 0;
     81 
     82 	sl->esc = ESC_NONE;
     83 	sl->ubuf_len = 0;
     84 }
     85 
     86 void
     87 sl_mode(struct slackline *sl, enum mode mode)
     88 {
     89 	sl->mode = mode;
     90 }
     91 
     92 size_t
     93 sl_postobyte(struct slackline *sl, size_t pos)
     94 {
     95 	char *ptr = &sl->buf[0];
     96 	size_t byte = 0;
     97 
     98 	for (;pos > 0; pos--)
     99 		byte += grapheme_next_character_break_utf8(ptr+byte,
    100 		    sl->blen-byte);
    101 
    102 	return byte;
    103 }
    104 
    105 char *
    106 sl_postoptr(struct slackline *sl, size_t pos)
    107 {
    108 	return &sl->buf[sl_postobyte(sl, pos)];
    109 }
    110 
    111 void
    112 sl_backspace(struct slackline *sl)
    113 {
    114 	char *ncur;
    115 
    116 	if (sl->rcur == 0)
    117 		return;
    118 
    119 	ncur = sl_postoptr(sl, sl->rcur - 1);
    120 
    121 	if (sl->rcur < sl->rlen)
    122 		memmove(ncur, sl->ptr, sl->last - sl->ptr);
    123 
    124 	sl->rcur--;
    125 	sl->rlen--;
    126 	sl->bcur = sl_postobyte(sl, sl->rcur);
    127 	sl->blen = sl_postobyte(sl, sl->rlen);
    128 
    129 	sl->last -= sl->ptr - ncur;
    130 	*sl->last = '\0';
    131 
    132 	sl->ptr = ncur;
    133 }
    134 
    135 void
    136 sl_move(struct slackline *sl, enum direction dir)
    137 {
    138 	switch (dir) {
    139 	case HOME:
    140 		sl->bcur = sl->rcur = 0;
    141 		sl->ptr = sl->buf;
    142 		return;
    143 	case END:
    144 		sl->rcur = sl->rlen;
    145 		break;
    146 	case RIGHT:
    147 		if (sl->rcur < sl->rlen)
    148 			sl->rcur++;
    149 		break;
    150 	case LEFT:
    151 		if (sl->rcur > 0)
    152 			sl->rcur--;
    153 		break;
    154 	}
    155 
    156 	sl->bcur = sl_postobyte(sl, sl->rcur);
    157 	sl->ptr = sl->buf + sl->bcur;
    158 }
    159 
    160 static void
    161 sl_default(struct slackline *sl, int key)
    162 {
    163 	switch (key) {
    164 	case ESC_KEY:
    165 		sl->esc = ESC;
    166 		break;
    167 	case CTRL_U:
    168 		sl_reset(sl);
    169 		break;
    170 	case CTRL_W: /* erase previous word */
    171 		while (sl->rcur != 0 && strchr(IS_WORD_BREAK, *(sl->ptr-1)) != NULL)
    172 			sl_backspace(sl);
    173 		while (sl->rcur != 0 && strchr(IS_WORD_BREAK, *(sl->ptr-1)) == NULL)
    174 			sl_backspace(sl);
    175 		break;
    176 	case BACKSPACE:
    177 	case VT_BACKSPACE:
    178 		sl_backspace(sl);
    179 		break;
    180 	default:
    181 		break;
    182 	}
    183 }
    184 
    185 static int
    186 sl_esc(struct slackline *sl, int key)
    187 {
    188 	/* handle escape sequences */
    189 	switch (sl->esc) {
    190 	case ESC_NONE:
    191 		break;
    192 	case ESC:
    193 		sl->esc = key == '[' ? ESC_BRACKET : ESC_NONE;
    194 		return 1;
    195 	case ESC_BRACKET:
    196 		switch (key) {
    197 		case 'A':	/* up    */
    198 		case 'B':	/* down  */
    199 			break;
    200 		case 'C':	/* right */
    201 			sl_move(sl, RIGHT);
    202 			break;
    203 		case 'D':	/* left */
    204 			sl_move(sl, LEFT);
    205 			break;
    206 		case 'H':	/* Home  */
    207 			sl_move(sl, HOME);
    208 			break;
    209 		case 'F':	/* End   */
    210 			sl_move(sl, END);
    211 			break;
    212 		case 'P':	/* delete */
    213 			if (sl->rcur == sl->rlen)
    214 				break;
    215 			sl_move(sl, RIGHT);
    216 			sl_backspace(sl);
    217 			break;
    218 		case '0':
    219 		case '1':
    220 		case '2':
    221 		case '3':
    222 		case '4':
    223 		case '5':
    224 		case '6':
    225 		case '7':
    226 		case '8':
    227 		case '9':
    228 			sl->nummod = key;
    229 			sl->esc = ESC_BRACKET_NUM;
    230 			return 1;
    231 		}
    232 		sl->esc = ESC_NONE;
    233 		return 1;
    234 	case ESC_BRACKET_NUM:
    235 		switch(key) {
    236 		case '~':
    237 			switch(sl->nummod) {
    238 			case '1':	/* Home */
    239 			case '7':
    240 				sl_move(sl, HOME);
    241 				break;
    242 			case '4':	/* End */
    243 			case '8':
    244 				sl_move(sl, END);
    245 				break;
    246 			case '3':	/* Delete */
    247 				if (sl->rcur == sl->rlen)
    248 					break;
    249 				sl_move(sl, RIGHT);
    250 				sl_backspace(sl);
    251 				break;
    252 			}
    253 			sl->esc = ESC_NONE;
    254 			return 1;
    255 		}
    256 	}
    257 
    258 	return 0;
    259 }
    260 
    261 int
    262 sl_keystroke(struct slackline *sl, int key)
    263 {
    264 	uint_least32_t cp;
    265 
    266 	if (sl == NULL || sl->rlen < sl->rcur)
    267 		return -1;
    268 	if (sl_esc(sl, key))
    269 		return 0;
    270 	if (!iscntrl((unsigned char) key))
    271 		goto compose;
    272 
    273 	switch (sl->mode) {
    274 	case SL_DEFAULT:
    275 		sl_default(sl, key);
    276 		break;
    277 	case SL_EMACS:
    278 		sl_default(sl, key);
    279 		sl_emacs(sl, key);
    280 		break;
    281 	case SL_VI:
    282 		/* TODO: implement vi-mode */
    283 		break;
    284 	}
    285 	return 0;
    286 
    287 compose:
    288 	/* byte-wise composing of UTF-8 runes */
    289 	sl->ubuf[sl->ubuf_len++] = key;
    290 	if (grapheme_decode_utf8(sl->ubuf, sl->ubuf_len, &cp) > sl->ubuf_len ||
    291 	    cp == GRAPHEME_INVALID_CODEPOINT)
    292 		return 0;
    293 
    294 	if (sl->blen + sl->ubuf_len >= sl->bufsize) {
    295 		char *nbuf;
    296 
    297 		if ((nbuf = realloc(sl->buf, sl->bufsize * 2)) == NULL)
    298 			return -1;
    299 
    300 		sl->ptr = nbuf + (sl->ptr - sl->buf);
    301 		sl->last = nbuf + (sl->last - sl->buf);
    302 		sl->buf = nbuf;
    303 		sl->bufsize *= 2;
    304 	}
    305 
    306 	/* add character to buffer */
    307 	if (sl->rcur < sl->rlen) {	/* insert into buffer */
    308 		char *cur = sl_postoptr(sl, sl->rcur);
    309 		char *end = sl_postoptr(sl, sl->rlen);
    310 		char *ncur = cur + sl->ubuf_len;
    311 
    312 		memmove(ncur, cur, end - cur);
    313 	}
    314 
    315 	memcpy(sl_postoptr(sl, sl->rcur), sl->ubuf, sl->ubuf_len);
    316 
    317 	sl->ptr  += sl->ubuf_len;
    318 	sl->last += sl->ubuf_len;
    319 	sl->bcur += sl->ubuf_len;
    320 	sl->blen += sl->ubuf_len;
    321 	sl->ubuf_len = 0;
    322 
    323 	sl->rcur++;
    324 	sl->rlen++;
    325 
    326 	*sl->last = '\0';
    327 
    328 	return 0;
    329 }