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.c309
1 files changed, 242 insertions, 67 deletions
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 5ff7f721ba..897c393488 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -52,6 +52,7 @@
#include "nvim/channel.h"
#include "nvim/channel_defs.h"
#include "nvim/cursor.h"
+#include "nvim/cursor_shape.h"
#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
@@ -64,6 +65,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
@@ -91,9 +93,15 @@
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/vim_defs.h"
+#include "nvim/vterm/keyboard.h"
+#include "nvim/vterm/mouse.h"
+#include "nvim/vterm/parser.h"
+#include "nvim/vterm/pen.h"
+#include "nvim/vterm/screen.h"
+#include "nvim/vterm/state.h"
+#include "nvim/vterm/vterm.h"
+#include "nvim/vterm/vterm_keycodes_defs.h"
#include "nvim/window.h"
-#include "vterm/vterm.h"
-#include "vterm/vterm_keycodes.h"
typedef struct {
VimState state;
@@ -160,14 +168,20 @@ struct terminal {
int invalid_start, invalid_end; // invalid rows in libvterm screen
struct {
int row, col;
+ int shape;
bool visible;
+ bool blink;
} cursor;
- bool pending_resize; // pending width/height
- bool color_set[16];
+ struct {
+ bool resize; ///< pending width/height
+ bool cursor; ///< pending cursor shape or blink change
+ StringBuilder *send; ///< When there is a pending TermRequest autocommand, block and store input.
+ } pending;
+
+ bool theme_updates; ///< Send a theme update notification when 'bg' changes
- // When there is a pending TermRequest autocommand, block and store input.
- StringBuilder *pending_send;
+ bool color_set[16];
char *selection_buffer; /// libvterm selection buffer
StringBuilder selection; /// Growable array containing full selection data
@@ -181,6 +195,7 @@ static VTermScreenCallbacks vterm_screen_callbacks = {
.movecursor = term_movecursor,
.settermprop = term_settermprop,
.bell = term_bell,
+ .theme = term_theme,
.sb_pushline = term_sb_push, // Called before a line goes offscreen.
.sb_popline = term_sb_pop,
};
@@ -207,24 +222,24 @@ static void emit_termrequest(void **argv)
apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, false, AUGROUP_ALL, buf, NULL, &data);
xfree(payload);
- StringBuilder *term_pending_send = term->pending_send;
- term->pending_send = NULL;
+ StringBuilder *term_pending_send = term->pending.send;
+ term->pending.send = NULL;
if (kv_size(*pending_send)) {
terminal_send(term, pending_send->items, pending_send->size);
kv_destroy(*pending_send);
}
if (term_pending_send != pending_send) {
- term->pending_send = term_pending_send;
+ term->pending.send = term_pending_send;
}
xfree(pending_send);
}
static void schedule_termrequest(Terminal *term, char *payload, size_t payload_length)
{
- term->pending_send = xmalloc(sizeof(StringBuilder));
- kv_init(*term->pending_send);
+ term->pending.send = xmalloc(sizeof(StringBuilder));
+ kv_init(*term->pending.send);
multiqueue_put(main_loop.events, emit_termrequest, term, payload, (void *)payload_length,
- term->pending_send);
+ term->pending.send);
}
static int parse_osc8(VTermStringFragment frag, int *attr)
@@ -352,7 +367,7 @@ static void term_output_callback(const char *s, size_t len, void *user_data)
/// Initializes terminal properties, and triggers TermOpen.
///
-/// The PTY process (TerminalOptions.data) was already started by termopen(),
+/// The PTY process (TerminalOptions.data) was already started by jobstart(),
/// via ex_terminal() or the term:// BufReadCmd.
///
/// @param buf Buffer used for presentation of the terminal.
@@ -363,7 +378,7 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
// Create a new terminal instance and configure it
Terminal *term = *termpp = xcalloc(1, sizeof(Terminal));
term->opts = opts;
- term->cursor.visible = true;
+
// Associate the terminal instance with the new buffer
term->buf_handle = buf->handle;
buf->terminal = term;
@@ -387,6 +402,28 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
vterm_state_set_selection_callbacks(state, &vterm_selection_callbacks, term,
term->selection_buffer, SELECTIONBUF_SIZE);
+ VTermValue cursor_shape;
+ switch (shape_table[SHAPE_IDX_TERM].shape) {
+ case SHAPE_BLOCK:
+ cursor_shape.number = VTERM_PROP_CURSORSHAPE_BLOCK;
+ break;
+ case SHAPE_HOR:
+ cursor_shape.number = VTERM_PROP_CURSORSHAPE_UNDERLINE;
+ break;
+ case SHAPE_VER:
+ cursor_shape.number = VTERM_PROP_CURSORSHAPE_BAR_LEFT;
+ break;
+ }
+ vterm_state_set_termprop(state, VTERM_PROP_CURSORSHAPE, &cursor_shape);
+
+ VTermValue cursor_blink;
+ if (shape_table[SHAPE_IDX_TERM].blinkon != 0 && shape_table[SHAPE_IDX_TERM].blinkoff != 0) {
+ cursor_blink.boolean = true;
+ } else {
+ cursor_blink.boolean = false;
+ }
+ vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &cursor_blink);
+
// 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;
@@ -565,7 +602,7 @@ void terminal_check_size(Terminal *term)
vterm_set_size(term->vt, height, width);
vterm_screen_flush_damage(term->vts);
- term->pending_resize = true;
+ term->pending.resize = true;
invalidate_terminal(term, -1, -1);
}
@@ -598,12 +635,12 @@ bool terminal_enter(void)
int save_w_p_cuc = curwin->w_p_cuc;
OptInt save_w_p_so = curwin->w_p_so;
OptInt save_w_p_siso = curwin->w_p_siso;
- if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) {
+ if (curwin->w_p_cul && curwin->w_p_culopt_flags & kOptCuloptFlagNumber) {
if (strcmp(curwin->w_p_culopt, "number") != 0) {
save_w_p_culopt = curwin->w_p_culopt;
curwin->w_p_culopt = xstrdup("number");
}
- curwin->w_p_culopt_flags = CULOPT_NBR;
+ curwin->w_p_culopt_flags = kOptCuloptFlagNumber;
} else {
curwin->w_p_cul = false;
}
@@ -614,15 +651,25 @@ bool terminal_enter(void)
curwin->w_p_so = 0;
curwin->w_p_siso = 0;
+ // Update the cursor shape table and flush changes to the UI
+ s->term->pending.cursor = true;
+ refresh_cursor(s->term);
+
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_custom_title_later();
+ if (!s->term->cursor.visible) {
+ // Hide cursor if it should be hidden
+ ui_busy_start();
+ }
+ ui_cursor_shape();
apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf);
may_trigger_modechanged();
+ // Tell the terminal it has focus
+ terminal_focus(s->term, true);
+
s->state.execute = terminal_execute;
s->state.check = terminal_check;
state_enter(&s->state);
@@ -634,6 +681,9 @@ bool terminal_enter(void)
RedrawingDisabled = s->save_rd;
apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf);
+ // Restore the terminal cursor to what is set in 'guicursor'
+ (void)parse_shape_opt(SHAPE_CURSOR);
+
if (save_curwin == curwin->handle) { // Else: window was closed.
curwin->w_p_cul = save_w_p_cul;
if (save_w_p_culopt) {
@@ -648,8 +698,9 @@ bool terminal_enter(void)
free_string_option(save_w_p_culopt);
}
- // draw the unfocused cursor
- invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
+ // Tell the terminal it lost focus
+ terminal_focus(s->term, false);
+
if (curbuf->terminal == s->term && !s->close) {
terminal_check_cursor();
}
@@ -658,7 +709,11 @@ bool terminal_enter(void)
} else {
unshowmode(true);
}
- ui_busy_stop();
+ if (!s->term->cursor.visible) {
+ // If cursor was hidden, show it again
+ ui_busy_stop();
+ }
+ ui_cursor_shape();
if (s->close) {
bool wipe = s->term->buf_handle != 0;
s->term->destroy = true;
@@ -728,21 +783,32 @@ static int terminal_execute(VimState *state, int key)
{
TerminalState *s = (TerminalState *)state;
- switch (key) {
+ // Check for certain control keys like Ctrl-C and Ctrl-\. We still send the
+ // unmerged key and modifiers to the terminal.
+ int tmp_mod_mask = mod_mask;
+ int mod_key = merge_modifiers(key, &tmp_mod_mask);
+
+ switch (mod_key) {
case K_LEFTMOUSE:
case K_LEFTDRAG:
case K_LEFTRELEASE:
- case K_MOUSEMOVE:
case K_MIDDLEMOUSE:
case K_MIDDLEDRAG:
case K_MIDDLERELEASE:
case K_RIGHTMOUSE:
case K_RIGHTDRAG:
case K_RIGHTRELEASE:
+ case K_X1MOUSE:
+ case K_X1DRAG:
+ case K_X1RELEASE:
+ case K_X2MOUSE:
+ case K_X2DRAG:
+ case K_X2RELEASE:
case K_MOUSEDOWN:
case K_MOUSEUP:
case K_MOUSELEFT:
case K_MOUSERIGHT:
+ case K_MOUSEMOVE:
if (send_mouse_event(s->term, key)) {
return 0;
}
@@ -786,13 +852,13 @@ static int terminal_execute(VimState *state, int key)
FALLTHROUGH;
default:
- if (key == Ctrl_C) {
+ if (mod_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) {
+ if (mod_key == Ctrl_BSL && !s->got_bsl) {
s->got_bsl = true;
break;
}
@@ -809,6 +875,19 @@ static int terminal_execute(VimState *state, int key)
return 0;
}
if (s->term != curbuf->terminal) {
+ // Active terminal buffer changed, flush terminal's cursor state to the UI
+ curbuf->terminal->pending.cursor = true;
+
+ if (!s->term->cursor.visible) {
+ // If cursor was hidden, show it again
+ ui_busy_stop();
+ }
+
+ if (!curbuf->terminal->cursor.visible) {
+ // Hide cursor if it should be hidden
+ ui_busy_start();
+ }
+
invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
invalidate_terminal(curbuf->terminal,
curbuf->terminal->cursor.row,
@@ -856,8 +935,8 @@ static void terminal_send(Terminal *term, const char *data, size_t size)
if (term->closed) {
return;
}
- if (term->pending_send) {
- kv_concat_len(*term->pending_send, data, size);
+ if (term->pending.send) {
+ kv_concat_len(*term->pending.send, data, size);
return;
}
term->opts.write_cb(data, size, term->opts.data);
@@ -868,28 +947,28 @@ static bool is_filter_char(int c)
unsigned flag = 0;
switch (c) {
case 0x08:
- flag = TPF_BS;
+ flag = kOptTpfFlagBS;
break;
case 0x09:
- flag = TPF_HT;
+ flag = kOptTpfFlagHT;
break;
case 0x0A:
case 0x0D:
break;
case 0x0C:
- flag = TPF_FF;
+ flag = kOptTpfFlagFF;
break;
case 0x1b:
- flag = TPF_ESC;
+ flag = kOptTpfFlagESC;
break;
case 0x7F:
- flag = TPF_DEL;
+ flag = kOptTpfFlagDEL;
break;
default:
if (c < ' ') {
- flag = TPF_C0;
+ flag = kOptTpfFlagC0;
} else if (c >= 0x80 && c <= 0x9F) {
- flag = TPF_C1;
+ flag = kOptTpfFlagC1;
}
}
return !!(tpf_flags & flag);
@@ -946,9 +1025,9 @@ static void terminal_send_key(Terminal *term, int c)
c = Ctrl_AT;
}
- VTermKey key = convert_key(c, &mod);
+ VTermKey key = convert_key(&c, &mod);
- if (key) {
+ if (key != VTERM_KEY_NONE) {
vterm_keyboard_key(term->vt, key, mod);
} else if (!IS_SPECIAL(c)) {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
@@ -1062,14 +1141,6 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
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,
- is_focused(term) && wp == curwin
- ? win_hl_attr(wp, HLF_TERM)
- : win_hl_attr(wp, HLF_TERMNC));
- }
-
term_attrs[col] = attr_id;
}
}
@@ -1084,6 +1155,31 @@ bool terminal_running(const Terminal *term)
return !term->closed;
}
+void terminal_notify_theme(Terminal *term, bool dark)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (!term->theme_updates) {
+ return;
+ }
+
+ char buf[10];
+ ssize_t ret = snprintf(buf, sizeof(buf), "\x1b[997;%cn", dark ? '1' : '2');
+ assert(ret > 0);
+ assert((size_t)ret <= sizeof(buf));
+ terminal_send(term, buf, (size_t)ret);
+}
+
+static void terminal_focus(const Terminal *term, bool focus)
+ FUNC_ATTR_NONNULL_ALL
+{
+ VTermState *state = vterm_obtain_state(term->vt);
+ if (focus) {
+ vterm_state_focus_in(state);
+ } else {
+ vterm_state_focus_out(state);
+ }
+}
+
// }}}
// libvterm callbacks {{{
@@ -1105,8 +1201,7 @@ static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void
Terminal *term = data;
term->cursor.row = new_pos.row;
term->cursor.col = new_pos.col;
- invalidate_terminal(term, old_pos.row, old_pos.row + 1);
- invalidate_terminal(term, new_pos.row, new_pos.row + 1);
+ invalidate_terminal(term, -1, -1);
return 1;
}
@@ -1134,8 +1229,17 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
break;
case VTERM_PROP_CURSORVISIBLE:
+ if (is_focused(term)) {
+ if (!val->boolean && term->cursor.visible) {
+ // Hide the cursor
+ ui_busy_start();
+ } else if (val->boolean && !term->cursor.visible) {
+ // Unhide the cursor
+ ui_busy_stop();
+ }
+ invalidate_terminal(term, -1, -1);
+ }
term->cursor.visible = val->boolean;
- invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
break;
case VTERM_PROP_TITLE: {
@@ -1171,6 +1275,22 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
term->forward_mouse = (bool)val->number;
break;
+ case VTERM_PROP_CURSORBLINK:
+ term->cursor.blink = val->boolean;
+ term->pending.cursor = true;
+ invalidate_terminal(term, -1, -1);
+ break;
+
+ case VTERM_PROP_CURSORSHAPE:
+ term->cursor.shape = val->number;
+ term->pending.cursor = true;
+ invalidate_terminal(term, -1, -1);
+ break;
+
+ case VTERM_PROP_THEMEUPDATES:
+ term->theme_updates = val->boolean;
+ break;
+
default:
return 0;
}
@@ -1181,7 +1301,15 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
/// Called when the terminal wants to ring the system bell.
static int term_bell(void *data)
{
- vim_beep(BO_TERM);
+ vim_beep(kOptBoFlagTerm);
+ return 1;
+}
+
+/// Called when the terminal wants to query the system theme.
+static int term_theme(bool *dark, void *data)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *dark = (*p_bg == 'd');
return 1;
}
@@ -1266,7 +1394,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
// copy to vterm state
memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
for (size_t col = cols_to_copy; col < (size_t)cols; col++) {
- cells[col].chars[0] = 0;
+ cells[col].schar = 0;
cells[col].width = 1;
}
@@ -1327,19 +1455,23 @@ static int term_selection_set(VTermSelectionMask mask, VTermStringFragment frag,
// }}}
// input handling {{{
-static void convert_modifiers(int key, 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_SHIFT) && *key >= 'A' && *key <= 'Z') {
+ // vterm interprets CTRL+A as SHIFT+CTRL, change to CTRL+a
+ *key += ('a' - 'A');
+ }
}
if (mod_mask & MOD_MASK_ALT) {
*statep |= VTERM_MOD_ALT;
}
- switch (key) {
+ switch (*key) {
case K_S_TAB:
case K_S_UP:
case K_S_DOWN:
@@ -1371,11 +1503,11 @@ static void convert_modifiers(int key, VTermModifier *statep)
}
}
-static VTermKey convert_key(int key, VTermModifier *statep)
+static VTermKey convert_key(int *key, VTermModifier *statep)
{
convert_modifiers(key, statep);
- switch (key) {
+ switch (*key) {
case K_BS:
return VTERM_KEY_BACKSPACE;
case K_S_TAB:
@@ -1678,8 +1810,6 @@ static bool send_mouse_event(Terminal *term, int c)
pressed = true; FALLTHROUGH;
case K_LEFTRELEASE:
button = 1; break;
- case K_MOUSEMOVE:
- button = 0; break;
case K_MIDDLEDRAG:
case K_MIDDLEMOUSE:
pressed = true; FALLTHROUGH;
@@ -1690,6 +1820,16 @@ static bool send_mouse_event(Terminal *term, int c)
pressed = true; FALLTHROUGH;
case K_RIGHTRELEASE:
button = 3; break;
+ case K_X1DRAG:
+ case K_X1MOUSE:
+ pressed = true; FALLTHROUGH;
+ case K_X1RELEASE:
+ button = 8; break;
+ case K_X2DRAG:
+ case K_X2MOUSE:
+ pressed = true; FALLTHROUGH;
+ case K_X2RELEASE:
+ button = 9; break;
case K_MOUSEDOWN:
pressed = true; button = 4; break;
case K_MOUSEUP:
@@ -1698,12 +1838,14 @@ static bool send_mouse_event(Terminal *term, int c)
pressed = true; button = 7; break;
case K_MOUSERIGHT:
pressed = true; button = 6; break;
+ case K_MOUSEMOVE:
+ button = 0; break;
default:
return false;
}
VTermModifier mod = VTERM_MOD_NONE;
- convert_modifiers(c, &mod);
+ convert_modifiers(&c, &mod);
mouse_action(term, button, row, col - offset, pressed, mod);
return false;
}
@@ -1776,12 +1918,8 @@ static void fetch_row(Terminal *term, int row, int end_col)
while (col < end_col) {
VTermScreenCell cell;
fetch_cell(term, row, col, &cell);
- if (cell.chars[0]) {
- int cell_len = 0;
- for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell.chars[i]; i++) {
- cell_len += utf_char2bytes((int)cell.chars[i], ptr + cell_len);
- }
- ptr += cell_len;
+ if (cell.schar) {
+ schar_get_adv(&ptr, cell.schar);
line_len = (size_t)(ptr - term->textbuf);
} else {
*ptr++ = ' ';
@@ -1802,7 +1940,7 @@ static bool fetch_cell(Terminal *term, int row, int col, VTermScreenCell *cell)
} else {
// fill the pointer with an empty cell
*cell = (VTermScreenCell) {
- .chars = { 0 },
+ .schar = 0,
.width = 1,
};
return false;
@@ -1848,12 +1986,49 @@ static void refresh_terminal(Terminal *term)
refresh_size(term, buf);
refresh_scrollback(term, buf);
refresh_screen(term, buf);
+ refresh_cursor(term);
aucmd_restbuf(&aco);
int ml_added = buf->b_ml.ml_line_count - ml_before;
adjust_topline(term, buf, ml_added);
}
+static void refresh_cursor(Terminal *term)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (!is_focused(term) || !term->pending.cursor) {
+ return;
+ }
+ term->pending.cursor = false;
+
+ if (term->cursor.blink) {
+ // For the TUI, this value doesn't actually matter, as long as it's non-zero. The terminal
+ // emulator dictates the blink frequency, not the application.
+ // For GUIs we just pick an arbitrary value, for now.
+ shape_table[SHAPE_IDX_TERM].blinkon = 500;
+ shape_table[SHAPE_IDX_TERM].blinkoff = 500;
+ } else {
+ shape_table[SHAPE_IDX_TERM].blinkon = 0;
+ shape_table[SHAPE_IDX_TERM].blinkoff = 0;
+ }
+
+ switch (term->cursor.shape) {
+ case VTERM_PROP_CURSORSHAPE_BLOCK:
+ shape_table[SHAPE_IDX_TERM].shape = SHAPE_BLOCK;
+ break;
+ case VTERM_PROP_CURSORSHAPE_UNDERLINE:
+ shape_table[SHAPE_IDX_TERM].shape = SHAPE_HOR;
+ shape_table[SHAPE_IDX_TERM].percentage = 20;
+ break;
+ case VTERM_PROP_CURSORSHAPE_BAR_LEFT:
+ shape_table[SHAPE_IDX_TERM].shape = SHAPE_VER;
+ shape_table[SHAPE_IDX_TERM].percentage = 25;
+ break;
+ }
+
+ ui_mode_info_set();
+}
+
/// Calls refresh_terminal() on all invalidated_terminals.
static void refresh_timer_cb(TimeWatcher *watcher, void *data)
{
@@ -1874,11 +2049,11 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data)
static void refresh_size(Terminal *term, buf_T *buf)
{
- if (!term->pending_resize || term->closed) {
+ if (!term->pending.resize || term->closed) {
return;
}
- term->pending_resize = false;
+ term->pending.resize = false;
int width, height;
vterm_get_size(term->vt, &height, &width);
term->invalid_start = 0;