diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/cursor.c | 2 | ||||
-rw-r--r-- | src/nvim/cursor_shape.c | 3 | ||||
-rw-r--r-- | src/nvim/cursor_shape.h | 3 | ||||
-rw-r--r-- | src/nvim/highlight.h | 1 | ||||
-rw-r--r-- | src/nvim/highlight_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/highlight_group.c | 1 | ||||
-rw-r--r-- | src/nvim/option_vars.h | 2 | ||||
-rw-r--r-- | src/nvim/options.lua | 8 | ||||
-rw-r--r-- | src/nvim/state.c | 10 | ||||
-rw-r--r-- | src/nvim/terminal.c | 184 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 2 |
11 files changed, 174 insertions, 43 deletions
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 2b18c51571..a248a4133e 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -341,9 +341,11 @@ void check_cursor_col(win_T *win) } else if (win->w_cursor.col >= len) { // Allow cursor past end-of-line when: // - in Insert mode or restarting Insert mode + // - in Terminal mode // - in Visual mode and 'selection' isn't "old" // - 'virtualedit' is set if ((State & MODE_INSERT) || restart_edit + || (State & MODE_TERMINAL) || (VIsual_active && *p_sel != 'o') || (cur_ve_flags & kOptVeFlagOnemore) || virtual_active(win)) { diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 1f11367618..a058394b9f 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -45,6 +45,7 @@ cursorentry_T shape_table[SHAPE_IDX_COUNT] = { { "more", 0, 0, 0, 0, 0, 0, 0, 0, "m", SHAPE_MOUSE }, { "more_lastline", 0, 0, 0, 0, 0, 0, 0, 0, "ml", SHAPE_MOUSE }, { "showmatch", 0, 0, 0, 100, 100, 100, 0, 0, "sm", SHAPE_CURSOR }, + { "terminal", 0, 0, 0, 0, 0, 0, 0, 0, "t", SHAPE_CURSOR }, }; /// Converts cursor_shapes into an Array of Dictionaries @@ -321,6 +322,8 @@ int cursor_get_mode_idx(void) { if (State == MODE_SHOWMATCH) { return SHAPE_IDX_SM; + } else if (State == MODE_TERMINAL) { + return SHAPE_IDX_TERM; } else if (State & VREPLACE_FLAG) { return SHAPE_IDX_R; } else if (State & REPLACE_FLAG) { diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h index 21967a81f4..6d9e7de2e5 100644 --- a/src/nvim/cursor_shape.h +++ b/src/nvim/cursor_shape.h @@ -23,7 +23,8 @@ typedef enum { SHAPE_IDX_MORE = 14, ///< Hit-return or More SHAPE_IDX_MOREL = 15, ///< Hit-return or More in last line SHAPE_IDX_SM = 16, ///< showing matching paren - SHAPE_IDX_COUNT = 17, + SHAPE_IDX_TERM = 17, ///< Terminal mode + SHAPE_IDX_COUNT = 18, } ModeShape; typedef enum { diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index 1be70100de..a89d778474 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -15,7 +15,6 @@ EXTERN const char *hlf_names[] INIT( = { [HLF_8] = "SpecialKey", [HLF_EOB] = "EndOfBuffer", [HLF_TERM] = "TermCursor", - [HLF_TERMNC] = "TermCursorNC", [HLF_AT] = "NonText", [HLF_D] = "Directory", [HLF_E] = "ErrorMsg", diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index a3c4062714..cbbc28311f 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -63,7 +63,6 @@ typedef enum { ///< displayed different from what it is HLF_EOB, ///< after the last line in the buffer HLF_TERM, ///< terminal cursor focused - HLF_TERMNC, ///< terminal cursor unfocused HLF_AT, ///< @ characters at end of screen, characters that don't really exist in the text HLF_D, ///< directories in CTRL-D listing HLF_E, ///< error messages diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index cc1b833c3c..f1f5f47630 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -178,7 +178,6 @@ static const char *highlight_init_both[] = { "default link StatusLineTermNC StatusLineNC", "default link TabLine StatusLineNC", "default link TabLineFill TabLine", - "default link TermCursorNC NONE", "default link VertSplit WinSeparator", "default link VisualNOS Visual", "default link Whitespace NonText", diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 97455380cc..f4d0a9a4b0 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -12,7 +12,7 @@ // option_vars.h: definition of global variables for settable options #define HIGHLIGHT_INIT \ - "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ + "8:SpecialKey,~:EndOfBuffer,z:TermCursor,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \ "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \ "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \ diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 6dc32658fe..3142c30080 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -3631,7 +3631,9 @@ return { { abbreviation = 'gcr', cb = 'did_set_guicursor', - defaults = { if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20' }, + defaults = { + if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20,t:block-blinkon500-blinkoff500-TermCursor', + }, deny_duplicates = true, desc = [=[ Configures the cursor style for each mode. Works in the GUI and many @@ -3660,6 +3662,7 @@ return { ci Command-line Insert mode cr Command-line Replace mode sm showmatch in Insert mode + t Terminal mode a all modes The argument-list is a dash separated list of these arguments: hor{N} horizontal bar, {N} percent of the character height @@ -3676,7 +3679,8 @@ return { cursor is not shown. Times are in msec. When one of the numbers is zero, there is no blinking. E.g.: >vim set guicursor=n:blinkon0 - < - Default is "blinkon0" for each mode. + < + Default is "blinkon0" for each mode. {group-name} Highlight group that decides the color and font of the cursor. diff --git a/src/nvim/state.c b/src/nvim/state.c index 32e2a8d652..c4041dda07 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -138,14 +138,20 @@ void state_handle_k_event(void) /// Return true if in the current mode we need to use virtual. bool virtual_active(win_T *wp) { - unsigned cur_ve_flags = get_ve_flags(wp); - // While an operator is being executed we return "virtual_op", because // VIsual_active has already been reset, thus we can't check for "block" // being used. if (virtual_op != kNone) { return virtual_op; } + + // In Terminal mode the cursor can be positioned anywhere by the application + if (State & MODE_TERMINAL) { + return true; + } + + unsigned cur_ve_flags = get_ve_flags(wp); + return cur_ve_flags == kOptVeFlagAll || ((cur_ve_flags & kOptVeFlagBlock) && VIsual_active && VIsual_mode == Ctrl_V) || ((cur_ve_flags & kOptVeFlagInsert) && (State & MODE_INSERT)); 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; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 603db5b891..d514a597b1 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1339,7 +1339,7 @@ static void tui_set_mode(TUIData *tui, ModeShape mode) case SHAPE_VER: shape = 5; break; } - UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0)); + UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0 || c.blinkoff == 0)); unibi_out_ext(tui, tui->unibi_ext.set_cursor_style); } |