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