1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4// Terminal UI functions. Invoked (by ui_bridge.c) on the TUI thread.
5
6#include <assert.h>
7#include <stdbool.h>
8#include <stdio.h>
9#include <limits.h>
10
11#include <uv.h>
12#include <unibilium.h>
13#if defined(HAVE_TERMIOS_H)
14# include <termios.h>
15#endif
16
17#include "nvim/lib/kvec.h"
18
19#include "nvim/ascii.h"
20#include "nvim/vim.h"
21#include "nvim/log.h"
22#include "nvim/ui.h"
23#include "nvim/highlight.h"
24#include "nvim/map.h"
25#include "nvim/main.h"
26#include "nvim/memory.h"
27#include "nvim/option.h"
28#include "nvim/api/vim.h"
29#include "nvim/api/private/helpers.h"
30#include "nvim/event/loop.h"
31#include "nvim/event/signal.h"
32#include "nvim/os/input.h"
33#include "nvim/os/os.h"
34#include "nvim/os/tty.h"
35#include "nvim/strings.h"
36#include "nvim/syntax.h"
37#include "nvim/ui_bridge.h"
38#include "nvim/ugrid.h"
39#include "nvim/tui/input.h"
40#include "nvim/tui/tui.h"
41#include "nvim/tui/terminfo.h"
42#include "nvim/cursor_shape.h"
43#include "nvim/macros.h"
44
45// Space reserved in two output buffers to make the cursor normal or invisible
46// when flushing. No existing terminal will require 32 bytes to do that.
47#define CNORM_COMMAND_MAX_SIZE 32
48#define OUTBUF_SIZE 0xffff
49
50#define TOO_MANY_EVENTS 1000000
51#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
52 && 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
53#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
54 ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
55#define LINUXSET0C "\x1b[?0c"
56#define LINUXSET1C "\x1b[?1c"
57
58#ifdef NVIM_UNIBI_HAS_VAR_FROM
59#define UNIBI_SET_NUM_VAR(var, num) \
60 do { \
61 (var) = unibi_var_from_num((num)); \
62 } while (0)
63#else
64#define UNIBI_SET_NUM_VAR(var, num) (var).i = (num);
65#endif
66
67typedef struct {
68 int top, bot, left, right;
69} Rect;
70
71typedef struct {
72 UIBridgeData *bridge;
73 Loop *loop;
74 unibi_var_t params[9];
75 char buf[OUTBUF_SIZE];
76 size_t bufpos;
77 char norm[CNORM_COMMAND_MAX_SIZE];
78 char invis[CNORM_COMMAND_MAX_SIZE];
79 size_t normlen, invislen;
80 TermInput input;
81 uv_loop_t write_loop;
82 unibi_term *ut;
83 union {
84 uv_tty_t tty;
85 uv_pipe_t pipe;
86 } output_handle;
87 bool out_isatty;
88 SignalWatcher winch_handle, cont_handle;
89 bool cont_received;
90 UGrid grid;
91 kvec_t(Rect) invalid_regions;
92 int row, col;
93 int out_fd;
94 bool scroll_region_is_full_screen;
95 bool can_change_scroll_region;
96 bool can_set_lr_margin;
97 bool can_set_left_right_margin;
98 bool can_scroll;
99 bool can_erase_chars;
100 bool immediate_wrap_after_last_column;
101 bool bce;
102 bool mouse_enabled;
103 bool busy, is_invisible;
104 bool cork, overflow;
105 bool cursor_color_changed;
106 bool is_starting;
107 cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
108 HlAttrs clear_attrs;
109 kvec_t(HlAttrs) attrs;
110 int print_attr_id;
111 bool default_attr;
112 bool can_clear_attr;
113 ModeShape showing_mode;
114 struct {
115 int enable_mouse, disable_mouse;
116 int enable_bracketed_paste, disable_bracketed_paste;
117 int enable_lr_margin, disable_lr_margin;
118 int enter_strikethrough_mode;
119 int set_rgb_foreground, set_rgb_background;
120 int set_cursor_color;
121 int reset_cursor_color;
122 int enable_focus_reporting, disable_focus_reporting;
123 int resize_screen;
124 int reset_scroll_region;
125 int set_cursor_style, reset_cursor_style;
126 int save_title, restore_title;
127 int get_bg;
128 int set_underline_style;
129 int set_underline_color;
130 } unibi_ext;
131 char *space_buf;
132} TUIData;
133
134static bool volatile got_winch = false;
135static bool did_user_set_dimensions = false;
136static bool cursor_style_enabled = false;
137
138#ifdef INCLUDE_GENERATED_DECLARATIONS
139# include "tui/tui.c.generated.h"
140#endif
141
142
143UI *tui_start(void)
144{
145 UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop().
146 ui->stop = tui_stop;
147 ui->grid_resize = tui_grid_resize;
148 ui->grid_clear = tui_grid_clear;
149 ui->grid_cursor_goto = tui_grid_cursor_goto;
150 ui->mode_info_set = tui_mode_info_set;
151 ui->update_menu = tui_update_menu;
152 ui->busy_start = tui_busy_start;
153 ui->busy_stop = tui_busy_stop;
154 ui->mouse_on = tui_mouse_on;
155 ui->mouse_off = tui_mouse_off;
156 ui->mode_change = tui_mode_change;
157 ui->grid_scroll = tui_grid_scroll;
158 ui->hl_attr_define = tui_hl_attr_define;
159 ui->bell = tui_bell;
160 ui->visual_bell = tui_visual_bell;
161 ui->default_colors_set = tui_default_colors_set;
162 ui->flush = tui_flush;
163 ui->suspend = tui_suspend;
164 ui->set_title = tui_set_title;
165 ui->set_icon = tui_set_icon;
166 ui->option_set= tui_option_set;
167 ui->raw_line = tui_raw_line;
168
169 memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
170 ui->ui_ext[kUILinegrid] = true;
171 ui->ui_ext[kUITermColors] = true;
172
173 return ui_bridge_attach(ui, tui_main, tui_scheduler);
174}
175
176static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index,
177 char * buf, size_t len)
178{
179 const char *str = unibi_get_str(data->ut, unibi_index);
180 if (!str) {
181 return 0U;
182 }
183 return unibi_run(str, data->params, buf, len);
184}
185
186static void termname_set_event(void **argv)
187{
188 char *termname = argv[0];
189 set_tty_option("term", termname);
190 // Do not free termname, it is freed by set_tty_option.
191}
192
193static void terminfo_start(UI *ui)
194{
195 TUIData *data = ui->data;
196 data->scroll_region_is_full_screen = true;
197 data->bufpos = 0;
198 data->default_attr = false;
199 data->can_clear_attr = false;
200 data->is_invisible = true;
201 data->busy = false;
202 data->cork = false;
203 data->overflow = false;
204 data->cursor_color_changed = false;
205 data->showing_mode = SHAPE_IDX_N;
206 data->unibi_ext.enable_mouse = -1;
207 data->unibi_ext.disable_mouse = -1;
208 data->unibi_ext.set_cursor_color = -1;
209 data->unibi_ext.reset_cursor_color = -1;
210 data->unibi_ext.enable_bracketed_paste = -1;
211 data->unibi_ext.disable_bracketed_paste = -1;
212 data->unibi_ext.enter_strikethrough_mode = -1;
213 data->unibi_ext.enable_lr_margin = -1;
214 data->unibi_ext.disable_lr_margin = -1;
215 data->unibi_ext.enable_focus_reporting = -1;
216 data->unibi_ext.disable_focus_reporting = -1;
217 data->unibi_ext.resize_screen = -1;
218 data->unibi_ext.reset_scroll_region = -1;
219 data->unibi_ext.set_cursor_style = -1;
220 data->unibi_ext.reset_cursor_style = -1;
221 data->unibi_ext.get_bg = -1;
222 data->unibi_ext.set_underline_color = -1;
223 data->out_fd = 1;
224 data->out_isatty = os_isatty(data->out_fd);
225
226 const char *term = os_getenv("TERM");
227#ifdef WIN32
228 os_tty_guess_term(&term, data->out_fd);
229 os_setenv("TERM", term, 1);
230 // Old os_getenv() pointer is invalid after os_setenv(), fetch it again.
231 term = os_getenv("TERM");
232#endif
233
234 // Set up unibilium/terminfo.
235 char *termname = NULL;
236 if (term) {
237 data->ut = unibi_from_term(term);
238 if (data->ut) {
239 termname = xstrdup(term);
240 }
241 }
242 if (!data->ut) {
243 data->ut = terminfo_from_builtin(term, &termname);
244 }
245 // Update 'term' option.
246 loop_schedule_deferred(&main_loop,
247 event_create(termname_set_event, 1, termname));
248
249 // None of the following work over SSH; see :help TERM .
250 const char *colorterm = os_getenv("COLORTERM");
251 const char *termprg = os_getenv("TERM_PROGRAM");
252 const char *vte_version_env = os_getenv("VTE_VERSION");
253 long vtev = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0;
254 bool iterm_env = termprg && strstr(termprg, "iTerm.app");
255 bool nsterm = (termprg && strstr(termprg, "Apple_Terminal"))
256 || terminfo_is_term_family(term, "nsterm");
257 bool konsole = terminfo_is_term_family(term, "konsole")
258 || os_getenv("KONSOLE_PROFILE_NAME")
259 || os_getenv("KONSOLE_DBUS_SESSION");
260 const char *konsolev_env = os_getenv("KONSOLE_VERSION");
261 long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10)
262 : (konsole ? 1 : 0);
263
264 patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
265 augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm);
266 data->can_change_scroll_region =
267 !!unibi_get_str(data->ut, unibi_change_scroll_region);
268 data->can_set_lr_margin =
269 !!unibi_get_str(data->ut, unibi_set_lr_margin);
270 data->can_set_left_right_margin =
271 !!unibi_get_str(data->ut, unibi_set_left_margin_parm)
272 && !!unibi_get_str(data->ut, unibi_set_right_margin_parm);
273 data->can_scroll =
274 !!unibi_get_str(data->ut, unibi_delete_line)
275 && !!unibi_get_str(data->ut, unibi_parm_delete_line)
276 && !!unibi_get_str(data->ut, unibi_insert_line)
277 && !!unibi_get_str(data->ut, unibi_parm_insert_line);
278 data->can_erase_chars = !!unibi_get_str(data->ut, unibi_erase_chars);
279 data->immediate_wrap_after_last_column =
280 terminfo_is_term_family(term, "conemu")
281 || terminfo_is_term_family(term, "cygwin")
282 || terminfo_is_term_family(term, "win32con")
283 || terminfo_is_term_family(term, "interix");
284 data->bce = unibi_get_bool(data->ut, unibi_back_color_erase);
285 data->normlen = unibi_pre_fmt_str(data, unibi_cursor_normal,
286 data->norm, sizeof data->norm);
287 data->invislen = unibi_pre_fmt_str(data, unibi_cursor_invisible,
288 data->invis, sizeof data->invis);
289 // Set 't_Co' from the result of unibilium & fix_terminfo.
290 t_colors = unibi_get_num(data->ut, unibi_max_colors);
291 // Enter alternate screen, save title, and clear.
292 // NOTE: Do this *before* changing terminal settings. #6433
293 unibi_out(ui, unibi_enter_ca_mode);
294 // Save title/icon to the "stack". #4063
295 unibi_out_ext(ui, data->unibi_ext.save_title);
296 unibi_out(ui, unibi_keypad_xmit);
297 unibi_out(ui, unibi_clear_screen);
298 // Ask the terminal to send us the background color.
299 unibi_out_ext(ui, data->unibi_ext.get_bg);
300 // Enable bracketed paste
301 unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);
302
303 uv_loop_init(&data->write_loop);
304 if (data->out_isatty) {
305 uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0);
306#ifdef WIN32
307 uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW);
308#else
309 uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO);
310#endif
311 } else {
312 uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
313 uv_pipe_open(&data->output_handle.pipe, data->out_fd);
314 }
315}
316
317static void terminfo_stop(UI *ui)
318{
319 TUIData *data = ui->data;
320 // Destroy output stuff
321 tui_mode_change(ui, (String)STRING_INIT, SHAPE_IDX_N);
322 tui_mouse_off(ui);
323 unibi_out(ui, unibi_exit_attribute_mode);
324 // Reset cursor to normal before exiting alternate screen.
325 unibi_out(ui, unibi_cursor_normal);
326 unibi_out(ui, unibi_keypad_local);
327 unibi_out(ui, unibi_exit_ca_mode);
328 // Restore title/icon from the "stack". #4063
329 unibi_out_ext(ui, data->unibi_ext.restore_title);
330 if (data->cursor_color_changed) {
331 unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
332 }
333 // Disable bracketed paste
334 unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste);
335 // Disable focus reporting
336 unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting);
337 flush_buf(ui);
338 uv_tty_reset_mode();
339 uv_close((uv_handle_t *)&data->output_handle, NULL);
340 uv_run(&data->write_loop, UV_RUN_DEFAULT);
341 if (uv_loop_close(&data->write_loop)) {
342 abort();
343 }
344 unibi_destroy(data->ut);
345}
346
347static void tui_terminal_start(UI *ui)
348{
349 TUIData *data = ui->data;
350 data->print_attr_id = -1;
351 ugrid_init(&data->grid);
352 terminfo_start(ui);
353 tui_guess_size(ui);
354 signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
355 tinput_start(&data->input);
356}
357
358static void tui_terminal_after_startup(UI *ui)
359 FUNC_ATTR_NONNULL_ALL
360{
361 TUIData *data = ui->data;
362
363 // Emit this after Nvim startup, not during. This works around a tmux
364 // 2.3 bug(?) which caused slow drawing during startup. #7649
365 unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);
366}
367
368static void tui_terminal_stop(UI *ui)
369{
370 TUIData *data = ui->data;
371 if (uv_is_closing(STRUCT_CAST(uv_handle_t, &data->output_handle))) {
372 // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075
373 ELOG("TUI already stopped (race?)");
374 ui->data = NULL; // Flag UI as "stopped".
375 return;
376 }
377 tinput_stop(&data->input);
378 signal_watcher_stop(&data->winch_handle);
379 terminfo_stop(ui);
380 ugrid_free(&data->grid);
381}
382
383static void tui_stop(UI *ui)
384{
385 tui_terminal_stop(ui);
386 ui->data = NULL; // Flag UI as "stopped".
387}
388
389/// Returns true if UI `ui` is stopped.
390static bool tui_is_stopped(UI *ui)
391{
392 return ui->data == NULL;
393}
394
395/// Main function of the TUI thread.
396static void tui_main(UIBridgeData *bridge, UI *ui)
397{
398 Loop tui_loop;
399 loop_init(&tui_loop, NULL);
400 TUIData *data = xcalloc(1, sizeof(TUIData));
401 ui->data = data;
402 data->bridge = bridge;
403 data->loop = &tui_loop;
404 data->is_starting = true;
405 kv_init(data->invalid_regions);
406 signal_watcher_init(data->loop, &data->winch_handle, ui);
407 signal_watcher_init(data->loop, &data->cont_handle, data);
408#ifdef UNIX
409 signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
410#endif
411
412 // TODO(bfredl): zero hl is empty, send this explicitly?
413 kv_push(data->attrs, HLATTRS_INIT);
414
415#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
416 data->input.tk_ti_hook_fn = tui_tk_ti_getstr;
417#endif
418 tinput_init(&data->input, &tui_loop);
419 tui_terminal_start(ui);
420
421 // Allow main thread to continue, we are ready to handle UI callbacks.
422 CONTINUE(bridge);
423
424 loop_schedule_deferred(&main_loop,
425 event_create(show_termcap_event, 1, data->ut));
426
427 // "Active" loop: first ~100 ms of startup.
428 for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) {
429 ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1);
430 }
431 if (!tui_is_stopped(ui)) {
432 tui_terminal_after_startup(ui);
433 // Tickle `main_loop` with a dummy event, else the initial "focus-gained"
434 // terminal response may not get processed until user hits a key.
435 loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
436 }
437 // "Passive" (I/O-driven) loop: TUI thread "main loop".
438 while (!tui_is_stopped(ui)) {
439 loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed
440 }
441
442 ui_bridge_stopped(bridge);
443 tinput_destroy(&data->input);
444 signal_watcher_stop(&data->cont_handle);
445 signal_watcher_close(&data->cont_handle, NULL);
446 signal_watcher_close(&data->winch_handle, NULL);
447 loop_close(&tui_loop, false);
448 kv_destroy(data->invalid_regions);
449 kv_destroy(data->attrs);
450 xfree(data->space_buf);
451 xfree(data);
452}
453
454/// Handoff point between the main (ui_bridge) thread and the TUI thread.
455static void tui_scheduler(Event event, void *d)
456{
457 UI *ui = d;
458 TUIData *data = ui->data;
459 loop_schedule_fast(data->loop, event); // `tui_loop` local to tui_main().
460}
461
462#ifdef UNIX
463static void sigcont_cb(SignalWatcher *watcher, int signum, void *data)
464{
465 ((TUIData *)data)->cont_received = true;
466}
467#endif
468
469static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
470{
471 got_winch = true;
472 UI *ui = data;
473 if (tui_is_stopped(ui)) {
474 return;
475 }
476
477 tui_guess_size(ui);
478 ui_schedule_refresh();
479}
480
481static bool attrs_differ(UI *ui, int id1, int id2, bool rgb)
482{
483 TUIData *data = ui->data;
484 if (id1 == id2) {
485 return false;
486 } else if (id1 < 0 || id2 < 0) {
487 return true;
488 }
489 HlAttrs a1 = kv_A(data->attrs, (size_t)id1);
490 HlAttrs a2 = kv_A(data->attrs, (size_t)id2);
491
492 if (rgb) {
493 return a1.rgb_fg_color != a2.rgb_fg_color
494 || a1.rgb_bg_color != a2.rgb_bg_color
495 || a1.rgb_ae_attr != a2.rgb_ae_attr
496 || a1.rgb_sp_color != a2.rgb_sp_color;
497 } else {
498 return a1.cterm_fg_color != a2.cterm_fg_color
499 || a1.cterm_bg_color != a2.cterm_bg_color
500 || a1.cterm_ae_attr != a2.cterm_ae_attr
501 || (a1.cterm_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)
502 && a1.rgb_sp_color != a2.rgb_sp_color);
503 }
504}
505
506static void update_attrs(UI *ui, int attr_id)
507{
508 TUIData *data = ui->data;
509
510 if (!attrs_differ(ui, attr_id, data->print_attr_id, ui->rgb)) {
511 data->print_attr_id = attr_id;
512 return;
513 }
514 data->print_attr_id = attr_id;
515 HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id);
516
517 int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1);
518 if (fg == -1) {
519 fg = ui->rgb ? data->clear_attrs.rgb_fg_color
520 : (data->clear_attrs.cterm_fg_color - 1);
521 }
522
523 int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1);
524 if (bg == -1) {
525 bg = ui->rgb ? data->clear_attrs.rgb_bg_color
526 : (data->clear_attrs.cterm_bg_color - 1);
527 }
528
529 int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr;
530 bool bold = attr & HL_BOLD;
531 bool italic = attr & HL_ITALIC;
532 bool reverse = attr & HL_INVERSE;
533 bool standout = attr & HL_STANDOUT;
534 bool strikethrough = attr & HL_STRIKETHROUGH;
535
536 bool underline;
537 bool undercurl;
538 if (data->unibi_ext.set_underline_style != -1) {
539 underline = attr & HL_UNDERLINE;
540 undercurl = attr & HL_UNDERCURL;
541 } else {
542 underline = (attr & HL_UNDERLINE) || (attr & HL_UNDERCURL);
543 undercurl = false;
544 }
545
546 if (unibi_get_str(data->ut, unibi_set_attributes)) {
547 if (bold || reverse || underline || standout) {
548 UNIBI_SET_NUM_VAR(data->params[0], standout);
549 UNIBI_SET_NUM_VAR(data->params[1], underline);
550 UNIBI_SET_NUM_VAR(data->params[2], reverse);
551 UNIBI_SET_NUM_VAR(data->params[3], 0); // blink
552 UNIBI_SET_NUM_VAR(data->params[4], 0); // dim
553 UNIBI_SET_NUM_VAR(data->params[5], bold);
554 UNIBI_SET_NUM_VAR(data->params[6], 0); // blank
555 UNIBI_SET_NUM_VAR(data->params[7], 0); // protect
556 UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set
557 unibi_out(ui, unibi_set_attributes);
558 } else if (!data->default_attr) {
559 unibi_out(ui, unibi_exit_attribute_mode);
560 }
561 } else {
562 if (!data->default_attr) {
563 unibi_out(ui, unibi_exit_attribute_mode);
564 }
565 if (bold) {
566 unibi_out(ui, unibi_enter_bold_mode);
567 }
568 if (underline) {
569 unibi_out(ui, unibi_enter_underline_mode);
570 }
571 if (standout) {
572 unibi_out(ui, unibi_enter_standout_mode);
573 }
574 if (reverse) {
575 unibi_out(ui, unibi_enter_reverse_mode);
576 }
577 }
578 if (italic) {
579 unibi_out(ui, unibi_enter_italics_mode);
580 }
581 if (strikethrough && data->unibi_ext.enter_strikethrough_mode != -1) {
582 unibi_out_ext(ui, data->unibi_ext.enter_strikethrough_mode);
583 }
584 if (undercurl && data->unibi_ext.set_underline_style != -1) {
585 UNIBI_SET_NUM_VAR(data->params[0], 3);
586 unibi_out_ext(ui, data->unibi_ext.set_underline_style);
587 }
588 if ((undercurl || underline) && data->unibi_ext.set_underline_color != -1) {
589 int color = attrs.rgb_sp_color;
590 if (color != -1) {
591 UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red
592 UNIBI_SET_NUM_VAR(data->params[1], (color >> 8) & 0xff); // green
593 UNIBI_SET_NUM_VAR(data->params[2], color & 0xff); // blue
594 unibi_out_ext(ui, data->unibi_ext.set_underline_color);
595 }
596 }
597 if (ui->rgb) {
598 if (fg != -1) {
599 UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red
600 UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green
601 UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue
602 unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground);
603 }
604
605 if (bg != -1) {
606 UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red
607 UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green
608 UNIBI_SET_NUM_VAR(data->params[2], bg & 0xff); // blue
609 unibi_out_ext(ui, data->unibi_ext.set_rgb_background);
610 }
611 } else {
612 if (fg != -1) {
613 UNIBI_SET_NUM_VAR(data->params[0], fg);
614 unibi_out(ui, unibi_set_a_foreground);
615 }
616
617 if (bg != -1) {
618 UNIBI_SET_NUM_VAR(data->params[0], bg);
619 unibi_out(ui, unibi_set_a_background);
620 }
621 }
622
623 data->default_attr = fg == -1 && bg == -1
624 && !bold && !italic && !underline && !undercurl && !reverse && !standout
625 && !strikethrough;
626
627 // Non-BCE terminals can't clear with non-default background color. Some BCE
628 // terminals don't support attributes either, so don't rely on it. But assume
629 // italic and bold has no effect if there is no text.
630 data->can_clear_attr = !reverse && !standout && !underline && !undercurl
631 && !strikethrough && (data->bce || bg == -1);
632}
633
634static void final_column_wrap(UI *ui)
635{
636 TUIData *data = ui->data;
637 UGrid *grid = &data->grid;
638 if (grid->row != -1 && grid->col == ui->width) {
639 grid->col = 0;
640 if (grid->row < MIN(ui->height, grid->height - 1)) {
641 grid->row++;
642 }
643 }
644}
645
646/// It is undocumented, but in the majority of terminals and terminal emulators
647/// printing at the right margin does not cause an automatic wrap until the
648/// next character is printed, holding the cursor in place until then.
649static void print_cell(UI *ui, UCell *ptr)
650{
651 TUIData *data = ui->data;
652 UGrid *grid = &data->grid;
653 if (!data->immediate_wrap_after_last_column) {
654 // Printing the next character finally advances the cursor.
655 final_column_wrap(ui);
656 }
657 update_attrs(ui, ptr->attr);
658 out(ui, ptr->data, strlen(ptr->data));
659 grid->col++;
660 if (data->immediate_wrap_after_last_column) {
661 // Printing at the right margin immediately advances the cursor.
662 final_column_wrap(ui);
663 }
664}
665
666static bool cheap_to_print(UI *ui, int row, int col, int next)
667{
668 TUIData *data = ui->data;
669 UGrid *grid = &data->grid;
670 UCell *cell = grid->cells[row] + col;
671 while (next) {
672 next--;
673 if (attrs_differ(ui, cell->attr,
674 data->print_attr_id, ui->rgb)) {
675 if (data->default_attr) {
676 return false;
677 }
678 }
679 if (strlen(cell->data) > 1) {
680 return false;
681 }
682 cell++;
683 }
684 return true;
685}
686
687/// This optimizes several cases where it is cheaper to do something other
688/// than send a full cursor positioning control sequence. However, there are
689/// some further optimizations that may seem obvious but that will not work.
690///
691/// We cannot use VT (ASCII 0/11) for moving the cursor up, because VT means
692/// move the cursor down on a DEC terminal. Similarly, on a DEC terminal FF
693/// (ASCII 0/12) means the same thing and does not mean home. VT, CVT, and
694/// TAB also stop at software-defined tabulation stops, not at a fixed set
695/// of row/column positions.
696static void cursor_goto(UI *ui, int row, int col)
697{
698 TUIData *data = ui->data;
699 UGrid *grid = &data->grid;
700 if (row == grid->row && col == grid->col) {
701 return;
702 }
703 if (0 == row && 0 == col) {
704 unibi_out(ui, unibi_cursor_home);
705 ugrid_goto(grid, row, col);
706 return;
707 }
708 if (grid->row == -1) {
709 goto safe_move;
710 }
711 if (0 == col ? col != grid->col :
712 row != grid->row ? false :
713 1 == col ? 2 < grid->col && cheap_to_print(ui, grid->row, 0, col) :
714 2 == col ? 5 < grid->col && cheap_to_print(ui, grid->row, 0, col) :
715 false) {
716 // Motion to left margin from anywhere else, or CR + printing chars is
717 // even less expensive than using BSes or CUB.
718 unibi_out(ui, unibi_carriage_return);
719 ugrid_goto(grid, grid->row, 0);
720 }
721 if (row == grid->row) {
722 if (col < grid->col
723 // Deferred right margin wrap terminals have inconsistent ideas about
724 // where the cursor actually is during a deferred wrap. Relative
725 // motion calculations have OBOEs that cannot be compensated for,
726 // because two terminals that claim to be the same will implement
727 // different cursor positioning rules.
728 && (data->immediate_wrap_after_last_column || grid->col < ui->width)) {
729 int n = grid->col - col;
730 if (n <= 4) { // This might be just BS, so it is considered really cheap.
731 while (n--) {
732 unibi_out(ui, unibi_cursor_left);
733 }
734 } else {
735 UNIBI_SET_NUM_VAR(data->params[0], n);
736 unibi_out(ui, unibi_parm_left_cursor);
737 }
738 ugrid_goto(grid, row, col);
739 return;
740 } else if (col > grid->col) {
741 int n = col - grid->col;
742 if (n <= 2) {
743 while (n--) {
744 unibi_out(ui, unibi_cursor_right);
745 }
746 } else {
747 UNIBI_SET_NUM_VAR(data->params[0], n);
748 unibi_out(ui, unibi_parm_right_cursor);
749 }
750 ugrid_goto(grid, row, col);
751 return;
752 }
753 }
754 if (col == grid->col) {
755 if (row > grid->row) {
756 int n = row - grid->row;
757 if (n <= 4) { // This might be just LF, so it is considered really cheap.
758 while (n--) {
759 unibi_out(ui, unibi_cursor_down);
760 }
761 } else {
762 UNIBI_SET_NUM_VAR(data->params[0], n);
763 unibi_out(ui, unibi_parm_down_cursor);
764 }
765 ugrid_goto(grid, row, col);
766 return;
767 } else if (row < grid->row) {
768 int n = grid->row - row;
769 if (n <= 2) {
770 while (n--) {
771 unibi_out(ui, unibi_cursor_up);
772 }
773 } else {
774 UNIBI_SET_NUM_VAR(data->params[0], n);
775 unibi_out(ui, unibi_parm_up_cursor);
776 }
777 ugrid_goto(grid, row, col);
778 return;
779 }
780 }
781
782safe_move:
783 unibi_goto(ui, row, col);
784 ugrid_goto(grid, row, col);
785}
786
787static void clear_region(UI *ui, int top, int bot, int left, int right,
788 int attr_id)
789{
790 TUIData *data = ui->data;
791 UGrid *grid = &data->grid;
792
793 update_attrs(ui, attr_id);
794
795 // Background is set to the default color and the right edge matches the
796 // screen end, try to use terminal codes for clearing the requested area.
797 if (data->can_clear_attr
798 && left == 0 && right == ui->width && bot == ui->height) {
799 if (top == 0) {
800 unibi_out(ui, unibi_clear_screen);
801 ugrid_goto(&data->grid, top, left);
802 } else {
803 cursor_goto(ui, top, 0);
804 unibi_out(ui, unibi_clr_eos);
805 }
806 } else {
807 int width = right-left;
808
809 // iterate through each line and clear
810 for (int row = top; row < bot; row++) {
811 cursor_goto(ui, row, left);
812 if (data->can_clear_attr && right == ui->width) {
813 unibi_out(ui, unibi_clr_eol);
814 } else if (data->can_erase_chars && data->can_clear_attr && width >= 5) {
815 UNIBI_SET_NUM_VAR(data->params[0], width);
816 unibi_out(ui, unibi_erase_chars);
817 } else {
818 out(ui, data->space_buf, (size_t)width);
819 grid->col += width;
820 if (data->immediate_wrap_after_last_column) {
821 // Printing at the right margin immediately advances the cursor.
822 final_column_wrap(ui);
823 }
824 }
825 }
826 }
827}
828
829static void set_scroll_region(UI *ui, int top, int bot, int left, int right)
830{
831 TUIData *data = ui->data;
832 UGrid *grid = &data->grid;
833
834 UNIBI_SET_NUM_VAR(data->params[0], top);
835 UNIBI_SET_NUM_VAR(data->params[1], bot);
836 unibi_out(ui, unibi_change_scroll_region);
837 if (left != 0 || right != ui->width - 1) {
838 unibi_out_ext(ui, data->unibi_ext.enable_lr_margin);
839 if (data->can_set_lr_margin) {
840 UNIBI_SET_NUM_VAR(data->params[0], left);
841 UNIBI_SET_NUM_VAR(data->params[1], right);
842 unibi_out(ui, unibi_set_lr_margin);
843 } else {
844 UNIBI_SET_NUM_VAR(data->params[0], left);
845 unibi_out(ui, unibi_set_left_margin_parm);
846 UNIBI_SET_NUM_VAR(data->params[0], right);
847 unibi_out(ui, unibi_set_right_margin_parm);
848 }
849 }
850 grid->row = -1;
851}
852
853static void reset_scroll_region(UI *ui, bool fullwidth)
854{
855 TUIData *data = ui->data;
856 UGrid *grid = &data->grid;
857
858 if (0 <= data->unibi_ext.reset_scroll_region) {
859 unibi_out_ext(ui, data->unibi_ext.reset_scroll_region);
860 } else {
861 UNIBI_SET_NUM_VAR(data->params[0], 0);
862 UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1);
863 unibi_out(ui, unibi_change_scroll_region);
864 }
865 if (!fullwidth) {
866 if (data->can_set_lr_margin) {
867 UNIBI_SET_NUM_VAR(data->params[0], 0);
868 UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1);
869 unibi_out(ui, unibi_set_lr_margin);
870 } else {
871 UNIBI_SET_NUM_VAR(data->params[0], 0);
872 unibi_out(ui, unibi_set_left_margin_parm);
873 UNIBI_SET_NUM_VAR(data->params[0], ui->width - 1);
874 unibi_out(ui, unibi_set_right_margin_parm);
875 }
876 unibi_out_ext(ui, data->unibi_ext.disable_lr_margin);
877 }
878 grid->row = -1;
879}
880
881static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height)
882{
883 TUIData *data = ui->data;
884 UGrid *grid = &data->grid;
885 ugrid_resize(grid, (int)width, (int)height);
886
887 xfree(data->space_buf);
888 data->space_buf = xmalloc((size_t)width * sizeof(*data->space_buf));
889 memset(data->space_buf, ' ', (size_t)width);
890
891 // resize might not always be followed by a clear before flush
892 // so clip the invalid region
893 for (size_t i = 0; i < kv_size(data->invalid_regions); i++) {
894 Rect *r = &kv_A(data->invalid_regions, i);
895 r->bot = MIN(r->bot, grid->height);
896 r->right = MIN(r->right, grid->width);
897 }
898
899 if (!got_winch && (!data->is_starting || did_user_set_dimensions)) {
900 // Resize the _host_ terminal.
901 UNIBI_SET_NUM_VAR(data->params[0], (int)height);
902 UNIBI_SET_NUM_VAR(data->params[1], (int)width);
903 unibi_out_ext(ui, data->unibi_ext.resize_screen);
904 // DECSLPP does not reset the scroll region.
905 if (data->scroll_region_is_full_screen) {
906 reset_scroll_region(ui, ui->width == grid->width);
907 }
908 } else { // Already handled the SIGWINCH signal; avoid double-resize.
909 got_winch = false;
910 grid->row = -1;
911 }
912}
913
914static void tui_grid_clear(UI *ui, Integer g)
915{
916 TUIData *data = ui->data;
917 UGrid *grid = &data->grid;
918 ugrid_clear(grid);
919 kv_size(data->invalid_regions) = 0;
920 clear_region(ui, 0, grid->height, 0, grid->width, 0);
921}
922
923static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col)
924{
925 TUIData *data = ui->data;
926
927 // cursor position is validated in tui_flush
928 data->row = (int)row;
929 data->col = (int)col;
930}
931
932CursorShape tui_cursor_decode_shape(const char *shape_str)
933{
934 CursorShape shape;
935 if (strequal(shape_str, "block")) {
936 shape = SHAPE_BLOCK;
937 } else if (strequal(shape_str, "vertical")) {
938 shape = SHAPE_VER;
939 } else if (strequal(shape_str, "horizontal")) {
940 shape = SHAPE_HOR;
941 } else {
942 WLOG("Unknown shape value '%s'", shape_str);
943 shape = SHAPE_BLOCK;
944 }
945 return shape;
946}
947
948static cursorentry_T decode_cursor_entry(Dictionary args)
949{
950 cursorentry_T r = shape_table[0];
951
952 for (size_t i = 0; i < args.size; i++) {
953 char *key = args.items[i].key.data;
954 Object value = args.items[i].value;
955
956 if (strequal(key, "cursor_shape")) {
957 r.shape = tui_cursor_decode_shape(args.items[i].value.data.string.data);
958 } else if (strequal(key, "blinkon")) {
959 r.blinkon = (int)value.data.integer;
960 } else if (strequal(key, "blinkoff")) {
961 r.blinkoff = (int)value.data.integer;
962 } else if (strequal(key, "attr_id")) {
963 r.id = (int)value.data.integer;
964 }
965 }
966 return r;
967}
968
969static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args)
970{
971 cursor_style_enabled = guicursor_enabled;
972 if (!guicursor_enabled) {
973 return; // Do not send cursor style control codes.
974 }
975 TUIData *data = ui->data;
976
977 assert(args.size);
978
979 // cursor style entries as defined by `shape_table`.
980 for (size_t i = 0; i < args.size; i++) {
981 assert(args.items[i].type == kObjectTypeDictionary);
982 cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary);
983 data->cursor_shapes[i] = r;
984 }
985
986 tui_set_mode(ui, data->showing_mode);
987}
988
989static void tui_update_menu(UI *ui)
990{
991 // Do nothing; menus are for GUI only
992}
993
994static void tui_busy_start(UI *ui)
995{
996 ((TUIData *)ui->data)->busy = true;
997}
998
999static void tui_busy_stop(UI *ui)
1000{
1001 ((TUIData *)ui->data)->busy = false;
1002}
1003
1004static void tui_mouse_on(UI *ui)
1005{
1006 TUIData *data = ui->data;
1007 if (!data->mouse_enabled) {
1008 unibi_out_ext(ui, data->unibi_ext.enable_mouse);
1009 data->mouse_enabled = true;
1010 }
1011}
1012
1013static void tui_mouse_off(UI *ui)
1014{
1015 TUIData *data = ui->data;
1016 if (data->mouse_enabled) {
1017 unibi_out_ext(ui, data->unibi_ext.disable_mouse);
1018 data->mouse_enabled = false;
1019 }
1020}
1021
1022static void tui_set_mode(UI *ui, ModeShape mode)
1023{
1024 if (!cursor_style_enabled) {
1025 return;
1026 }
1027 TUIData *data = ui->data;
1028 cursorentry_T c = data->cursor_shapes[mode];
1029
1030 if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) {
1031 HlAttrs aep = kv_A(data->attrs, c.id);
1032 if (aep.rgb_ae_attr & HL_INVERSE) {
1033 // We interpret "inverse" as "default" (no termcode for "inverse"...).
1034 // Hopefully the user's default cursor color is inverse.
1035 unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
1036 } else {
1037 UNIBI_SET_NUM_VAR(data->params[0], aep.rgb_bg_color);
1038 unibi_out_ext(ui, data->unibi_ext.set_cursor_color);
1039 data->cursor_color_changed = true;
1040 }
1041 } else if (c.id == 0) {
1042 // No cursor color for this mode; reset to default.
1043 unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
1044 }
1045
1046 int shape;
1047 switch (c.shape) {
1048 default: abort(); break;
1049 case SHAPE_BLOCK: shape = 1; break;
1050 case SHAPE_HOR: shape = 3; break;
1051 case SHAPE_VER: shape = 5; break;
1052 }
1053 UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0));
1054 unibi_out_ext(ui, data->unibi_ext.set_cursor_style);
1055}
1056
1057/// @param mode editor mode
1058static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
1059{
1060 TUIData *data = ui->data;
1061 tui_set_mode(ui, (ModeShape)mode_idx);
1062 data->is_starting = false; // mode entered, no longer starting
1063 data->showing_mode = (ModeShape)mode_idx;
1064}
1065
1066static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow,
1067 Integer startcol, Integer endcol,
1068 Integer rows, Integer cols)
1069{
1070 (void)cols; // unused
1071 TUIData *data = ui->data;
1072 UGrid *grid = &data->grid;
1073 int top = (int)startrow, bot = (int)endrow-1;
1074 int left = (int)startcol, right = (int)endcol-1;
1075
1076 bool fullwidth = left == 0 && right == ui->width-1;
1077 data->scroll_region_is_full_screen = fullwidth
1078 && top == 0 && bot == ui->height-1;
1079
1080 ugrid_scroll(grid, top, bot, left, right, (int)rows);
1081
1082 bool can_scroll = data->can_scroll
1083 && (data->scroll_region_is_full_screen
1084 || (data->can_change_scroll_region
1085 && ((left == 0 && right == ui->width - 1)
1086 || data->can_set_lr_margin
1087 || data->can_set_left_right_margin)));
1088
1089 if (can_scroll) {
1090 // Change terminal scroll region and move cursor to the top
1091 if (!data->scroll_region_is_full_screen) {
1092 set_scroll_region(ui, top, bot, left, right);
1093 }
1094 cursor_goto(ui, top, left);
1095
1096 if (rows > 0) {
1097 if (rows == 1) {
1098 unibi_out(ui, unibi_delete_line);
1099 } else {
1100 UNIBI_SET_NUM_VAR(data->params[0], (int)rows);
1101 unibi_out(ui, unibi_parm_delete_line);
1102 }
1103 } else {
1104 if (rows == -1) {
1105 unibi_out(ui, unibi_insert_line);
1106 } else {
1107 UNIBI_SET_NUM_VAR(data->params[0], -(int)rows);
1108 unibi_out(ui, unibi_parm_insert_line);
1109 }
1110 }
1111
1112 // Restore terminal scroll region and cursor
1113 if (!data->scroll_region_is_full_screen) {
1114 reset_scroll_region(ui, fullwidth);
1115 }
1116 } else {
1117 // Mark the moved region as invalid for redrawing later
1118 if (rows > 0) {
1119 endrow = endrow - rows;
1120 } else {
1121 startrow = startrow - rows;
1122 }
1123 invalidate(ui, (int)startrow, (int)endrow, (int)startcol, (int)endcol);
1124 }
1125}
1126
1127static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs,
1128 HlAttrs cterm_attrs, Array info)
1129{
1130 TUIData *data = ui->data;
1131 kv_a(data->attrs, (size_t)id) = attrs;
1132}
1133
1134static void tui_bell(UI *ui)
1135{
1136 unibi_out(ui, unibi_bell);
1137}
1138
1139static void tui_visual_bell(UI *ui)
1140{
1141 unibi_out(ui, unibi_flash_screen);
1142}
1143
1144static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg,
1145 Integer rgb_sp,
1146 Integer cterm_fg, Integer cterm_bg)
1147{
1148 TUIData *data = ui->data;
1149
1150 data->clear_attrs.rgb_fg_color = (int)rgb_fg;
1151 data->clear_attrs.rgb_bg_color = (int)rgb_bg;
1152 data->clear_attrs.rgb_sp_color = (int)rgb_sp;
1153 data->clear_attrs.cterm_fg_color = (int)cterm_fg;
1154 data->clear_attrs.cterm_bg_color = (int)cterm_bg;
1155
1156 data->print_attr_id = -1;
1157 invalidate(ui, 0, data->grid.height, 0, data->grid.width);
1158}
1159
1160static void tui_flush(UI *ui)
1161{
1162 TUIData *data = ui->data;
1163 UGrid *grid = &data->grid;
1164
1165 size_t nrevents = loop_size(data->loop);
1166 if (nrevents > TOO_MANY_EVENTS) {
1167 WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents);
1168 // Back-pressure: UI events may accumulate much faster than the terminal
1169 // device can serve them. Even if SIGINT/CTRL-C is received, user must still
1170 // wait for the TUI event-queue to drain, and if there are ~millions of
1171 // events in the queue, it could take hours. Clearing the queue allows the
1172 // UI to recover. #1234 #5396
1173 loop_purge(data->loop);
1174 tui_busy_stop(ui); // avoid hidden cursor
1175 }
1176
1177 while (kv_size(data->invalid_regions)) {
1178 Rect r = kv_pop(data->invalid_regions);
1179 assert(r.bot <= grid->height && r.right <= grid->width);
1180
1181 for (int row = r.top; row < r.bot; row++) {
1182 int clear_attr = grid->cells[row][r.right-1].attr;
1183 int clear_col;
1184 for (clear_col = r.right; clear_col > 0; clear_col--) {
1185 UCell *cell = &grid->cells[row][clear_col-1];
1186 if (!(cell->data[0] == ' ' && cell->data[1] == NUL
1187 && cell->attr == clear_attr)) {
1188 break;
1189 }
1190 }
1191
1192 UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
1193 cursor_goto(ui, row, curcol);
1194 print_cell(ui, cell);
1195 });
1196 if (clear_col < r.right) {
1197 clear_region(ui, row, row+1, clear_col, r.right, clear_attr);
1198 }
1199 }
1200 }
1201
1202 cursor_goto(ui, data->row, data->col);
1203
1204 flush_buf(ui);
1205}
1206
1207/// Dumps termcap info to the messages area, if 'verbose' >= 3.
1208static void show_termcap_event(void **argv)
1209{
1210 if (p_verbose < 3) {
1211 return;
1212 }
1213 const unibi_term *const ut = argv[0];
1214 if (!ut) {
1215 abort();
1216 }
1217 verbose_enter();
1218 // XXX: (future) if unibi_term is modified (e.g. after a terminal
1219 // query-response) this is a race condition.
1220 terminfo_info_msg(ut);
1221 verbose_leave();
1222 verbose_stop(); // flush now
1223}
1224
1225#ifdef UNIX
1226static void suspend_event(void **argv)
1227{
1228 UI *ui = argv[0];
1229 TUIData *data = ui->data;
1230 bool enable_mouse = data->mouse_enabled;
1231 tui_terminal_stop(ui);
1232 data->cont_received = false;
1233 stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
1234 kill(0, SIGTSTP);
1235 while (!data->cont_received) {
1236 // poll the event loop until SIGCONT is received
1237 loop_poll_events(data->loop, -1);
1238 }
1239 tui_terminal_start(ui);
1240 tui_terminal_after_startup(ui);
1241 if (enable_mouse) {
1242 tui_mouse_on(ui);
1243 }
1244 stream_set_blocking(input_global_fd(), false); // libuv expects this
1245 // resume the main thread
1246 CONTINUE(data->bridge);
1247}
1248#endif
1249
1250static void tui_suspend(UI *ui)
1251{
1252#ifdef UNIX
1253 TUIData *data = ui->data;
1254 // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
1255 // before continuing. This is done in another callback to avoid
1256 // loop_poll_events recursion
1257 multiqueue_put_event(data->loop->fast_events,
1258 event_create(suspend_event, 1, ui));
1259#endif
1260}
1261
1262static void tui_set_title(UI *ui, String title)
1263{
1264 TUIData *data = ui->data;
1265 if (!(title.data && unibi_get_str(data->ut, unibi_to_status_line)
1266 && unibi_get_str(data->ut, unibi_from_status_line))) {
1267 return;
1268 }
1269 unibi_out(ui, unibi_to_status_line);
1270 out(ui, title.data, title.size);
1271 unibi_out(ui, unibi_from_status_line);
1272}
1273
1274static void tui_set_icon(UI *ui, String icon)
1275{
1276}
1277
1278static void tui_option_set(UI *ui, String name, Object value)
1279{
1280 TUIData *data = ui->data;
1281 if (strequal(name.data, "termguicolors")) {
1282 ui->rgb = value.data.boolean;
1283
1284 data->print_attr_id = -1;
1285 invalidate(ui, 0, data->grid.height, 0, data->grid.width);
1286 }
1287}
1288
1289static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
1290 Integer endcol, Integer clearcol, Integer clearattr,
1291 LineFlags flags, const schar_T *chunk,
1292 const sattr_T *attrs)
1293{
1294 TUIData *data = ui->data;
1295 UGrid *grid = &data->grid;
1296 for (Integer c = startcol; c < endcol; c++) {
1297 memcpy(grid->cells[linerow][c].data, chunk[c-startcol], sizeof(schar_T));
1298 assert((size_t)attrs[c-startcol] < kv_size(data->attrs));
1299 grid->cells[linerow][c].attr = attrs[c-startcol];
1300 }
1301 UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
1302 cursor_goto(ui, (int)linerow, curcol);
1303 print_cell(ui, cell);
1304 });
1305
1306 if (clearcol > endcol) {
1307 ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol,
1308 (sattr_T)clearattr);
1309 clear_region(ui, (int)linerow, (int)linerow+1, (int)endcol, (int)clearcol,
1310 (int)clearattr);
1311 }
1312
1313 if (flags & kLineFlagWrap && ui->width == grid->width
1314 && linerow + 1 < grid->height) {
1315 // Only do line wrapping if the grid width is equal to the terminal
1316 // width and the line continuation is within the grid.
1317
1318 if (endcol != grid->width) {
1319 // Print the last char of the row, if we haven't already done so.
1320 int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
1321 cursor_goto(ui, (int)linerow, grid->width - size);
1322 print_cell(ui, &grid->cells[linerow][grid->width - size]);
1323 }
1324
1325 // Wrap the cursor over to the next line. The next line will be
1326 // printed immediately without an intervening newline.
1327 final_column_wrap(ui);
1328 }
1329}
1330
1331static void invalidate(UI *ui, int top, int bot, int left, int right)
1332{
1333 TUIData *data = ui->data;
1334 Rect *intersects = NULL;
1335
1336 for (size_t i = 0; i < kv_size(data->invalid_regions); i++) {
1337 Rect *r = &kv_A(data->invalid_regions, i);
1338 // adjacent regions are treated as overlapping
1339 if (!(top > r->bot || bot < r->top)
1340 && !(left > r->right || right < r->left)) {
1341 intersects = r;
1342 break;
1343 }
1344 }
1345
1346 if (intersects) {
1347 // If top/bot/left/right intersects with a invalid rect, we replace it
1348 // by the union
1349 intersects->top = MIN(top, intersects->top);
1350 intersects->bot = MAX(bot, intersects->bot);
1351 intersects->left = MIN(left, intersects->left);
1352 intersects->right = MAX(right, intersects->right);
1353 } else {
1354 // Else just add a new entry;
1355 kv_push(data->invalid_regions, ((Rect) { top, bot, left, right }));
1356 }
1357}
1358
1359/// Tries to get the user's wanted dimensions (columns and rows) for the entire
1360/// application (i.e., the host terminal).
1361static void tui_guess_size(UI *ui)
1362{
1363 TUIData *data = ui->data;
1364 int width = 0, height = 0;
1365
1366 // 1 - look for non-default 'columns' and 'lines' options during startup
1367 if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) {
1368 did_user_set_dimensions = true;
1369 assert(Columns >= INT_MIN && Columns <= INT_MAX);
1370 assert(Rows >= INT_MIN && Rows <= INT_MAX);
1371 width = (int)Columns;
1372 height = (int)Rows;
1373 goto end;
1374 }
1375
1376 // 2 - try from a system call(ioctl/TIOCGWINSZ on unix)
1377 if (data->out_isatty
1378 && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) {
1379 goto end;
1380 }
1381
1382 // 3 - use $LINES/$COLUMNS if available
1383 const char *val;
1384 int advance;
1385 if ((val = os_getenv("LINES"))
1386 && sscanf(val, "%d%n", &height, &advance) != EOF && advance
1387 && (val = os_getenv("COLUMNS"))
1388 && sscanf(val, "%d%n", &width, &advance) != EOF && advance) {
1389 goto end;
1390 }
1391
1392 // 4 - read from terminfo if available
1393 height = unibi_get_num(data->ut, unibi_lines);
1394 width = unibi_get_num(data->ut, unibi_columns);
1395
1396end:
1397 if (width <= 0 || height <= 0) {
1398 // use the defaults
1399 width = DFLT_COLS;
1400 height = DFLT_ROWS;
1401 }
1402
1403 data->bridge->bridge.width = ui->width = width;
1404 data->bridge->bridge.height = ui->height = height;
1405}
1406
1407static void unibi_goto(UI *ui, int row, int col)
1408{
1409 TUIData *data = ui->data;
1410 UNIBI_SET_NUM_VAR(data->params[0], row);
1411 UNIBI_SET_NUM_VAR(data->params[1], col);
1412 unibi_out(ui, unibi_cursor_address);
1413}
1414
1415#define UNIBI_OUT(fn) \
1416 do { \
1417 TUIData *data = ui->data; \
1418 const char *str = NULL; \
1419 if (unibi_index >= 0) { \
1420 str = fn(data->ut, (unsigned)unibi_index); \
1421 } \
1422 if (str) { \
1423 unibi_var_t vars[26 + 26]; \
1424 size_t orig_pos = data->bufpos; \
1425 \
1426 memset(&vars, 0, sizeof(vars)); \
1427 data->cork = true; \
1428retry: \
1429 unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \
1430 if (data->overflow) { \
1431 data->bufpos = orig_pos; \
1432 flush_buf(ui); \
1433 goto retry; \
1434 } \
1435 data->cork = false; \
1436 } \
1437 } while (0)
1438static void unibi_out(UI *ui, int unibi_index)
1439{
1440 UNIBI_OUT(unibi_get_str);
1441}
1442static void unibi_out_ext(UI *ui, int unibi_index)
1443{
1444 UNIBI_OUT(unibi_get_ext_str);
1445}
1446#undef UNIBI_OUT
1447
1448static void out(void *ctx, const char *str, size_t len)
1449{
1450 UI *ui = ctx;
1451 TUIData *data = ui->data;
1452 size_t available = sizeof(data->buf) - data->bufpos;
1453
1454 if (data->cork && data->overflow) {
1455 return;
1456 }
1457
1458 if (len > available) {
1459 if (data->cork) {
1460 data->overflow = true;
1461 return;
1462 } else {
1463 flush_buf(ui);
1464 }
1465 }
1466
1467 memcpy(data->buf + data->bufpos, str, len);
1468 data->bufpos += len;
1469}
1470
1471static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str,
1472 const char *val)
1473{
1474 if (!unibi_get_str(ut, str)) {
1475 unibi_set_str(ut, str, val);
1476 }
1477}
1478
1479static int unibi_find_ext_str(unibi_term *ut, const char *name)
1480{
1481 size_t max = unibi_count_ext_str(ut);
1482 for (size_t i = 0; i < max; i++) {
1483 const char * n = unibi_get_ext_str_name(ut, i);
1484 if (n && 0 == strcmp(n, name)) {
1485 return (int)i;
1486 }
1487 }
1488 return -1;
1489}
1490
1491static int unibi_find_ext_bool(unibi_term *ut, const char *name)
1492{
1493 size_t max = unibi_count_ext_bool(ut);
1494 for (size_t i = 0; i < max; i++) {
1495 const char * n = unibi_get_ext_bool_name(ut, i);
1496 if (n && 0 == strcmp(n, name)) {
1497 return (int)i;
1498 }
1499 }
1500 return -1;
1501}
1502
1503/// Patches the terminfo records after loading from system or built-in db.
1504/// Several entries in terminfo are known to be deficient or outright wrong;
1505/// and several terminal emulators falsely announce incorrect terminal types.
1506static void patch_terminfo_bugs(TUIData *data, const char *term,
1507 const char *colorterm, long vte_version,
1508 long konsolev, bool iterm_env, bool nsterm)
1509{
1510 unibi_term *ut = data->ut;
1511 const char *xterm_version = os_getenv("XTERM_VERSION");
1512#if 0 // We don't need to identify this specifically, for now.
1513 bool roxterm = !!os_getenv("ROXTERM_ID");
1514#endif
1515 bool xterm = terminfo_is_term_family(term, "xterm")
1516 // Treat Terminal.app as generic xterm-like, for now.
1517 || nsterm;
1518 bool kitty = terminfo_is_term_family(term, "xterm-kitty");
1519 bool linuxvt = terminfo_is_term_family(term, "linux");
1520 bool bsdvt = terminfo_is_bsd_console(term);
1521 bool rxvt = terminfo_is_term_family(term, "rxvt");
1522 bool teraterm = terminfo_is_term_family(term, "teraterm");
1523 bool putty = terminfo_is_term_family(term, "putty");
1524 bool screen = terminfo_is_term_family(term, "screen");
1525 bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX");
1526 bool st = terminfo_is_term_family(term, "st");
1527 bool gnome = terminfo_is_term_family(term, "gnome")
1528 || terminfo_is_term_family(term, "vte");
1529 bool iterm = terminfo_is_term_family(term, "iterm")
1530 || terminfo_is_term_family(term, "iterm2")
1531 || terminfo_is_term_family(term, "iTerm.app")
1532 || terminfo_is_term_family(term, "iTerm2.app");
1533 bool alacritty = terminfo_is_term_family(term, "alacritty");
1534 // None of the following work over SSH; see :help TERM .
1535 bool iterm_pretending_xterm = xterm && iterm_env;
1536 bool gnome_pretending_xterm = xterm && colorterm
1537 && strstr(colorterm, "gnome-terminal");
1538 bool mate_pretending_xterm = xterm && colorterm
1539 && strstr(colorterm, "mate-terminal");
1540 bool true_xterm = xterm && !!xterm_version && !bsdvt;
1541 bool cygwin = terminfo_is_term_family(term, "cygwin");
1542
1543 char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal);
1544 if (fix_normal) {
1545 if (STARTS_WITH(fix_normal, "\x1b[?12l")) {
1546 // terminfo typically includes DECRST 12 as part of setting up the
1547 // normal cursor, which interferes with the user's control via
1548 // set_cursor_style. When DECRST 12 is present, skip over it, but honor
1549 // the rest of the cnorm setting.
1550 fix_normal += sizeof "\x1b[?12l" - 1;
1551 unibi_set_str(ut, unibi_cursor_normal, fix_normal);
1552 }
1553 if (linuxvt
1554 && strlen(fix_normal) >= (sizeof LINUXSET0C - 1)
1555 && !memcmp(strchr(fix_normal, 0) - (sizeof LINUXSET0C - 1),
1556 LINUXSET0C, sizeof LINUXSET0C - 1)) {
1557 // The Linux terminfo entry similarly includes a Linux-idiosyncractic
1558 // cursor shape reset in cnorm, which similarly interferes with
1559 // set_cursor_style.
1560 fix_normal[strlen(fix_normal) - (sizeof LINUXSET0C - 1)] = 0;
1561 }
1562 }
1563 char *fix_invisible = (char *)unibi_get_str(ut, unibi_cursor_invisible);
1564 if (fix_invisible) {
1565 if (linuxvt
1566 && strlen(fix_invisible) >= (sizeof LINUXSET1C - 1)
1567 && !memcmp(strchr(fix_invisible, 0) - (sizeof LINUXSET1C - 1),
1568 LINUXSET1C, sizeof LINUXSET1C - 1)) {
1569 // The Linux terminfo entry similarly includes a Linux-idiosyncractic
1570 // cursor shape reset in cinvis, which similarly interferes with
1571 // set_cursor_style.
1572 fix_invisible[strlen(fix_invisible) - (sizeof LINUXSET1C - 1)] = 0;
1573 }
1574 }
1575
1576 if (tmux || screen || kitty) {
1577 // Disable BCE in some cases we know it is not working. #8806
1578 unibi_set_bool(ut, unibi_back_color_erase, false);
1579 }
1580
1581 if (xterm) {
1582 // Termit, LXTerminal, GTKTerm2, GNOME Terminal, MATE Terminal, roxterm,
1583 // and EvilVTE falsely claim to be xterm and do not support important xterm
1584 // control sequences that we use. In an ideal world, these would have
1585 // their own terminal types and terminfo entries, like PuTTY does, and not
1586 // claim to be xterm. Or they would mimic xterm properly enough to be
1587 // treatable as xterm.
1588
1589 // 2017-04 terminfo.src lacks these. Xterm-likes have them.
1590 unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;");
1591 unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
1592 unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr");
1593 unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
1594 unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m");
1595
1596 if (true_xterm) {
1597 // 2017-04 terminfo.src lacks these. genuine Xterm has them.
1598 unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds");
1599 unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds");
1600 unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds");
1601 }
1602
1603#ifdef WIN32
1604 // XXX: workaround libuv implicit LF => CRLF conversion. #10558
1605 unibi_set_str(ut, unibi_cursor_down, "\x1b[B");
1606#endif
1607 } else if (rxvt) {
1608 // 2017-04 terminfo.src lacks these. Unicode rxvt has them.
1609 unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
1610 unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m");
1611 unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]2");
1612 unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
1613 // 2017-04 terminfo.src has older control sequences.
1614 unibi_set_str(ut, unibi_enter_ca_mode, "\x1b[?1049h");
1615 unibi_set_str(ut, unibi_exit_ca_mode, "\x1b[?1049l");
1616 } else if (screen) {
1617 // per the screen manual; 2017-04 terminfo.src lacks these.
1618 unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
1619 unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
1620 } else if (tmux) {
1621 unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
1622 unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
1623 unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
1624 unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m");
1625 } else if (terminfo_is_term_family(term, "interix")) {
1626 // 2017-04 terminfo.src lacks this.
1627 unibi_set_if_empty(ut, unibi_carriage_return, "\x0d");
1628 } else if (linuxvt) {
1629 unibi_set_if_empty(ut, unibi_parm_up_cursor, "\x1b[%p1%dA");
1630 unibi_set_if_empty(ut, unibi_parm_down_cursor, "\x1b[%p1%dB");
1631 unibi_set_if_empty(ut, unibi_parm_right_cursor, "\x1b[%p1%dC");
1632 unibi_set_if_empty(ut, unibi_parm_left_cursor, "\x1b[%p1%dD");
1633 } else if (putty) {
1634 // No bugs in the vanilla terminfo for our purposes.
1635 } else if (iterm) {
1636 // 2017-04 terminfo.src has older control sequences.
1637 unibi_set_str(ut, unibi_enter_ca_mode, "\x1b[?1049h");
1638 unibi_set_str(ut, unibi_exit_ca_mode, "\x1b[?1049l");
1639 // 2017-04 terminfo.src lacks these.
1640 unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr");
1641 unibi_set_if_empty(ut, unibi_orig_pair, "\x1b[39;49m");
1642 unibi_set_if_empty(ut, unibi_enter_dim_mode, "\x1b[2m");
1643 unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
1644 unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m");
1645 unibi_set_if_empty(ut, unibi_exit_underline_mode, "\x1b[24m");
1646 unibi_set_if_empty(ut, unibi_exit_standout_mode, "\x1b[27m");
1647 } else if (st) {
1648 // No bugs in the vanilla terminfo for our purposes.
1649 }
1650
1651// At this time (2017-07-12) it seems like all terminals that support 256
1652// color codes can use semicolons in the terminal code and be fine.
1653// However, this is not correct according to the spec. So to reward those
1654// terminals that also support colons, we output the code that way on these
1655// specific ones.
1656
1657// using colons like ISO 8613-6:1994/ITU T.416:1993 says.
1658#define XTERM_SETAF_256_COLON \
1659 "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38:5:%p1%d%;m"
1660#define XTERM_SETAB_256_COLON \
1661 "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48:5:%p1%d%;m"
1662
1663#define XTERM_SETAF_256 \
1664 "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
1665#define XTERM_SETAB_256 \
1666 "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m"
1667#define XTERM_SETAF_16 \
1668 "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e39%;m"
1669#define XTERM_SETAB_16 \
1670 "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
1671
1672 data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
1673 "\x1b]11;?\x07");
1674
1675 // Terminals with 256-colour SGR support despite what terminfo says.
1676 if (unibi_get_num(ut, unibi_max_colors) < 256) {
1677 // See http://fedoraproject.org/wiki/Features/256_Color_Terminals
1678 if (true_xterm || iterm || iterm_pretending_xterm) {
1679 unibi_set_num(ut, unibi_max_colors, 256);
1680 unibi_set_str(ut, unibi_set_a_foreground, XTERM_SETAF_256_COLON);
1681 unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB_256_COLON);
1682 } else if (konsolev || xterm || gnome || rxvt || st || putty
1683 || linuxvt // Linux 4.8+ supports 256-colour SGR.
1684 || mate_pretending_xterm || gnome_pretending_xterm
1685 || tmux
1686 || (colorterm && strstr(colorterm, "256"))
1687 || (term && strstr(term, "256"))) {
1688 unibi_set_num(ut, unibi_max_colors, 256);
1689 unibi_set_str(ut, unibi_set_a_foreground, XTERM_SETAF_256);
1690 unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB_256);
1691 }
1692 }
1693 // Terminals with 16-colour SGR support despite what terminfo says.
1694 if (unibi_get_num(ut, unibi_max_colors) < 16) {
1695 if (colorterm) {
1696 unibi_set_num(ut, unibi_max_colors, 16);
1697 unibi_set_if_empty(ut, unibi_set_a_foreground, XTERM_SETAF_16);
1698 unibi_set_if_empty(ut, unibi_set_a_background, XTERM_SETAB_16);
1699 }
1700 }
1701
1702 // Blacklist of terminals that cannot be trusted to report DECSCUSR support.
1703 if (!(st || (vte_version != 0 && vte_version < 3900) || konsolev)) {
1704 data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se");
1705 data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss");
1706 }
1707
1708 // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So
1709 // adding them to terminal types, that have such control sequences but lack
1710 // the correct terminfo entries, is a fixup, not an augmentation.
1711 if (-1 == data->unibi_ext.set_cursor_style) {
1712 // DECSCUSR (cursor shape) is widely supported.
1713 // https://github.com/gnachman/iTerm2/pull/92
1714 if ((!bsdvt && (!konsolev || konsolev >= 180770))
1715 && ((xterm && !vte_version) // anything claiming xterm compat
1716 // per MinTTY 0.4.3-1 release notes from 2009
1717 || putty
1718 // per https://bugzilla.gnome.org/show_bug.cgi?id=720821
1719 || (vte_version >= 3900)
1720 || (konsolev >= 180770) // #9364
1721 || tmux // per tmux manual page
1722 // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html
1723 || screen
1724 || st // #7641
1725 || rxvt // per command.C
1726 // per analysis of VT100Terminal.m
1727 || iterm || iterm_pretending_xterm
1728 || teraterm // per TeraTerm "Supported Control Functions" doco
1729 || alacritty // https://github.com/jwilm/alacritty/pull/608
1730 || cygwin
1731 // Some linux-type terminals implement the xterm extension.
1732 // Example: console-terminal-emulator from the nosh toolset.
1733 || (linuxvt
1734 && (xterm_version || (vte_version > 0) || colorterm)))) {
1735 data->unibi_ext.set_cursor_style =
1736 (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");
1737 if (-1 == data->unibi_ext.reset_cursor_style) {
1738 data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
1739 "");
1740 }
1741 unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
1742 "\x1b[ q");
1743 } else if (linuxvt) {
1744 // Linux uses an idiosyncratic escape code to set the cursor shape and
1745 // does not support DECSCUSR.
1746 // See http://linuxgazette.net/137/anonymous.html for more info
1747 data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
1748 "\x1b[?"
1749 "%?"
1750 // The parameter passed to Ss is the DECSCUSR parameter, so the
1751 // terminal capability has to translate into the Linux idiosyncratic
1752 // parameter.
1753 //
1754 // linuxvt only supports block and underline. It is also only
1755 // possible to have a steady block (no steady underline)
1756 "%p1%{2}%<" "%t%{8}" // blink block
1757 "%e%p1%{2}%=" "%t%{112}" // steady block
1758 "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block)
1759 "%e%p1%{4}%=" "%t%{4}" // steady underline
1760 "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
1761 "%e%p1%{6}%=" "%t%{2}" // steady bar
1762 "%e%{0}" // anything else
1763 "%;" "%dc");
1764 if (-1 == data->unibi_ext.reset_cursor_style) {
1765 data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
1766 "");
1767 }
1768 unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
1769 "\x1b[?c");
1770 } else if (konsolev > 0 && konsolev < 180770) {
1771 // Konsole before version 18.07.70: set up a nonce profile. This has
1772 // side-effects on temporary font resizing. #6798
1773 data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
1774 TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?"
1775 "%p1%{3}%<" "%t%{0}" // block
1776 "%e%p1%{5}%<" "%t%{2}" // underline
1777 "%e%{1}" // everything else is bar
1778 "%;%d;BlinkingCursorEnabled=%?"
1779 "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special,
1780 "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag.
1781 "%;%d\x07"));
1782 if (-1 == data->unibi_ext.reset_cursor_style) {
1783 data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
1784 "");
1785 }
1786 unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
1787 "\x1b]50;\x07");
1788 }
1789 }
1790}
1791
1792/// This adds stuff that is not in standard terminfo as extended unibilium
1793/// capabilities.
1794static void augment_terminfo(TUIData *data, const char *term,
1795 const char *colorterm, long vte_version,
1796 long konsolev, bool iterm_env, bool nsterm)
1797{
1798 unibi_term *ut = data->ut;
1799 bool xterm = terminfo_is_term_family(term, "xterm")
1800 // Treat Terminal.app as generic xterm-like, for now.
1801 || nsterm;
1802 bool bsdvt = terminfo_is_bsd_console(term);
1803 bool dtterm = terminfo_is_term_family(term, "dtterm");
1804 bool rxvt = terminfo_is_term_family(term, "rxvt");
1805 bool teraterm = terminfo_is_term_family(term, "teraterm");
1806 bool putty = terminfo_is_term_family(term, "putty");
1807 bool screen = terminfo_is_term_family(term, "screen");
1808 bool tmux = terminfo_is_term_family(term, "tmux") || !!os_getenv("TMUX");
1809 bool iterm = terminfo_is_term_family(term, "iterm")
1810 || terminfo_is_term_family(term, "iterm2")
1811 || terminfo_is_term_family(term, "iTerm.app")
1812 || terminfo_is_term_family(term, "iTerm2.app");
1813 bool alacritty = terminfo_is_term_family(term, "alacritty");
1814 // None of the following work over SSH; see :help TERM .
1815 bool iterm_pretending_xterm = xterm && iterm_env;
1816
1817 const char *xterm_version = os_getenv("XTERM_VERSION");
1818 bool true_xterm = xterm && !!xterm_version && !bsdvt;
1819
1820 // Only define this capability for terminal types that we know understand it.
1821 if (dtterm // originated this extension
1822 || xterm // per xterm ctlseqs doco
1823 || konsolev // per commentary in VT102Emulation.cpp
1824 || teraterm // per TeraTerm "Supported Control Functions" doco
1825 || rxvt) { // per command.C
1826 data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut,
1827 "ext.resize_screen",
1828 "\x1b[8;%p1%d;%p2%dt");
1829 }
1830 if (putty || xterm || rxvt) {
1831 data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut,
1832 "ext.reset_scroll_region",
1833 "\x1b[r");
1834 }
1835
1836 // terminfo describes strikethrough modes as rmxx/smxx with respect
1837 // to the ECMA-48 strikeout/crossed-out attributes.
1838 data->unibi_ext.enter_strikethrough_mode = (int)unibi_find_ext_str(
1839 ut, "smxx");
1840
1841 // Dickey ncurses terminfo does not include the setrgbf and setrgbb
1842 // capabilities, proposed by RĂ¼diger Sonderfeld on 2013-10-15. Adding
1843 // them here when terminfo lacks them is an augmentation, not a fixup.
1844 // https://gist.github.com/XVilka/8346728
1845
1846 // At this time (2017-07-12) it seems like all terminals that support rgb
1847 // color codes can use semicolons in the terminal code and be fine.
1848 // However, this is not correct according to the spec. So to reward those
1849 // terminals that also support colons, we output the code that way on these
1850 // specific ones.
1851
1852 // can use colons like ISO 8613-6:1994/ITU T.416:1993 says.
1853 bool has_colon_rgb = !tmux && !screen
1854 && !vte_version // VTE colon-support has a big memory leak. #7573
1855 && (iterm || iterm_pretending_xterm // per VT100Terminal.m
1856 // per http://invisible-island.net/xterm/xterm.log.html#xterm_282
1857 || true_xterm);
1858
1859 data->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf");
1860 if (-1 == data->unibi_ext.set_rgb_foreground) {
1861 if (has_colon_rgb) {
1862 data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf",
1863 "\x1b[38:2:%p1%d:%p2%d:%p3%dm");
1864 } else {
1865 data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf",
1866 "\x1b[38;2;%p1%d;%p2%d;%p3%dm");
1867 }
1868 }
1869 data->unibi_ext.set_rgb_background = unibi_find_ext_str(ut, "setrgbb");
1870 if (-1 == data->unibi_ext.set_rgb_background) {
1871 if (has_colon_rgb) {
1872 data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb",
1873 "\x1b[48:2:%p1%d:%p2%d:%p3%dm");
1874 } else {
1875 data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb",
1876 "\x1b[48;2;%p1%d;%p2%d;%p3%dm");
1877 }
1878 }
1879
1880 if (iterm || iterm_pretending_xterm) {
1881 // FIXME: Bypassing tmux like this affects the cursor colour globally, in
1882 // all panes, which is not particularly desirable. A better approach
1883 // would use a tmux control sequence and an extra if(screen) test.
1884 data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
1885 ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\"));
1886 } else if ((xterm || rxvt || alacritty)
1887 && (vte_version == 0 || vte_version >= 3900)) {
1888 // Supported in urxvt, newer VTE.
1889 data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
1890 ut, "ext.set_cursor_color", "\033]12;#%p1%06x\007");
1891 }
1892
1893 if (-1 != data->unibi_ext.set_cursor_color) {
1894 data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(
1895 ut, "ext.reset_cursor_color", "\x1b]112\x07");
1896 }
1897
1898 data->unibi_ext.save_title = (int)unibi_add_ext_str(
1899 ut, "ext.save_title", "\x1b[22;0;0t");
1900 data->unibi_ext.restore_title = (int)unibi_add_ext_str(
1901 ut, "ext.restore_title", "\x1b[23;0;0t");
1902
1903 /// Terminals usually ignore unrecognized private modes, and there is no
1904 /// known ambiguity with these. So we just set them unconditionally.
1905 data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(
1906 ut, "ext.enable_lr_margin", "\x1b[?69h");
1907 data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(
1908 ut, "ext.disable_lr_margin", "\x1b[?69l");
1909 data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(
1910 ut, "ext.enable_bpaste", "\x1b[?2004h");
1911 data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(
1912 ut, "ext.disable_bpaste", "\x1b[?2004l");
1913 // For urxvt send BOTH xterm and old urxvt sequences. #8695
1914 data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(
1915 ut, "ext.enable_focus",
1916 rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
1917 data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(
1918 ut, "ext.disable_focus",
1919 rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
1920 data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(
1921 ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h");
1922 data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(
1923 ut, "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l");
1924
1925 // Extended underline.
1926 // terminfo will have Smulx for this (but no support for colors yet).
1927 data->unibi_ext.set_underline_style = unibi_find_ext_str(ut, "Smulx");
1928 if (data->unibi_ext.set_underline_style == -1) {
1929 int ext_bool_Su = unibi_find_ext_bool(ut, "Su"); // used by kitty
1930 if (vte_version >= 5102
1931 || (ext_bool_Su != -1
1932 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su))) {
1933 data->unibi_ext.set_underline_style = (int)unibi_add_ext_str(
1934 ut, "ext.set_underline_style", "\x1b[4:%p1%dm");
1935 }
1936 }
1937 if (data->unibi_ext.set_underline_style != -1) {
1938 // Only support colon syntax. #9270
1939 data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(
1940 ut, "ext.set_underline_color", "\x1b[58:2::%p1%d:%p2%d:%p3%dm");
1941 }
1942}
1943
1944static void flush_buf(UI *ui)
1945{
1946 uv_write_t req;
1947 uv_buf_t bufs[3];
1948 uv_buf_t *bufp = &bufs[0];
1949 TUIData *data = ui->data;
1950
1951 if (data->bufpos <= 0 && data->busy == data->is_invisible) {
1952 return;
1953 }
1954
1955 if (!data->is_invisible) {
1956 // cursor is visible. Write a "cursor invisible" command before writing the
1957 // buffer.
1958 bufp->base = data->invis;
1959 bufp->len = UV_BUF_LEN(data->invislen);
1960 bufp++;
1961 data->is_invisible = true;
1962 }
1963
1964 if (data->bufpos > 0) {
1965 bufp->base = data->buf;
1966 bufp->len = UV_BUF_LEN(data->bufpos);
1967 bufp++;
1968 }
1969
1970 if (!data->busy) {
1971 assert(data->is_invisible);
1972 // not busy and the cursor is invisible. Write a "cursor normal" command
1973 // after writing the buffer.
1974 bufp->base = data->norm;
1975 bufp->len = UV_BUF_LEN(data->normlen);
1976 bufp++;
1977 data->is_invisible = data->busy;
1978 }
1979
1980 uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
1981 bufs, (unsigned)(bufp - bufs), NULL);
1982 uv_run(&data->write_loop, UV_RUN_DEFAULT);
1983 data->bufpos = 0;
1984 data->overflow = false;
1985}
1986
1987#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
1988/// Try to get "kbs" code from stty because "the terminfo kbs entry is extremely
1989/// unreliable." (Vim, Bash, and tmux also do this.)
1990///
1991/// @see tmux/tty-keys.c fe4e9470bb504357d073320f5d305b22663ee3fd
1992/// @see https://bugzilla.redhat.com/show_bug.cgi?id=142659
1993static const char *tui_get_stty_erase(void)
1994{
1995 static char stty_erase[2] = { 0 };
1996#if defined(HAVE_TERMIOS_H)
1997 struct termios t;
1998 if (tcgetattr(input_global_fd(), &t) != -1) {
1999 stty_erase[0] = (char)t.c_cc[VERASE];
2000 stty_erase[1] = '\0';
2001 DLOG("stty/termios:erase=%s", stty_erase);
2002 }
2003#endif
2004 return stty_erase;
2005}
2006
2007/// libtermkey hook to override terminfo entries.
2008/// @see TermInput.tk_ti_hook_fn
2009static const char *tui_tk_ti_getstr(const char *name, const char *value,
2010 void *data)
2011{
2012 static const char *stty_erase = NULL;
2013 if (stty_erase == NULL) {
2014 stty_erase = tui_get_stty_erase();
2015 }
2016
2017 if (strequal(name, "key_backspace")) {
2018 DLOG("libtermkey:kbs=%s", value);
2019 if (stty_erase[0] != 0) {
2020 return stty_erase;
2021 }
2022 } else if (strequal(name, "key_dc")) {
2023 DLOG("libtermkey:kdch1=%s", value);
2024 // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>."
2025 if (value != NULL && value != (char *)-1 && strequal(stty_erase, value)) {
2026 return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR;
2027 }
2028 } else if (strequal(name, "key_mouse")) {
2029 DLOG("libtermkey:kmous=%s", value);
2030 // If key_mouse is found, libtermkey uses its terminfo driver (driver-ti.c)
2031 // for mouse input, which by accident only supports X10 protocol.
2032 // Force libtermkey to fallback to its CSI driver (driver-csi.c). #7948
2033 return NULL;
2034 }
2035
2036 return value;
2037}
2038#endif
2039