1/* see license file for copyright and license details */
2
3#include "redline.h"
4#include "../paths.h"
5
6#include <ctype.h>
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <signal.h>
11#include <stdint.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/ioctl.h>
16#include <sys/stat.h>
17#include <sys/types.h>
18#include <termios.h>
19#include <unistd.h>
20
21#define REDLINE_DEFAULT_HISTORY_MAX_LEN 100
22#define REDLINE_MAX_LINE (1024 * 1024)
23#define REDLINE_INITIAL_BUFLEN 4096
24
25#define ENTER 13
26#define CTRL_A 1
27#define CTRL_B 2
28#define CTRL_C 3
29#define CTRL_D 4
30#define CTRL_E 5
31#define CTRL_F 6
32#define CTRL_H 8
33#define CTRL_K 11
34#define CTRL_L 12
35#define CTRL_N 14
36#define CTRL_P 16
37#define CTRL_T 20
38#define CTRL_U 21
39#define CTRL_W 23
40#define CTRL_Y 25
41#define CTRL_Z 26
42#define CTRL_QUIT 28
43#define BACKSPACE 127
44#define ESC 27
45
46struct redlineState {
47 int in_completion;
48 int ifd;
49 int ofd;
50 char *buf;
51 size_t buflen;
52 const char *prompt;
53 size_t plen;
54 size_t pos;
55 size_t oldpos;
56 size_t len;
57 size_t cols;
58 size_t oldrows;
59 int oldrpos;
60 int history_index;
61};
62
63struct abuf {
64 char *b;
65 int len;
66};
67
68static struct termios orig_termios;
69static int rawmode = 0;
70static int mlmode = 0;
71static int history_max_len = REDLINE_DEFAULT_HISTORY_MAX_LEN;
72static int history_len = 0;
73static char **history = NULL;
74static char *kill_buffer = NULL;
75static volatile sig_atomic_t winch_received = 0;
76static struct sigaction orig_sigwinch;
77
78static void redlineEditStop(struct redlineState *l);
79
80static void (*completionCallback)(const char *, struct redlineCompletions *) = NULL;
81
82/* return the number of bytes that compose the utf-8 character starting at c */
83static int
84utf8ByteLen(char c)
85{
86 unsigned char uc = (unsigned char)c;
87 if ((uc & 0x80) == 0)
88 return 1;
89 if ((uc & 0xE0) == 0xC0)
90 return 2;
91 if ((uc & 0xF0) == 0xE0)
92 return 3;
93 if ((uc & 0xF8) == 0xF0)
94 return 4;
95 return 1;
96}
97
98/* decode character starting at s */
99static uint32_t
100utf8DecodeChar(const char *s, size_t *len)
101{
102 uint32_t cp = 0;
103 int l = utf8ByteLen(*s);
104 *len = l;
105 if (l == 1) {
106 cp = ((unsigned char)*s);
107 } else if (l == 2) {
108 cp = ((unsigned char)*s & 0x1F) << 6;
109 cp |= ((unsigned char)*(s + 1) & 0x3F);
110 } else if (l == 3) {
111 cp = ((unsigned char)*s & 0x0F) << 12;
112 cp |= ((unsigned char)*(s + 1) & 0x3F) << 6;
113 cp |= ((unsigned char)*(s + 2) & 0x3F);
114 } else if (l == 4) {
115 cp = ((unsigned char)*s & 0x07) << 18;
116 cp |= ((unsigned char)*(s + 1) & 0x3F) << 12;
117 cp |= ((unsigned char)*(s + 2) & 0x3F) << 6;
118 cp |= ((unsigned char)*(s + 3) & 0x3F);
119 }
120 return cp;
121}
122
123static int
124isZWJ(uint32_t cp)
125{
126 return cp == 0x200D;
127}
128
129static int
130isCombiningMark(uint32_t cp)
131{
132 return (cp >= 0x0300 && cp <= 0x036F) || (cp >= 0x1DC0 && cp <= 0x1DFF)
133 || (cp >= 0x20D0 && cp <= 0x20FF) || (cp >= 0xFE20 && cp <= 0xFE2F);
134}
135
136static int
137isVariationSelector(uint32_t cp)
138{
139 return (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF);
140}
141
142static int
143isSkinToneModifier(uint32_t cp)
144{
145 return cp >= 0x1F3FB && cp <= 0x1F3FF;
146}
147
148static int
149isGraphemeExtend(uint32_t cp)
150{
151 return isCombiningMark(cp) || isVariationSelector(cp) || isSkinToneModifier(cp);
152}
153
154/* decode character going backward from pos */
155static uint32_t
156utf8DecodePrev(const char *buf, size_t pos, size_t *cplen)
157{
158 size_t i = 1;
159 while (pos >= i && i <= 4) {
160 unsigned char uc = (unsigned char)buf[pos - i];
161 if ((uc & 0x80) == 0) {
162 if (i == 1) {
163 *cplen = 1;
164 return uc;
165 }
166 break;
167 }
168 if ((uc & 0xC0) == 0xC0) {
169 int l = utf8ByteLen(buf[pos - i]);
170 if ((size_t)l == i) {
171 *cplen = i;
172 return utf8DecodeChar(buf + pos - i, cplen);
173 }
174 break;
175 }
176 i++;
177 }
178 *cplen = 1;
179 return (unsigned char)buf[pos - 1];
180}
181
182/* calculate width of utf-8 char pos */
183static size_t
184utf8PrevCharLen(const char *buf, size_t pos)
185{
186 size_t len = 0;
187 size_t next_len = 0;
188 uint32_t cp;
189 if (pos == 0)
190 return 0;
191 cp = utf8DecodePrev(buf, pos, &len);
192 pos -= len;
193 while (pos > 0 && isGraphemeExtend(cp)) {
194 cp = utf8DecodePrev(buf, pos, &next_len);
195 len += next_len;
196 pos -= next_len;
197 }
198 if (pos > 0 && isZWJ(cp)) {
199 size_t j = utf8PrevCharLen(buf, pos);
200 if (j > 0)
201 len += j;
202 }
203 return len;
204}
205
206/* calculate width of next utf-8 char */
207static size_t
208utf8NextCharLen(const char *buf, size_t pos, size_t len)
209{
210 size_t clen = 0;
211 size_t offset = 0;
212 uint32_t cp;
213 if (pos >= len)
214 return 0;
215 cp = utf8DecodeChar(buf + pos, &clen);
216 offset = clen;
217 while (pos + offset < len) {
218 size_t next_len = 0;
219 uint32_t next_cp = utf8DecodeChar(buf + pos + offset, &next_len);
220 if (isGraphemeExtend(next_cp)) {
221 offset += next_len;
222 } else if (isZWJ(cp)) {
223 offset += next_len;
224 cp = next_cp;
225 } else {
226 break;
227 }
228 }
229 return offset;
230}
231
232/* get columns needed to display char */
233static int
234utf8CharWidth(uint32_t cp)
235{
236 if (cp == 0)
237 return 0;
238 if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0))
239 return 0;
240 if ((cp >= 0x1100 && cp <= 0x115f) || (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f)
241 || (cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff)
242 || (cp >= 0xfe10 && cp <= 0xfe19) || (cp >= 0xfe30 && cp <= 0xfe6f)
243 || (cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6)
244 || (cp >= 0x20000 && cp <= 0x2fffd) || (cp >= 0x30000 && cp <= 0x3fffd)) {
245 return 2;
246 }
247 return 1;
248}
249
250/* get ansi escape sequence length */
251static size_t
252ansiEscapeLen(const char *s, size_t len)
253{
254 size_t i = 0;
255 if (len < 2 || s[0] != '\x1b' || s[1] != '[')
256 return 0;
257 i = 2;
258 while (i < len) {
259 char c = s[i];
260 if ((c >= '0' && c <= '9') || c == ';' || c == '?' || c == '"') {
261 i++;
262 } else if (c >= 'A' && c <= 'Z') {
263 return i + 1;
264 } else if (c >= 'a' && c <= 'z') {
265 return i + 1;
266 } else {
267 break;
268 }
269 }
270 return 0;
271}
272
273/* calculate width of string */
274static size_t
275utf8StrWidth(const char *s, size_t len)
276{
277 size_t width = 0;
278 size_t i = 0;
279 while (i < len) {
280 size_t elen = ansiEscapeLen(s + i, len - i);
281 if (elen > 0) {
282 i += elen;
283 continue;
284 }
285 size_t clen = 0;
286 uint32_t cp = utf8DecodeChar(s + i, &clen);
287 width += utf8CharWidth(cp);
288 i += clen;
289 }
290 return width;
291}
292
293/* get single character width */
294static int
295utf8SingleCharWidth(const char *s, size_t len)
296{
297 size_t clen = 0;
298 uint32_t cp = utf8DecodeChar(s, &clen);
299 (void)len;
300 return utf8CharWidth(cp);
301}
302
303static int
304isUnsupportedTerm(void)
305{
306 char *term = getenv("TERM");
307 int i;
308 static char *unsupported[] = {"dumb", "cons25", "emacs", NULL};
309 if (term == NULL)
310 return 0;
311 for (i = 0; unsupported[i]; i++) {
312 if (strcasecmp(term, unsupported[i]) == 0)
313 return 1;
314 }
315 return 0;
316}
317
318static void
319sigwinchHandler(int sig)
320{
321 (void)sig;
322 winch_received = 1;
323}
324
325static int
326enableRawMode(int fd)
327{
328 struct termios raw;
329 struct sigaction sa;
330
331 if (!isatty(STDIN_FILENO))
332 return -1;
333 if (tcgetattr(fd, &orig_termios) == -1)
334 return -1;
335
336 raw = orig_termios;
337 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP);
338 raw.c_oflag &= ~(OPOST);
339 raw.c_cflag |= (CS8);
340 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
341 raw.c_cc[VMIN] = 1;
342 raw.c_cc[VTIME] = 0;
343
344 if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
345 return -1;
346
347 rawmode = 1;
348
349 /* register sigwinch handler */
350 sa.sa_handler = sigwinchHandler;
351 sigemptyset(&sa.sa_mask);
352 sa.sa_flags = 0;
353 sigaction(SIGWINCH, &sa, &orig_sigwinch);
354
355 return 0;
356}
357
358static void
359disableRawMode(int fd)
360{
361 if (rawmode) {
362 tcsetattr(fd, TCSAFLUSH, &orig_termios);
363 sigaction(SIGWINCH, &orig_sigwinch, NULL);
364 rawmode = 0;
365 }
366}
367
368static int
369getCursorPosition(int ifd, int ofd)
370{
371 char buf[32];
372 int cols, rows;
373 unsigned int i = 0;
374
375 if (write(ofd, "\x1b[6n", 4) != 4)
376 return -1;
377
378 while (i < sizeof(buf) - 1) {
379 if (read(ifd, buf + i, 1) != 1)
380 break;
381 if (buf[i] == 'R')
382 break;
383 i++;
384 }
385 buf[i] = '\0';
386
387 if (buf[0] != 27 || buf[1] != '[')
388 return -1;
389 if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2)
390 return -1;
391 return cols;
392}
393
394static int
395getColumns(int ifd, int ofd)
396{
397 struct winsize ws;
398 char *cols_env;
399 int tty_fd;
400 int cols = 0;
401
402 if (ioctl(ofd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
403 return ws.ws_col;
404 if (ioctl(ifd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
405 return ws.ws_col;
406 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
407 return ws.ws_col;
408 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
409 return ws.ws_col;
410 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
411 return ws.ws_col;
412
413 tty_fd = open(ARUU_PATH_DEVTTY, O_RDWR | O_NOCTTY);
414 if (tty_fd >= 0) {
415 if (ioctl(tty_fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20) {
416 cols = ws.ws_col;
417 }
418 close(tty_fd);
419 if (cols >= 20)
420 return cols;
421 }
422
423 cols_env = getenv("COLUMNS");
424 if (cols_env) {
425 cols = atoi(cols_env);
426 if (cols >= 20)
427 return cols;
428 }
429
430 /* fallback to cursor position query */
431 int start;
432
433 if (!isatty(ifd) || !isatty(ofd))
434 return 80;
435
436 start = getCursorPosition(ifd, ofd);
437 if (start == -1)
438 return 80;
439
440 if (write(ofd, "\x1b[999C", 6) != 6)
441 return 80;
442 cols = getCursorPosition(ifd, ofd);
443 if (cols == -1)
444 return 80;
445
446 if (cols > start) {
447 char seq[32];
448 snprintf(seq, sizeof(seq), "\x1b[%dD", cols - start);
449 if (write(ofd, seq, strlen(seq)) == -1) {}
450 }
451 if (cols < 20)
452 return 80;
453 return cols;
454}
455
456static void
457redlineBeep(void)
458{
459 fprintf(stderr, "\x7");
460 fflush(stderr);
461}
462
463static void
464freeCompletions(struct redlineCompletions *lc)
465{
466 size_t i;
467 if (lc->cvec) {
468 for (i = 0; i < lc->len; i++) {
469 free(lc->cvec[i]);
470 }
471 free(lc->cvec);
472 }
473}
474
475static size_t
476longestCommonPrefix(struct redlineCompletions *lc)
477{
478 size_t i, j;
479 if (lc->len == 0)
480 return 0;
481 for (i = 0;; i++) {
482 char c = lc->cvec[0][i];
483 if (c == '\0')
484 return i;
485 for (j = 1; j < lc->len; j++) {
486 if (lc->cvec[j][i] != c) {
487 return i;
488 }
489 }
490 }
491}
492
493static void
494printCompletions(struct redlineState *ls, struct redlineCompletions *lc)
495{
496 size_t max_len = 0;
497 size_t i, j, k;
498 size_t col_width, num_cols, num_rows;
499 size_t sp, len, idx;
500 const char *comp;
501 const char *name;
502
503 for (i = 0; i < lc->len; i++) {
504 comp = lc->cvec[i];
505 sp = strlen(comp);
506 if (sp > 0 && comp[sp - 1] == ' ') {
507 sp--;
508 }
509 while (sp > 0 && comp[sp - 1] != ' ') {
510 sp--;
511 }
512 len = strlen(comp + sp);
513 if (len > 0 && (comp + sp)[len - 1] == ' ') {
514 len--;
515 }
516 if (len > max_len) {
517 max_len = len;
518 }
519 }
520
521 col_width = max_len + 2;
522 num_cols = ls->cols / col_width;
523 if (num_cols == 0)
524 num_cols = 1;
525 num_rows = (lc->len + num_cols - 1) / num_cols;
526
527 if (write(ls->ofd, "\r\n", 2) == -1) {}
528 for (i = 0; i < num_rows; i++) {
529 for (j = 0; j < num_cols; j++) {
530 idx = j * num_rows + i;
531 if (idx < lc->len) {
532 comp = lc->cvec[idx];
533 sp = strlen(comp);
534 if (sp > 0 && comp[sp - 1] == ' ') {
535 sp--;
536 }
537 while (sp > 0 && comp[sp - 1] != ' ') {
538 sp--;
539 }
540 name = comp + sp;
541 len = strlen(name);
542 if (len > 0 && name[len - 1] == ' ') {
543 len--;
544 }
545 if (write(ls->ofd, name, len) == -1) {}
546 if (j < num_cols - 1) {
547 for (k = len; k < col_width; k++) {
548 if (write(ls->ofd, " ", 1) == -1) {}
549 }
550 }
551 }
552 }
553 if (write(ls->ofd, "\r\n", 2) == -1) {}
554 }
555}
556
557static void
558abInit(struct abuf *ab)
559{
560 ab->b = NULL;
561 ab->len = 0;
562}
563
564static void
565abAppend(struct abuf *ab, const char *s, int len)
566{
567 char *new = realloc(ab->b, ab->len + len);
568 if (new == NULL)
569 return;
570 memcpy(new + ab->len, s, len);
571 ab->b = new;
572 ab->len += len;
573}
574
575static void
576abFree(struct abuf *ab)
577{
578 free(ab->b);
579}
580
581static void
582refreshSingleLine(struct redlineState *l, int flags)
583{
584 char seq[64];
585 size_t pwidth = utf8StrWidth(l->prompt, l->plen);
586 int fd = l->ofd;
587 char *buf = l->buf;
588 size_t len = l->len;
589 size_t pos = l->pos;
590 size_t poscol;
591 size_t lencol;
592 struct abuf ab;
593
594 poscol = utf8StrWidth(buf, pos);
595 lencol = utf8StrWidth(buf, len);
596
597 while (pwidth + poscol >= l->cols) {
598 size_t clen = utf8NextCharLen(buf, 0, len);
599 int cwidth = utf8SingleCharWidth(buf, clen);
600 buf += clen;
601 len -= clen;
602 pos -= clen;
603 poscol -= cwidth;
604 lencol -= cwidth;
605 }
606
607 while (pwidth + lencol > l->cols) {
608 size_t clen = utf8PrevCharLen(buf, len);
609 int cwidth = utf8SingleCharWidth(buf + len - clen, clen);
610 len -= clen;
611 lencol -= cwidth;
612 }
613
614 abInit(&ab);
615 snprintf(seq, sizeof(seq), "\r");
616 abAppend(&ab, seq, strlen(seq));
617
618 if (flags & 1) {
619 abAppend(&ab, l->prompt, l->plen);
620 abAppend(&ab, buf, len);
621 }
622
623 snprintf(seq, sizeof(seq), "\x1b[0K");
624 abAppend(&ab, seq, strlen(seq));
625
626 if (flags & 1) {
627 snprintf(seq, sizeof(seq), "\r\x1b[%dC", (int)(poscol + pwidth));
628 abAppend(&ab, seq, strlen(seq));
629 }
630
631 if (write(fd, ab.b, ab.len) == -1) {}
632 abFree(&ab);
633}
634
635static void
636refreshMultiLine(struct redlineState *l, int flags)
637{
638 char seq[64];
639 size_t pwidth = utf8StrWidth(l->prompt, l->plen);
640 size_t bufwidth;
641 size_t poswidth;
642 int rows;
643 int rpos2;
644 int col;
645 int old_rows = l->oldrows;
646 int rpos = l->oldrpos;
647 int fd = l->ofd, j;
648 struct abuf ab;
649
650 (void)flags;
651
652 bufwidth = utf8StrWidth(l->buf, l->len);
653 poswidth = utf8StrWidth(l->buf, l->pos);
654 rows = (pwidth + bufwidth + l->cols - 1) / l->cols;
655 l->oldrows = rows;
656
657 abInit(&ab);
658
659 /* move cursor up to the first row, column 0 of the input area */
660 if (rpos > 1) {
661 snprintf(seq, 64, "\r\x1b[%dA", rpos - 1);
662 abAppend(&ab, seq, strlen(seq));
663 } else {
664 abAppend(&ab, "\r", 1);
665 }
666
667 /* clear all old rows */
668 for (j = 0; j < old_rows; j++) {
669 abAppend(&ab, "\x1b[0K", 4);
670 if (j < old_rows - 1) {
671 abAppend(&ab, "\n\r", 2);
672 }
673 }
674
675 /* move cursor back to the first row, column 0 */
676 if (old_rows > 1) {
677 snprintf(seq, 64, "\r\x1b[%dA", old_rows - 1);
678 abAppend(&ab, seq, strlen(seq));
679 } else {
680 abAppend(&ab, "\r", 1);
681 }
682
683 /* print prompt and new buffer */
684 abAppend(&ab, l->prompt, l->plen);
685 abAppend(&ab, l->buf, l->len);
686
687 /* if cursor is at the end of the line and wraps, print a newline */
688 if (l->pos && l->pos == l->len && (poswidth + pwidth) % l->cols == 0) {
689 abAppend(&ab, "\n\r", 2);
690 rows++;
691 if (rows > (int)l->oldrows)
692 l->oldrows = rows;
693 }
694
695 /* calculate cursor row and column */
696 rpos2 = (pwidth + poswidth + l->cols) / l->cols;
697 col = (pwidth + poswidth) % l->cols;
698
699 /* move cursor to the correct row and column */
700 if (rows - rpos2 > 0) {
701 snprintf(seq, 64, "\x1b[%dA", rows - rpos2);
702 abAppend(&ab, seq, strlen(seq));
703 }
704 if (col) {
705 snprintf(seq, 64, "\r\x1b[%dC", col);
706 abAppend(&ab, seq, strlen(seq));
707 } else {
708 abAppend(&ab, "\r", 1);
709 }
710
711 l->oldpos = l->pos;
712 l->oldrpos = rpos2;
713
714 if (write(fd, ab.b, ab.len) == -1) {}
715 abFree(&ab);
716}
717
718static void
719refreshLineWithFlags(struct redlineState *l, int flags)
720{
721 if (mlmode)
722 refreshMultiLine(l, flags);
723 else
724 refreshSingleLine(l, flags);
725}
726
727static void
728refreshLine(struct redlineState *l)
729{
730 refreshLineWithFlags(l, 1);
731}
732
733static int
734completeLine(struct redlineState *ls, int keypressed)
735{
736 struct redlineCompletions lc = {0, NULL};
737 size_t lcp_len;
738 int c = keypressed;
739 int proceed = 1;
740 char query[128];
741 char answer = 0;
742
743 if (c != 9) {
744 ls->in_completion = 0;
745 return c;
746 }
747
748 completionCallback(ls->buf, &lc);
749 if (lc.len == 0) {
750 redlineBeep();
751 ls->in_completion = 0;
752 c = 0;
753 } else if (lc.len == 1) {
754 size_t nwritten = snprintf(ls->buf, ls->buflen, "%s", lc.cvec[0]);
755 ls->len = ls->pos = nwritten;
756 refreshLine(ls);
757 ls->in_completion = 0;
758 c = 0;
759 } else {
760 lcp_len = longestCommonPrefix(&lc);
761 if (lcp_len > ls->len) {
762 size_t nwritten = snprintf(ls->buf, ls->buflen, "%.*s", (int)lcp_len, lc.cvec[0]);
763 ls->len = ls->pos = nwritten;
764 refreshLine(ls);
765 ls->in_completion = 1;
766 c = 0;
767 } else {
768 /* prefix cannot be expanded further */
769 if (ls->in_completion == 0) {
770 /* first tab: beep and wait for the second tab
771 */
772 redlineBeep();
773 ls->in_completion = 1;
774 c = 0;
775 } else {
776 /* second tab: display possibilities */
777 if (lc.len > 100) {
778 snprintf(
779 query,
780 sizeof(query),
781 "\r\nDisplay all %d "
782 "possibilities? (y or n) ",
783 (int)lc.len
784 );
785 if (write(ls->ofd, query, strlen(query)) == -1) {}
786 while (1) {
787 if (read(ls->ifd, &answer, 1) != 1) {
788 proceed = 0;
789 break;
790 }
791 if (answer == 'y' || answer == 'Y' || answer == ' ' || answer == '\t') {
792 proceed = 1;
793 break;
794 }
795 if (answer == 'n' || answer == 'N' || answer == 27 || answer == 3 || answer == 4) {
796 proceed = 0;
797 break;
798 }
799 redlineBeep();
800 }
801 }
802 if (proceed) {
803 printCompletions(ls, &lc);
804 } else {
805 if (write(ls->ofd, "\r\n", 2) == -1) {}
806 }
807 ls->oldrows = 0;
808 refreshLine(ls);
809 ls->in_completion = 0;
810 c = 0;
811 }
812 }
813 }
814
815 freeCompletions(&lc);
816 return c;
817}
818
819static int
820redlineEditInsert(struct redlineState *l, const char *c, int clen)
821{
822 if (l->len + clen >= l->buflen) {
823 return 0;
824 }
825 if (l->len == l->pos) {
826 memcpy(l->buf + l->pos, c, clen);
827 l->pos += clen;
828 l->len += clen;
829 l->buf[l->len] = '\0';
830 refreshLine(l);
831 } else {
832 memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos);
833 memcpy(l->buf + l->pos, c, clen);
834 l->pos += clen;
835 l->len += clen;
836 l->buf[l->len] = '\0';
837 refreshLine(l);
838 }
839 return 1;
840}
841
842static void
843redlineEditBackspace(struct redlineState *l)
844{
845 if (l->pos > 0 && l->len > 0) {
846 size_t clen = utf8PrevCharLen(l->buf, l->pos);
847 memmove(l->buf + l->pos - clen, l->buf + l->pos, l->len - l->pos);
848 l->pos -= clen;
849 l->len -= clen;
850 l->buf[l->len] = '\0';
851 refreshLine(l);
852 }
853}
854
855static void
856redlineEditDelete(struct redlineState *l)
857{
858 if (l->len > 0 && l->pos < l->len) {
859 size_t clen = utf8NextCharLen(l->buf, l->pos, l->len);
860 memmove(l->buf + l->pos, l->buf + l->pos + clen, l->len - l->pos - clen);
861 l->len -= clen;
862 l->buf[l->len] = '\0';
863 refreshLine(l);
864 }
865}
866
867static void
868redlineEditMoveLeft(struct redlineState *l)
869{
870 if (l->pos > 0) {
871 l->pos -= utf8PrevCharLen(l->buf, l->pos);
872 refreshLine(l);
873 }
874}
875
876static void
877redlineEditMoveRight(struct redlineState *l)
878{
879 if (l->pos != l->len) {
880 l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
881 refreshLine(l);
882 }
883}
884
885static void
886redlineEditMoveHome(struct redlineState *l)
887{
888 if (l->pos != 0) {
889 l->pos = 0;
890 refreshLine(l);
891 }
892}
893
894static void
895redlineEditMoveEnd(struct redlineState *l)
896{
897 if (l->pos != l->len) {
898 l->pos = l->len;
899 refreshLine(l);
900 }
901}
902
903static void
904redlineEditMoveWordLeft(struct redlineState *l)
905{
906 if (l->pos > 0) {
907 while (l->pos > 0 && l->buf[l->pos - 1] == ' ')
908 l->pos -= utf8PrevCharLen(l->buf, l->pos);
909 while (l->pos > 0 && l->buf[l->pos - 1] != ' ')
910 l->pos -= utf8PrevCharLen(l->buf, l->pos);
911 refreshLine(l);
912 }
913}
914
915static void
916redlineEditMoveWordRight(struct redlineState *l)
917{
918 if (l->pos < l->len) {
919 while (l->pos < l->len && l->buf[l->pos] == ' ')
920 l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
921 while (l->pos < l->len && l->buf[l->pos] != ' ')
922 l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
923 refreshLine(l);
924 }
925}
926
927static void
928killBufferSave(const char *text, size_t len)
929{
930 free(kill_buffer);
931 kill_buffer = malloc(len + 1);
932 if (kill_buffer) {
933 memcpy(kill_buffer, text, len);
934 kill_buffer[len] = '\0';
935 }
936}
937
938static void
939redlineEditDeleteWordRight(struct redlineState *l)
940{
941 size_t old_pos = l->pos;
942 size_t diff;
943 if (l->pos < l->len) {
944 while (l->pos < l->len && l->buf[l->pos] == ' ')
945 l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
946 while (l->pos < l->len && l->buf[l->pos] != ' ')
947 l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
948 diff = l->pos - old_pos;
949 l->pos = old_pos;
950 killBufferSave(l->buf + l->pos, diff);
951 memmove(l->buf + l->pos, l->buf + l->pos + diff, l->len - l->pos - diff + 1);
952 l->len -= diff;
953 refreshLine(l);
954 }
955}
956
957static void
958redlineEditDeletePrevWord(struct redlineState *l)
959{
960 size_t old_pos = l->pos;
961 size_t diff;
962 if (l->pos > 0) {
963 while (l->pos > 0 && l->buf[l->pos - 1] == ' ')
964 l->pos -= utf8PrevCharLen(l->buf, l->pos);
965 while (l->pos > 0 && l->buf[l->pos - 1] != ' ')
966 l->pos -= utf8PrevCharLen(l->buf, l->pos);
967 diff = old_pos - l->pos;
968 killBufferSave(l->buf + l->pos, diff);
969 memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1);
970 l->len -= diff;
971 refreshLine(l);
972 }
973}
974
975void
976redlineHistoryAdd(const char *line)
977{
978 char *linecopy;
979 if (history_max_len == 0)
980 return;
981 if (history == NULL) {
982 history = malloc(sizeof(char *) * history_max_len);
983 if (history == NULL)
984 return;
985 memset(history, 0, sizeof(char *) * history_max_len);
986 }
987 if (history_len && strcmp(history[history_len - 1], line) == 0)
988 return;
989 linecopy = strdup(line);
990 if (!linecopy)
991 return;
992 if (history_len == history_max_len) {
993 free(history[0]);
994 memmove(history, history + 1, sizeof(char *) * (history_max_len - 1));
995 history_len--;
996 }
997 history[history_len] = linecopy;
998 history_len++;
999}
1000
1001void
1002redlineHistorySetMaxLen(int len)
1003{
1004 char **new;
1005 if (len < 1)
1006 return;
1007 if (history) {
1008 int tocopy = history_len;
1009 new = malloc(sizeof(char *) * len);
1010 if (new == NULL)
1011 return;
1012 if (len < tocopy) {
1013 int j;
1014 for (j = 0; j < tocopy - len; j++)
1015 free(history[j]);
1016 tocopy = len;
1017 }
1018 memset(new, 0, sizeof(char *) * len);
1019 memcpy(new, history + (history_len - tocopy), sizeof(char *) * tocopy);
1020 free(history);
1021 history = new;
1022 }
1023 history_max_len = len;
1024 if (history_len > history_max_len)
1025 history_len = history_max_len;
1026}
1027
1028int
1029redlineHistorySave(const char *filename)
1030{
1031 mode_t old_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
1032 FILE *fp;
1033 int j;
1034
1035 fp = fopen(filename, "w");
1036 umask(old_umask);
1037 if (fp == NULL)
1038 return -1;
1039 chmod(filename, S_IRUSR | S_IWUSR);
1040 for (j = 0; j < history_len; j++) {
1041 fprintf(fp, "%s\n", history[j]);
1042 }
1043 fclose(fp);
1044 return 0;
1045}
1046
1047int
1048redlineHistoryLoad(const char *filename)
1049{
1050 FILE *fp = fopen(filename, "r");
1051 char buf[REDLINE_INITIAL_BUFLEN];
1052 if (fp == NULL)
1053 return -1;
1054 while (fgets(buf, sizeof(buf), fp) != NULL) {
1055 char *p = strchr(buf, '\r');
1056 if (!p)
1057 p = strchr(buf, '\n');
1058 if (p)
1059 *p = '\0';
1060 redlineHistoryAdd(buf);
1061 }
1062 fclose(fp);
1063 return 0;
1064}
1065
1066char *
1067redlineHistoryGet(int idx)
1068{
1069 if (idx >= 0 && idx < history_len)
1070 return history[idx];
1071 return NULL;
1072}
1073
1074int
1075redlineHistoryLen(void)
1076{
1077 return history_len;
1078}
1079
1080static void
1081redlineEditHistoryNext(struct redlineState *l, int dir)
1082{
1083 if (history_len > 1) {
1084 const char *src;
1085 size_t len;
1086 free(history[history_len - 1 - l->history_index]);
1087 history[history_len - 1 - l->history_index] = strdup(l->buf);
1088 l->history_index += (dir == 1) ? 1 : -1;
1089 if (l->history_index < 0) {
1090 l->history_index = 0;
1091 return;
1092 } else if (l->history_index >= history_len) {
1093 l->history_index = history_len - 1;
1094 return;
1095 }
1096 src = history[history_len - 1 - l->history_index];
1097 len = strlen(src);
1098 if (len >= l->buflen)
1099 len = l->buflen - 1;
1100 memcpy(l->buf, src, len);
1101 l->buf[len] = '\0';
1102 l->len = l->pos = len;
1103 refreshLine(l);
1104 }
1105}
1106
1107static char *
1108redlineReadLine(FILE *fp)
1109{
1110 char *line = NULL;
1111 size_t len = 0, cap = 0;
1112 while (1) {
1113 if (len + 1 >= cap) {
1114 size_t newcap = cap ? cap * 2 : 16;
1115 char *new = realloc(line, newcap);
1116 if (!new) {
1117 free(line);
1118 return NULL;
1119 }
1120 line = new;
1121 cap = newcap;
1122 }
1123 int c = fgetc(fp);
1124 if (c == EOF || c == '\n') {
1125 if (c == EOF && len == 0) {
1126 free(line);
1127 return NULL;
1128 }
1129 line[len] = '\0';
1130 return line;
1131 }
1132 line[len++] = c;
1133 }
1134}
1135
1136static char *
1137redlineNoTTY(void)
1138{
1139 return redlineReadLine(stdin);
1140}
1141
1142void
1143redlineClearScreen(void)
1144{
1145 if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) == -1) {}
1146}
1147
1148static char *
1149redlineEditFeed(struct redlineState *l)
1150{
1151 char c;
1152 int nread;
1153 char seq[3];
1154 char param[8];
1155 size_t plen;
1156 char final;
1157 char p;
1158 int is_word_jump;
1159 char tmp[32];
1160 size_t prevlen;
1161 size_t currlen;
1162 size_t prevstart;
1163 char utf8[4];
1164 int utf8len;
1165 int i;
1166
1167 if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY"))
1168 return redlineNoTTY();
1169
1170 while (1) {
1171 nread = read(l->ifd, &c, 1);
1172 if (nread < 0) {
1173 if (errno == EINTR) {
1174 if (winch_received) {
1175 winch_received = 0;
1176 l->cols = getColumns(l->ifd, l->ofd);
1177 refreshLine(l);
1178 }
1179 continue;
1180 }
1181 return (errno == EAGAIN || errno == EWOULDBLOCK) ? "more" : NULL;
1182 } else if (nread == 0) {
1183 return NULL;
1184 }
1185 break;
1186 }
1187
1188 if ((l->in_completion || c == 9) && completionCallback != NULL) {
1189 int retval = completeLine(l, c);
1190 if (retval == 0)
1191 return "more";
1192 c = retval;
1193 }
1194
1195 switch (c) {
1196 case 10:
1197 case ENTER:
1198 if (mlmode)
1199 redlineEditMoveEnd(l);
1200 return strdup(l->buf);
1201 case CTRL_C:
1202 if (write(l->ofd, "^C", 2) == -1) {}
1203 errno = EAGAIN;
1204 return NULL;
1205 case CTRL_Z:
1206 break;
1207 case CTRL_QUIT:
1208 if (write(l->ofd, "^\\", 2) == -1) {}
1209 redlineEditStop(l);
1210 fflush(stdout);
1211 {
1212 struct sigaction sa, osa;
1213 sa.sa_handler = SIG_DFL;
1214 sigemptyset(&sa.sa_mask);
1215 sa.sa_flags = 0;
1216 sigaction(SIGQUIT, &sa, &osa);
1217 kill(getpid(), SIGQUIT);
1218 sigaction(SIGQUIT, &osa, NULL);
1219 }
1220 enableRawMode(l->ifd);
1221 refreshLine(l);
1222 break;
1223 case BACKSPACE:
1224 case 8:
1225 redlineEditBackspace(l);
1226 break;
1227 case CTRL_D:
1228 if (l->len > 0) {
1229 redlineEditDelete(l);
1230 } else {
1231 errno = ENOENT;
1232 return NULL;
1233 }
1234 break;
1235 case CTRL_T:
1236 if (l->pos > 0 && l->pos < l->len) {
1237 prevlen = utf8PrevCharLen(l->buf, l->pos);
1238 currlen = utf8NextCharLen(l->buf, l->pos, l->len);
1239 prevstart = l->pos - prevlen;
1240 if (prevlen > sizeof(tmp) || currlen > sizeof(tmp))
1241 break;
1242 memcpy(tmp, l->buf + l->pos, currlen);
1243 memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen);
1244 memcpy(l->buf + prevstart, tmp, currlen);
1245 if (l->pos + currlen <= l->len)
1246 l->pos += currlen;
1247 refreshLine(l);
1248 }
1249 break;
1250 case CTRL_B:
1251 redlineEditMoveLeft(l);
1252 break;
1253 case CTRL_F:
1254 redlineEditMoveRight(l);
1255 break;
1256 case CTRL_P:
1257 redlineEditHistoryNext(l, 1);
1258 break;
1259 case CTRL_N:
1260 redlineEditHistoryNext(l, 0);
1261 break;
1262 case ESC:
1263 if (read(l->ifd, seq, 1) == -1)
1264 break;
1265 if (seq[0] == '[' || seq[0] == 'O') {
1266 if (read(l->ifd, seq + 1, 1) == -1)
1267 break;
1268 if (seq[0] == '[') {
1269 if (seq[1] >= '0' && seq[1] <= '9') {
1270 plen = 1;
1271 final = 0;
1272 param[0] = seq[1];
1273 while (plen < sizeof(param)) {
1274 if (read(l->ifd, &p, 1) != 1)
1275 break;
1276 if ((p >= '0' && p <= '9') || p == ';') {
1277 param[plen++] = p;
1278 } else {
1279 final = p;
1280 break;
1281 }
1282 }
1283 if (final == '~') {
1284 if (plen == 1 && param[0] == '3') {
1285 redlineEditDelete(l);
1286 }
1287 } else if (final == 'D' || final == 'C') {
1288 is_word_jump = 0;
1289 if (plen == 3 && param[0] == '1' && param[1] == ';'
1290 && (param[2] == '5' || param[2] == '3')) {
1291 is_word_jump = 1;
1292 } else if (plen == 1 && (param[0] == '5' || param[0] == '3')) {
1293 is_word_jump = 1;
1294 }
1295 if (is_word_jump) {
1296 if (final == 'D') {
1297 redlineEditMoveWordLeft(l);
1298 } else {
1299 redlineEditMoveWordRight(l);
1300 }
1301 }
1302 }
1303 } else {
1304 switch (seq[1]) {
1305 case 'A':
1306 redlineEditHistoryNext(l, 1);
1307 break;
1308 case 'B':
1309 redlineEditHistoryNext(l, 0);
1310 break;
1311 case 'C':
1312 redlineEditMoveRight(l);
1313 break;
1314 case 'D':
1315 redlineEditMoveLeft(l);
1316 break;
1317 case 'H':
1318 redlineEditMoveHome(l);
1319 break;
1320 case 'F':
1321 redlineEditMoveEnd(l);
1322 break;
1323 }
1324 }
1325 } else if (seq[0] == 'O') {
1326 switch (seq[1]) {
1327 case 'H':
1328 redlineEditMoveHome(l);
1329 break;
1330 case 'F':
1331 redlineEditMoveEnd(l);
1332 break;
1333 }
1334 }
1335 } else {
1336 if (seq[0] == 'b' || seq[0] == 'B') {
1337 redlineEditMoveWordLeft(l);
1338 } else if (seq[0] == 'f' || seq[0] == 'F') {
1339 redlineEditMoveWordRight(l);
1340 } else if (seq[0] == 'd' || seq[0] == 'D') {
1341 redlineEditDeleteWordRight(l);
1342 } else if (seq[0] == 127 || seq[0] == 8) {
1343 redlineEditDeletePrevWord(l);
1344 }
1345 }
1346 break;
1347 default:
1348 if (c < 32)
1349 break;
1350 utf8len = utf8ByteLen(c);
1351 utf8[0] = c;
1352 if (utf8len > 1) {
1353 for (i = 1; i < utf8len; i++) {
1354 if (read(l->ifd, utf8 + i, 1) != 1)
1355 break;
1356 }
1357 }
1358 if (redlineEditInsert(l, utf8, utf8len) == 0)
1359 return NULL;
1360 break;
1361 case CTRL_U:
1362 killBufferSave(l->buf, l->pos);
1363 memmove(l->buf, l->buf + l->pos, l->len - l->pos + 1);
1364 l->len -= l->pos;
1365 l->pos = 0;
1366 refreshLine(l);
1367 break;
1368 case CTRL_K:
1369 killBufferSave(l->buf + l->pos, l->len - l->pos);
1370 l->buf[l->pos] = '\0';
1371 l->len = l->pos;
1372 refreshLine(l);
1373 break;
1374 case CTRL_A:
1375 redlineEditMoveHome(l);
1376 break;
1377 case CTRL_E:
1378 redlineEditMoveEnd(l);
1379 break;
1380 case CTRL_L:
1381 redlineClearScreen();
1382 refreshLine(l);
1383 break;
1384 case CTRL_W:
1385 redlineEditDeletePrevWord(l);
1386 break;
1387 case CTRL_Y:
1388 if (kill_buffer) {
1389 redlineEditInsert(l, kill_buffer, strlen(kill_buffer));
1390 }
1391 break;
1392 }
1393 return "more";
1394}
1395
1396static int
1397redlineEditStart(
1398 struct redlineState *l,
1399 int stdin_fd,
1400 int stdout_fd,
1401 char *buf,
1402 size_t buflen,
1403 const char *prompt
1404)
1405{
1406 l->in_completion = 0;
1407 l->ifd = stdin_fd;
1408 l->ofd = stdout_fd;
1409 l->buf = buf;
1410 l->buflen = buflen;
1411 l->prompt = prompt;
1412 l->plen = strlen(prompt);
1413 l->pos = 0;
1414 l->oldpos = 0;
1415 l->len = 0;
1416 l->cols = getColumns(stdin_fd, stdout_fd);
1417 l->oldrows = 0;
1418 l->oldrpos = 0;
1419 l->history_index = 0;
1420 l->buf[0] = '\0';
1421
1422 if (enableRawMode(l->ifd) == -1)
1423 return -1;
1424 refreshLine(l);
1425 return 0;
1426}
1427
1428static void
1429redlineEditStop(struct redlineState *l)
1430{
1431 if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY"))
1432 return;
1433 disableRawMode(l->ifd);
1434 printf("\n");
1435}
1436
1437char *
1438redline(const char *prompt)
1439{
1440 struct redlineState l;
1441 char *buf;
1442 char *res;
1443
1444 if (!isatty(STDIN_FILENO) || isUnsupportedTerm()) {
1445 if (write(STDOUT_FILENO, prompt, strlen(prompt)) == -1) {}
1446 return redlineNoTTY();
1447 }
1448
1449 buf = malloc(REDLINE_INITIAL_BUFLEN);
1450 if (buf == NULL)
1451 return NULL;
1452 if (redlineEditStart(&l, STDIN_FILENO, STDOUT_FILENO, buf, REDLINE_INITIAL_BUFLEN, prompt)
1453 == -1) {
1454 free(buf);
1455 return NULL;
1456 }
1457 redlineHistoryAdd("");
1458 while (1) {
1459 res = redlineEditFeed(&l);
1460 if (res == NULL || strcmp(res, "more") != 0) {
1461 break;
1462 }
1463 }
1464 redlineEditStop(&l);
1465 if (history_len > 0) {
1466 history_len--;
1467 free(history[history_len]);
1468 }
1469 free(l.buf);
1470 if (res == NULL && errno == EAGAIN) {
1471 kill(getpid(), SIGINT);
1472 }
1473 return res;
1474}
1475
1476void
1477redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *))
1478{
1479 completionCallback = cb;
1480}
1481
1482void
1483redlineAddCompletion(struct redlineCompletions *lc, const char *str)
1484{
1485 size_t len = strlen(str);
1486 char *copy, **cvec;
1487
1488 copy = malloc(len + 1);
1489 if (copy == NULL)
1490 return;
1491 memcpy(copy, str, len + 1);
1492 cvec = realloc(lc->cvec, sizeof(char *) * (lc->len + 1));
1493 if (cvec == NULL) {
1494 free(copy);
1495 return;
1496 }
1497 lc->cvec = cvec;
1498 lc->cvec[lc->len++] = copy;
1499}
1500
1501void
1502redlineSetMultiLine(int ml)
1503{
1504 mlmode = ml;
1505}