diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index e83ab5bd94..78ebd72e4d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -112,6 +112,16 @@ typedef struct { bool got_bsl; ///< if the last input was bool got_bsl_o; ///< if left terminal mode with bool cursor_visible; ///< cursor's current visibility; ensures matched busy_start/stop UI events + + // These fields remember the prior values of window options before entering terminal mode. + // Valid only when save_curwin_handle != 0. + handle_T save_curwin_handle; + bool save_w_p_cul; + char *save_w_p_culopt; + uint8_t save_w_p_culopt_flags; + int save_w_p_cuc; + OptInt save_w_p_so; + OptInt save_w_p_siso; } TerminalState; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -671,6 +681,75 @@ void terminal_check_size(Terminal *term) invalidate_terminal(term, -1, -1); } +static void set_terminal_winopts(TerminalState *const s) + FUNC_ATTR_NONNULL_ALL +{ + assert(s->save_curwin_handle == 0); + + // Disable these options in terminal-mode. They are nonsense because cursor is + // placed at end of buffer to "follow" output. #11072 + s->save_curwin_handle = curwin->handle; + s->save_w_p_cul = curwin->w_p_cul; + s->save_w_p_culopt = NULL; + s->save_w_p_culopt_flags = curwin->w_p_culopt_flags; + s->save_w_p_cuc = curwin->w_p_cuc; + s->save_w_p_so = curwin->w_p_so; + s->save_w_p_siso = curwin->w_p_siso; + + if (curwin->w_p_cul && curwin->w_p_culopt_flags & kOptCuloptFlagNumber) { + if (!strequal(curwin->w_p_culopt, "number")) { + s->save_w_p_culopt = curwin->w_p_culopt; + curwin->w_p_culopt = xstrdup("number"); + } + curwin->w_p_culopt_flags = kOptCuloptFlagNumber; + } else { + curwin->w_p_cul = false; + } + curwin->w_p_cuc = false; + curwin->w_p_so = 0; + curwin->w_p_siso = 0; + + if (curwin->w_p_cuc != s->save_w_p_cuc) { + redraw_later(curwin, UPD_SOME_VALID); + } else if (curwin->w_p_cul != s->save_w_p_cul + || (curwin->w_p_cul && curwin->w_p_culopt_flags != s->save_w_p_culopt_flags)) { + redraw_later(curwin, UPD_VALID); + } +} + +static void unset_terminal_winopts(TerminalState *const s) + FUNC_ATTR_NONNULL_ALL +{ + assert(s->save_curwin_handle != 0); + + win_T *const wp = handle_get_window(s->save_curwin_handle); + if (!wp) { + free_string_option(s->save_w_p_culopt); + s->save_curwin_handle = 0; + return; + } + + if (win_valid(wp)) { // No need to redraw if window not in curtab. + if (s->save_w_p_cuc != wp->w_p_cuc) { + redraw_later(wp, UPD_SOME_VALID); + } else if (s->save_w_p_cul != wp->w_p_cul + || (s->save_w_p_cul && s->save_w_p_culopt_flags != wp->w_p_culopt_flags)) { + redraw_later(wp, UPD_VALID); + } + } + + wp->w_p_cul = s->save_w_p_cul; + if (s->save_w_p_culopt) { + free_string_option(wp->w_p_culopt); + wp->w_p_culopt = s->save_w_p_culopt; + } + wp->w_p_culopt_flags = s->save_w_p_culopt_flags; + wp->w_p_cuc = s->save_w_p_cuc; + wp->w_p_so = s->save_w_p_so; + wp->w_p_siso = s->save_w_p_siso; + s->save_curwin_handle = 0; +} + /// Implements MODE_TERMINAL state. :help Terminal-mode bool terminal_enter(void) { @@ -692,33 +771,7 @@ bool terminal_enter(void) mapped_ctrl_c |= MODE_TERMINAL; // Always map CTRL-C to avoid interrupt. RedrawingDisabled = false; - // Disable these options in terminal-mode. They are nonsense because cursor is - // placed at end of buffer to "follow" output. #11072 - handle_T save_curwin = curwin->handle; - bool save_w_p_cul = curwin->w_p_cul; - char *save_w_p_culopt = NULL; - uint8_t save_w_p_culopt_flags = curwin->w_p_culopt_flags; - 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 & 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 = kOptCuloptFlagNumber; - } else { - curwin->w_p_cul = false; - } - curwin->w_p_cuc = false; - curwin->w_p_so = 0; - curwin->w_p_siso = 0; - if (curwin->w_p_cuc != save_w_p_cuc) { - redraw_later(curwin, UPD_SOME_VALID); - } else if (curwin->w_p_cul != save_w_p_cul - || (curwin->w_p_cul && curwin->w_p_culopt_flags != save_w_p_culopt_flags)) { - redraw_later(curwin, UPD_VALID); - } + set_terminal_winopts(s); s->term->pending.cursor = true; // Update the cursor shape table adjust_topline(s->term, buf, 0); // scroll to end @@ -749,25 +802,7 @@ bool terminal_enter(void) // 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. - if (save_w_p_cuc != curwin->w_p_cuc) { - redraw_later(curwin, UPD_SOME_VALID); - } else if (save_w_p_cul != curwin->w_p_cul - || (save_w_p_cul && save_w_p_culopt_flags != curwin->w_p_culopt_flags)) { - redraw_later(curwin, UPD_VALID); - } - curwin->w_p_cul = save_w_p_cul; - if (save_w_p_culopt) { - free_string_option(curwin->w_p_culopt); - curwin->w_p_culopt = save_w_p_culopt; - } - curwin->w_p_culopt_flags = save_w_p_culopt_flags; - curwin->w_p_cuc = save_w_p_cuc; - curwin->w_p_so = save_w_p_so; - curwin->w_p_siso = save_w_p_siso; - } else if (save_w_p_culopt) { - free_string_option(save_w_p_culopt); - } + unset_terminal_winopts(s); // Tell the terminal it lost focus terminal_focus(s->term, false); @@ -954,11 +989,19 @@ static int terminal_execute(VimState *state, int key) if (curbuf->terminal == NULL) { return 0; } + if (s->save_curwin_handle != curwin->handle) { + // Terminal window changed, update window options. + unset_terminal_winopts(s); + set_terminal_winopts(s); + } if (s->term != curbuf->terminal) { // Active terminal buffer changed, flush terminal's cursor state to the UI + terminal_focus(s->term, false); + s->term = curbuf->terminal; s->term->pending.cursor = true; invalidate_terminal(s->term, -1, -1); + terminal_focus(s->term, true); } return 1; } diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 943e445c25..16736db484 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -343,6 +343,73 @@ describe(':terminal buffer', function() |*4 ]]) end) + + it('reports focus notifications when requested', function() + feed([[]]) + exec_lua(function() + local function new_test_term() + local chan = vim.api.nvim_open_term(0, { + on_input = function(_, term, buf, data) + if data == '\27[I' then + vim.api.nvim_chan_send(term, 'focused\n') + elseif data == '\27[O' then + vim.api.nvim_chan_send(term, 'unfocused\n') + end + end, + }) + vim.api.nvim_chan_send(chan, '\27[?1004h') -- Enable focus reporting + end + + vim.cmd 'edit bar' + new_test_term() + vim.cmd 'vnew foo' + new_test_term() + vim.cmd 'vsplit' + end) + screen:expect([[ + ^ │ │ | + │ │ |*4 + {17:foo }{18:foo bar }| + | + ]]) + + feed('i') + screen:expect([[ + focused │focused │ | + ^ │ │ | + │ │ |*3 + {17:foo }{18:foo bar }| + {3:-- TERMINAL --} | + ]]) + -- Next window has the same terminal; no new notifications. + command('wincmd w') + screen:expect([[ + focused │focused │ | + │^ │ | + │ │ |*3 + {18:foo }{17:foo }{18:bar }| + {3:-- TERMINAL --} | + ]]) + -- Next window has a different terminal; expect new unfocus and focus notifications. + command('wincmd w') + screen:expect([[ + focused │focused │focused | + unfocused │unfocuse│^ | + │ │ |*3 + {18:foo foo }{17:bar }| + {3:-- TERMINAL --} | + ]]) + -- Leaving terminal mode; expect a new unfocus notification. + feed([[]]) + screen:expect([[ + focused │focused │focused | + unfocused │unfocuse│unfocused | + │ │^ | + │ │ |*2 + {18:foo foo }{17:bar }| + | + ]]) + end) end) describe(':terminal buffer', function() diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 2dcaf203d4..8d07323b45 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -415,6 +415,173 @@ describe(':terminal window', function() ]]) end) + it('restores window options when switching terminals', function() + -- Make this a screen test to also check for proper redrawing. + screen:set_default_attr_ids({ + [1] = { bold = true }, + [2] = { foreground = Screen.colors.Gray0, background = 7, underline = true }, + [3] = { foreground = 5, background = 7, underline = true }, + [4] = { reverse = true }, + [5] = { bold = true, foreground = 5 }, + [6] = { foreground = 12 }, + [7] = { reverse = true, bold = true }, + [12] = { underline = true }, + [17] = { foreground = Screen.colors.Gray0, background = 2 }, + [18] = { foreground = 8, background = 2 }, + [19] = { background = 7 }, + }) + + feed([[]]) + command([[ + file foo + setlocal cursorline + vsplit + setlocal nocursorline cursorcolumn + ]]) + screen:expect([[ + {19:t}ty ready │tty ready | + ^rows: 5, cols: 25 │{12:rows: 5, cols: 25 }| + {19: } │ |*3 + {17:foo }{18:foo }| + | + ]]) + + feed('i') + screen:expect([[ + tty ready │tty ready | + rows: 5, cols: 25 │{12:rows: 5, cols: 25 }| + ^ │ | + │ |*2 + {17:foo }{18:foo }| + {1:-- TERMINAL --} | + ]]) + command('wincmd p') + screen:expect([[ + {19:t}ty ready │tty ready | + {19:r}ows: 5, cols: 25 │rows: 5, cols: 25 | + │^ | + {19: } │ |*2 + {18:foo }{17:foo }| + {1:-- TERMINAL --} | + ]]) + feed([[]]) + screen:expect([[ + {19:t}ty ready │tty ready | + {19:r}ows: 5, cols: 25 │rows: 5, cols: 25 | + │{12:^ }| + {19: } │ |*2 + {18:foo }{17:foo }| + | + ]]) + + -- Ensure things work when switching tabpages. + command('tab split | setlocal cursorline cursorcolumn') + screen:expect([[ + {2: }{3:2}{2: foo }{1: foo }{4: }{2:X}| + {19:t}ty ready | + {19:r}ows: 5, cols: 25 | + {12:^rows: 5, cols: 50 }| + {19: } |*2 + | + ]]) + feed('i') + screen:expect([[ + {2: }{3:2}{2: foo }{1: foo }{4: }{2:X}| + tty ready | + rows: 5, cols: 25 | + rows: 5, cols: 50 | + ^ | + | + {1:-- TERMINAL --} | + ]]) + command('tabprevious') + screen:expect([[ + {1: }{5:2}{1: foo }{2: foo }{4: }{2:X}| + {19:r}ows: 5, cols: 25 │rows: 5, cols: 25 | + rows: 5, cols: 50 │rows: 5, cols: 50 | + {19: } │^ | + {19: } │ | + {18:foo }{17:foo }| + {1:-- TERMINAL --} | + ]]) + feed([[]]) + screen:expect([[ + {1: }{5:2}{1: foo }{2: foo }{4: }{2:X}| + {19:r}ows: 5, cols: 25 │rows: 5, cols: 25 | + rows: 5, cols: 50 │rows: 5, cols: 50 | + {19: } │{12: }| + {19: } │^ | + {18:foo }{17:foo }| + | + ]]) + command('tabnext') + screen:expect([[ + {2: }{3:2}{2: foo }{1: foo }{4: }{2:X}| + {19:t}ty ready | + {19:r}ows: 5, cols: 25 | + {19:r}ows: 5, cols: 50 | + {12:^ }| + {19: } | + | + ]]) + + -- Closing windows shouldn't break things. + command('tabprevious') + feed('i') + screen:expect([[ + {1: }{5:2}{1: foo }{2: foo }{4: }{2:X}| + {19:r}ows: 5, cols: 25 │rows: 5, cols: 25 | + rows: 5, cols: 50 │rows: 5, cols: 50 | + {19: } │ | + {19: } │^ | + {18:foo }{17:foo }| + {1:-- TERMINAL --} | + ]]) + command('quit') + screen:expect([[ + {1: foo }{2: foo }{4: }{2:X}| + tty ready | + rows: 5, cols: 25 | + rows: 5, cols: 50 | + ^ | + | + {1:-- TERMINAL --} | + ]]) + feed([[]]) + screen:expect([[ + {1: foo }{2: foo }{4: }{2:X}| + {19:t}ty ready | + {19:r}ows: 5, cols: 25 | + {19:r}ows: 5, cols: 50 | + ^ | + {19: } | + | + ]]) + + -- Switching to a non-terminal. + command('vnew') + feed([[pi]]) + screen:expect([[ + {1: }{5:2}{1: foo }{2: foo }{4: }{2:X}| + │rows: 5, cols: 25 | + {6:~ }│rows: 5, cols: 50 | + {6:~ }│ | + {6:~ }│^ | + {4:[No Name] }{17:foo }| + {1:-- TERMINAL --} | + ]]) + command('wincmd p') + screen:expect([[ + {1: }{5:2}{1: [No Name] }{2: foo }{4: }{2:X}| + ^ │{19:r}ows: 5, cols: 25 | + {6:~ }│{19:r}ows: 5, cols: 50 | + {6:~ }│ | + {6:~ }│{19: } | + {7:[No Name] }{18:foo }| + | + ]]) + end) + it('not unnecessarily redrawn by events', function() eq('t', eval('mode()')) exec_lua(function()