1 #include "console.h"
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <stdbool.h>
6 #include <errno.h>
7 #include <string.h>
8 #include <signal.h>
9 #include <ctype.h>
10 #include <assert.h>
11 #include <pthread.h>
12 #include <stdarg.h>
13 #include <stdint.h>
14 
15 #define ESC(x) "\x1B" x
16 #define CSI(x) ESC("[" x)
17 #define SGR(x) CSI(x "m")
18 
19 static bool initialized = false;
20 typedef struct listent_s listent_t;
21 
22 struct listent_s {
23     listent_t *prev;
24     listent_t *next;
25     char content[];
26 };
27 
28 typedef struct {
29     listent_t *first;
30     listent_t *last;
31 } fifo_t;
32 
33 static fifo_t lines;
34 static fifo_t history;
35 
remove_entry(fifo_t * fifo,listent_t * entry)36 static void remove_entry(fifo_t *fifo, listent_t *entry)
37 {
38     if (fifo->last == entry) {
39         fifo->last = entry->prev;
40     }
41     if (fifo->first == entry) {
42         fifo->first = entry->next;
43     }
44     if (entry->next) {
45         entry->next->prev = entry->prev;
46     }
47     if (entry->prev) {
48         entry->prev->next = entry->next;
49     }
50     free(entry);
51 }
52 
add_entry(fifo_t * fifo,const char * content)53 static void add_entry(fifo_t *fifo, const char *content)
54 {
55     size_t length = strlen(content);
56     listent_t *entry = malloc(sizeof(*entry) + length + 1);
57     entry->next = NULL;
58     entry->prev = fifo->last;
59     memcpy(entry->content, content, length);
60     entry->content[length] = 0;
61     if (fifo->last) {
62         fifo->last->next = entry;
63     }
64     fifo->last = entry;
65     if (!fifo->first) {
66         fifo->first = entry;
67     }
68 }
69 
reverse_find(listent_t * entry,const char * string,bool exact)70 static listent_t *reverse_find(listent_t *entry, const char *string, bool exact)
71 {
72     while (entry) {
73         if (exact && strcmp(entry->content, string) == 0) {
74             return entry;
75         }
76         if (!exact && strstr(entry->content, string)) {
77             return entry;
78         }
79         entry = entry->prev;
80     }
81     return NULL;
82 }
83 
is_term(void)84 static bool is_term(void)
85 {
86     if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false;
87 #ifdef _WIN32
88     if (AllocConsole()) {
89         FreeConsole();
90         return false;
91     }
92 
93     unsigned long input_mode, output_mode;
94 
95     GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
96     GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
97     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
98     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
99 
100     CONSOLE_SCREEN_BUFFER_INFO before = {0,};
101     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &before);
102 
103     printf(SGR("0"));
104 
105     CONSOLE_SCREEN_BUFFER_INFO after = {0,};
106     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &after);
107 
108     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
109     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
110 
111 
112     if (before.dwCursorPosition.X != after.dwCursorPosition.X ||
113         before.dwCursorPosition.Y != after.dwCursorPosition.Y) {
114         printf("\r      \r");
115         return false;
116     }
117     return true;
118 #else
119     return getenv("TERM");
120 #endif
121 }
122 
123 static unsigned width, height;
124 
raw_getc(void)125 static char raw_getc(void)
126 {
127 #ifdef _WIN32
128     char c;
129     unsigned long ret;
130     ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL);
131 #else
132     ssize_t ret;
133     char c;
134 
135     do {
136         ret = read(STDIN_FILENO, &c, 1);
137     } while (ret == -1 && errno == EINTR);
138 #endif
139     return ret == 1? c : EOF;
140 }
141 
142 #ifdef _WIN32
143 #pragma clang diagnostic ignored "-Wmacro-redefined"
144 #include <Windows.h>
145 
update_size(void)146 static void update_size(void)
147 {
148     CONSOLE_SCREEN_BUFFER_INFO csbi;
149     GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
150     width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
151     height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
152 }
153 
154 static unsigned long input_mode, output_mode;
155 
cleanup(void)156 static void cleanup(void)
157 {
158     printf(CSI("!p")); // reset
159     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
160     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
161     fflush(stdout);
162 }
163 
initialize(void)164 static bool initialize(void)
165 {
166     if (!is_term()) return false;
167     update_size();
168     if (width == 0 || height == 0) {
169         return false;
170     }
171 
172     static bool once = false;
173     if (!once) {
174         atexit(cleanup);
175         GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
176         GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
177         once = true;
178     }
179     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
180     SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
181 
182     printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
183 
184     fflush(stdout);
185     initialized = true;
186     return true;
187 }
188 #else
189 #include <sys/ioctl.h>
190 
update_size(void)191 static void update_size(void)
192 {
193     struct winsize winsize;
194     ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
195     width = winsize.ws_col;
196     height = winsize.ws_row;
197 }
198 
terminal_resized(int ignored)199 static void terminal_resized(int ignored)
200 {
201     update_size();
202 }
203 
204 #include <termios.h>
205 static struct termios terminal;
206 
207 
cleanup(void)208 static void cleanup(void)
209 {
210     printf(CSI("!p")); // reset
211     tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal);
212     fflush(stdout);
213 }
214 
initialize(void)215 static bool initialize(void)
216 {
217     if (!is_term()) return false;
218     update_size();
219     if (width == 0 || height == 0) {
220         return false;
221     }
222 
223     static bool once = false;
224     if (!once) {
225         atexit(cleanup);
226         signal(SIGWINCH, terminal_resized);
227         tcgetattr(STDIN_FILENO, &terminal);
228 #ifdef _WIN32
229         _setmode(STDIN_FILENO, _O_TEXT);
230 #endif
231         once = true;
232     }
233     struct termios raw_terminal;
234     raw_terminal = terminal;
235     raw_terminal.c_lflag = 0;
236     tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal);
237 
238     printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
239 
240     fflush(stdout);
241     initialized = true;
242     return true;
243 }
244 #endif
245 
246 static struct {
247     char *content;
248     size_t allocation_size;
249     size_t length;
250     size_t position;
251     size_t scroll;
252     bool reverse_search;
253     listent_t *search_line;
254 } line;
255 
256 #define CTL(x) ((x) - 'A' + 1)
257 
258 static const char *prompt = "";
259 static size_t prompt_length = 0;
260 static bool repeat_empty = false;
261 
redraw_prompt(bool force)262 static bool redraw_prompt(bool force)
263 {
264     if (line.reverse_search) {
265         if (!force) return false;
266         if (line.length == 0) {
267             printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1);
268             return true;
269         }
270         if (!line.search_line) {
271             printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content);
272             return true;
273         }
274         const char *loc = strstr(line.search_line->content, line.content);
275         printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"),
276                prompt,
277                (int)(loc - line.search_line->content),
278                line.search_line->content,
279                line.content,
280                loc + line.length,
281                (unsigned)(loc - line.search_line->content + line.length + prompt_length + 1));
282         return true;
283     }
284 
285     size_t max = width - 1 - prompt_length;
286 
287     if (line.scroll && line.length <= max) {
288         line.scroll = 0;
289         force = true;
290     }
291 
292     if (line.scroll > line.length - max) {
293         line.scroll = line.length - max;
294         force = true;
295     }
296 
297     if (line.position < line.scroll + 1 && line.position) {
298         line.scroll = line.position - 1;
299         force = true;
300     }
301 
302     if (line.position == 0 && line.scroll) {
303         line.scroll = 0;
304         force = true;
305     }
306 
307     if (line.position > line.scroll + max) {
308         line.scroll = line.position - max;
309         force = true;
310     }
311 
312     if (!force && line.length <= max) {
313         return false;
314     }
315 
316     if (line.length <= max) {
317         printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1));
318         return true;
319     }
320 
321     size_t left = max;
322     const char *string = line.content + line.scroll;
323     printf("\r" CSI("K") "%s", prompt);
324     if (line.scroll) {
325         printf(SGR("2") "%c" SGR("0"), *string);
326         string++;
327         left--;
328     }
329     if (line.scroll + max == line.length) {
330         printf("%s", string);
331     }
332     else {
333         printf("%.*s", (int)(left - 1), string);
334         string += left;
335         left = 1;
336         printf(SGR("2") "%c" SGR("0"), *string);
337     }
338     printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1));
339 
340     return true;
341 }
342 
set_position(size_t position)343 static void set_position(size_t position)
344 {
345     if (position > line.length) {
346         printf("\a");
347         return;
348     }
349     line.position = position;
350     if (!redraw_prompt(false)) {
351         printf(CSI("%uG"), (unsigned)(position + prompt_length + 1));
352     }
353 }
354 
set_line(const char * content)355 static void set_line(const char *content)
356 {
357     line.length = strlen(content);
358     if (line.length + 1 > line.allocation_size) {
359         line.content = realloc(line.content, line.length + 1);
360         line.allocation_size = line.length + 1;
361     }
362     else if (line.allocation_size > 256 && line.length < 128) {
363         line.content = realloc(line.content, line.length + 1);
364         line.allocation_size = line.length + 1;
365     }
366     line.position = line.length;
367     strcpy(line.content, content);
368     redraw_prompt(true);
369 }
370 
insert(const char * string)371 static void insert(const char *string)
372 {
373     size_t insertion_length = strlen(string);
374     size_t new_length = insertion_length + line.length;
375     bool need_realloc = false;
376     while (line.allocation_size < new_length + 1) {
377         line.allocation_size *= 2;
378         need_realloc = true;
379     }
380     if (need_realloc) {
381         line.content = realloc(line.content, line.allocation_size);
382     }
383     memmove(line.content + line.position + insertion_length,
384             line.content + line.position,
385             line.length - line.position);
386     memcpy(line.content + line.position, string, insertion_length);
387     line.position += insertion_length;
388     line.content[new_length] = 0;
389     line.length = new_length;
390     if (!redraw_prompt(line.position != line.length)) {
391         printf("%s", string);
392     }
393 }
394 
delete(size_t size,bool forward)395 static void delete(size_t size, bool forward)
396 {
397     if (line.length < size) {
398         printf("\a");
399         return;
400     }
401     if (forward) {
402         if (line.position > line.length - size) {
403             printf("\a");
404             return;
405         }
406         else {
407             line.position += size;
408         }
409     }
410     else if (line.position < size) {
411         printf("\a");
412         return;
413     }
414     memmove(line.content + line.position - size,
415             line.content + line.position,
416             line.length - line.position);
417     line.length -= size;
418     line.content[line.length] = 0;
419     line.position -= size;
420 
421     if (!redraw_prompt(line.position != line.length)) {
422         printf(CSI("%uG") CSI("K"),
423                (unsigned)(line.position + prompt_length + 1));
424     }
425 }
426 
move_word(bool forward)427 static void move_word(bool forward)
428 {
429     signed offset = forward? 1 : -1;
430     size_t end = forward? line.length : 0;
431     signed check_offset = forward? 0 : -1;
432     if (line.position == end) {
433         printf("\a");
434         return;
435     }
436     line.position += offset;
437     while (line.position != end && isalnum(line.content[line.position + check_offset])) {
438         line.position += offset;
439     }
440     if (!redraw_prompt(false)) {
441         printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1));
442     }
443 }
444 
delete_word(bool forward)445 static void delete_word(bool forward)
446 {
447     size_t original_pos = line.position;
448     signed offset = forward? 1 : -1;
449     size_t end = forward? line.length : 0;
450     signed check_offset = forward? 0 : -1;
451     if (line.position == end) {
452         printf("\a");
453         return;
454     }
455     line.position += offset;
456     while (line.position != end && isalnum(line.content[line.position + check_offset])) {
457         line.position += offset;
458     }
459     if (forward) {
460         delete(line.position - original_pos, false);
461     }
462     else {
463         delete(original_pos - line.position, true);
464     }
465 }
466 
467 #define MOD_ALT(x) (0x100 | x)
468 #define MOD_SHIFT(x) (0x200 | x)
469 #define MOD_CTRL(x) (0x400 | x)
470 #define MOD_SPECIAL(x) (0x800 | x)
471 
get_extended_key(void)472 static unsigned get_extended_key(void)
473 {
474     unsigned modifiers = 0;
475     char c = 0;
476 restart:
477     c = raw_getc();
478     if (c == 0x1B) {
479         modifiers = MOD_SHIFT(MOD_ALT(0));
480         goto restart;
481     }
482     else if (c != '[' && c != 'O') {
483         return MOD_ALT(c);
484     }
485     unsigned ret = 0;
486     while (true) {
487         c = raw_getc();
488         if (c >= '0' && c <= '9') {
489             ret = ret * 10 + c - '0';
490         }
491         else if (c == ';') {
492             if (ret == 1) {
493                 modifiers |= MOD_ALT(0);
494             }
495             else if (ret == 2) {
496                 modifiers |= MOD_SHIFT(0);
497             }
498             else if (ret == 5) {
499                 modifiers |= MOD_CTRL(0);
500             }
501             ret = 0;
502         }
503         else if (c == '~') {
504             return MOD_SPECIAL(ret) | modifiers;
505         }
506         else {
507             if (ret == 1) {
508                 modifiers |= MOD_ALT(0);
509             }
510             else if (ret == 2) {
511                 modifiers |= MOD_SHIFT(0);
512             }
513             else if (ret == 5) {
514                 modifiers |= MOD_CTRL(0);
515             }
516             return c | modifiers;
517         }
518     }
519 }
520 
521 #define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0)
522 static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER;
523 static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER;
524 static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER;
525 
reverse_search_mainloop(void)526 static char reverse_search_mainloop(void)
527 {
528     while (true) {
529         char c = raw_getc();
530         pthread_mutex_lock(&terminal_lock);
531 
532         switch (c) {
533             case CTL('C'):
534                 line.search_line = NULL;
535                 set_line("");
536                 pthread_mutex_unlock(&terminal_lock);
537                 return CTL('A');
538             case CTL('R'):
539                 line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false);
540                 if (!line.search_line) {
541                     printf("\a");
542                 }
543                 redraw_prompt(true);
544                 break;
545             case CTL('W'):
546                 delete_word(false);
547                 redraw_prompt(true);
548                 break;
549 #ifndef _WIN32
550             case CTL('Z'):
551                 set_line("");
552                 raise(SIGSTOP);
553                 initialize(); // Reinitialize
554                 redraw_prompt(true);
555                 break;
556 #endif
557             case CTL('H'):
558             case 0x7F: // Backspace
559                 delete(1, false);
560                 redraw_prompt(true);
561                 break;
562             default:
563                 if (c >= ' ') {
564                     char string[2] = {c, 0};
565                     insert(string);
566                     line.search_line = reverse_find(line.search_line?: history.last, line.content, false);
567                     if (!line.search_line) {
568                         printf("\a");
569                     }
570                     redraw_prompt(true);
571                 }
572                 else {
573                     pthread_mutex_unlock(&terminal_lock);
574                     return c;
575                 }
576                 break;
577         }
578         pthread_mutex_unlock(&terminal_lock);
579         fflush(stdout);
580     }
581 
582 }
583 
584 
585 static
586 #ifdef _WIN32
587 int __stdcall
588 #else
589 void *
590 #endif
mainloop(char * (* completer)(const char * substring,uintptr_t * context))591 mainloop(char *(*completer)(const char *substring, uintptr_t *context))
592 {
593     listent_t *history_line = NULL;
594     uintptr_t complete_context = 0;
595     size_t completion_length = 0;
596     while (true) {
597         char c;
598         if (line.reverse_search) {
599             c = reverse_search_mainloop();
600             line.reverse_search = false;
601             if (line.search_line) {
602                 size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length;
603                 set_line(line.search_line->content);
604                 line.search_line = NULL;
605                 set_position(pos);
606             }
607             else {
608                 redraw_prompt(true);
609             }
610         }
611         else {
612             c = raw_getc();
613         }
614         pthread_mutex_lock(&terminal_lock);
615 
616         switch (c) {
617             case CTL('A'):
618                 set_position(0);
619                 complete_context = completion_length = 0;
620                 break;
621             case CTL('B'):
622                 set_position(line.position - 1);
623                 complete_context = completion_length = 0;
624                 break;
625             case CTL('C'):
626                 if (line.length) {
627                     set_line("");
628                     history_line = NULL;
629                     complete_context = completion_length = 0;
630                 }
631                 else {
632 #ifdef _WIN32
633                     raise(SIGINT);
634 #else
635                     kill(getpid(), SIGINT);
636 #endif
637                 }
638                 break;
639             case CTL('D'):
640                 if (line.length) {
641                     delete(1, true);
642                     complete_context = completion_length = 0;
643                 }
644                 else {
645                     pthread_mutex_lock(&lines_lock);
646                     add_entry(&lines, CON_EOF);
647                     pthread_cond_signal(&lines_cond);
648                     pthread_mutex_unlock(&lines_lock);
649                 }
650                 break;
651             case CTL('E'):
652                 set_position(line.length);
653                 complete_context = completion_length = 0;
654                 break;
655             case CTL('F'):
656                 set_position(line.position + 1);
657                 complete_context = completion_length = 0;
658                 break;
659             case CTL('K'):
660                 printf(CSI("K"));
661                 if (!redraw_prompt(false)) {
662                     line.length = line.position;
663                     line.content[line.length] = 0;
664                 }
665                 complete_context = completion_length = 0;
666                 break;
667             case CTL('R'):
668                 complete_context = completion_length = 0;
669                 line.reverse_search = true;
670                 set_line("");
671 
672                 break;
673             case CTL('T'):
674                 if (line.length < 2) {
675                     printf("\a");
676                     break;
677                 }
678                 if (line.position && line.position == line.length) {
679                     line.position--;
680                 }
681                 if (line.position == 0) {
682                     printf("\a");
683                     break;
684                 }
685                 SWAP(line.content + line.position,
686                      line.content + line.position - 1);
687                 line.position++;
688                 redraw_prompt(true);
689                 complete_context = completion_length = 0;
690                 break;
691             case CTL('W'):
692                 delete_word(false);
693                 complete_context = completion_length = 0;
694                 break;
695 #ifndef _WIN32
696             case CTL('Z'):
697                 set_line("");
698                 complete_context = completion_length = 0;
699                 raise(SIGSTOP);
700                 initialize(); // Reinitialize
701                 break;
702 #endif
703             case '\r':
704             case '\n':
705                 pthread_mutex_lock(&lines_lock);
706                 if (line.length == 0 && repeat_empty && history.last) {
707                     add_entry(&lines, history.last->content);
708                 }
709                 else {
710                     add_entry(&lines, line.content);
711                 }
712                 pthread_cond_signal(&lines_cond);
713                 pthread_mutex_unlock(&lines_lock);
714                 if (line.length) {
715                     listent_t *dup = reverse_find(history.last, line.content, true);
716                     if (dup) {
717                         remove_entry(&history, dup);
718                     }
719                     add_entry(&history, line.content);
720                     set_line("");
721                     history_line = NULL;
722                 }
723                 complete_context = completion_length = 0;
724                 break;
725             case CTL('H'):
726             case 0x7F: // Backspace
727                 delete(1, false);
728                 complete_context = completion_length = 0;
729                 break;
730             case 0x1B:
731                 switch (get_extended_key()) {
732                     case MOD_SPECIAL(1): // Home
733                     case MOD_SPECIAL(7):
734                     case 'H':
735                         set_position(0);
736                         complete_context = completion_length = 0;
737                         break;
738                     case MOD_SPECIAL(8): // End
739                     case 'F':
740                         set_position(line.length);
741                         complete_context = completion_length = 0;
742                         break;
743                     case MOD_SPECIAL(3): // Delete
744                         delete(1, true);
745                         complete_context = completion_length = 0;
746                         break;
747                     case 'A': // Up
748                         if (!history_line) {
749                             history_line = history.last;
750                         }
751                         else {
752                             history_line = history_line->prev;
753                         }
754                         if (history_line) {
755                             set_line(history_line->content);
756                             complete_context = completion_length = 0;
757                         }
758                         else {
759                             history_line = history.first;
760                             printf("\a");
761                         }
762 
763                         break;
764                     case 'B': // Down
765                         if (!history_line) {
766                             printf("\a");
767                             break;
768                         }
769                         history_line = history_line->next;
770                         if (history_line) {
771                             set_line(history_line->content);
772                             complete_context = completion_length = 0;
773                         }
774                         else {
775                             set_line("");
776                             complete_context = completion_length = 0;
777                         }
778                         break;
779                     case 'C': // Right
780                         set_position(line.position + 1);
781                         complete_context = completion_length = 0;
782                         break;
783                     case 'D': // Left
784                         set_position(line.position - 1);
785                         complete_context = completion_length = 0;
786                         break;
787                     case MOD_ALT('b'):
788                     case MOD_ALT('D'):
789                         move_word(false);
790                         complete_context = completion_length = 0;
791                         break;
792                     case MOD_ALT('f'):
793                     case MOD_ALT('C'):
794                         move_word(true);
795                         complete_context = completion_length = 0;
796                         break;
797                     case MOD_ALT(0x7f): // ALT+Backspace
798                         delete_word(false);
799                         complete_context = completion_length = 0;
800                         break;
801                     case MOD_ALT('('): // ALT+Delete
802                         delete_word(true);
803                         complete_context = completion_length = 0;
804                         break;
805                     default:
806                         printf("\a");
807                         break;
808                 }
809                 break;
810             case '\t': {
811                 char temp = line.content[line.position - completion_length];
812                 line.content[line.position - completion_length] = 0;
813                 char *completion = completer? completer(line.content, &complete_context) : NULL;
814                 line.content[line.position - completion_length] = temp;
815                 if (completion) {
816                     if (completion_length) {
817                         delete(completion_length, false);
818                     }
819                     insert(completion);
820                     completion_length = strlen(completion);
821                     free(completion);
822                 }
823                 else {
824                     printf("\a");
825                 }
826                 break;
827             }
828             default:
829                 if (c >= ' ') {
830                     char string[2] = {c, 0};
831                     insert(string);
832                     complete_context = completion_length = 0;
833                 }
834                 else {
835                     printf("\a");
836                 }
837                 break;
838         }
839         fflush(stdout);
840         pthread_mutex_unlock(&terminal_lock);
841     }
842     return 0;
843 }
844 
CON_readline(const char * new_prompt)845 char *CON_readline(const char *new_prompt)
846 {
847     pthread_mutex_lock(&terminal_lock);
848     const char *old_prompt = prompt;
849     prompt = new_prompt;
850     prompt_length = strlen(prompt);
851     redraw_prompt(true);
852     fflush(stdout);
853     pthread_mutex_unlock(&terminal_lock);
854 
855     pthread_mutex_lock(&lines_lock);
856     while (!lines.first) {
857         pthread_cond_wait(&lines_cond, &lines_lock);
858     }
859     char *ret = strdup(lines.first->content);
860     remove_entry(&lines, lines.first);
861     pthread_mutex_unlock(&lines_lock);
862 
863     pthread_mutex_lock(&terminal_lock);
864     prompt = old_prompt;
865     prompt_length = strlen(prompt);
866     redraw_prompt(true);
867     fflush(stdout);
868     pthread_mutex_unlock(&terminal_lock);
869     return ret;
870 }
871 
CON_readline_async(void)872 char *CON_readline_async(void)
873 {
874     char *ret = NULL;
875     pthread_mutex_lock(&lines_lock);
876     if (lines.first) {
877         ret = strdup(lines.first->content);
878         remove_entry(&lines, lines.first);
879     }
880     pthread_mutex_unlock(&lines_lock);
881     return ret;
882 }
883 
CON_start(char * (* completer)(const char * substring,uintptr_t * context))884 bool CON_start(char *(*completer)(const char *substring, uintptr_t *context))
885 {
886     if (!initialize()) {
887         return false;
888     }
889     set_line("");
890     pthread_t thread;
891     return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0;
892 }
893 
CON_attributed_print(const char * string,CON_attributes_t * attributes)894 void CON_attributed_print(const char *string, CON_attributes_t *attributes)
895 {
896     if (!initialized) {
897         printf("%s", string);
898         return;
899     }
900     static bool pending_newline = false;
901     pthread_mutex_lock(&terminal_lock);
902     printf(ESC("8"));
903     bool needs_reset = false;
904     if (attributes) {
905         if (attributes->color) {
906             if (attributes->color >= 0x10) {
907                 printf(SGR("%d"), attributes->color - 0x11 + 90);
908             }
909             else {
910                 printf(SGR("%d"), attributes->color - 1 + 30);
911             }
912             needs_reset = true;
913         }
914         if (attributes->background) {
915             if (attributes->background >= 0x10) {
916                 printf(SGR("%d"), attributes->background - 0x11 + 100);
917             }
918             else {
919                 printf(SGR("%d"), attributes->background - 1 + 40);
920             }
921             needs_reset = true;
922         }
923         if (attributes->bold) {
924             printf(SGR("1"));
925             needs_reset = true;
926         }
927         if (attributes->italic) {
928             printf(SGR("3"));
929             needs_reset = true;
930         }
931         if (attributes->underline) {
932             printf(SGR("4"));
933             needs_reset = true;
934         }
935     }
936     const char *it = string;
937     bool need_redraw_prompt = false;
938     while (*it) {
939         if (pending_newline) {
940             need_redraw_prompt = true;
941             printf("\n" CSI("K") "\n" CSI("A"));
942             pending_newline = false;
943             continue;
944         }
945         if (*it == '\n') {
946             printf("%.*s", (int)(it - string), string);
947             string = it + 1;
948             pending_newline = true;
949         }
950         it++;
951     }
952     if (*string) {
953         printf("%s", string);
954     }
955     if (needs_reset) {
956         printf(SGR("0"));
957     }
958     printf(ESC("7") CSI("B"));
959     if (need_redraw_prompt) {
960         redraw_prompt(true);
961     }
962     else {
963         set_position(line.position);
964     }
965     fflush(stdout);
966     pthread_mutex_unlock(&terminal_lock);
967 }
968 
CON_print(const char * string)969 void CON_print(const char *string)
970 {
971     CON_attributed_print(string, NULL);
972 }
973 
CON_vprintf(const char * fmt,va_list args)974 void CON_vprintf(const char *fmt, va_list args)
975 {
976     char *string = NULL;
977     vasprintf(&string, fmt, args);
978     CON_attributed_print(string, NULL);
979     free(string);
980 }
981 
CON_attributed_vprintf(const char * fmt,CON_attributes_t * attributes,va_list args)982 void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args)
983 {
984     char *string = NULL;
985     vasprintf(&string, fmt, args);
986     CON_attributed_print(string, attributes);
987     free(string);
988 }
989 
CON_printf(const char * fmt,...)990 void CON_printf(const char *fmt, ...)
991 {
992     va_list args;
993     va_start(args, fmt);
994     CON_vprintf(fmt, args);
995     va_end(args);
996 }
997 
998 
CON_attributed_printf(const char * fmt,CON_attributes_t * attributes,...)999 void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...)
1000 {
1001     va_list args;
1002     va_start(args, attributes);
1003     CON_attributed_vprintf(fmt, attributes, args);
1004     va_end(args);
1005 }
1006 
CON_set_async_prompt(const char * string)1007 void CON_set_async_prompt(const char *string)
1008 {
1009     pthread_mutex_lock(&terminal_lock);
1010     prompt = string;
1011     prompt_length = strlen(string);
1012     redraw_prompt(true);
1013     fflush(stdout);
1014     pthread_mutex_unlock(&terminal_lock);
1015 }
1016 
CON_set_repeat_empty(bool repeat)1017 void CON_set_repeat_empty(bool repeat)
1018 {
1019     repeat_empty = repeat;
1020 }
1021