commit 25ce44845d2adf94dfc91c70eb792a50d8274aa1
parent 1883fe39bdee47a246a913ba021237d1893aaa90
Author: zeertzjq <zeertzjq@outlook.com>
Date: Wed, 21 Jan 2026 18:42:47 +0800
fix(terminal): restore options properly when switching buffer (#37485)
Diffstat:
3 files changed, 104 insertions(+), 26 deletions(-)
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
@@ -737,29 +737,50 @@ static void unset_terminal_winopts(TerminalState *const s)
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;
+ goto end;
}
- 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);
+ winopt_T *winopts = NULL;
+ if (wp->w_buffer->handle != s->term->buf_handle) { // Buffer no longer in "wp".
+ buf_T *buf = handle_get_buffer(s->term->buf_handle);
+ if (buf == NULL) {
+ goto end; // Nothing to restore as the buffer was deleted.
+ }
+ for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
+ WinInfo *wip = kv_A(buf->b_wininfo, i);
+ if (wip->wi_win == wp && wip->wi_optset) {
+ winopts = &wip->wi_opt;
+ break;
+ }
+ }
+ if (winopts == NULL) {
+ goto end; // Nothing to restore as there is no matching WinInfo.
}
+ } else {
+ winopts = &wp->w_onebuf_opt;
+ 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_culopt_flags = s->save_w_p_culopt_flags;
}
- 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;
+ free_string_option(winopts->wo_culopt);
+ winopts->wo_culopt = s->save_w_p_culopt;
+ s->save_w_p_culopt = NULL;
}
- 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;
+ winopts->wo_cul = s->save_w_p_cul;
+ winopts->wo_cuc = s->save_w_p_cuc;
+ winopts->wo_so = s->save_w_p_so;
+ winopts->wo_siso = s->save_w_p_siso;
+
+end:
+ free_string_option(s->save_w_p_culopt);
s->save_curwin_handle = 0;
}
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
@@ -33,21 +33,35 @@ describe(':terminal buffer', function()
end)
it('terminal-mode forces various options', function()
+ local expr =
+ '[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]'
+
feed([[<C-\><C-N>]])
command('setlocal cursorline cursorlineopt=both cursorcolumn scrolloff=4 sidescrolloff=7')
- eq(
- { 'both', 1, 1, 4, 7 },
- eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')
- )
+ eq({ 'both', 1, 1, 4, 7 }, eval(expr))
eq('nt', eval('mode(1)'))
- -- Enter terminal-mode ("insert" mode in :terminal).
+ -- Enter Terminal mode ("insert" mode in :terminal).
feed('i')
eq('t', eval('mode(1)'))
- eq(
- { 'number', 1, 0, 0, 0 },
- eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')
- )
+ eq({ 'number', 1, 0, 0, 0 }, eval(expr))
+
+ -- Return to Normal mode.
+ feed([[<C-\><C-N>]])
+ eq('nt', eval('mode(1)'))
+ eq({ 'both', 1, 1, 4, 7 }, eval(expr))
+
+ -- Enter Terminal mode again.
+ feed('i')
+ eq('t', eval('mode(1)'))
+ eq({ 'number', 1, 0, 0, 0 }, eval(expr))
+
+ -- Delete the terminal buffer and return to the previous buffer.
+ command('bwipe!')
+ feed('<Ignore>') -- Add input to separate two RPC requests
+ eq('n', eval('mode(1)'))
+ -- Window options in the old buffer should be unchanged. #37484
+ eq({ 'both', 0, 0, -1, -1 }, eval(expr))
end)
it('terminal-mode does not change cursorlineopt if cursorline is disabled', function()
diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua
@@ -436,7 +436,7 @@ describe(':terminal window', function()
file foo
setlocal cursorline
vsplit
- setlocal nocursorline cursorcolumn
+ setlocal nocursorline cursorcolumn cursorlineopt=number
]])
screen:expect([[
{19:t}ty ready │tty ready |
@@ -580,6 +580,49 @@ describe(':terminal window', function()
{7:[No Name] }{18:foo [-] }|
|
]])
+
+ command('wincmd l | enew | setlocal cursorline nocursorcolumn')
+ screen:expect([[
+ {1: }{5:2}{1: [No Name] }{2: foo }{4: }{2:X}|
+ │{12:^ }|
+ {6:~ }│{6:~ }|*3
+ {4:[No Name] }{7:[No Name] }|
+ |
+ ]])
+ command('buffer # | startinsert')
+ 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 --} |
+ ]])
+ -- Switching to another buffer shouldn't change window options there. #37484
+ command('buffer # | call setline(1, ["aaa", "bbb", "ccc"]) | normal! jl')
+ screen:expect([[
+ {1: }{5:2}{1:+ [No Name] }{2: foo }{4: }{2:X}|
+ │aaa |
+ {6:~ }│{12:b^bb }|
+ {6:~ }│ccc |
+ {6:~ }│{6:~ }|
+ {4:[No Name] }{7:[No Name] [+] }|
+ |
+ ]])
+ -- Window options are restored when switching back to the terminal buffer.
+ command('buffer #')
+ screen:expect([[
+ {1: }{5:2}{1: foo }{2: foo }{4: }{2:X}|
+ │{19:r}ows: 5, cols: 25 |
+ {6:~ }│{19:r}ows: 5, cols: 50 |
+ {6:~ }│^ |
+ {6:~ }│{19: } |
+ {4:[No Name] }{17:foo [-] }|
+ |
+ ]])
+ -- 'cursorlineopt' should still be "number".
+ eq('number', eval('&l:cursorlineopt'))
end)
it('not unnecessarily redrawn by events', function()