aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/terminal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/terminal.c')
-rw-r--r--src/nvim/terminal.c165
1 files changed, 145 insertions, 20 deletions
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 2b05a8047e..b916660024 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -40,8 +40,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <vterm.h>
-#include <vterm_keycodes.h>
#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;
@@ -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)
@@ -215,11 +227,67 @@ 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)
+ FUNC_ATTR_NONNULL_ALL
{
- if (frag.str == NULL) {
+ Terminal *term = user;
+
+ if (frag.str == NULL || frag.len == 0) {
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;
}
@@ -227,7 +295,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;
}
@@ -307,14 +375,18 @@ 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);
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;
@@ -326,14 +398,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));
}
@@ -684,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++;
@@ -718,6 +786,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;
@@ -769,6 +843,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]
@@ -845,7 +921,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)) {
@@ -982,6 +1058,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,
@@ -1180,10 +1260,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);
@@ -1198,6 +1275,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, &regtype, 1);
+
+ tv_list_append_string(args, &regname, 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 {{{