diff options
Diffstat (limited to 'src/nvim/terminal.c')
-rw-r--r-- | src/nvim/terminal.c | 546 |
1 files changed, 370 insertions, 176 deletions
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index fd416b3dcc..51bf22b31c 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1,18 +1,20 @@ -// VT220/xterm-like terminal emulator implementation for nvim. Powered by -// libvterm (http://www.leonerd.org.uk/code/libvterm/). +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// VT220/xterm-like terminal emulator. +// Powered by libvterm http://www.leonerd.org.uk/code/libvterm // // libvterm is a pure C99 terminal emulation library with abstract input and // display. This means that the library needs to read data from the master fd // and feed VTerm instances, which will invoke user callbacks with screen // update instructions that must be mirrored to the real display. // -// Keys are pressed in VTerm instances by calling +// Keys are sent to VTerm instances by calling // vterm_keyboard_key/vterm_keyboard_unichar, which generates byte streams that // must be fed back to the master fd. // -// This implementation uses nvim buffers as the display mechanism for both -// the visible screen and the scrollback buffer. When focused, the window -// "pins" to the bottom of the buffer and mirrors libvterm screen state. +// Nvim buffers are used as the display mechanism for both the visible screen +// and the scrollback buffer. // // When a line becomes invisible due to a decrease in screen height or because // a line was pushed up during normal terminal output, we store the line @@ -23,18 +25,17 @@ // scrollback buffer, which is mirrored in the nvim buffer displaying lines // that were previously invisible. // -// The vterm->nvim synchronization is performed in intervals of 10 -// milliseconds. This is done to minimize screen updates when receiving large -// bursts of data. +// The vterm->nvim synchronization is performed in intervals of 10 milliseconds, +// to minimize screen updates when receiving large bursts of data. // // This module is decoupled from the processes that normally feed it data, so // it's possible to use it as a general purpose console buffer (possibly as a // log/display mechanism for nvim in the future) // -// Inspired by vimshell (http://www.wana.at/vimshell/) and -// Conque (https://code.google.com/p/conque/). Libvterm usage instructions (plus -// some extra code) were taken from -// pangoterm (http://www.leonerd.org.uk/code/pangoterm/) +// Inspired by: vimshell http://www.wana.at/vimshell +// Conque https://code.google.com/p/conque +// Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm + #include <assert.h> #include <stdio.h> #include <stdint.h> @@ -42,11 +43,13 @@ #include <vterm.h> +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/terminal.h" #include "nvim/message.h" #include "nvim/memory.h" #include "nvim/option.h" +#include "nvim/highlight.h" #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/buffer.h" @@ -59,7 +62,6 @@ #include "nvim/edit.h" #include "nvim/mouse.h" #include "nvim/memline.h" -#include "nvim/mark.h" #include "nvim/map.h" #include "nvim/misc1.h" #include "nvim/move.h" @@ -80,17 +82,15 @@ typedef struct terminal_state { Terminal *term; int save_rd; // saved value of RedrawingDisabled bool close; - bool got_bs; // if the last input was <C-\> + bool got_bsl; // if the last input was <C-\> } TerminalState; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "terminal.c.generated.h" #endif -#define SCROLLBACK_BUFFER_DEFAULT_SIZE 1000 // Delay for refreshing the terminal buffer after receiving updates from -// libvterm. This is greatly improves performance when receiving large bursts -// of data. +// libvterm. Improves performance when receiving large bursts of data. #define REFRESH_DELAY 10 static TimeWatcher refresh_timer; @@ -102,48 +102,42 @@ typedef struct { } ScrollbackLine; struct terminal { - // options passed to terminal_open - TerminalOptions opts; - // libvterm structures + TerminalOptions opts; // options passed to terminal_open VTerm *vt; VTermScreen *vts; // buffer used to: // - convert VTermScreen cell arrays into utf8 strings // - receive data from libvterm as a result of key presses. char textbuf[0x1fff]; - // Scrollback buffer storage for libvterm. - // TODO(tarruda): Use a doubly-linked list - ScrollbackLine **sb_buffer; - // number of rows pushed to sb_buffer - size_t sb_current; - // sb_buffer size; - size_t sb_size; + + ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm + size_t sb_current; // number of rows pushed to sb_buffer + size_t sb_size; // sb_buffer size // "virtual index" that points to the first sb_buffer row that we need to // push to the terminal buffer when refreshing the scrollback. When negative, // it actually points to entries that are no longer in sb_buffer (because the // window height has increased) and must be deleted from the terminal buffer int sb_pending; + // buf_T instance that acts as a "drawing surface" for libvterm // we can't store a direct reference to the buffer because the // refresh_timer_cb may be called after the buffer was freed, and there's // no way to know if the memory was reused. - uint64_t buf_handle; + handle_T buf_handle; // program exited bool closed, destroy; + // some vterm properties bool forward_mouse; - // invalid rows libvterm screen - int invalid_start, invalid_end; + int invalid_start, invalid_end; // invalid rows in libvterm screen struct { int row, col; bool visible; } cursor; - // which mouse button is pressed - int pressed_button; - // pending width/height - bool pending_resize; - // With a reference count of 0 the terminal can be freed. - size_t refcount; + int pressed_button; // which mouse button is pressed + bool pending_resize; // pending width/height + + size_t refcount; // reference count }; static VTermScreenCallbacks vterm_screen_callbacks = { @@ -166,7 +160,7 @@ void terminal_init(void) invalidated_terminals = pmap_new(ptr_t)(); time_watcher_init(&main_loop, &refresh_timer, NULL); // refresh_timer_cb will redraw the screen which can call vimscript - refresh_timer.events = queue_new_child(main_loop.events); + refresh_timer.events = multiqueue_new_child(main_loop.events); // initialize a rgb->color index map for cterm attributes(VTermScreenCell // only has RGB information and we need color indexes for terminal UIs) @@ -174,7 +168,7 @@ void terminal_init(void) VTerm *vt = vterm_new(24, 80); VTermState *state = vterm_obtain_state(vt); - for (int color_index = 0; color_index < 256; color_index++) { + for (int color_index = 255; color_index >= 0; color_index--) { VTermColor color; // Some of the default 16 colors has the same color as the later // 240 colors. To avoid collisions, we will use the custom colors @@ -187,13 +181,14 @@ void terminal_init(void) vterm_state_get_palette_color(state, color_index, &color); } map_put(int, int)(color_indexes, - RGB(color.red, color.green, color.blue), color_index + 1); + RGB_(color.red, color.green, color.blue), + color_index + 1); } VTermColor fg, bg; vterm_state_get_default_colors(state, &fg, &bg); - default_vt_fg = RGB(fg.red, fg.green, fg.blue); - default_vt_bg = RGB(bg.red, bg.green, bg.blue); + default_vt_fg = RGB_(fg.red, fg.green, fg.blue); + default_vt_bg = RGB_(bg.red, bg.green, bg.blue); default_vt_bg_rgb = bg; vterm_free(vt); } @@ -201,7 +196,7 @@ void terminal_init(void) void terminal_teardown(void) { time_watcher_stop(&refresh_timer); - queue_free(refresh_timer.events); + multiqueue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); map_free(int, int)(color_indexes); @@ -236,25 +231,26 @@ Terminal *terminal_open(TerminalOptions opts) rv->invalid_start = 0; rv->invalid_end = opts.height; refresh_screen(rv, curbuf); - set_option_value((uint8_t *)"buftype", 0, (uint8_t *)"terminal", OPT_LOCAL); - // some sane settings for terminal buffers - set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL); - set_option_value((uint8_t *)"number", false, NULL, OPT_LOCAL); - set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL); + set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666 + + // Default settings for terminal buffers + curbuf->b_p_ma = false; // 'nomodifiable' + curbuf->b_p_ul = -1; // 'undolevels' + curbuf->b_p_scbk = p_scbk; // 'scrollback' + curbuf->b_p_tw = 0; // 'textwidth' + set_option_value("wrap", false, NULL, OPT_LOCAL); + set_option_value("list", false, NULL, OPT_LOCAL); + buf_set_term_title(curbuf, (char *)curbuf->b_ffname); RESET_BINDING(curwin); - // Apply TermOpen autocmds so the user can configure the terminal + // Reset cursor in current window. + curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; + + // Apply TermOpen autocmds _before_ configuring the scrollback buffer. apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); - // Configure the scrollback buffer. Try to get the size from: - // - // - b:terminal_scrollback_buffer_size - // - g:terminal_scrollback_buffer_size - // - SCROLLBACK_BUFFER_DEFAULT_SIZE - // - // but limit to 100k. - int size = get_config_int("terminal_scrollback_buffer_size"); - rv->sb_size = size > 0 ? (size_t)size : SCROLLBACK_BUFFER_DEFAULT_SIZE; - rv->sb_size = MIN(rv->sb_size, 100000); + // Configure the scrollback buffer. + rv->sb_size = curbuf->b_p_scbk < 0 + ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk); rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); if (!true_color) { @@ -306,8 +302,16 @@ void terminal_close(Terminal *term, char *msg) } term->forward_mouse = false; - term->closed = true; + + // flush any pending changes to the buffer + if (!exiting) { + block_autocmds(); + refresh_terminal(term); + unblock_autocmds(); + } + buf_T *buf = handle_get_buffer(term->buf_handle); + term->closed = true; if (!msg || exiting) { // If no msg was given, this was called by close_buffer(buffer.c). Or if @@ -333,22 +337,22 @@ void terminal_close(Terminal *term, char *msg) void terminal_resize(Terminal *term, uint16_t width, uint16_t height) { if (term->closed) { - // will be called after exited if two windows display the same terminal and - // one of the is closed as a consequence of pressing a key. + // If two windows display the same terminal and one is closed by keypress. return; } + bool force = width == UINT16_MAX || height == UINT16_MAX; int curwidth, curheight; vterm_get_size(term->vt, &curheight, &curwidth); - if (!width) { + if (force || !width) { width = (uint16_t)curwidth; } - if (!height) { + if (force || !height) { height = (uint16_t)curheight; } - if (curheight == height && curwidth == width) { + if (!force && curheight == height && curwidth == width) { return; } @@ -356,6 +360,15 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) return; } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer && wp->w_buffer->terminal == term) { + const uint16_t win_width = + (uint16_t)(MAX(0, wp->w_width - win_col_off(wp))); + width = MAX(width, win_width); + height = (uint16_t)MAX(height, wp->w_height); + } + } + vterm_set_size(term->vt, height, width); vterm_screen_flush_damage(term->vts); term->pending_resize = true; @@ -365,26 +378,33 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) void terminal_enter(void) { buf_T *buf = curbuf; + assert(buf->terminal); // Should only be called when curbuf has a terminal. TerminalState state, *s = &state; memset(s, 0, sizeof(TerminalState)); s->term = buf->terminal; - assert(s->term && "should only be called when curbuf has a terminal"); // Ensure the terminal is properly sized. terminal_resize(s->term, 0, 0); - checkpcmark(); - setpcmark(); int save_state = State; s->save_rd = RedrawingDisabled; State = TERM_FOCUS; mapped_ctrl_c |= TERM_FOCUS; // Always map CTRL-C to avoid interrupt. RedrawingDisabled = false; - // go to the bottom when the terminal is focused - adjust_topline(s->term, buf, false); + + // Disable these options in terminal-mode. They are nonsense because cursor is + // placed at end of buffer to "follow" output. + win_T *save_curwin = curwin; + int save_w_p_cul = curwin->w_p_cul; + int save_w_p_cuc = curwin->w_p_cuc; + curwin->w_p_cul = false; + curwin->w_p_cuc = false; + + adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); showmode(); + curwin->w_redr_status = true; // For mode() in statusline. #8323 ui_busy_start(); redraw(false); @@ -394,6 +414,11 @@ void terminal_enter(void) restart_edit = 0; State = save_state; RedrawingDisabled = s->save_rd; + if (save_curwin == curwin) { // save_curwin may be invalid (window closed)! + curwin->w_p_cul = save_w_p_cul; + curwin->w_p_cuc = save_w_p_cuc; + } + // draw the unfocused cursor invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); unshowmode(true); @@ -413,14 +438,6 @@ static int terminal_execute(VimState *state, int key) TerminalState *s = (TerminalState *)state; switch (key) { - case K_FOCUSGAINED: // nvim has been given focus - apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf); - break; - - case K_FOCUSLOST: // nvim has lost focus - apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf); - break; - // Temporary fix until paste events gets implemented case K_PASTE: break; @@ -444,7 +461,7 @@ static int terminal_execute(VimState *state, int key) case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; - queue_process_events(main_loop.events); + multiqueue_process_events(main_loop.events); s->term->refcount--; if (s->term->buf_handle == 0) { s->close = true; @@ -452,15 +469,19 @@ static int terminal_execute(VimState *state, int key) } break; + case K_COMMAND: + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case Ctrl_N: - if (s->got_bs) { + if (s->got_bsl) { return 0; } - // FALLTHROUGH + FALLTHROUGH; default: - if (key == Ctrl_BSL && !s->got_bs) { - s->got_bs = true; + if (key == Ctrl_BSL && !s->got_bsl) { + s->got_bsl = true; break; } if (s->term->closed) { @@ -468,7 +489,7 @@ static int terminal_execute(VimState *state, int key) return 0; } - s->got_bs = false; + s->got_bsl = false; terminal_send_key(s->term, key); } @@ -508,9 +529,36 @@ void terminal_send(Terminal *term, char *data, size_t size) term->opts.write_cb(data, size, term->opts.data); } +void terminal_paste(long count, char_u **y_array, size_t y_size) +{ + for (int i = 0; i < count; i++) { // -V756 + // feed the lines to the terminal + for (size_t j = 0; j < y_size; j++) { + if (j) { + // terminate the previous line + terminal_send(curbuf->terminal, "\n", 1); + } + terminal_send(curbuf->terminal, (char *)y_array[j], STRLEN(y_array[j])); + } + } +} + +void terminal_flush_output(Terminal *term) +{ + size_t len = vterm_output_read(term->vt, term->textbuf, + sizeof(term->textbuf)); + terminal_send(term, term->textbuf, len); +} + void terminal_send_key(Terminal *term, int c) { VTermModifier mod = VTERM_MOD_NONE; + + // Convert K_ZERO back to ASCII + if (c == K_ZERO) { + c = Ctrl_AT; + } + VTermKey key = convert_key(c, &mod); if (key) { @@ -519,9 +567,7 @@ void terminal_send_key(Terminal *term, int c) vterm_keyboard_unichar(term->vt, (uint32_t)c, mod); } - size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); - terminal_send(term, term->textbuf, (size_t)len); + terminal_flush_output(term); } void terminal_receive(Terminal *term, char *data, size_t len) @@ -535,7 +581,7 @@ void terminal_receive(Terminal *term, char *data, size_t len) } void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, - int *term_attrs) + int *term_attrs) { int height, width; vterm_get_size(term->vt, &height, &width); @@ -551,8 +597,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, VTermScreenCell cell; fetch_cell(term, row, col, &cell); // Get the rgb value set by libvterm. - int vt_fg = RGB(cell.fg.red, cell.fg.green, cell.fg.blue); - int vt_bg = RGB(cell.bg.red, cell.bg.green, cell.bg.blue); + int vt_fg = RGB_(cell.fg.red, cell.fg.green, cell.fg.blue); + int vt_bg = RGB_(cell.bg.red, cell.bg.green, cell.bg.blue); vt_fg = vt_fg != default_vt_fg ? vt_fg : - 1; vt_bg = vt_bg != default_vt_bg ? vt_bg : - 1; // Since libvterm does not expose the color index used by the program, we @@ -571,26 +617,34 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int attr_id = 0; if (hl_attrs || vt_fg != -1 || vt_bg != -1) { - attr_id = get_attr_entry(&(attrentry_T) { + attr_id = hl_get_term_attr(&(HlAttrs) { .cterm_ae_attr = (int16_t)hl_attrs, .cterm_fg_color = vt_fg_idx, .cterm_bg_color = vt_bg_idx, .rgb_ae_attr = (int16_t)hl_attrs, .rgb_fg_color = vt_fg, .rgb_bg_color = vt_bg, + .rgb_sp_color = -1, }); } if (term->cursor.visible && term->cursor.row == row && term->cursor.col == col) { - attr_id = hl_combine_attr(attr_id, is_focused(term) && wp == curwin ? - hl_attr(HLF_TERM) : hl_attr(HLF_TERMNC)); + attr_id = hl_combine_attr(attr_id, + is_focused(term) && wp == curwin + ? win_hl_attr(wp, HLF_TERM) + : win_hl_attr(wp, HLF_TERMNC)); } term_attrs[col] = attr_id; } } +Buffer terminal_buf(const Terminal *term) +{ + return term->buf_handle; +} + // }}} // libvterm callbacks {{{ @@ -618,6 +672,20 @@ static int term_movecursor(VTermPos new, VTermPos old, int visible, return 1; } +static void buf_set_term_title(buf_T *buf, char *title) + FUNC_ATTR_NONNULL_ALL +{ + Error err = ERROR_INIT; + dict_set_var(buf->b_vars, + STATIC_CSTR_AS_STRING("term_title"), + STRING_OBJ(cstr_as_string(title)), + false, + false, + &err); + api_clear_error(&err); + status_redraw_buf(buf); +} + static int term_settermprop(VTermProp prop, VTermValue *val, void *data) { Terminal *term = data; @@ -633,12 +701,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) case VTERM_PROP_TITLE: { buf_T *buf = handle_get_buffer(term->buf_handle); - Error err; - api_free_object(dict_set_value(buf->b_vars, - cstr_as_string("term_title"), - STRING_OBJ(cstr_as_string(val->string)), - false, - &err)); + buf_set_term_title(buf, val->string); break; } @@ -655,14 +718,19 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) static int term_bell(void *data) { - ui_putc('\x07'); + ui_call_bell(); return 1; } -// the scrollback push/pop handlers were copied almost verbatim from pangoterm +// Scrollback push handler (from pangoterm). static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { Terminal *term = data; + + if (!term->sb_size) { + return 0; + } + // copy vterm cells into sb_buffer size_t c = (size_t)cols; ScrollbackLine *sbrow = NULL; @@ -674,10 +742,12 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) xfree(term->sb_buffer[term->sb_current - 1]); } + // Make room at the start by shifting to the right. memmove(term->sb_buffer + 1, term->sb_buffer, sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); } else if (term->sb_current > 0) { + // Make room at the start by shifting to the right. memmove(term->sb_buffer + 1, term->sb_buffer, sizeof(term->sb_buffer[0]) * term->sb_current); } @@ -687,6 +757,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) sbrow->cols = c; } + // New row is added at the start of the storage buffer. term->sb_buffer[0] = sbrow; if (term->sb_current < term->sb_size) { term->sb_current++; @@ -702,6 +773,11 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) return 1; } +/// Scrollback pop handler (from pangoterm). +/// +/// @param cols +/// @param cells VTerm state to update. +/// @param data Terminal static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) { Terminal *term = data; @@ -714,24 +790,24 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) term->sb_pending--; } - // restore vterm state - size_t c = (size_t)cols; ScrollbackLine *sbrow = term->sb_buffer[0]; term->sb_current--; + // Forget the "popped" row by shifting the rest onto it. memmove(term->sb_buffer, term->sb_buffer + 1, sizeof(term->sb_buffer[0]) * (term->sb_current)); - size_t cols_to_copy = c; + size_t cols_to_copy = (size_t)cols; if (cols_to_copy > sbrow->cols) { cols_to_copy = sbrow->cols; } // copy to vterm state memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); - for (size_t col = cols_to_copy; col < c; col++) { + for (size_t col = cols_to_copy; col < (size_t)cols; col++) { cells[col].chars[0] = 0; cells[col].width = 1; } + xfree(sbrow); pmap_put(ptr_t)(invalidated_terminals, term, NULL); @@ -741,26 +817,60 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) // }}} // input handling {{{ -static void convert_modifiers(VTermModifier *statep) +static void convert_modifiers(int key, VTermModifier *statep) { if (mod_mask & MOD_MASK_SHIFT) { *statep |= VTERM_MOD_SHIFT; } if (mod_mask & MOD_MASK_CTRL) { *statep |= VTERM_MOD_CTRL; } if (mod_mask & MOD_MASK_ALT) { *statep |= VTERM_MOD_ALT; } + + switch (key) { + case K_S_TAB: + case K_S_UP: + case K_S_DOWN: + case K_S_LEFT: + case K_S_RIGHT: + case K_S_F1: + case K_S_F2: + case K_S_F3: + case K_S_F4: + case K_S_F5: + case K_S_F6: + case K_S_F7: + case K_S_F8: + case K_S_F9: + case K_S_F10: + case K_S_F11: + case K_S_F12: + *statep |= VTERM_MOD_SHIFT; + break; + + case K_C_LEFT: + case K_C_RIGHT: + *statep |= VTERM_MOD_CTRL; + break; + } } static VTermKey convert_key(int key, VTermModifier *statep) { - convert_modifiers(statep); + convert_modifiers(key, statep); switch (key) { case K_BS: return VTERM_KEY_BACKSPACE; + case K_S_TAB: FALLTHROUGH; case TAB: return VTERM_KEY_TAB; case Ctrl_M: return VTERM_KEY_ENTER; case ESC: return VTERM_KEY_ESCAPE; + case K_S_UP: FALLTHROUGH; case K_UP: return VTERM_KEY_UP; + case K_S_DOWN: FALLTHROUGH; case K_DOWN: return VTERM_KEY_DOWN; + case K_S_LEFT: FALLTHROUGH; + case K_C_LEFT: FALLTHROUGH; case K_LEFT: return VTERM_KEY_LEFT; + case K_S_RIGHT: FALLTHROUGH; + case K_C_RIGHT: FALLTHROUGH; case K_RIGHT: return VTERM_KEY_RIGHT; case K_INS: return VTERM_KEY_INS; @@ -770,22 +880,22 @@ static VTermKey convert_key(int key, VTermModifier *statep) case K_PAGEUP: return VTERM_KEY_PAGEUP; case K_PAGEDOWN: return VTERM_KEY_PAGEDOWN; - case K_K0: + case K_K0: FALLTHROUGH; case K_KINS: return VTERM_KEY_KP_0; - case K_K1: + case K_K1: FALLTHROUGH; case K_KEND: return VTERM_KEY_KP_1; case K_K2: return VTERM_KEY_KP_2; - case K_K3: + case K_K3: FALLTHROUGH; case K_KPAGEDOWN: return VTERM_KEY_KP_3; case K_K4: return VTERM_KEY_KP_4; case K_K5: return VTERM_KEY_KP_5; case K_K6: return VTERM_KEY_KP_6; - case K_K7: + case K_K7: FALLTHROUGH; case K_KHOME: return VTERM_KEY_KP_7; case K_K8: return VTERM_KEY_KP_8; - case K_K9: + case K_K9: FALLTHROUGH; case K_KPAGEUP: return VTERM_KEY_KP_9; - case K_KDEL: + case K_KDEL: FALLTHROUGH; case K_KPOINT: return VTERM_KEY_KP_PERIOD; case K_KENTER: return VTERM_KEY_KP_ENTER; case K_KPLUS: return VTERM_KEY_KP_PLUS; @@ -793,6 +903,57 @@ static VTermKey convert_key(int key, VTermModifier *statep) case K_KMULTIPLY: return VTERM_KEY_KP_MULT; case K_KDIVIDE: return VTERM_KEY_KP_DIVIDE; + case K_S_F1: FALLTHROUGH; + case K_F1: return VTERM_KEY_FUNCTION(1); + case K_S_F2: FALLTHROUGH; + case K_F2: return VTERM_KEY_FUNCTION(2); + case K_S_F3: FALLTHROUGH; + case K_F3: return VTERM_KEY_FUNCTION(3); + case K_S_F4: FALLTHROUGH; + case K_F4: return VTERM_KEY_FUNCTION(4); + case K_S_F5: FALLTHROUGH; + case K_F5: return VTERM_KEY_FUNCTION(5); + case K_S_F6: FALLTHROUGH; + case K_F6: return VTERM_KEY_FUNCTION(6); + case K_S_F7: FALLTHROUGH; + case K_F7: return VTERM_KEY_FUNCTION(7); + case K_S_F8: FALLTHROUGH; + case K_F8: return VTERM_KEY_FUNCTION(8); + case K_S_F9: FALLTHROUGH; + case K_F9: return VTERM_KEY_FUNCTION(9); + case K_S_F10: FALLTHROUGH; + case K_F10: return VTERM_KEY_FUNCTION(10); + case K_S_F11: FALLTHROUGH; + case K_F11: return VTERM_KEY_FUNCTION(11); + case K_S_F12: FALLTHROUGH; + case K_F12: return VTERM_KEY_FUNCTION(12); + + case K_F13: return VTERM_KEY_FUNCTION(13); + case K_F14: return VTERM_KEY_FUNCTION(14); + case K_F15: return VTERM_KEY_FUNCTION(15); + case K_F16: return VTERM_KEY_FUNCTION(16); + case K_F17: return VTERM_KEY_FUNCTION(17); + case K_F18: return VTERM_KEY_FUNCTION(18); + case K_F19: return VTERM_KEY_FUNCTION(19); + case K_F20: return VTERM_KEY_FUNCTION(20); + case K_F21: return VTERM_KEY_FUNCTION(21); + case K_F22: return VTERM_KEY_FUNCTION(22); + case K_F23: return VTERM_KEY_FUNCTION(23); + case K_F24: return VTERM_KEY_FUNCTION(24); + case K_F25: return VTERM_KEY_FUNCTION(25); + case K_F26: return VTERM_KEY_FUNCTION(26); + case K_F27: return VTERM_KEY_FUNCTION(27); + case K_F28: return VTERM_KEY_FUNCTION(28); + case K_F29: return VTERM_KEY_FUNCTION(29); + case K_F30: return VTERM_KEY_FUNCTION(30); + case K_F31: return VTERM_KEY_FUNCTION(31); + case K_F32: return VTERM_KEY_FUNCTION(32); + case K_F33: return VTERM_KEY_FUNCTION(33); + case K_F34: return VTERM_KEY_FUNCTION(34); + case K_F35: return VTERM_KEY_FUNCTION(35); + case K_F36: return VTERM_KEY_FUNCTION(36); + case K_F37: return VTERM_KEY_FUNCTION(37); + default: return VTERM_KEY_NONE; } } @@ -830,11 +991,11 @@ static bool send_mouse_event(Terminal *term, int c) bool drag = false; switch (c) { - case K_LEFTDRAG: drag = true; // FALLTHROUGH + case K_LEFTDRAG: drag = true; FALLTHROUGH; case K_LEFTMOUSE: button = 1; break; - case K_MIDDLEDRAG: drag = true; // FALLTHROUGH + case K_MIDDLEDRAG: drag = true; FALLTHROUGH; case K_MIDDLEMOUSE: button = 2; break; - case K_RIGHTDRAG: drag = true; // FALLTHROUGH + case K_RIGHTDRAG: drag = true; FALLTHROUGH; case K_RIGHTMOUSE: button = 3; break; case K_MOUSEDOWN: button = 4; break; case K_MOUSEUP: button = 5; break; @@ -843,7 +1004,7 @@ static bool send_mouse_event(Terminal *term, int c) mouse_action(term, button, row, col, drag, 0); size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); + sizeof(term->textbuf)); terminal_send(term, term->textbuf, (size_t)len); return false; } @@ -877,7 +1038,7 @@ static bool send_mouse_event(Terminal *term, int c) // terminal buffer refresh & misc {{{ -void fetch_row(Terminal *term, int row, int end_col) +static void fetch_row(Terminal *term, int row, int end_col) { int col = 0; size_t line_len = 0; @@ -947,33 +1108,34 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row) static void refresh_terminal(Terminal *term) { - // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid. buf_T *buf = handle_get_buffer(term->buf_handle); bool valid = true; if (!buf || !(valid = buf_valid(buf))) { - // destroyed by `close_buffer`. Dont do anything else + // Destroyed by `close_buffer`. Do not do anything else. if (!valid) { term->buf_handle = 0; } return; } - bool pending_resize = term->pending_resize; + long ml_before = buf->b_ml.ml_line_count; WITH_BUFFER(buf, { refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); - redraw_buf_later(buf, NOT_VALID); }); - adjust_topline(term, buf, pending_resize); + long ml_added = buf->b_ml.ml_line_count - ml_before; + adjust_topline(term, buf, ml_added); } -// libuv timer callback. This will enqueue on_refresh to be processed as an -// event. +// Calls refresh_terminal() on all invalidated_terminals. static void refresh_timer_cb(TimeWatcher *watcher, void *data) { - if (exiting) { - // bad things can happen if we redraw when exiting, and there's no need to - // update the buffer. - goto end; + refresh_pending = false; + if (exiting // Cannot redraw (requires event loop) during teardown/exit. + || (State & CMDPREVIEW) + // WM_LIST (^D) is not redrawn, unlike the normal wildmenu. So we must + // skip redraws to keep it visible. + || wild_menu_showing == WM_LIST) { + return; } Terminal *term; void *stub; (void)(stub); @@ -982,11 +1144,12 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) map_foreach(invalidated_terminals, term, stub, { refresh_terminal(term); }); + bool any_visible = is_term_visible(); pmap_clear(ptr_t)(invalidated_terminals); unblock_autocmds(); - redraw(true); -end: - refresh_pending = false; + if (any_visible) { + redraw(true); + } } static void refresh_size(Terminal *term, buf_T *buf) @@ -1003,7 +1166,37 @@ static void refresh_size(Terminal *term, buf_T *buf) term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data); } -// Refresh the scrollback of a invalidated terminal +/// Adjusts scrollback storage after 'scrollback' option changed. +static void on_scrollback_option_changed(Terminal *term, buf_T *buf) +{ + const size_t scbk = curbuf->b_p_scbk < 0 + ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk); + assert(term->sb_current < SIZE_MAX); + if (term->sb_pending > 0) { // Pending rows must be processed first. + abort(); + } + + // Delete lines exceeding the new 'scrollback' limit. + if (scbk < term->sb_current) { + size_t diff = term->sb_current - scbk; + for (size_t i = 0; i < diff; i++) { + ml_delete(1, false); + term->sb_current--; + xfree(term->sb_buffer[term->sb_current]); + } + deleted_lines(1, (long)diff); + } + + // Resize the scrollback storage. + size_t sb_region = sizeof(ScrollbackLine *) * scbk; + if (scbk != term->sb_size) { + term->sb_buffer = xrealloc(term->sb_buffer, sb_region); + } + + term->sb_size = scbk; +} + +// Refresh the scrollback of an invalidated terminal. static void refresh_scrollback(Terminal *term, buf_T *buf) { int width, height; @@ -1032,9 +1225,11 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(buf->b_ml.ml_line_count, false); deleted_lines(buf->b_ml.ml_line_count, 1); } + + on_scrollback_option_changed(term, buf); } -// Refresh the screen(visible part of the buffer when the terminal is +// Refresh the screen (visible part of the buffer when the terminal is // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { @@ -1043,8 +1238,7 @@ static void refresh_screen(Terminal *term, buf_T *buf) int height; int width; vterm_get_size(term->vt, &height, &width); - // It's possible that the terminal height decreased and `term->invalid_end` - // doesn't reflect it yet + // Terminal height may have decreased before `invalid_end` reflects it. term->invalid_end = MIN(term->invalid_end, height); for (int r = term->invalid_start, linenr = row_to_linenr(term, r); @@ -1062,11 +1256,23 @@ static void refresh_screen(Terminal *term, buf_T *buf) int change_start = row_to_linenr(term, term->invalid_start); int change_end = change_start + changed; - changed_lines(change_start, 0, change_end, added); + changed_lines(change_start, 0, change_end, added, true); term->invalid_start = INT_MAX; term->invalid_end = -1; } +/// @return true if any invalidated terminal buffer is visible to the user +static bool is_term_visible(void) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer->terminal + && pmap_has(ptr_t)(invalidated_terminals, wp->w_buffer->terminal)) { + return true; + } + } + return false; +} + static void redraw(bool restore_cursor) { Terminal *term = curbuf->terminal; @@ -1074,7 +1280,8 @@ static void redraw(bool restore_cursor) restore_cursor = true; } - int save_row, save_col; + int save_row = 0; + int save_col = 0; if (restore_cursor) { // save the current row/col to restore after updating screen when not // focused @@ -1082,55 +1289,51 @@ static void redraw(bool restore_cursor) save_col = ui_current_col(); } block_autocmds(); - validate_cursor(); if (must_redraw) { update_screen(0); } - redraw_statuslines(); - - if (need_maketitle) { + if (need_maketitle) { // Update title in terminal-mode. #7248 maketitle(); } - showruler(false); - - if (term && is_focused(term)) { - curwin->w_wrow = term->cursor.row; - curwin->w_wcol = term->cursor.col + win_col_off(curwin); - setcursor(); - } else if (restore_cursor) { + if (restore_cursor) { ui_cursor_goto(save_row, save_col); } else if (term) { - // exiting terminal focus, put the window cursor in a valid position - int height, width; - vterm_get_size(term->vt, &height, &width); - curwin->w_wrow = height - 1; - curwin->w_wcol = 0; - setcursor(); + curwin->w_wrow = term->cursor.row; + curwin->w_wcol = term->cursor.col + win_col_off(curwin); + curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count, + row_to_linenr(term, term->cursor.row)); + // Nudge cursor when returning to normal-mode. + int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); + curwin->w_cursor.col = MAX(0, term->cursor.col + win_col_off(curwin) + off); + curwin->w_cursor.coladd = 0; + mb_check_adjust_col(curwin); } unblock_autocmds(); ui_flush(); } -static void adjust_topline(Terminal *term, buf_T *buf, bool force) +static void adjust_topline(Terminal *term, buf_T *buf, long added) { int height, width; vterm_get_size(term->vt, &height, &width); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == buf) { - // for every window that displays a terminal, ensure the cursor is in a - // valid line - wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, buf->b_ml.ml_line_count); - if (force || curbuf != buf || is_focused(term)) { - // if the terminal is not in the current window or if it's focused, - // adjust topline/cursor so the window will "follow" the terminal - // output - wp->w_cursor.lnum = buf->b_ml.ml_line_count; + linenr_T ml_end = buf->b_ml.ml_line_count; + bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end? + + if (following || (wp == curwin && is_focused(term))) { + // "Follow" the terminal output + wp->w_cursor.lnum = ml_end; set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1)); + } else { + // Ensure valid cursor for each window displaying this terminal. + wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end); } + mb_check_adjust_col(wp); } } } @@ -1152,12 +1355,14 @@ static bool is_focused(Terminal *term) #define GET_CONFIG_VALUE(k, o) \ do { \ - Error err; \ + Error err = ERROR_INIT; \ /* Only called from terminal_open where curbuf->terminal is the */ \ /* context */ \ o = dict_get_value(curbuf->b_vars, cstr_as_string(k), &err); \ + api_clear_error(&err); \ if (o.type == kObjectTypeNil) { \ o = dict_get_value(&globvardict, cstr_as_string(k), &err); \ + api_clear_error(&err); \ } \ } while (0) @@ -1172,17 +1377,6 @@ static char *get_config_string(char *key) return NULL; } -static int get_config_int(char *key) -{ - Object obj; - GET_CONFIG_VALUE(key, obj); - if (obj.type == kObjectTypeInteger) { - return (int)obj.data.integer; - } - api_free_object(obj); - return 0; -} - // }}} // vim: foldmethod=marker |