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 }