aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/terminal.c75
-rw-r--r--test/functional/terminal/cursor_spec.lua140
2 files changed, 133 insertions, 82 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);
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 8ebad14f15..93e55bb103 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -149,6 +149,73 @@ describe(':terminal cursor', function()
|*5
{3:-- TERMINAL --} |
]])
+
+ -- Cursor is hidden; now request to show it while in a TermLeave autocmd.
+ -- Process events (via :sleep) to handle the escape sequence now.
+ command([[autocmd TermLeave * ++once call chansend(&channel, "\e[?25h") | sleep 1m]])
+ feed([[<C-\><C-N>]]) -- Exit terminal mode; cursor should not remain hidden
+ screen:expect([[
+ tty ready |
+ ^ |
+ |*5
+ ]])
+
+ command('bwipeout! | let chan = nvim_open_term(0, {})')
+ feed('i')
+ -- Hide the cursor, switch to a non-terminal buffer, then show the cursor; it shouldn't remain
+ -- hidden after we're kicked out of terminal mode in the new buffer.
+ -- Must ensure these actions happen within the same terminal_execute call. The stream is
+ -- internal, so polling the event loop isn't necessary (terminal_receive is directly called).
+ command([[call chansend(chan, "\e[?25l") | new floob | call chansend(chan, "\e[?25h")]])
+ screen:expect([[
+ ^ |
+ {4:~ }|
+ {5:floob }|
+ |*2
+ {18:[Scratch] }|
+ |
+ ]])
+
+ feed('<C-W>pi')
+ screen:expect([[
+ |
+ {4:~ }|
+ {1:floob }|
+ ^ |
+ |
+ {17:[Scratch] }|
+ {3:-- TERMINAL --} |
+ ]])
+ end)
+
+ it('becomes visible on TermLeave if hidden immediately by events #32456', function()
+ skip(is_os('win'), '#31587')
+ -- Reproducing the issue is quite fragile; it's easiest done in a lone test case like this
+ -- with no prior commands.
+ feed([[<C-\><C-N>]])
+ screen:expect([[
+ tty ready |
+ ^ |
+ |*5
+ ]])
+
+ -- Hide the cursor such that the escape sequence is processed as a side effect of showmode in
+ -- terminal_enter handling events (skip_showmode -> char_avail -> vpeekc -> os_breakcheck).
+ -- This requires a particular set of actions; :startinsert repros better than feed('i') here.
+ hide_cursor()
+ command('mode | startinsert')
+ screen:expect([[
+ tty ready |
+ |*5
+ {3:-- TERMINAL --} |
+ ]])
+
+ feed([[<C-\><C-N>]])
+ screen:expect([[
+ tty ready |
+ ^ |
+ |*5
+ ]])
end)
end)
@@ -358,55 +425,50 @@ describe(':terminal cursor', function()
eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id)
end)
- it('restores visibility on TermLeave #32456', function()
- skip(is_os('win'), '#31587')
- feed([[<C-\><C-N>]]) -- Exit terminal mode
- screen:expect([[
- tty ready |
- ^ |
- |*5
- ]])
-
- tt.hide_cursor()
- -- :startinsert repros the issue more reliably than feed('i')
- command('mode | startinsert')
- screen:expect([[
- tty ready |
- |*5
- {3:-- TERMINAL --} |
- ]])
-
- feed([[<C-\><C-N>]]) -- Exit terminal mode
- screen:expect([[
- tty ready |
- ^ |
- |*5
+ it('uses the correct attributes', function()
+ feed([[<C-\><C-N>]])
+ command([[
+ bwipeout!
+ let chan1 = nvim_open_term(0, {})
+ vnew
+ let chan2 = nvim_open_term(0, {})
]])
-
feed('i')
screen:expect([[
- tty ready |
- |*5
+ ^ │ |
+ │ |*4
+ {17:[Scratch] }{18:[Scratch] }|
{3:-- TERMINAL --} |
]])
+ eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
+ eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+ eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
- -- Cursor currently hidden; request to show it while in a TermLeave autocmd.
- -- Process events (via :sleep) to handle the escape sequence immediately.
- command([[autocmd TermLeave * ++once call chansend(b:terminal_job_id, "\e[?25h") | sleep 1m]])
- feed([[<C-\><C-N>]]) -- Exit terminal mode
- screen:expect([[
- tty ready |
- ^ |
- |*5
- ]])
+ -- Modify cursor in the non-current terminal; should not affect this cursor.
+ command([[call chansend(chan1, "\e[4 q")]])
+ screen:expect_unchanged()
+ eq('block', screen._mode_info[terminal_mode_idx].cursor_shape)
+ eq(500, screen._mode_info[terminal_mode_idx].blinkon)
+ eq(500, screen._mode_info[terminal_mode_idx].blinkoff)
- feed('i')
+ -- Modify cursor in the current terminal.
+ command([[call chansend(chan2, "\e[6 q")]])
+ screen:expect_unchanged()
+ eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape)
+ eq(0, screen._mode_info[terminal_mode_idx].blinkon)
+ eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
+
+ -- Check the cursor in the other terminal reflects our changes from before.
+ command('wincmd p')
screen:expect([[
- tty ready |
- ^ |
- |*4
+ │^ |
+ │ |*4
+ {18:[Scratch] }{17:[Scratch] }|
{3:-- TERMINAL --} |
]])
+ eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape)
+ eq(0, screen._mode_info[terminal_mode_idx].blinkon)
+ eq(0, screen._mode_info[terminal_mode_idx].blinkoff)
end)
end)