diff options
author | Sean Dewar <6256228+seandewar@users.noreply.github.com> | 2025-03-06 08:31:50 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-06 08:31:50 +0000 |
commit | fa46441264b28e9272973ead7bea65f27868e94c (patch) | |
tree | 8c58e641725b5f2c7046e7a49e33ad4147161bbe /src | |
parent | 41b07b128ccbdc445c3a56618b0c6ec6ed0f3438 (diff) | |
download | rneovim-fa46441264b28e9272973ead7bea65f27868e94c.tar.gz rneovim-fa46441264b28e9272973ead7bea65f27868e94c.tar.bz2 rneovim-fa46441264b28e9272973ead7bea65f27868e94c.zip |
fix(terminal): improve cursor refresh handling (#32596)
Problem: terminal mode cursor refresh logic has too many edge cases where it
fails when events change curbuf.
Solution: change the logic. Introduce cursor_visible to TerminalState to more
reliably track if terminal mode has changed busy. Move visibility handling to
refresh_cursor and move its call in refresh_terminal to terminal_check to avoid
temporarily changed curbufs from influencing cursor state.
This has the effect of "debouncing" shape/visibility updates to once per
terminal state tick (with the final attributes taking effect, as expected). I
think this is OK, but as a result it may also be warranted to update when
redrawing during the same state tick (e.g: from events executing :redraw); this
can be added later, if wanted.
Also move previous tests to a more appropriate place.
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/terminal.c | 75 |
1 files changed, 32 insertions, 43 deletions
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index e0ebcc05b8..8300b536e2 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -106,10 +106,11 @@ typedef struct { VimState state; Terminal *term; - int save_rd; // saved value of RedrawingDisabled + int save_rd; ///< saved value of RedrawingDisabled bool close; - bool got_bsl; // if the last input was <C-\> - bool got_bsl_o; // if left terminal mode with <c-\><c-o> + bool got_bsl; ///< if the last input was <C-\> + bool got_bsl_o; ///< if left terminal mode with <c-\><c-o> + bool cursor_visible; ///< cursor's current visibility; ensures matched busy_start/stop UI events } TerminalState; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -673,6 +674,7 @@ bool terminal_enter(void) assert(buf->terminal); // Should only be called when curbuf has a terminal. TerminalState s[1] = { 0 }; s->term = buf->terminal; + s->cursor_visible = true; // Assume visible; may change via refresh_cursor later. stop_insert_mode = false; // Ensure the terminal is properly sized. Ideally window size management @@ -685,10 +687,6 @@ bool terminal_enter(void) State = MODE_TERMINAL; mapped_ctrl_c |= MODE_TERMINAL; // Always map CTRL-C to avoid interrupt. RedrawingDisabled = false; - if (!s->term->cursor.visible) { - // Hide cursor if it should be hidden. Do so right after setting State, before events. - ui_busy_start(); - } // Disable these options in terminal-mode. They are nonsense because cursor is // placed at end of buffer to "follow" output. #11072 @@ -715,10 +713,7 @@ 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); - + s->term->pending.cursor = true; // Update the cursor shape table adjust_topline(s->term, buf, 0); // scroll to end showmode(); curwin->w_redr_status = true; // For mode() in statusline. #8323 @@ -739,8 +734,8 @@ bool terminal_enter(void) } State = save_state; RedrawingDisabled = s->save_rd; - if (!s->term->cursor.visible) { - // If cursor was hidden, show it again. Do so right after restoring State, before events. + if (!s->cursor_visible) { + // If cursor was hidden, show it again. Do so right after restoring State. ui_busy_stop(); } @@ -805,10 +800,13 @@ static void terminal_check_cursor(void) // 0 if the main loop must exit static int terminal_check(VimState *state) { + TerminalState *const s = (TerminalState *)state; + if (stop_insert_mode) { return 0; } + assert(s->term == curbuf->terminal); terminal_check_cursor(); validate_cursor(curwin); @@ -835,6 +833,7 @@ static int terminal_check(VimState *state) } setcursor(); + refresh_cursor(s->term, &s->cursor_visible); ui_flush(); return 1; } @@ -937,23 +936,9 @@ static int terminal_execute(VimState *state, int key) } 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, - curbuf->terminal->cursor.row + 1); s->term = curbuf->terminal; + s->term->pending.cursor = true; + invalidate_terminal(s->term, -1, -1); } return 1; } @@ -1291,17 +1276,8 @@ 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, -1, -1); break; case VTERM_PROP_TITLE: { @@ -2042,23 +2018,35 @@ static void refresh_terminal(Terminal *term) } linenr_T ml_before = buf->b_ml.ml_line_count; - // refresh_ functions assume the terminal buffer is current + // Some refresh_ functions assume the terminal buffer is current. Don't call refresh_cursor here, + // as we don't want a terminal that was possibly made temporarily current influencing the cursor. aco_save_T aco; aucmd_prepbuf(&aco, buf); 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) +static void refresh_cursor(Terminal *term, bool *cursor_visible) FUNC_ATTR_NONNULL_ALL { - if (!is_focused(term) || !term->pending.cursor) { + if (!is_focused(term)) { + return; + } + if (term->cursor.visible != *cursor_visible) { + *cursor_visible = term->cursor.visible; + if (*cursor_visible) { + ui_busy_stop(); + } else { + ui_busy_start(); + } + } + + if (!term->pending.cursor) { return; } term->pending.cursor = false; @@ -2166,6 +2154,7 @@ static void adjust_scrollback(Terminal *term, buf_T *buf) // Refresh the scrollback of an invalidated terminal. static void refresh_scrollback(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(seandewar): remove this condition int width, height; vterm_get_size(term->vt, &height, &width); |