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