mirror of
https://github.com/neovim/neovim.git
synced 2025-12-13 10:02:49 +00:00
fix(terminal): update winopts and focus when switching terminals
Problem: window options and terminal focus notifications not updated when switching terminals without leaving terminal mode. Solution: update them.
This commit is contained in:
@@ -112,6 +112,16 @@ typedef struct {
|
||||
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
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -343,6 +343,73 @@ describe(':terminal buffer', function()
|
||||
|*4
|
||||
]])
|
||||
end)
|
||||
|
||||
it('reports focus notifications when requested', function()
|
||||
feed([[<C-\><C-N>]])
|
||||
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([[<C-\><C-N>]])
|
||||
screen:expect([[
|
||||
focused │focused │focused |
|
||||
unfocused │unfocuse│unfocused |
|
||||
│ │^ |
|
||||
│ │ |*2
|
||||
{18:foo foo }{17:bar }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(':terminal buffer', function()
|
||||
|
||||
@@ -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([[<C-\><C-N>]])
|
||||
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([[<C-\><C-N>]])
|
||||
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([[<C-\><C-N>]])
|
||||
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([[<C-\><C-N>]])
|
||||
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([[<C-W>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()
|
||||
|
||||
Reference in New Issue
Block a user