slackline.c - lchat - A line oriented chat front end for ii.
 (HTM) git clone git://git.suckless.org/lchat
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) 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 }