Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/term.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include <string.h>
8
#include <stdio.h>
9
#include <stdarg.h>
10
#include <stdlib.h> // getenv
11
#include <inttypes.h>
12
13
#include "common.h"
14
#include "tty.h"
15
#include "term.h"
16
#include "stringbuf.h" // str_next_ofs
17
18
#if defined(_WIN32)
19
#include <windows.h>
20
#define STDOUT_FILENO 1
21
#else
22
#include <unistd.h>
23
#include <errno.h>
24
#include <sys/ioctl.h>
25
#if defined(__linux__)
26
#include <linux/kd.h>
27
#endif
28
#endif
29
30
#define IC_CSI "\x1B["
31
32
// color support; colors are auto mapped smaller palettes if needed. (see `term_color.c`)
33
typedef enum palette_e {
34
MONOCHROME, // no color
35
ANSI8, // only basic 8 ANSI color (ESC[<idx>m, idx: 30-37, +10 for background)
36
ANSI16, // basic + bright ANSI colors (ESC[<idx>m, idx: 30-37, 90-97, +10 for background)
37
ANSI256, // ANSI 256 color palette (ESC[38;5;<idx>m, idx: 0-15 standard color, 16-231 6x6x6 rbg colors, 232-255 gray shades)
38
ANSIRGB // direct rgb colors supported (ESC[38;2;<r>;<g>;<b>m)
39
} palette_t;
40
41
// The terminal screen
42
struct term_s {
43
int fd_out; // output handle
44
ssize_t width; // screen column width
45
ssize_t height; // screen row height
46
ssize_t raw_enabled; // is raw mode active? counted by start/end pairs
47
bool nocolor; // show colors?
48
bool silent; // enable beep?
49
bool is_utf8; // utf-8 output? determined by the tty
50
attr_t attr; // current text attributes
51
palette_t palette; // color support
52
buffer_mode_t bufmode; // buffer mode
53
stringbuf_t* buf; // buffer for buffered output
54
tty_t* tty; // used on posix to get the cursor position
55
alloc_t* mem; // allocator
56
#ifdef _WIN32
57
HANDLE hcon; // output console handler
58
WORD hcon_default_attr; // default text attributes
59
WORD hcon_orig_attr; // original text attributes
60
DWORD hcon_orig_mode; // original console mode
61
DWORD hcon_mode; // used console mode
62
UINT hcon_orig_cp; // original console code-page (locale)
63
COORD hcon_save_cursor; // saved cursor position (for escape sequence emulation)
64
#endif
65
};
66
67
static bool term_write_direct(term_t* term, const char* s, ssize_t n );
68
static void term_append_buf(term_t* term, const char* s, ssize_t n);
69
70
//-------------------------------------------------------------
71
// Colors
72
//-------------------------------------------------------------
73
74
#include "term_color.c"
75
76
//-------------------------------------------------------------
77
// Helpers
78
//-------------------------------------------------------------
79
80
ic_private void term_left(term_t* term, ssize_t n) {
81
if (n <= 0) return;
82
term_writef( term, IC_CSI "%zdD", n );
83
}
84
85
ic_private void term_right(term_t* term, ssize_t n) {
86
if (n <= 0) return;
87
term_writef( term, IC_CSI "%zdC", n );
88
}
89
90
ic_private void term_up(term_t* term, ssize_t n) {
91
if (n <= 0) return;
92
term_writef( term, IC_CSI "%zdA", n );
93
}
94
95
ic_private void term_down(term_t* term, ssize_t n) {
96
if (n <= 0) return;
97
term_writef( term, IC_CSI "%zdB", n );
98
}
99
100
ic_private void term_clear_line(term_t* term) {
101
term_write( term, "\r" IC_CSI "K");
102
}
103
104
ic_private void term_clear_to_end_of_line(term_t* term) {
105
term_write(term, IC_CSI "K");
106
}
107
108
ic_private void term_start_of_line(term_t* term) {
109
term_write( term, "\r" );
110
}
111
112
ic_private ssize_t term_get_width(term_t* term) {
113
return term->width;
114
}
115
116
ic_private ssize_t term_get_height(term_t* term) {
117
return term->height;
118
}
119
120
ic_private void term_attr_reset(term_t* term) {
121
term_write(term, IC_CSI "m" );
122
}
123
124
ic_private void term_underline(term_t* term, bool on) {
125
term_write(term, on ? IC_CSI "4m" : IC_CSI "24m" );
126
}
127
128
ic_private void term_reverse(term_t* term, bool on) {
129
term_write(term, on ? IC_CSI "7m" : IC_CSI "27m");
130
}
131
132
ic_private void term_bold(term_t* term, bool on) {
133
term_write(term, on ? IC_CSI "1m" : IC_CSI "22m" );
134
}
135
136
ic_private void term_italic(term_t* term, bool on) {
137
term_write(term, on ? IC_CSI "3m" : IC_CSI "23m" );
138
}
139
140
ic_private void term_writeln(term_t* term, const char* s) {
141
term_write(term,s);
142
term_write(term,"\n");
143
}
144
145
ic_private void term_write_char(term_t* term, char c) {
146
char buf[2];
147
buf[0] = c;
148
buf[1] = 0;
149
term_write_n(term, buf, 1 );
150
}
151
152
ic_private attr_t term_get_attr( const term_t* term ) {
153
return term->attr;
154
}
155
156
ic_private void term_set_attr( term_t* term, attr_t attr ) {
157
if (term->nocolor) return;
158
if (attr.x.color != term->attr.x.color && attr.x.color != IC_COLOR_NONE) {
159
term_color(term,attr.x.color);
160
if (term->palette < ANSIRGB && color_is_rgb(attr.x.color)) {
161
term->attr.x.color = attr.x.color; // actual color may have been approximated but we keep the actual color to avoid updating every time
162
}
163
}
164
if (attr.x.bgcolor != term->attr.x.bgcolor && attr.x.bgcolor != IC_COLOR_NONE) {
165
term_bgcolor(term,attr.x.bgcolor);
166
if (term->palette < ANSIRGB && color_is_rgb(attr.x.bgcolor)) {
167
term->attr.x.bgcolor = attr.x.bgcolor;
168
}
169
}
170
if (attr.x.bold != term->attr.x.bold && attr.x.bold != IC_NONE) {
171
term_bold(term,attr.x.bold == IC_ON);
172
}
173
if (attr.x.underline != term->attr.x.underline && attr.x.underline != IC_NONE) {
174
term_underline(term,attr.x.underline == IC_ON);
175
}
176
if (attr.x.reverse != term->attr.x.reverse && attr.x.reverse != IC_NONE) {
177
term_reverse(term,attr.x.reverse == IC_ON);
178
}
179
if (attr.x.italic != term->attr.x.italic && attr.x.italic != IC_NONE) {
180
term_italic(term,attr.x.italic == IC_ON);
181
}
182
assert(attr.x.color == term->attr.x.color || attr.x.color == IC_COLOR_NONE);
183
assert(attr.x.bgcolor == term->attr.x.bgcolor || attr.x.bgcolor == IC_COLOR_NONE);
184
assert(attr.x.bold == term->attr.x.bold || attr.x.bold == IC_NONE);
185
assert(attr.x.reverse == term->attr.x.reverse || attr.x.reverse == IC_NONE);
186
assert(attr.x.underline == term->attr.x.underline || attr.x.underline == IC_NONE);
187
assert(attr.x.italic == term->attr.x.italic || attr.x.italic == IC_NONE);
188
}
189
190
191
/*
192
ic_private void term_clear_lines_to_end(term_t* term) {
193
term_write(term, "\r" IC_CSI "J");
194
}
195
196
ic_private void term_show_cursor(term_t* term, bool on) {
197
term_write(term, on ? IC_CSI "?25h" : IC_CSI "?25l");
198
}
199
*/
200
201
//-------------------------------------------------------------
202
// Formatted output
203
//-------------------------------------------------------------
204
205
ic_private void term_writef(term_t* term, const char* fmt, ...) {
206
va_list ap;
207
va_start(ap, fmt);
208
term_vwritef(term,fmt,ap);
209
va_end(ap);
210
}
211
212
ic_private void term_vwritef(term_t* term, const char* fmt, va_list args ) {
213
sbuf_append_vprintf(term->buf, fmt, args);
214
}
215
216
ic_private void term_write_formatted( term_t* term, const char* s, const attr_t* attrs ) {
217
term_write_formatted_n( term, s, attrs, ic_strlen(s));
218
}
219
220
ic_private void term_write_formatted_n( term_t* term, const char* s, const attr_t* attrs, ssize_t len ) {
221
if (attrs == NULL) {
222
// write directly
223
term_write(term,s);
224
}
225
else {
226
// ensure raw mode from now on
227
if (term->raw_enabled <= 0) {
228
term_start_raw(term);
229
}
230
// and output with text attributes
231
const attr_t default_attr = term_get_attr(term);
232
attr_t attr = attr_none();
233
ssize_t i = 0;
234
ssize_t n = 0;
235
while( i+n < len && s[i+n] != 0 ) {
236
if (!attr_is_eq(attr,attrs[i+n])) {
237
if (n > 0) {
238
term_write_n( term, s+i, n );
239
i += n;
240
n = 0;
241
}
242
attr = attrs[i];
243
term_set_attr( term, attr_update_with(default_attr,attr) );
244
}
245
n++;
246
}
247
if (n > 0) {
248
term_write_n( term, s+i, n );
249
i += n;
250
n = 0;
251
}
252
assert(s[i] != 0 || i == len);
253
term_set_attr(term, default_attr);
254
}
255
}
256
257
//-------------------------------------------------------------
258
// Write to the terminal
259
// The buffered functions are used to reduce cursor flicker
260
// during refresh
261
//-------------------------------------------------------------
262
263
ic_private void term_beep(term_t* term) {
264
if (term->silent) return;
265
fprintf(stderr,"\x7");
266
fflush(stderr);
267
}
268
269
ic_private void term_write_repeat(term_t* term, const char* s, ssize_t count) {
270
for (; count > 0; count--) {
271
term_write(term, s);
272
}
273
}
274
275
ic_private void term_write(term_t* term, const char* s) {
276
if (s == NULL || s[0] == 0) return;
277
ssize_t n = ic_strlen(s);
278
term_write_n(term,s,n);
279
}
280
281
// Primitive terminal write; all writes go through here
282
ic_private void term_write_n(term_t* term, const char* s, ssize_t n) {
283
if (s == NULL || n <= 0) return;
284
// write to buffer to reduce flicker and to process escape sequences (this may flush too)
285
term_append_buf(term, s, n);
286
}
287
288
289
//-------------------------------------------------------------
290
// Buffering
291
//-------------------------------------------------------------
292
293
294
ic_private void term_flush(term_t* term) {
295
if (sbuf_len(term->buf) > 0) {
296
//term_show_cursor(term,false);
297
term_write_direct(term, sbuf_string(term->buf), sbuf_len(term->buf));
298
//term_show_cursor(term,true);
299
sbuf_clear(term->buf);
300
}
301
}
302
303
ic_private buffer_mode_t term_set_buffer_mode(term_t* term, buffer_mode_t mode) {
304
buffer_mode_t oldmode = term->bufmode;
305
if (oldmode != mode) {
306
if (mode == UNBUFFERED) {
307
term_flush(term);
308
}
309
term->bufmode = mode;
310
}
311
return oldmode;
312
}
313
314
static void term_check_flush(term_t* term, bool contains_nl) {
315
if (term->bufmode == UNBUFFERED ||
316
sbuf_len(term->buf) > 4000 ||
317
(term->bufmode == LINEBUFFERED && contains_nl))
318
{
319
term_flush(term);
320
}
321
}
322
323
//-------------------------------------------------------------
324
// Init
325
//-------------------------------------------------------------
326
327
static void term_init_raw(term_t* term);
328
329
ic_private term_t* term_new(alloc_t* mem, tty_t* tty, bool nocolor, bool silent, int fd_out )
330
{
331
term_t* term = mem_zalloc_tp(mem, term_t);
332
if (term == NULL) return NULL;
333
334
term->fd_out = (fd_out < 0 ? STDOUT_FILENO : fd_out);
335
term->nocolor = nocolor || (isatty(term->fd_out) == 0);
336
term->silent = silent;
337
term->mem = mem;
338
term->tty = tty; // can be NULL
339
term->width = 80;
340
term->height = 25;
341
term->is_utf8 = tty_is_utf8(tty);
342
term->palette = ANSI16; // almost universally supported
343
term->buf = sbuf_new(mem);
344
term->bufmode = LINEBUFFERED;
345
term->attr = attr_default();
346
347
// respect NO_COLOR
348
if (getenv("NO_COLOR") != NULL) {
349
term->nocolor = true;
350
}
351
if (!term->nocolor) {
352
// detect color palette
353
// COLORTERM takes precedence
354
const char* colorterm = getenv("COLORTERM");
355
const char* eterm = getenv("TERM");
356
if (ic_contains(colorterm,"24bit") || ic_contains(colorterm,"truecolor") || ic_contains(colorterm,"direct")) {
357
term->palette = ANSIRGB;
358
}
359
else if (ic_contains(colorterm,"8bit") || ic_contains(colorterm,"256color")) { term->palette = ANSI256; }
360
else if (ic_contains(colorterm,"4bit") || ic_contains(colorterm,"16color")) { term->palette = ANSI16; }
361
else if (ic_contains(colorterm,"3bit") || ic_contains(colorterm,"8color")) { term->palette = ANSI8; }
362
else if (ic_contains(colorterm,"1bit") || ic_contains(colorterm,"nocolor") || ic_contains(colorterm,"monochrome")) {
363
term->palette = MONOCHROME;
364
}
365
// otherwise check for some specific terminals
366
else if (getenv("WT_SESSION") != NULL) { term->palette = ANSIRGB; } // Windows terminal
367
else if (getenv("ITERM_SESSION_ID") != NULL) { term->palette = ANSIRGB; } // iTerm2 terminal
368
else if (getenv("VSCODE_PID") != NULL) { term->palette = ANSIRGB; } // vscode terminal
369
else {
370
// and otherwise fall back to checking TERM
371
if (ic_contains(eterm,"truecolor") || ic_contains(eterm,"direct") || ic_contains(colorterm,"24bit")) {
372
term->palette = ANSIRGB;
373
}
374
else if (ic_contains(eterm,"alacritty") || ic_contains(eterm,"kitty")) {
375
term->palette = ANSIRGB;
376
}
377
else if (ic_contains(eterm,"256color") || ic_contains(eterm,"gnome")) {
378
term->palette = ANSI256;
379
}
380
else if (ic_contains(eterm,"16color")){ term->palette = ANSI16; }
381
else if (ic_contains(eterm,"8color")) { term->palette = ANSI8; }
382
else if (ic_contains(eterm,"monochrome") || ic_contains(eterm,"nocolor") || ic_contains(eterm,"dumb")) {
383
term->palette = MONOCHROME;
384
}
385
}
386
debug_msg("term: color-bits: %d (COLORTERM=%s, TERM=%s)\n", term_get_color_bits(term), colorterm, eterm);
387
}
388
389
// read COLUMS/LINES from the environment for a better initial guess.
390
const char* env_columns = getenv("COLUMNS");
391
if (env_columns != NULL) { ic_atoz(env_columns, &term->width); }
392
const char* env_lines = getenv("LINES");
393
if (env_lines != NULL) { ic_atoz(env_lines, &term->height); }
394
395
// initialize raw terminal output and terminal dimensions
396
term_init_raw(term);
397
term_update_dim(term);
398
term_attr_reset(term); // ensure we are at default settings
399
400
return term;
401
}
402
403
ic_private bool term_is_interactive(const term_t* term) {
404
ic_unused(term);
405
// check dimensions (0 is used for debuggers)
406
// if (term->width <= 0) return false;
407
408
// check editing support
409
const char* eterm = getenv("TERM");
410
debug_msg("term: TERM=%s\n", eterm);
411
if (eterm != NULL &&
412
(strstr("dumb|DUMB|cons25|CONS25|emacs|EMACS",eterm) != NULL)) {
413
return false;
414
}
415
416
return true;
417
}
418
419
ic_private bool term_enable_beep(term_t* term, bool enable) {
420
bool prev = term->silent;
421
term->silent = !enable;
422
return prev;
423
}
424
425
ic_private bool term_enable_color(term_t* term, bool enable) {
426
bool prev = !term->nocolor;
427
term->nocolor = !enable;
428
return prev;
429
}
430
431
ic_private void term_free(term_t* term) {
432
if (term == NULL) return;
433
term_flush(term);
434
term_end_raw(term, true);
435
sbuf_free(term->buf); term->buf = NULL;
436
mem_free(term->mem, term);
437
}
438
439
//-------------------------------------------------------------
440
// For best portability and applications inserting CSI SGR (ESC[ .. m)
441
// codes themselves in strings, we interpret these at the
442
// lowest level so we can have a `term_get_attr` function which
443
// is needed for bracketed styles etc.
444
//-------------------------------------------------------------
445
446
static void term_append_esc(term_t* term, const char* const s, ssize_t len) {
447
if (s[1]=='[' && s[len-1] == 'm') {
448
// it is a CSI SGR sequence: ESC[ ... m
449
if (term->nocolor) return; // ignore escape sequences if nocolor is set
450
term->attr = attr_update_with(term->attr, attr_from_esc_sgr(s,len));
451
}
452
// and write out the escape sequence as-is
453
sbuf_append_n(term->buf, s, len);
454
}
455
456
457
static void term_append_utf8(term_t* term, const char* s, ssize_t len) {
458
ssize_t nread;
459
unicode_t uchr = unicode_from_qutf8((const uint8_t*)s, len, &nread);
460
uint8_t c;
461
if (unicode_is_raw(uchr, &c)) {
462
// write bytes as is; this also ensure that on non-utf8 terminals characters between 0x80-0xFF
463
// go through _as is_ due to the qutf8 encoding.
464
sbuf_append_char(term->buf,(char)c);
465
}
466
else if (!term->is_utf8) {
467
// on non-utf8 terminals still send utf-8 and hope for the best
468
// todo: we could try to convert to the locale first?
469
sbuf_append_n(term->buf, s, len);
470
// sbuf_appendf(term->buf, "\x1B[%" PRIu32 "u", uchr); // unicode escape code
471
}
472
else {
473
// write utf-8 as is
474
sbuf_append_n(term->buf, s, len);
475
}
476
}
477
478
static void term_append_buf( term_t* term, const char* s, ssize_t len ) {
479
ssize_t pos = 0;
480
bool newline = false;
481
while (pos < len) {
482
// handle ascii sequences in bulk
483
ssize_t ascii = 0;
484
ssize_t next;
485
while ((next = str_next_ofs(s, len, pos+ascii, NULL)) > 0 &&
486
(uint8_t)s[pos + ascii] > '\x1B' && (uint8_t)s[pos + ascii] <= 0x7F )
487
{
488
ascii += next;
489
}
490
if (ascii > 0) {
491
sbuf_append_n(term->buf, s+pos, ascii);
492
pos += ascii;
493
}
494
if (next <= 0) break;
495
496
const uint8_t c = (uint8_t)s[pos];
497
// handle utf8 sequences (for non-utf8 terminals)
498
if (c >= 0x80) {
499
term_append_utf8(term, s+pos, next);
500
}
501
// handle escape sequence (note: str_next_ofs considers whole CSI escape sequences at a time)
502
else if (next > 1 && c == '\x1B') {
503
term_append_esc(term, s+pos, next);
504
}
505
else if (c < ' ' && c != 0 && (c < '\x07' || c > '\x0D')) {
506
// ignore control characters except \a, \b, \t, \n, \r, and form-feed and vertical tab.
507
}
508
else {
509
if (c == '\n') { newline = true; }
510
sbuf_append_n(term->buf, s+pos, next);
511
}
512
pos += next;
513
}
514
// possibly flush
515
term_check_flush(term, newline);
516
}
517
518
//-------------------------------------------------------------
519
// Platform dependent: Write directly to the terminal
520
//-------------------------------------------------------------
521
522
#if !defined(_WIN32)
523
524
// write to the console without further processing
525
static bool term_write_direct(term_t* term, const char* s, ssize_t n) {
526
ssize_t count = 0;
527
while( count < n ) {
528
ssize_t nwritten = write(term->fd_out, s + count, to_size_t(n - count));
529
if (nwritten > 0) {
530
count += nwritten;
531
}
532
else if (errno != EINTR && errno != EAGAIN) {
533
debug_msg("term: write failed: length %i, errno %i: \"%s\"\n", n, errno, s);
534
return false;
535
}
536
}
537
return true;
538
}
539
540
#else
541
542
//----------------------------------------------------------------------------------
543
// On windows we use the new virtual terminal processing if it is available (Windows Terminal)
544
// but fall back to ansi escape emulation on older systems but also for example
545
// the PS terminal
546
//
547
// note: we use row/col as 1-based ANSI escape while windows X/Y coords are 0-based.
548
//-----------------------------------------------------------------------------------
549
550
#if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
551
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0)
552
#endif
553
#if !defined(ENABLE_LVB_GRID_WORLDWIDE)
554
#define ENABLE_LVB_GRID_WORLDWIDE (0)
555
#endif
556
557
// direct write to the console without further processing
558
static bool term_write_console(term_t* term, const char* s, ssize_t n ) {
559
DWORD written;
560
// WriteConsoleA(term->hcon, s, (DWORD)(to_size_t(n)), &written, NULL);
561
WriteFile(term->hcon, s, (DWORD)(to_size_t(n)), &written, NULL); // so it can be redirected
562
return (written == (DWORD)(to_size_t(n)));
563
}
564
565
static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col) {
566
*row = 0;
567
*col = 0;
568
CONSOLE_SCREEN_BUFFER_INFO info;
569
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return false;
570
*row = (ssize_t)info.dwCursorPosition.Y + 1;
571
*col = (ssize_t)info.dwCursorPosition.X + 1;
572
return true;
573
}
574
575
static void term_move_cursor_to( term_t* term, ssize_t row, ssize_t col ) {
576
CONSOLE_SCREEN_BUFFER_INFO info;
577
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
578
if (col > info.dwSize.X) col = info.dwSize.X;
579
if (row > info.dwSize.Y) row = info.dwSize.Y;
580
if (col <= 0) col = 1;
581
if (row <= 0) row = 1;
582
COORD coord;
583
coord.X = (SHORT)col - 1;
584
coord.Y = (SHORT)row - 1;
585
SetConsoleCursorPosition( term->hcon, coord);
586
}
587
588
static void term_cursor_save(term_t* term) {
589
memset(&term->hcon_save_cursor, 0, sizeof(term->hcon_save_cursor));
590
CONSOLE_SCREEN_BUFFER_INFO info;
591
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return;
592
term->hcon_save_cursor = info.dwCursorPosition;
593
}
594
595
static void term_cursor_restore(term_t* term) {
596
if (term->hcon_save_cursor.X == 0) return;
597
SetConsoleCursorPosition(term->hcon, term->hcon_save_cursor);
598
}
599
600
static void term_move_cursor( term_t* term, ssize_t drow, ssize_t dcol, ssize_t n ) {
601
CONSOLE_SCREEN_BUFFER_INFO info;
602
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
603
COORD cur = info.dwCursorPosition;
604
ssize_t col = (ssize_t)cur.X + 1 + n*dcol;
605
ssize_t row = (ssize_t)cur.Y + 1 + n*drow;
606
term_move_cursor_to( term, row, col );
607
}
608
609
static void term_cursor_visible( term_t* term, bool visible ) {
610
CONSOLE_CURSOR_INFO info;
611
if (!GetConsoleCursorInfo(term->hcon,&info)) return;
612
info.bVisible = visible;
613
SetConsoleCursorInfo(term->hcon,&info);
614
}
615
616
static void term_erase_line( term_t* term, ssize_t mode ) {
617
CONSOLE_SCREEN_BUFFER_INFO info;
618
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
619
DWORD written;
620
COORD start;
621
ssize_t length;
622
if (mode == 2) {
623
// entire line
624
start.X = 0;
625
start.Y = info.dwCursorPosition.Y;
626
length = (ssize_t)info.srWindow.Right + 1;
627
}
628
else if (mode == 1) {
629
// to start of line
630
start.X = 0;
631
start.Y = info.dwCursorPosition.Y;
632
length = info.dwCursorPosition.X;
633
}
634
else {
635
// to end of line
636
length = (ssize_t)info.srWindow.Right - info.dwCursorPosition.X + 1;
637
start = info.dwCursorPosition;
638
}
639
FillConsoleOutputAttribute( term->hcon, term->hcon_default_attr, (DWORD)length, start, &written );
640
FillConsoleOutputCharacterA( term->hcon, ' ', (DWORD)length, start, &written );
641
}
642
643
static void term_clear_screen(term_t* term, ssize_t mode) {
644
CONSOLE_SCREEN_BUFFER_INFO info;
645
if (!GetConsoleScreenBufferInfo(term->hcon, &info)) return;
646
COORD start;
647
start.X = 0;
648
start.Y = 0;
649
ssize_t length;
650
ssize_t width = (ssize_t)info.dwSize.X;
651
if (mode == 2) {
652
// entire screen
653
length = width * info.dwSize.Y;
654
}
655
else if (mode == 1) {
656
// to cursor
657
length = (width * ((ssize_t)info.dwCursorPosition.Y - 1)) + info.dwCursorPosition.X;
658
}
659
else {
660
// from cursor
661
start = info.dwCursorPosition;
662
length = (width * ((ssize_t)info.dwSize.Y - info.dwCursorPosition.Y)) + (width - info.dwCursorPosition.X + 1);
663
}
664
DWORD written;
665
FillConsoleOutputAttribute(term->hcon, term->hcon_default_attr, (DWORD)length, start, &written);
666
FillConsoleOutputCharacterA(term->hcon, ' ', (DWORD)length, start, &written);
667
}
668
669
static WORD attr_color[8] = {
670
0, // black
671
FOREGROUND_RED, // maroon
672
FOREGROUND_GREEN, // green
673
FOREGROUND_RED | FOREGROUND_GREEN, // orange
674
FOREGROUND_BLUE, // navy
675
FOREGROUND_RED | FOREGROUND_BLUE, // purple
676
FOREGROUND_GREEN | FOREGROUND_BLUE, // teal
677
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // light gray
678
};
679
680
static void term_set_win_attr( term_t* term, attr_t ta ) {
681
WORD def_attr = term->hcon_default_attr;
682
CONSOLE_SCREEN_BUFFER_INFO info;
683
if (!GetConsoleScreenBufferInfo( term->hcon, &info )) return;
684
WORD cur_attr = info.wAttributes;
685
WORD attr = cur_attr;
686
if (ta.x.color != IC_COLOR_NONE) {
687
if (ta.x.color >= IC_ANSI_BLACK && ta.x.color <= IC_ANSI_SILVER) {
688
attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_BLACK];
689
}
690
else if (ta.x.color >= IC_ANSI_GRAY && ta.x.color <= IC_ANSI_WHITE) {
691
attr = (attr & 0xFFF0) | attr_color[ta.x.color - IC_ANSI_GRAY] | FOREGROUND_INTENSITY;
692
}
693
else if (ta.x.color == IC_ANSI_DEFAULT) {
694
attr = (attr & 0xFFF0) | (def_attr & 0x000F);
695
}
696
}
697
if (ta.x.bgcolor != IC_COLOR_NONE) {
698
if (ta.x.bgcolor >= IC_ANSI_BLACK && ta.x.bgcolor <= IC_ANSI_SILVER) {
699
attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_BLACK] << 4);
700
}
701
else if (ta.x.bgcolor >= IC_ANSI_GRAY && ta.x.bgcolor <= IC_ANSI_WHITE) {
702
attr = (attr & 0xFF0F) | (WORD)(attr_color[ta.x.bgcolor - IC_ANSI_GRAY] << 4) | BACKGROUND_INTENSITY;
703
}
704
else if (ta.x.bgcolor == IC_ANSI_DEFAULT) {
705
attr = (attr & 0xFF0F) | (def_attr & 0x00F0);
706
}
707
}
708
if (ta.x.underline != IC_NONE) {
709
attr = (attr & ~COMMON_LVB_UNDERSCORE) | (ta.x.underline == IC_ON ? COMMON_LVB_UNDERSCORE : 0);
710
}
711
if (ta.x.reverse != IC_NONE) {
712
attr = (attr & ~COMMON_LVB_REVERSE_VIDEO) | (ta.x.reverse == IC_ON ? COMMON_LVB_REVERSE_VIDEO : 0);
713
}
714
if (attr != cur_attr) {
715
SetConsoleTextAttribute(term->hcon, attr);
716
}
717
}
718
719
static ssize_t esc_param( const char* s, ssize_t def ) {
720
if (*s == '?') s++;
721
ssize_t n = def;
722
ic_atoz(s, &n);
723
return n;
724
}
725
726
static void esc_param2( const char* s, ssize_t* p1, ssize_t* p2, ssize_t def ) {
727
if (*s == '?') s++;
728
*p1 = def;
729
*p2 = def;
730
ic_atoz2(s, p1, p2);
731
}
732
733
// Emulate escape sequences on older windows.
734
static void term_write_esc( term_t* term, const char* s, ssize_t len ) {
735
ssize_t row;
736
ssize_t col;
737
738
if (s[1] == '[') {
739
switch (s[len-1]) {
740
case 'A':
741
term_move_cursor(term, -1, 0, esc_param(s+2, 1));
742
break;
743
case 'B':
744
term_move_cursor(term, 1, 0, esc_param(s+2, 1));
745
break;
746
case 'C':
747
term_move_cursor(term, 0, 1, esc_param(s+2, 1));
748
break;
749
case 'D':
750
term_move_cursor(term, 0, -1, esc_param(s+2, 1));
751
break;
752
case 'H':
753
esc_param2(s+2, &row, &col, 1);
754
term_move_cursor_to(term, row, col);
755
break;
756
case 'K':
757
term_erase_line(term, esc_param(s+2, 0));
758
break;
759
case 'm':
760
term_set_win_attr( term, attr_from_esc_sgr(s,len) );
761
break;
762
763
// support some less standard escape codes (currently not used by isocline)
764
case 'E': // line down
765
term_get_cursor_pos(term, &row, &col);
766
row += esc_param(s+2, 1);
767
term_move_cursor_to(term, row, 1);
768
break;
769
case 'F': // line up
770
term_get_cursor_pos(term, &row, &col);
771
row -= esc_param(s+2, 1);
772
term_move_cursor_to(term, row, 1);
773
break;
774
case 'G': // absolute column
775
term_get_cursor_pos(term, &row, &col);
776
col = esc_param(s+2, 1);
777
term_move_cursor_to(term, row, col);
778
break;
779
case 'J':
780
term_clear_screen(term, esc_param(s+2, 0));
781
break;
782
case 'h':
783
if (strncmp(s+2, "?25h", 4) == 0) {
784
term_cursor_visible(term, true);
785
}
786
break;
787
case 'l':
788
if (strncmp(s+2, "?25l", 4) == 0) {
789
term_cursor_visible(term, false);
790
}
791
break;
792
case 's':
793
term_cursor_save(term);
794
break;
795
case 'u':
796
term_cursor_restore(term);
797
break;
798
// otherwise ignore
799
}
800
}
801
else if (s[1] == '7') {
802
term_cursor_save(term);
803
}
804
else if (s[1] == '8') {
805
term_cursor_restore(term);
806
}
807
else {
808
// otherwise ignore
809
}
810
}
811
812
static bool term_write_direct(term_t* term, const char* s, ssize_t len ) {
813
term_cursor_visible(term,false); // reduce flicker
814
ssize_t pos = 0;
815
if ((term->hcon_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) {
816
// use the builtin virtual terminal processing. (enables truecolor for example)
817
term_write_console(term, s, len);
818
pos = len;
819
}
820
else {
821
// emulate escape sequences
822
while( pos < len ) {
823
// handle non-control in bulk (including utf-8 sequences)
824
// (We don't need to handle utf-8 separately as we set the codepage to always be in utf-8 mode)
825
ssize_t nonctrl = 0;
826
ssize_t next;
827
while( (next = str_next_ofs( s, len, pos+nonctrl, NULL )) > 0 &&
828
(uint8_t)s[pos + nonctrl] >= ' ' && (uint8_t)s[pos + nonctrl] <= 0x7F) {
829
nonctrl += next;
830
}
831
if (nonctrl > 0) {
832
term_write_console(term, s+pos, nonctrl);
833
pos += nonctrl;
834
}
835
if (next <= 0) break;
836
837
if ((uint8_t)s[pos] >= 0x80) {
838
// utf8 is already processed
839
term_write_console(term, s+pos, next);
840
}
841
else if (next > 1 && s[pos] == '\x1B') {
842
// handle control (note: str_next_ofs considers whole CSI escape sequences at a time)
843
term_write_esc(term, s+pos, next);
844
}
845
else if (next == 1 && (s[pos] == '\r' || s[pos] == '\n' || s[pos] == '\t' || s[pos] == '\b')) {
846
term_write_console( term, s+pos, next);
847
}
848
else {
849
// ignore
850
}
851
pos += next;
852
}
853
}
854
term_cursor_visible(term,true);
855
assert(pos == len);
856
return (pos == len);
857
858
}
859
#endif
860
861
862
863
//-------------------------------------------------------------
864
// Update terminal dimensions
865
//-------------------------------------------------------------
866
867
#if !defined(_WIN32)
868
869
// send escape query that may return a response on the tty
870
static bool term_esc_query_raw( term_t* term, const char* query, char* buf, ssize_t buflen )
871
{
872
if (buf==NULL || buflen <= 0 || query[0] == 0) return false;
873
bool osc = (query[1] == ']');
874
if (!term_write_direct(term, query, ic_strlen(query))) return false;
875
debug_msg("term: read tty query response to: ESC %s\n", query + 1);
876
return tty_read_esc_response( term->tty, query[1], osc, buf, buflen );
877
}
878
879
static bool term_esc_query( term_t* term, const char* query, char* buf, ssize_t buflen )
880
{
881
if (!tty_start_raw(term->tty)) return false;
882
bool ok = term_esc_query_raw(term,query,buf,buflen);
883
tty_end_raw(term->tty);
884
return ok;
885
}
886
887
// get the cursor position via an ESC[6n
888
static bool term_get_cursor_pos( term_t* term, ssize_t* row, ssize_t* col)
889
{
890
// send escape query
891
char buf[128];
892
if (!term_esc_query(term,"\x1B[6n",buf,128)) return false;
893
if (!ic_atoz2(buf,row,col)) return false;
894
return true;
895
}
896
897
static void term_set_cursor_pos( term_t* term, ssize_t row, ssize_t col ) {
898
term_writef( term, IC_CSI "%zd;%zdH", row, col );
899
}
900
901
ic_private bool term_update_dim(term_t* term) {
902
ssize_t cols = 0;
903
ssize_t rows = 0;
904
struct winsize ws;
905
if (ioctl(term->fd_out, TIOCGWINSZ, &ws) >= 0) {
906
// ioctl succeeded
907
cols = ws.ws_col; // debuggers return 0 for the column
908
rows = ws.ws_row;
909
}
910
else {
911
// determine width by querying the cursor position
912
debug_msg("term: ioctl term-size failed: %d,%d\n", ws.ws_row, ws.ws_col);
913
ssize_t col0 = 0;
914
ssize_t row0 = 0;
915
if (term_get_cursor_pos(term,&row0,&col0)) {
916
term_set_cursor_pos(term,999,999);
917
ssize_t col1 = 0;
918
ssize_t row1 = 0;
919
if (term_get_cursor_pos(term,&row1,&col1)) {
920
cols = col1;
921
rows = row1;
922
}
923
term_set_cursor_pos(term,row0,col0);
924
}
925
else {
926
// cannot query position
927
// return 0 column
928
}
929
}
930
931
// update width and return whether it changed.
932
bool changed = (term->width != cols || term->height != rows);
933
debug_msg("terminal dim: %zd,%zd: %s\n", rows, cols, changed ? "changed" : "unchanged");
934
if (cols > 0) {
935
term->width = cols;
936
term->height = rows;
937
}
938
return changed;
939
}
940
941
#else
942
943
ic_private bool term_update_dim(term_t* term) {
944
if (term->hcon == 0) {
945
term->hcon = GetConsoleWindow();
946
}
947
ssize_t rows = 0;
948
ssize_t cols = 0;
949
CONSOLE_SCREEN_BUFFER_INFO sbinfo;
950
if (GetConsoleScreenBufferInfo(term->hcon, &sbinfo)) {
951
cols = (ssize_t)sbinfo.srWindow.Right - (ssize_t)sbinfo.srWindow.Left + 1;
952
rows = (ssize_t)sbinfo.srWindow.Bottom - (ssize_t)sbinfo.srWindow.Top + 1;
953
}
954
bool changed = (term->width != cols || term->height != rows);
955
term->width = cols;
956
term->height = rows;
957
debug_msg("term: update dim: %zd, %zd\n", term->height, term->width );
958
return changed;
959
}
960
961
#endif
962
963
964
965
//-------------------------------------------------------------
966
// Enable/disable terminal raw mode
967
//-------------------------------------------------------------
968
969
#if !defined(_WIN32)
970
971
// On non-windows, the terminal is set in raw mode by the tty.
972
973
ic_private void term_start_raw(term_t* term) {
974
term->raw_enabled++;
975
}
976
977
ic_private void term_end_raw(term_t* term, bool force) {
978
if (term->raw_enabled <= 0) return;
979
if (!force) {
980
term->raw_enabled--;
981
}
982
else {
983
term->raw_enabled = 0;
984
}
985
}
986
987
static bool term_esc_query_color_raw(term_t* term, int color_idx, uint32_t* color ) {
988
char buf[128+1];
989
snprintf(buf,128,"\x1B]4;%d;?\x1B\\", color_idx);
990
if (!term_esc_query_raw( term, buf, buf, 128 )) {
991
debug_msg("esc query response not received\n");
992
return false;
993
}
994
if (buf[0] != '4') return false;
995
const char* rgb = strchr(buf,':');
996
if (rgb==NULL) return false;
997
rgb++; // skip ':'
998
unsigned int r,g,b;
999
if (sscanf(rgb,"%x/%x/%x",&r,&g,&b) != 3) return false;
1000
if (rgb[2]!='/') { // 48-bit rgb, hexadecimal round to 24-bit
1001
r = (r+0x7F)/0x100; // note: can "overflow", e.g. 0xFFFF -> 0x100. (and we need `ic_cap8` to convert.)
1002
g = (g+0x7F)/0x100;
1003
b = (b+0x7F)/0x100;
1004
}
1005
*color = (ic_cap8(r)<<16) | (ic_cap8(g)<<8) | ic_cap8(b);
1006
debug_msg("color query: %02x,%02x,%02x: %06x\n", r, g, b, *color);
1007
return true;
1008
}
1009
1010
// update ansi 16 color palette for better color approximation
1011
static void term_update_ansi16(term_t* term) {
1012
debug_msg("update ansi colors\n");
1013
#if defined(GIO_CMAP)
1014
// try ioctl first (on Linux)
1015
uint8_t cmap[48];
1016
memset(cmap,0,48);
1017
if (ioctl(term->fd_out,GIO_CMAP,&cmap) >= 0) {
1018
// success
1019
for(ssize_t i = 0; i < 48; i+=3) {
1020
uint32_t color = ((uint32_t)(cmap[i]) << 16) | ((uint32_t)(cmap[i+1]) << 8) | cmap[i+2];
1021
debug_msg("term (ioctl) ansi color %d: 0x%06x\n", i, color);
1022
ansi256[i] = color;
1023
}
1024
return;
1025
}
1026
else {
1027
debug_msg("ioctl GIO_CMAP failed: entry 1: 0x%02x%02x%02x\n", cmap[3], cmap[4], cmap[5]);
1028
}
1029
#endif
1030
// this seems to be unreliable on some systems (Ubuntu+Gnome terminal) so only enable when known ok.
1031
#if __APPLE__
1032
// otherwise use OSC 4 escape sequence query
1033
if (tty_start_raw(term->tty)) {
1034
for(ssize_t i = 0; i < 16; i++) {
1035
uint32_t color;
1036
if (!term_esc_query_color_raw(term, i, &color)) break;
1037
debug_msg("term ansi color %d: 0x%06x\n", i, color);
1038
ansi256[i] = color;
1039
}
1040
tty_end_raw(term->tty);
1041
}
1042
#endif
1043
}
1044
1045
static void term_init_raw(term_t* term) {
1046
if (term->palette < ANSIRGB) {
1047
term_update_ansi16(term);
1048
}
1049
}
1050
1051
#else
1052
1053
ic_private void term_start_raw(term_t* term) {
1054
if (term->raw_enabled++ > 0) return;
1055
CONSOLE_SCREEN_BUFFER_INFO info;
1056
if (GetConsoleScreenBufferInfo(term->hcon, &info)) {
1057
term->hcon_orig_attr = info.wAttributes;
1058
}
1059
term->hcon_orig_cp = GetConsoleOutputCP();
1060
SetConsoleOutputCP(CP_UTF8);
1061
if (term->hcon_mode == 0) {
1062
// first time initialization
1063
DWORD mode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LVB_GRID_WORLDWIDE; // for \r \n and \b
1064
// use escape sequence handling if available and the terminal supports it (so we can use rgb colors in Windows terminal)
1065
// Unfortunately, in plain powershell, we can successfully enable terminal processing
1066
// but it still fails to render correctly; so we require the palette be large enough (like in Windows Terminal)
1067
if (term->palette >= ANSI256 && SetConsoleMode(term->hcon, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
1068
term->hcon_mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
1069
debug_msg("term: console mode: virtual terminal processing enabled\n");
1070
}
1071
// no virtual terminal processing, emulate instead
1072
else if (SetConsoleMode(term->hcon, mode)) {
1073
term->hcon_mode = mode;
1074
term->palette = ANSI16;
1075
}
1076
GetConsoleMode(term->hcon, &mode);
1077
debug_msg("term: console mode: orig: 0x%x, new: 0x%x, current 0x%x\n", term->hcon_orig_mode, term->hcon_mode, mode);
1078
}
1079
else {
1080
SetConsoleMode(term->hcon, term->hcon_mode);
1081
}
1082
}
1083
1084
ic_private void term_end_raw(term_t* term, bool force) {
1085
if (term->raw_enabled <= 0) return;
1086
if (!force && term->raw_enabled > 1) {
1087
term->raw_enabled--;
1088
}
1089
else {
1090
term->raw_enabled = 0;
1091
SetConsoleMode(term->hcon, term->hcon_orig_mode);
1092
SetConsoleOutputCP(term->hcon_orig_cp);
1093
SetConsoleTextAttribute(term->hcon, term->hcon_orig_attr);
1094
}
1095
}
1096
1097
static void term_init_raw(term_t* term) {
1098
term->hcon = GetStdHandle(STD_OUTPUT_HANDLE);
1099
GetConsoleMode(term->hcon, &term->hcon_orig_mode);
1100
CONSOLE_SCREEN_BUFFER_INFOEX info;
1101
memset(&info, 0, sizeof(info));
1102
info.cbSize = sizeof(info);
1103
if (GetConsoleScreenBufferInfoEx(term->hcon, &info)) {
1104
// store default attributes
1105
term->hcon_default_attr = info.wAttributes;
1106
// update our color table with the actual colors used.
1107
for (unsigned i = 0; i < 16; i++) {
1108
COLORREF cr = info.ColorTable[i];
1109
uint32_t color = (ic_cap8(GetRValue(cr))<<16) | (ic_cap8(GetGValue(cr))<<8) | ic_cap8(GetBValue(cr)); // COLORREF = BGR
1110
// index is also in reverse in the bits 0 and 2
1111
unsigned j = (i&0x08) | ((i&0x04)>>2) | (i&0x02) | (i&0x01)<<2;
1112
debug_msg("term: ansi color %d is 0x%06x\n", j, color);
1113
ansi256[j] = color;
1114
}
1115
}
1116
else {
1117
DWORD err = GetLastError();
1118
debug_msg("term: cannot get console screen buffer: %d %x", err, err);
1119
}
1120
term_start_raw(term); // initialize the hcon_mode
1121
term_end_raw(term,false);
1122
}
1123
1124
#endif
1125
1126