lchat

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

slackline.c (5533B)


      1 /*
      2  * Copyright (c) 2015-2022 Jan Klemkow <j.klemkow@wemelug.de>
      3  * Copyright (c) 2022 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.h"
     26 
     27 enum direction {LEFT, RIGHT, HOME, END};
     28 
     29 struct slackline *
     30 sl_init(void)
     31 {
     32 	struct slackline *sl = malloc(sizeof *sl);
     33 
     34 	if (sl == NULL)
     35 		return NULL;
     36 
     37 	sl->bufsize = BUFSIZ;
     38 	if ((sl->buf = malloc(sl->bufsize)) == NULL) {
     39 		free(sl);
     40 		return NULL;
     41 	}
     42 
     43 	memset(sl->ubuf, 0, sizeof(sl->ubuf));
     44 	sl->ubuf_len = 0;
     45 
     46 	sl_reset(sl);
     47 
     48 	return sl;
     49 }
     50 
     51 void
     52 sl_free(struct slackline *sl)
     53 {
     54 	free(sl->buf);
     55 	free(sl);
     56 }
     57 
     58 void
     59 sl_reset(struct slackline *sl)
     60 {
     61 	sl->buf[0] = '\0';
     62 	sl->ptr = sl->buf;
     63 	sl->last = sl->buf;
     64 
     65 	sl->bcur = 0;
     66 	sl->blen = 0;
     67 	sl->rcur = 0;
     68 	sl->rlen = 0;
     69 
     70 	sl->esc = ESC_NONE;
     71 	sl->ubuf_len = 0;
     72 }
     73 
     74 static size_t
     75 sl_postobyte(struct slackline *sl, size_t pos)
     76 {
     77 	char *ptr = &sl->buf[0];
     78 	size_t byte = 0;
     79 
     80 	for (;pos > 0; pos--)
     81 		byte += grapheme_next_character_break_utf8(ptr+byte,
     82 		    sl->blen-byte);
     83 
     84 	return byte;
     85 }
     86 
     87 static char *
     88 sl_postoptr(struct slackline *sl, size_t pos)
     89 {
     90 	return &sl->buf[sl_postobyte(sl, pos)];
     91 }
     92 
     93 static void
     94 sl_backspace(struct slackline *sl)
     95 {
     96 	char *ncur;
     97 
     98 	if (sl->rcur == 0)
     99 		return;
    100 
    101 	ncur = sl_postoptr(sl, sl->rcur - 1);
    102 
    103 	if (sl->rcur < sl->rlen)
    104 		memmove(ncur, sl->ptr, sl->last - sl->ptr);
    105 
    106 	sl->rcur--;
    107 	sl->rlen--;
    108 	sl->bcur = sl_postobyte(sl, sl->rcur);
    109 	sl->blen = sl_postobyte(sl, sl->rlen);
    110 
    111 	sl->last -= sl->ptr - ncur;
    112 	*sl->last = '\0';
    113 
    114 	sl->ptr = ncur;
    115 }
    116 
    117 static void
    118 sl_move(struct slackline *sl, enum direction dir)
    119 {
    120 	switch (dir) {
    121 	case HOME:
    122 		sl->bcur = sl->rcur = 0;
    123 		sl->ptr = sl->buf;
    124 		return;
    125 	case END:
    126 		sl->rcur = sl->rlen;
    127 		break;
    128 	case RIGHT:
    129 		if (sl->rcur < sl->rlen)
    130 			sl->rcur++;
    131 		break;
    132 	case LEFT:
    133 		if (sl->rcur > 0)
    134 			sl->rcur--;
    135 		break;
    136 	}
    137 
    138 	sl->bcur = sl_postobyte(sl, sl->rcur);
    139 	sl->ptr = sl->buf + sl->bcur;
    140 }
    141 
    142 int
    143 sl_keystroke(struct slackline *sl, int key)
    144 {
    145 	uint_least32_t cp;
    146 
    147 	if (sl == NULL || sl->rlen < sl->rcur)
    148 		return -1;
    149 
    150 	/* handle escape sequences */
    151 	switch (sl->esc) {
    152 	case ESC_NONE:
    153 		break;
    154 	case ESC:
    155 		sl->esc = key == '[' ? ESC_BRACKET : ESC_NONE;
    156 		return 0;
    157 	case ESC_BRACKET:
    158 		switch (key) {
    159 		case 'A':	/* up    */
    160 		case 'B':	/* down  */
    161 			break;
    162 		case 'C':	/* right */
    163 			sl_move(sl, RIGHT);
    164 			break;
    165 		case 'D':	/* left */
    166 			sl_move(sl, LEFT);
    167 			break;
    168 		case 'H':	/* Home  */
    169 			sl_move(sl, HOME);
    170 			break;
    171 		case 'F':	/* End   */
    172 			sl_move(sl, END);
    173 			break;
    174 		case 'P':	/* delete */
    175 			if (sl->rcur == sl->rlen)
    176 				break;
    177 			sl_move(sl, RIGHT);
    178 			sl_backspace(sl);
    179 			break;
    180 		case '0':
    181 		case '1':
    182 		case '2':
    183 		case '3':
    184 		case '4':
    185 		case '5':
    186 		case '6':
    187 		case '7':
    188 		case '8':
    189 		case '9':
    190 			sl->nummod = key;
    191 			sl->esc = ESC_BRACKET_NUM;
    192 			return 0;
    193 		}
    194 		sl->esc = ESC_NONE;
    195 		return 0;
    196 	case ESC_BRACKET_NUM:
    197 		switch(key) {
    198 		case '~':
    199 			switch(sl->nummod) {
    200 			case '1':	/* Home */
    201 			case '7':
    202 				sl_move(sl, HOME);
    203 				break;
    204 			case '4':	/* End */
    205 			case '8':
    206 				sl_move(sl, END);
    207 				break;
    208 			case '3':	/* Delete */
    209 				if (sl->rcur == sl->rlen)
    210 					break;
    211 				sl_move(sl, RIGHT);
    212 				sl_backspace(sl);
    213 				break;
    214 			}
    215 			sl->esc = ESC_NONE;
    216 			return 0;
    217 		}
    218 	}
    219 
    220 	if (!iscntrl((unsigned char) key))
    221 		goto compose;
    222 
    223 	/* handle ctl keys */
    224 	switch (key) {
    225 	case 27:	/* Escape */
    226 		sl->esc = ESC;
    227 		return 0;
    228 	case 127:	/* backspace */
    229 	case 8:		/* backspace */
    230 		sl_backspace(sl);
    231 		return 0;
    232 	case 21: /* ctrl+u -- clearline */
    233 		sl_reset(sl);
    234 		return 0;
    235 	case 23: /* ctrl+w -- erase previous word */
    236 		while (sl->rcur != 0 && isspace((unsigned char) *(sl->ptr-1)))
    237 			sl_backspace(sl);
    238 
    239 		while (sl->rcur != 0 && !isspace((unsigned char) *(sl->ptr-1)))
    240 			sl_backspace(sl);
    241 		return 0;
    242 	default:
    243 		return 0;
    244 	}
    245 
    246 compose:
    247 	/* byte-wise composing of UTF-8 runes */
    248 	sl->ubuf[sl->ubuf_len++] = key;
    249 	if (grapheme_decode_utf8(sl->ubuf, sl->ubuf_len, &cp) > sl->ubuf_len ||
    250 	    cp == GRAPHEME_INVALID_CODEPOINT)
    251 		return 0;
    252 
    253 	if (sl->blen + sl->ubuf_len >= sl->bufsize) {
    254 		char *nbuf;
    255 
    256 		if ((nbuf = realloc(sl->buf, sl->bufsize * 2)) == NULL)
    257 			return -1;
    258 
    259 		sl->ptr = nbuf + (sl->ptr - sl->buf);
    260 		sl->last = nbuf + (sl->last - sl->buf);
    261 		sl->buf = nbuf;
    262 		sl->bufsize *= 2;
    263 	}
    264 
    265 	/* add character to buffer */
    266 	if (sl->rcur < sl->rlen) {	/* insert into buffer */
    267 		char *cur = sl_postoptr(sl, sl->rcur);
    268 		char *end = sl_postoptr(sl, sl->rlen);
    269 		char *ncur = cur + sl->ubuf_len;
    270 
    271 		memmove(ncur, cur, end - cur);
    272 	}
    273 
    274 	memcpy(sl_postoptr(sl, sl->rcur), sl->ubuf, sl->ubuf_len);
    275 
    276 	sl->ptr  += sl->ubuf_len;
    277 	sl->last += sl->ubuf_len;
    278 	sl->bcur += sl->ubuf_len;
    279 	sl->blen += sl->ubuf_len;
    280 	sl->ubuf_len = 0;
    281 
    282 	sl->rcur++;
    283 	sl->rlen++;
    284 
    285 	*sl->last = '\0';
    286 
    287 	return 0;
    288 }