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 | |
67 | typedef struct { |
68 | int top, bot, left, right; |
69 | } Rect; |
70 | |
71 | typedef 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 | |
134 | static bool volatile got_winch = false; |
135 | static bool did_user_set_dimensions = false; |
136 | static bool cursor_style_enabled = false; |
137 | |
138 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
139 | # include "tui/tui.c.generated.h" |
140 | #endif |
141 | |
142 | |
143 | UI *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 | |
176 | static 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 | |
186 | static 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 | |
193 | static 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 | |
317 | static 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 | |
347 | static 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 | |
358 | static 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 | |
368 | static 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 | |
383 | static 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. |
390 | static bool tui_is_stopped(UI *ui) |
391 | { |
392 | return ui->data == NULL; |
393 | } |
394 | |
395 | /// Main function of the TUI thread. |
396 | static 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. |
455 | static 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 |
463 | static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) |
464 | { |
465 | ((TUIData *)data)->cont_received = true; |
466 | } |
467 | #endif |
468 | |
469 | static 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 | |
481 | static 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 | |
506 | static 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 | |
634 | static 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. |
649 | static 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 | |
666 | static 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. |
696 | static 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 | |
782 | safe_move: |
783 | unibi_goto(ui, row, col); |
784 | ugrid_goto(grid, row, col); |
785 | } |
786 | |
787 | static 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 | |
829 | static 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 | |
853 | static 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 | |
881 | static 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 | |
914 | static 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 | |
923 | static 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 | |
932 | CursorShape 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 | |
948 | static 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 | |
969 | static 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 | |
989 | static void (UI *ui) |
990 | { |
991 | // Do nothing; menus are for GUI only |
992 | } |
993 | |
994 | static void tui_busy_start(UI *ui) |
995 | { |
996 | ((TUIData *)ui->data)->busy = true; |
997 | } |
998 | |
999 | static void tui_busy_stop(UI *ui) |
1000 | { |
1001 | ((TUIData *)ui->data)->busy = false; |
1002 | } |
1003 | |
1004 | static 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 | |
1013 | static 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 | |
1022 | static 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 |
1058 | static 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 | |
1066 | static 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 | |
1127 | static 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 | |
1134 | static void tui_bell(UI *ui) |
1135 | { |
1136 | unibi_out(ui, unibi_bell); |
1137 | } |
1138 | |
1139 | static void tui_visual_bell(UI *ui) |
1140 | { |
1141 | unibi_out(ui, unibi_flash_screen); |
1142 | } |
1143 | |
1144 | static 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 | |
1160 | static 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. |
1208 | static 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 |
1226 | static 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 | |
1250 | static 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 | |
1262 | static 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 | |
1274 | static void tui_set_icon(UI *ui, String icon) |
1275 | { |
1276 | } |
1277 | |
1278 | static 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 | |
1289 | static 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 | |
1331 | static 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). |
1361 | static 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 | |
1396 | end: |
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 | |
1407 | static 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; \ |
1428 | retry: \ |
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) |
1438 | static void unibi_out(UI *ui, int unibi_index) |
1439 | { |
1440 | UNIBI_OUT(unibi_get_str); |
1441 | } |
1442 | static void unibi_out_ext(UI *ui, int unibi_index) |
1443 | { |
1444 | UNIBI_OUT(unibi_get_ext_str); |
1445 | } |
1446 | #undef UNIBI_OUT |
1447 | |
1448 | static 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 | |
1471 | static 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 | |
1479 | static 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 | |
1491 | static 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. |
1506 | static 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. |
1794 | static 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 | |
1944 | static 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 |
1993 | static 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 |
2009 | static 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 | |