From a18982cb839d7f05e32b1026bbd99b8aa34ad189 Mon Sep 17 00:00:00 2001 From: James Tirta Halim Date: Mon, 3 Jun 2024 11:00:20 +0700 Subject: refactor: replace '\0' with NUL --- src/nvim/terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2b05a8047e..2738eb874d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -845,7 +845,7 @@ void terminal_paste(int count, char **y_array, size_t y_size) } char *dst = buff; char *src = y_array[j]; - while (*src != '\0') { + while (*src != NUL) { len = (size_t)utf_ptr2len(src); int c = utf_ptr2char(src); if (!is_filter_char(c)) { -- cgit From c37695a5d5f2e8914fff86f3581bed70b4c85d3c Mon Sep 17 00:00:00 2001 From: James <89495599+IAKOBVS@users.noreply.github.com> Date: Tue, 11 Jun 2024 22:40:24 +0700 Subject: refactor: use S_LEN(s) instead of s, n (#29219) --- src/nvim/terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2738eb874d..027ff79696 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -833,9 +833,9 @@ void terminal_paste(int count, char **y_array, size_t y_size) if (j) { // terminate the previous line #ifdef MSWIN - terminal_send(curbuf->terminal, "\r\n", 2); + terminal_send(curbuf->terminal, S_LEN("\r\n")); #else - terminal_send(curbuf->terminal, "\n", 1); + terminal_send(curbuf->terminal, S_LEN("\n")); #endif } size_t len = strlen(y_array[j]); -- cgit From 3ad977f01d97e84b576e6965c5c9e4f75c10cb35 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:18:06 -0500 Subject: feat(terminal): add support for copying with OSC 52 in embedded terminal (#29117) When libvterm receives the OSC 52 escape sequence it ignores it because Nvim does not set any selection callbacks. Install selection callbacks that forward to the clipboard provider, so that setting the clipboard with OSC 52 in the embedded terminal writes to the system clipboard using the configured clipboard provider. --- src/nvim/terminal.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 027ff79696..818f8abbb5 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -112,6 +112,9 @@ typedef struct { // libvterm. Improves performance when receiving large bursts of data. #define REFRESH_DELAY 10 +#define TEXTBUF_SIZE 0x1fff +#define SELECTIONBUF_SIZE 0x0400 + static TimeWatcher refresh_timer; static bool refresh_pending = false; @@ -127,7 +130,7 @@ struct terminal { // buffer used to: // - convert VTermScreen cell arrays into utf8 strings // - receive data from libvterm as a result of key presses. - char textbuf[0x1fff]; + char textbuf[TEXTBUF_SIZE]; ScrollbackLine **sb_buffer; // Scrollback storage. size_t sb_current; // Lines stored in sb_buffer. @@ -166,6 +169,9 @@ struct terminal { // When there is a pending TermRequest autocommand, block and store input. StringBuilder *pending_send; + char *selection_buffer; /// libvterm selection buffer + StringBuilder selection; /// Growable array containing full selection data + size_t refcount; // reference count }; @@ -179,6 +185,12 @@ static VTermScreenCallbacks vterm_screen_callbacks = { .sb_popline = term_sb_pop, }; +static VTermSelectionCallbacks vterm_selection_callbacks = { + .set = term_selection_set, + // For security reasons we don't support querying the system clipboard from the embedded terminal + .query = NULL, +}; + static Set(ptr_t) invalidated_terminals = SET_INIT; static void emit_termrequest(void **argv) @@ -315,6 +327,11 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts) vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL); vterm_screen_reset(term->vts, 1); vterm_output_set_callback(term->vt, term_output_callback, term); + + term->selection_buffer = xcalloc(SELECTIONBUF_SIZE, 1); + vterm_state_set_selection_callbacks(state, &vterm_selection_callbacks, term, + term->selection_buffer, SELECTIONBUF_SIZE); + // force a initial refresh of the screen to ensure the buffer will always // have as many lines as screen rows when refresh_scrollback is called term->invalid_start = 0; @@ -769,6 +786,8 @@ void terminal_destroy(Terminal **termpp) } xfree(term->sb_buffer); xfree(term->title); + xfree(term->selection_buffer); + kv_destroy(term->selection); vterm_free(term->vt); xfree(term); *termpp = NULL; // coverity[dead-store] @@ -1198,6 +1217,54 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) return 1; } +static void term_clipboard_set(void **argv) +{ + VTermSelectionMask mask = (VTermSelectionMask)(long)argv[0]; + char *data = argv[1]; + + char regname; + switch (mask) { + case VTERM_SELECTION_CLIPBOARD: + regname = '+'; + break; + case VTERM_SELECTION_PRIMARY: + regname = '*'; + break; + default: + regname = '+'; + break; + } + + list_T *lines = tv_list_alloc(1); + tv_list_append_allocated_string(lines, data); + + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + + const char regtype = 'v'; + tv_list_append_string(args, ®type, 1); + + tv_list_append_string(args, ®name, 1); + eval_call_provider("clipboard", "set", args, true); +} + +static int term_selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user) +{ + Terminal *term = user; + if (frag.initial) { + kv_size(term->selection) = 0; + } + + kv_concat_len(term->selection, frag.str, frag.len); + + if (frag.final) { + char *data = xmemdupz(term->selection.items, kv_size(term->selection)); + multiqueue_put(main_loop.events, term_clipboard_set, (void *)mask, data); + } + + return 1; +} + // }}} // input handling {{{ -- cgit From d38912b59f97a4da0a2d0a24af226e6dd27e9b2c Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 11 Jun 2024 11:10:34 -0500 Subject: refactor(terminal): move :terminal defaults to _defaults.lua --- src/nvim/terminal.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 818f8abbb5..000f750413 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -343,14 +343,6 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts) refresh_screen(term, buf); set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL); - // Default settings for terminal buffers - buf->b_p_ma = false; // 'nomodifiable' - buf->b_p_ul = -1; // 'undolevels' - buf->b_p_scbk = // 'scrollback' (initialize local from global) - (p_scbk < 0) ? 10000 : MAX(1, p_scbk); - buf->b_p_tw = 0; // 'textwidth' - set_option_value(kOptWrap, BOOLEAN_OPTVAL(false), OPT_LOCAL); - set_option_value(kOptList, BOOLEAN_OPTVAL(false), OPT_LOCAL); if (buf->b_ffname != NULL) { buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname)); } -- cgit From 43d8435cf84468e776e0a6a98d05ae7bd62583b7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 13 Jun 2024 22:20:06 +0100 Subject: revert: "refactor: use S_LEN macro" (#29319) revert: "refactor: use S_LEN(s) instead of s, n (#29219)" This reverts commit c37695a5d5f2e8914fff86f3581bed70b4c85d3c. --- src/nvim/terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 000f750413..b16777be8a 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -844,9 +844,9 @@ void terminal_paste(int count, char **y_array, size_t y_size) if (j) { // terminate the previous line #ifdef MSWIN - terminal_send(curbuf->terminal, S_LEN("\r\n")); + terminal_send(curbuf->terminal, "\r\n", 2); #else - terminal_send(curbuf->terminal, S_LEN("\n")); + terminal_send(curbuf->terminal, "\n", 1); #endif } size_t len = strlen(y_array[j]); -- cgit From d1bd3d643e5846eee7343ba1a12bdcbbc8cee7b0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 19 Jul 2024 11:00:13 +0100 Subject: refactor: collapse statements in single assignments Problem: Variables are often assigned multiple places in common patterns. Solution: Replace these common patterns with different patterns that reduce the number of assignments. Use `MAX` and `MIN`: ```c if (x < y) { x = y; } // --> x = MAX(x, y); ``` ```c if (x > y) { x = y; } // --> x = MIN(x, y); ``` Use ternary: ```c int a; if (cond) { a = b; } els { a = c; } // --> int a = cond ? b : c; ``` --- src/nvim/terminal.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index b16777be8a..507a9e3a2a 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1191,10 +1191,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) memmove(term->sb_buffer, term->sb_buffer + 1, sizeof(term->sb_buffer[0]) * (term->sb_current)); - size_t cols_to_copy = (size_t)cols; - if (cols_to_copy > sbrow->cols) { - cols_to_copy = sbrow->cols; - } + size_t cols_to_copy = MIN((size_t)cols, sbrow->cols); // copy to vterm state memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); -- cgit From fa79a8ad6deefeea81c1959d69aa4c8b2d993f99 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 8 Aug 2024 12:28:47 +0200 Subject: build(deps): vendor libvterm at v0.3.3 Problem: Adding support for modern Nvim features (reflow, OSC 8, full utf8/emoji support) requires coupling libvterm to Nvim internals (e.g., utf8proc). Solution: Vendor libvterm at v0.3.3. --- src/nvim/terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 507a9e3a2a..abc9b3534b 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -40,8 +40,6 @@ #include #include #include -#include -#include #include "klib/kvec.h" #include "nvim/api/private/helpers.h" @@ -94,6 +92,8 @@ #include "nvim/ui.h" #include "nvim/vim_defs.h" #include "nvim/window.h" +#include "vterm/vterm.h" +#include "vterm/vterm_keycodes.h" typedef struct { VimState state; -- cgit From 8df6736ca14d09f87cf0a8486758ac5708819434 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 24 May 2023 10:04:49 +0200 Subject: feat(term): enable reflow by default (#21124) Problem: Contents of terminal buffer are not reflown when Nvim is resized. Solution: Enable reflow in libvterm by default. Now that libvterm is vendored, also fix "TUI rapid resize" test failures there. Note: Neovim's scrollback buffer does not support reflow (yet), so lines vanishing into the buffer due to a too small window will be restored without reflow. --- src/nvim/terminal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index abc9b3534b..54a0de9c22 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -319,8 +319,7 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts) // Set up screen term->vts = vterm_obtain_screen(term->vt); vterm_screen_enable_altscreen(term->vts, true); - // TODO(clason): reenable when https://github.com/neovim/neovim/issues/23762 is fixed - // vterm_screen_enable_reflow(term->vts, true); + vterm_screen_enable_reflow(term->vts, true); // delete empty lines at the end of the buffer vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term); vterm_screen_set_unrecognised_fallbacks(term->vts, &vterm_fallbacks, term); -- cgit From 4199671047b0cb62781995a8f6a4b66fb6e8b6fd Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 15 Aug 2024 06:09:14 -0500 Subject: feat(term): support OSC 8 hyperlinks in :terminal (#30050) --- src/nvim/terminal.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 54a0de9c22..2b44763ddd 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -227,11 +227,66 @@ static void schedule_termrequest(Terminal *term, char *payload, size_t payload_l term->pending_send); } +static int parse_osc8(VTermStringFragment frag, int *attr) + FUNC_ATTR_NONNULL_ALL +{ + // Parse the URI from the OSC 8 sequence and add the URL to our URL set. + // Skip the ID, we don't use it (for now) + size_t i = 0; + for (; i < frag.len; i++) { + if (frag.str[i] == ';') { + break; + } + } + + // Move past the semicolon + i++; + + if (i >= frag.len) { + // Invalid OSC sequence + return 0; + } + + // Find the terminator + const size_t start = i; + for (; i < frag.len; i++) { + if (frag.str[i] == '\a' || frag.str[i] == '\x1b') { + break; + } + } + + const size_t len = i - start; + if (len == 0) { + // Empty OSC 8, no URL + *attr = 0; + return 1; + } + + char *url = xmemdupz(&frag.str[start], len + 1); + url[len] = 0; + *attr = hl_add_url(0, url); + xfree(url); + + return 1; +} + static int on_osc(int command, VTermStringFragment frag, void *user) { + Terminal *term = user; + if (frag.str == NULL) { return 0; } + + if (command == 8) { + int attr = 0; + if (parse_osc8(frag, &attr)) { + VTermState *state = vterm_obtain_state(term->vt); + VTermValue value = { .number = attr }; + vterm_state_set_penattr(state, VTERM_ATTR_URI, VTERM_VALUETYPE_INT, &value); + } + } + if (!has_event(EVENT_TERMREQUEST)) { return 1; } @@ -239,7 +294,7 @@ static int on_osc(int command, VTermStringFragment frag, void *user) StringBuilder request = KV_INITIAL_VALUE; kv_printf(request, "\x1b]%d;", command); kv_concat_len(request, frag.str, frag.len); - schedule_termrequest(user, request.items, request.size); + schedule_termrequest(term, request.items, request.size); return 1; } @@ -992,6 +1047,10 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te }); } + if (cell.uri > 0) { + attr_id = hl_combine_attr(attr_id, cell.uri); + } + if (term->cursor.visible && term->cursor.row == row && term->cursor.col == col) { attr_id = hl_combine_attr(attr_id, -- cgit From 6d997f8068a89703823f1572c56a6331c9e024aa Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 19 Aug 2024 06:43:06 -0500 Subject: fix(terminal): handle C0 characters in OSC terminator (#30090) When a C0 character is present in an OSC terminator (i.e. after the ESC but before a \ (0x5c) or printable character), vterm executes the control character and resets the current string fragment. If the C0 character is the final byte in the sequence, the string fragment has a zero length. However, because the VT parser is still in the "escape" state, vterm attempts to subtract 1 from the string length (to account for the escape character). When the string fragment is empty, this causes an underflow in the unsigned size variable, resulting in a buffer overflow. The fix is simple: explicitly check if the string length is non-zero before subtracting. --- src/nvim/terminal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2b44763ddd..43f68f7321 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -271,10 +271,11 @@ static int parse_osc8(VTermStringFragment frag, int *attr) } static int on_osc(int command, VTermStringFragment frag, void *user) + FUNC_ATTR_NONNULL_ALL { Terminal *term = user; - if (frag.str == NULL) { + if (frag.str == NULL || frag.len == 0) { return 0; } -- cgit From 1d11808bfd2879bf278cd05a7095a6634fa5afec Mon Sep 17 00:00:00 2001 From: ibhagwan <59988195+ibhagwan@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:23:56 -0700 Subject: fix(terminal): interrupt/got_int hangs terminal (#30056) Upon `terminal_enter`, `mapped_ctrl_c` is set in order to avoid `CTRL-C` interrupts (which is proxied to the terminal process instead), `os_inchar` will then test `mapped_ctrl_c` against `State` and set `ctrl_c_interrupts=false` which prevents `process_ctrl_c` from setting `got_int=true` in a terminal state. However, if `got_int` is set outside of `process_ctrl_c`, e.g. via `interrupt()`, this will hang the neovim process as `terminal_execute` will enter an endless loop as `got_int` will never be cleared causing `safe_vgetc` to always return `Ctrl_C`. A minimal example reproducing this bug: ```vim :autocmd TermEnter * call timer_start(500, {-> interrupt()}) :terminal :startinsert ``` To fix, we make sure `got_int` is cleared inside `terminal_execute` when it detects `Ctrl_C`. Closes #20726 Co-authored-by: zeertzjq --- src/nvim/terminal.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 43f68f7321..ea3617098b 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -782,6 +782,12 @@ static int terminal_execute(VimState *state, int key) FALLTHROUGH; default: + if (key == Ctrl_C) { + // terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4 + // But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand, + // so ensure that it is cleared. + got_int = false; + } if (key == Ctrl_BSL && !s->got_bsl) { s->got_bsl = true; break; -- cgit From e697c1b43dfbeab132fee4149157f7abd08c51a0 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 22 Sep 2024 06:02:48 +0800 Subject: fix(paste): improve repeating of pasted text (#30438) - Fixes 'autoindent' being applied during redo. - Makes redoing a large paste significantly faster. - Stores pasted text in the register being recorded. Fix #28561 --- src/nvim/terminal.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ea3617098b..b916660024 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -748,6 +748,10 @@ static int terminal_execute(VimState *state, int key) } break; + case K_PASTE_START: + paste_repeat(1); + break; + case K_EVENT: // We cannot let an event free the terminal yet. It is still needed. s->term->refcount++; -- cgit