From 8516c2dc1f301c439695629fff771227dbe00d30 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 23 Nov 2024 14:22:06 +0600 Subject: refactor(options): autogenerate valid values and flag enums for options (#31089) Problem: Option metadata like list of valid values for an option and option flags are not listed in the `options.lua` file and are instead manually defined in C, which means option metadata is split between several places. Solution: Put metadata such as list of valid values for an option and option flags in `options.lua`, and autogenerate the corresponding C variables and enums. Supersedes #28659 Co-authored-by: glepnir --- src/nvim/terminal.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 5ff7f721ba..b4496d6758 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -598,12 +598,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; } @@ -868,28 +868,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); @@ -1181,7 +1181,7 @@ 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; } -- cgit From d1fd674df3eb07049c780d6b7821e17d471fdc4c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 10 Dec 2024 14:53:02 +0800 Subject: fix(ui): update title in more cases (#31508) --- src/nvim/terminal.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index b4496d6758..6d4af0fc57 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -619,6 +619,7 @@ bool terminal_enter(void) invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); showmode(); curwin->w_redr_status = true; // For mode() in statusline. #8323 + redraw_custom_title_later(); ui_busy_start(); apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); may_trigger_modechanged(); -- cgit From 0dd933265ff2e64786fd30f949e767e10f401519 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 17 Dec 2024 07:11:41 -0600 Subject: feat(terminal)!: cursor shape and blink (#31562) When a terminal application running inside the terminal emulator sets the cursor shape or blink status of the cursor, update the cursor in the parent terminal to match. This removes the "virtual cursor" that has been in use by the terminal emulator since the beginning. The original rationale for using the virtual cursor was to avoid having to support additional UI methods to change the cursor color for other (non-TUI) UIs, instead relying on the TermCursor and TermCursorNC highlight groups. The TermCursor highlight group is now used in the default 'guicursor' value, which has a new entry for Terminal mode. However, the TermCursorNC highlight group is no longer supported: since terminal windows now use the real cursor, when the window is not focused there is no cursor displayed in the window at all, so there is nothing to highlight. Users can still use the StatusLineTermNC highlight group to differentiate non-focused terminal windows. BREAKING CHANGE: The TermCursorNC highlight group is no longer supported. --- src/nvim/terminal.c | 184 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 151 insertions(+), 33 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 6d4af0fc57..fa08f3d6ca 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" @@ -160,14 +161,18 @@ 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; - // 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 @@ -207,24 +212,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) @@ -363,7 +368,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 +392,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 +592,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); } @@ -614,16 +641,28 @@ bool terminal_enter(void) curwin->w_p_so = 0; curwin->w_p_siso = 0; + // Save the existing cursor entry since it may be modified by the application + cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM]; + + // 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 redraw_custom_title_later(); - ui_busy_start(); + 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); @@ -635,6 +674,9 @@ bool terminal_enter(void) RedrawingDisabled = s->save_rd; apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); + shape_table[SHAPE_IDX_TERM] = save_cursorentry; + ui_mode_info_set(); + if (save_curwin == curwin->handle) { // Else: window was closed. curwin->w_p_cul = save_w_p_cul; if (save_w_p_culopt) { @@ -649,8 +691,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(); } @@ -659,7 +702,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; @@ -810,6 +857,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, @@ -857,8 +917,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); @@ -1063,14 +1123,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; } } @@ -1085,6 +1137,17 @@ bool terminal_running(const Terminal *term) return !term->closed; } +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 {{{ @@ -1106,8 +1169,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; } @@ -1135,8 +1197,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: { @@ -1172,6 +1243,18 @@ 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; + default: return 0; } @@ -1849,12 +1932,47 @@ 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; + break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: + shape_table[SHAPE_IDX_TERM].shape = SHAPE_VER; + break; + } + + ui_mode_info_set(); +} + /// Calls refresh_terminal() on all invalidated_terminals. static void refresh_timer_cb(TimeWatcher *watcher, void *data) { @@ -1875,11 +1993,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; -- cgit From 3db3947b0ed256d498b09eef95c4969e28387e66 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 18 Dec 2024 11:41:05 -0600 Subject: fix(terminal): restore cursor from 'guicursor' on TermLeave (#31620) Fixes: https://github.com/neovim/neovim/issues/31612 --- src/nvim/terminal.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index fa08f3d6ca..969c539f97 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -641,9 +641,6 @@ bool terminal_enter(void) curwin->w_p_so = 0; curwin->w_p_siso = 0; - // Save the existing cursor entry since it may be modified by the application - cursorentry_T save_cursorentry = shape_table[SHAPE_IDX_TERM]; - // Update the cursor shape table and flush changes to the UI s->term->pending.cursor = true; refresh_cursor(s->term); @@ -674,8 +671,8 @@ bool terminal_enter(void) RedrawingDisabled = s->save_rd; apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); - shape_table[SHAPE_IDX_TERM] = save_cursorentry; - ui_mode_info_set(); + // 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; -- cgit From 8ef41f590224dfeea2e51d9fec150e363fd72ee0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 19 Dec 2024 07:07:04 -0800 Subject: feat(jobs): jobstart(…,{term=true}), deprecate termopen() #31343 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: `termopen` has long been a superficial wrapper around `jobstart`, and has no real purpose. Also, `vim.system` and `nvim_open_term` presumably will replace all features of `jobstart` and `termopen`, so centralizing the logic will help with that. Solution: - Introduce `eval/deprecated.c`, where all deprecated eval funcs will live. - Introduce "term" flag of `jobstart`. - Deprecate `termopen`. --- 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 969c539f97..61eede5ae1 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -357,7 +357,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. -- cgit From c51bf5a6b24928ac04d0bb129b1b424d4c78f28d Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 23 Dec 2024 15:39:36 -0600 Subject: fix(terminal): set cursor cell percentage (#31703) Fixes: https://github.com/neovim/neovim/issues/31685 --- src/nvim/terminal.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 61eede5ae1..673a6ef4be 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1961,9 +1961,11 @@ static void refresh_cursor(Terminal *term) 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; } -- cgit From e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 18 Dec 2024 14:49:38 +0100 Subject: feat(terminal): support grapheme clusters, including emoji --- src/nvim/terminal.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 673a6ef4be..0743eda374 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -65,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" @@ -1347,7 +1348,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; } @@ -1857,12 +1858,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++ = ' '; @@ -1883,7 +1880,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; -- cgit From d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 3 Jan 2025 15:40:46 +0100 Subject: refactor: adopt vterm We have changed too much to consider it a mere bundled dependency (such as unicode handling in e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d), and can consider it our own at this point. --- src/nvim/terminal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 0743eda374..d7ed709906 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -93,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; -- cgit From 913e81c35f162c1e2647565397608f63f38d7043 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 9 Jan 2025 14:05:40 +0100 Subject: fix(getchar): do not simplify keycodes in terminal mode The code represents a useful pattern in normal mode where remapping `` will implicitly also remap `` unless you remap that explicitly. This relies on the _unmapped_ behavior being identical which is not true in terminal mode, as vterm can distinguish these keys. Vim seems to entangle this with kitty keyboard mode detection which is irrelevant for us. Conditional fallbacks depending on keyboard mode could be done completely inside `vterm/` without getchar.c getting involved, I would think. --- src/nvim/terminal.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index d7ed709906..ad343bad67 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1011,7 +1011,7 @@ 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) { vterm_keyboard_key(term->vt, key, mod); @@ -1415,19 +1415,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: @@ -1459,11 +1463,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: @@ -1791,7 +1795,7 @@ static bool send_mouse_event(Terminal *term, int c) } VTermModifier mod = VTERM_MOD_NONE; - convert_modifiers(c, &mod); + convert_modifiers(&c, &mod); mouse_action(term, button, row, col - offset, pressed, mod); return false; } -- cgit From f1c45fc7a4a595e460cd245172a5767bddeb09e9 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 14 Jan 2025 08:18:59 -0600 Subject: feat(terminal): support theme update notifications (DEC mode 2031) (#31999) --- src/nvim/terminal.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index ad343bad67..2ad5ac49ca 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -179,6 +179,8 @@ struct terminal { 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 + bool color_set[16]; char *selection_buffer; /// libvterm selection buffer @@ -193,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, }; @@ -1141,6 +1144,20 @@ 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 { @@ -1259,6 +1276,10 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) invalidate_terminal(term, -1, -1); break; + case VTERM_PROP_THEMEUPDATES: + term->theme_updates = val->boolean; + break; + default: return 0; } @@ -1273,6 +1294,14 @@ static int term_bell(void *data) 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; +} + /// Scrollback push handler: called just before a line goes offscreen (and libvterm will forget it), /// giving us a chance to store it. /// -- cgit From 6f0bde11ccd82d257fcda25ecad26227eba3335e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 15 Jan 2025 11:07:51 -0600 Subject: feat(terminal): add support for kitty keyboard protocol This commit adds basic support for the kitty keyboard protocol to Neovim's builtin terminal. For now only the first mode ("Disambiguate escape codes") is supported. --- src/nvim/terminal.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2ad5ac49ca..197a225209 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -783,7 +783,12 @@ 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: @@ -841,13 +846,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; } @@ -1016,7 +1021,7 @@ static void terminal_send_key(Terminal *term, int c) 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); -- cgit From 06a1f82f1cc37225b6acc46e63bd2eb36e034b1a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 21 Jan 2025 21:00:56 +0800 Subject: feat(terminal): forward X1 and X2 mouse events Ref: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons --- src/nvim/terminal.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'src/nvim/terminal.c') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 197a225209..897c393488 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -792,17 +792,23 @@ static int terminal_execute(VimState *state, int 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; } @@ -1804,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; @@ -1816,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: @@ -1824,6 +1838,8 @@ 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; } -- cgit