diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 4f023d6ab17..6d782cc0e44 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3545,9 +3545,10 @@ nvim_set_option_value({name}, {value}, {opts}) Tabpage Functions *api-tabpage* nvim_open_tabpage({buffer}, {config}) *nvim_open_tabpage()* - Opens a new tabpage + Opens a new tabpage. Attributes: ~ + not allowed when |textlock| is active Since: 0.12.0 Parameters: ~ diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 6be9305d48b..b49a547d633 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1673,7 +1673,7 @@ function vim.api.nvim_load_context(dict) end --- @return any function vim.api.nvim_notify(msg, log_level, opts) end ---- Opens a new tabpage +--- Opens a new tabpage. --- --- @param buffer integer Buffer to open in the first window of the new tabpage. --- Use 0 for current buffer. diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index fd40dd3ce4a..89b445e5c76 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -5,8 +5,10 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/api/tabpage.h" #include "nvim/api/vim.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/errors.h" @@ -188,7 +190,7 @@ Boolean nvim_tabpage_is_valid(Tabpage tabpage) return ret; } -/// Opens a new tabpage +/// Opens a new tabpage. /// /// @param buffer Buffer to open in the first window of the new tabpage. /// Use 0 for current buffer. @@ -199,80 +201,70 @@ Boolean nvim_tabpage_is_valid(Tabpage tabpage) /// @param[out] err Error details, if any /// @return Tabpage handle of the created tabpage Tabpage nvim_open_tabpage(Buffer buffer, Dict(tabpage_config) *config, Error *err) - FUNC_API_SINCE(14) + FUNC_API_SINCE(14) FUNC_API_TEXTLOCK_ALLOW_CMDWIN { #define HAS_KEY_X(d, key) HAS_KEY(d, tabpage_config, key) - - // Validate and get the buffer buf_T *buf = find_buffer_by_handle(buffer, err); if (buf == NULL) { return 0; } - if (buf == cmdwin_buf) { - api_set_error(err, kErrorTypeException, "%s", e_cmdwin); - return 0; - } - - bool enter = true; // Default to entering the new tabpage + bool enter = true; if (HAS_KEY_X(config, enter)) { enter = config->enter; } + if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) { + api_set_error(err, kErrorTypeException, "%s", e_cmdwin); + return 0; + } int after = 0; // Default to after current tabpage if (HAS_KEY_X(config, after)) { after = (int)config->after; - - // Validate the after position - if (after < 0) { - api_set_error(err, kErrorTypeValidation, "Invalid 'after' position: %d", after); + VALIDATE_INT(after >= 0, "after", config->after, { return 0; - } - - // Note: No validation for after > number of tabs since the underlying - // function handles this by appending at the end - } - - tabpage_T *newtp; - - if (enter) { - // Use the existing function if we want to enter the tabpage - if (win_new_tabpage(after, NULL) == OK) { - newtp = curtab; - } else { - api_set_error(err, kErrorTypeException, "Failed to create new tabpage"); - return 0; - } - } else { - // Create tabpage without entering it - newtp = win_new_tabpage_noenter(after, err); - if (newtp == NULL) { - api_set_error(err, kErrorTypeException, "Failed to create new tabpage"); - return 0; - } - } - - // Set the buffer in the new window if different from current - if (newtp->tp_curwin->w_buffer != buf) { - TRY_WRAP(err, { - win_set_buf(newtp->tp_curwin, buf, err); }); - if (ERROR_SET(err)) { - return 0; - } + // NOTE: win_new_tabpage will handle after > count by appending to the end. } - // Ensure tabpage wasn't immediately freed - if (find_tab_by_handle(newtp->handle, err) == NULL) { - api_clear_error(err); + tabpage_T *tp; + win_T *wp; + TRY_WRAP(err, { + tp = win_new_tabpage(after, NULL, enter, &wp); + }); + if (!tp) { + if (!ERROR_SET(err)) { // set error maybe more specific + api_set_error(err, kErrorTypeException, "Failed to create new tabpage"); + } + return 0; + } + if (!valid_tabpage(tp)) { + api_clear_error(err); // maybe set by win_new_tabpage, but wasn't fatal api_set_error(err, kErrorTypeException, "Tabpage was closed immediately"); return 0; } - if (!buf_valid(buf)) { - api_set_error(err, kErrorTypeException, "Buffer was deleted by autocmd"); - return 0; + + // Set the buffer in the new window if different from current + if (tabpage_win_valid(tp, wp) && wp->w_buffer != buf) { + // win_set_buf temporarily makes `wp` the curwin to set the buffer. + // If not entering `wp`, block Enter and Leave events. (cringe) + const bool au_no_enter_leave = curwin != wp; + if (au_no_enter_leave) { + autocmd_no_enter++; + autocmd_no_leave++; + } + win_set_buf(wp, buf, err); + if (au_no_enter_leave) { + autocmd_no_enter--; + autocmd_no_leave--; + } + if (!valid_tabpage(tp)) { + api_clear_error(err); // maybe set by win_new_tabpage/win_set_buf, but wasn't fatal + api_set_error(err, kErrorTypeException, "Tabpage was closed immediately"); + return 0; + } } - return newtp->handle; + return tp->handle; #undef HAS_KEY_X } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 8200dc3fde0..26b73580df2 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5646,7 +5646,7 @@ void ex_splitview(exarg_T *eap) // Either open new tab page or split the window. if (use_tab) { if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0 - ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { + ? 0 : (int)eap->line2 + 1, eap->arg, true, NULL)) { do_exedit(eap, old_curwin); apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf); diff --git a/src/nvim/window.c b/src/nvim/window.c index 52c7aa1acd6..2a68928a301 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -494,8 +494,7 @@ newwindow: // First create a new tab with the window, then go back to // the old tab and close the window there. win_T *wp = curwin; - if (win_new_tabpage(Prenum, NULL) == OK - && valid_tabpage(oldtab)) { + if (win_new_tabpage(Prenum, NULL, true, NULL) && valid_tabpage(oldtab)) { tabpage_T *newtab = curtab; goto_tabpage_tp(oldtab, true, true); if (curwin == wp) { @@ -4471,28 +4470,41 @@ void free_tabpage(tabpage_T *tp) /// are not completely setup yet and could cause dereferencing /// NULL pointers /// +/// NOTE: `first` and the return value may have already been freed by autocmds! +/// /// @param after Put new tabpage after tabpage "after", or after the current /// tabpage in case of 0. /// @param filename Will be passed to apply_autocmds(). -/// @return Was the new tabpage created successfully? FAIL or OK. -int win_new_tabpage(int after, char *filename) +/// @param enter Whether to enter the new tabpage. +/// @param first If not NULL, set to the window opened for the new tabpage. +/// @return pointer to new tabpage on success, NULL otherwise. +tabpage_T *win_new_tabpage(int after, char *filename, bool enter, win_T **first) { tabpage_T *old_curtab = curtab; - if (cmdwin_type != 0) { + if (enter && cmdwin_type != 0) { emsg(_(e_cmdwin)); - return FAIL; + return NULL; } if (window_layout_locked(CMD_tabnew)) { - return FAIL; + return NULL; } tabpage_T *newtp = alloc_tabpage(); // Remember the current windows in this Tab page. - if (leave_tabpage(curbuf, true) == FAIL) { - xfree(newtp); - return FAIL; + // Avoid side-effects via unuse_tabpage when not entering. + if (enter) { + if (leave_tabpage(curbuf, true) == FAIL) { + xfree(newtp); + return NULL; + } + } else { + unuse_tabpage(curtab); + // Save this to tell if we need to make room for the tabline. + curtab->tp_old_Rows_avail = ROWS_AVAIL; + firstwin = NULL; + lastwin = NULL; } newtp->tp_localdir = old_curtab->tp_localdir @@ -4501,59 +4513,79 @@ int win_new_tabpage(int after, char *filename) curtab = newtp; // Create a new empty window. - if (win_alloc_firstwin(old_curtab->tp_curwin) == OK) { - // Make the new Tab page the new topframe. - if (after == 1) { - // New tab page becomes the first one. - newtp->tp_next = first_tabpage; - first_tabpage = newtp; - } else { - tabpage_T *tp = old_curtab; + const int result = win_alloc_firstwin(old_curtab->tp_curwin); + assert(result == OK); // does not fail for first window of new tabpage + (void)result; + if (first) { + *first = curwin; + } - if (after > 0) { - // Put new tab page before tab page "after". - int n = 2; - for (tp = first_tabpage; tp->tp_next != NULL - && n < after; tp = tp->tp_next) { - n++; - } + // Make the new Tab page the new topframe. + if (after == 1) { + // New tab page becomes the first one. + newtp->tp_next = first_tabpage; + first_tabpage = newtp; + } else { + tabpage_T *tp = old_curtab; + + if (after > 0) { + // Put new tab page before tab page "after". + int n = 2; + for (tp = first_tabpage; tp->tp_next != NULL + && n < after; tp = tp->tp_next) { + n++; } - newtp->tp_next = tp->tp_next; - tp->tp_next = newtp; } - newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin; + newtp->tp_next = tp->tp_next; + tp->tp_next = newtp; + } + newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin; - win_init_size(); - firstwin->w_winrow = tabline_height(); - firstwin->w_prev_winrow = firstwin->w_winrow; - win_comp_scroll(curwin); + win_init_size(); + firstwin->w_winrow = tabline_height(); + firstwin->w_prev_winrow = firstwin->w_winrow; + win_comp_scroll(curwin); - newtp->tp_topframe = topframe; - last_status(false); + newtp->tp_topframe = topframe; + last_status(false); - if (curbuf->terminal) { - terminal_check_size(curbuf->terminal); - } + if (curbuf->terminal) { + terminal_check_size(curbuf->terminal); + } + if (enter) { redraw_all_later(UPD_NOT_VALID); - tabpage_check_windows(old_curtab); - lastused_tabpage = old_curtab; - entering_window(curwin); apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); + } else { + unuse_tabpage(curtab); + use_tabpage(old_curtab); + // Tabline maybe added, or its contents changed. + redraw_tabline = true; + if (curtab->tp_old_Rows_avail != ROWS_AVAIL) { + win_new_screen_rows(); + } - return OK; + // Trigger autocommands in the context of the new window. Let switch_win_noblock handle stuff + // like temporarily resetting VIsual_active. + switchwin_T switchwin; + const int sw_result = switch_win_noblock(&switchwin, newtp->tp_curwin, newtp, true); + assert(sw_result == OK); // tp_curwin is valid in newtp + (void)sw_result; + + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); + + restore_win_noblock(&switchwin, true); } - // Failed, get back the previous Tab page - enter_tabpage(curtab, curbuf, true, true); - return FAIL; + return newtp; } // Open a new tab page if ":tab cmd" was used. It will edit the same buffer, @@ -4569,7 +4601,7 @@ static int may_open_tabpage(void) cmdmod.cmod_tab = 0; // reset it to avoid doing it twice postponed_split_tab = 0; - int status = win_new_tabpage(n, NULL); + int status = win_new_tabpage(n, NULL, true, NULL) ? OK : FAIL; if (status == OK) { apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf); } @@ -4591,7 +4623,7 @@ int make_tabpages(int maxcount) int todo; for (todo = count - 1; todo > 0; todo--) { - if (win_new_tabpage(0, NULL) == FAIL) { + if (!win_new_tabpage(0, NULL, true, NULL)) { break; } } @@ -7827,89 +7859,3 @@ win_T *lastwin_nofloating(tabpage_T *tp) } return res; } - -/// Create a new tabpage without switching to it. -/// @param after Position in tabpage list (0 = after current, 1 = first) -/// @param[out] err Error details, if any -/// @return Handle of the created tabpage, or NULL on failure -tabpage_T *win_new_tabpage_noenter(int after, Error *err) -{ - tabpage_T *old_curtab = curtab; - - if (cmdwin_type != 0) { - api_set_error(err, kErrorTypeException, "%s", e_cmdwin); - return NULL; - } - - tabpage_T *newtp = alloc_tabpage(); - if (newtp == NULL) { - api_set_error(err, kErrorTypeException, "Failed to allocate new tabpage"); - return NULL; - } - - // Remember the current windows in this Tab page. - if (leave_tabpage(curbuf, true) == FAIL) { - xfree(newtp); - api_set_error(err, kErrorTypeException, "Failed to leave current tabpage"); - return NULL; - } - - // Copy localdir from current tabpage - newtp->tp_localdir = old_curtab->tp_localdir ? xstrdup(old_curtab->tp_localdir) : NULL; - - // Switch to new tabpage to set it up - curtab = newtp; - - // Create the initial window in the new tabpage - if (win_alloc_firstwin(old_curtab->tp_curwin) == OK) { - // Insert the new tabpage in the correct position - if (after == 1) { - // New tab page becomes the first one - newtp->tp_next = first_tabpage; - first_tabpage = newtp; - } else { - tabpage_T *tp = old_curtab; - if (after > 0) { - // Put new tab page before tab page "after" - int n = 2; - for (tp = first_tabpage; tp->tp_next != NULL && n < after; tp = tp->tp_next) { - n++; - } - } - newtp->tp_next = tp->tp_next; - tp->tp_next = newtp; - } - - // Set up the tabpage structure properly - newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin; - win_init_size(); - firstwin->w_winrow = tabline_height(); - firstwin->w_prev_winrow = firstwin->w_winrow; - win_comp_scroll(curwin); - newtp->tp_topframe = topframe; - last_status(false); - - if (curbuf->terminal) { - terminal_check_size(curbuf->terminal); - } - - redraw_all_later(UPD_NOT_VALID); - tabpage_check_windows(old_curtab); - lastused_tabpage = old_curtab; - - // Fire autocmds for new window and tabpage - entering_window(curwin); - apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); - apply_autocmds(EVENT_TABNEW, NULL, NULL, false, curbuf); - - // Now switch back to the original tabpage - enter_tabpage(old_curtab, curbuf, true, true); - - return newtp; - } - - // Failed, get back the previous Tab page - enter_tabpage(old_curtab, curbuf, true, true); - api_set_error(err, kErrorTypeException, "Failed to create window in new tabpage"); - return NULL; -} diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 748e652043f..f978c15d465 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -1,7 +1,9 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') local clear, eq, ok = n.clear, t.eq, t.ok +local matches = t.matches local exec = n.exec local feed = n.feed local api = n.api @@ -91,6 +93,23 @@ describe('api/tabpage', function() ) end) + it('checks textlock, cmdwin restrictions', function() + command('autocmd TextYankPost * ++once call nvim_open_tabpage(0, #{enter: 0})') + matches('E565:', pcall_err(command, 'yank')) + eq(1, fn.tabpagenr('$')) + + local other_buf = api.nvim_get_current_buf() + feed('q:') + -- OK when not entering and not opening a tabpage with the cmdwin's buffer. + matches('E11:', pcall_err(api.nvim_open_tabpage, 0, { enter = false })) + eq(1, fn.tabpagenr('$')) + matches('E11:', pcall_err(api.nvim_open_tabpage, other_buf, { enter = true })) + eq(1, fn.tabpagenr('$')) + local tp = api.nvim_open_tabpage(other_buf, { enter = false }) + eq(other_buf, api.nvim_win_get_buf(api.nvim_tabpage_get_win(tp))) + eq('command', fn.win_gettype()) + end) + it('does not switch window when textlocked or in the cmdwin', function() local target_win = api.nvim_get_current_win() feed('q:') @@ -179,17 +198,13 @@ describe('api/tabpage', function() local tabs = api.nvim_list_tabpages() eq(1, #tabs) local curtab = api.nvim_get_current_tabpage() - local tab = api.nvim_open_tabpage(0, { - enter = false, - }) + local tab = api.nvim_open_tabpage(0, { enter = false }) local newtabs = api.nvim_list_tabpages() eq(2, #newtabs) eq(tab, newtabs[2]) eq(curtab, api.nvim_get_current_tabpage()) - local tab2 = api.nvim_open_tabpage(0, { - enter = true, - }) + local tab2 = api.nvim_open_tabpage(0, { enter = true }) local newtabs2 = api.nvim_list_tabpages() eq(3, #newtabs2) eq({ @@ -235,47 +250,117 @@ describe('api/tabpage', function() end) it('respects the `enter` argument', function() + local screen = Screen.new(50, 8) eq(1, #api.nvim_list_tabpages()) local tab1 = api.nvim_get_current_tabpage() - local new_tab = api.nvim_open_tabpage(0, { - enter = false, - }) - + local new_tab = api.nvim_open_tabpage(0, { enter = false }) local newtabs = api.nvim_list_tabpages() eq(2, #newtabs) - eq(newtabs, { - tab1, - new_tab, - }) + eq(newtabs, { tab1, new_tab }) eq(api.nvim_get_current_tabpage(), tab1) + -- Tabline redrawn when not entering. + screen:expect([[ + {5: [No Name] }{24: [No Name] }{2: }{24:X}| + ^ | + {1:~ }|*5 + | + ]]) - local new_tab2 = api.nvim_open_tabpage(0, { - enter = true, - }) + local new_tab2 = api.nvim_open_tabpage(0, { enter = true }) local newtabs2 = api.nvim_list_tabpages() eq(3, #newtabs2) - eq(newtabs2, { - tab1, - new_tab2, - new_tab, - }) - + eq(newtabs2, { tab1, new_tab2, new_tab }) eq(api.nvim_get_current_tabpage(), new_tab2) + -- Tabline redrawn. (when entering) + screen:expect([[ + {24: [No Name] }{5: [No Name] }{24: [No Name] }{2: }{24:X}| + ^ | + {1:~ }|*5 + | + ]]) + + api.nvim_open_tabpage(0, { enter = false }) + -- Tabline redrawn when not entering, and when there's already one. + screen:expect([[ + {24: [No Name] }{5: [No Name] }{24: [No Name] [No Name] }{2: }{24:X}| + ^ | + {1:~ }|*5 + | + ]]) end) - it('applies `enter` autocmds in the context of the new tabpage', function() - api.nvim_create_autocmd('TabEnter', { - command = 'let g:entered_tab = nvim_get_current_tabpage()', - }) + it('applies autocmds in the context of the new tabpage', function() + exec([=[ + let g:events = [] + autocmd WinNew * let g:events += [['WinNew', nvim_get_current_tabpage(), win_getid()]] + autocmd WinEnter * let g:events += [['WinEnter', nvim_get_current_tabpage(), win_getid()]] + autocmd TabNew * let g:events += [['TabNew', nvim_get_current_tabpage(), win_getid()]] + autocmd TabEnter * let g:events += [['TabEnter', nvim_get_current_tabpage(), win_getid()]] + autocmd BufEnter * let g:events += [['BufEnter', nvim_get_current_tabpage(), win_getid()]] + autocmd BufLeave * let g:events += [['BufLeave', nvim_get_current_tabpage(), win_getid()]] + autocmd BufWinEnter * let g:events += [['BufWinEnter', nvim_get_current_tabpage(), win_getid()]] + ]=]) - local new_tab = api.nvim_open_tabpage(0, { - enter = true, - }) + local new_tab = api.nvim_open_tabpage(0, { enter = true }) + local new_win = api.nvim_tabpage_get_win(new_tab) + eq({ + { 'WinNew', new_tab, new_win }, + { 'WinEnter', new_tab, new_win }, + { 'TabNew', new_tab, new_win }, + { 'TabEnter', new_tab, new_win }, + }, api.nvim_get_var('events')) + eq(new_win, api.nvim_get_current_win()) - local entered_tab = assert(tonumber(api.nvim_get_var('entered_tab'))) + api.nvim_set_var('events', {}) + new_tab = api.nvim_open_tabpage(api.nvim_create_buf(true, true), { enter = true }) + new_win = api.nvim_tabpage_get_win(new_tab) + eq({ + { 'WinNew', new_tab, new_win }, + { 'WinEnter', new_tab, new_win }, + { 'TabNew', new_tab, new_win }, + { 'TabEnter', new_tab, new_win }, + { 'BufLeave', new_tab, new_win }, + { 'BufEnter', new_tab, new_win }, + { 'BufWinEnter', new_tab, new_win }, + }, api.nvim_get_var('events')) - eq(new_tab, entered_tab) + local curwin = new_win + api.nvim_set_var('events', {}) + new_tab = api.nvim_open_tabpage(0, { enter = false }) + new_win = api.nvim_tabpage_get_win(new_tab) + eq( + { { 'WinNew', new_tab, new_win }, { 'TabNew', new_tab, new_win } }, + api.nvim_get_var('events') + ) + eq(curwin, api.nvim_get_current_win()) + + api.nvim_set_var('events', {}) + new_tab = api.nvim_open_tabpage(api.nvim_create_buf(true, true), { enter = false }) + new_win = api.nvim_tabpage_get_win(new_tab) + eq({ + { 'WinNew', new_tab, new_win }, + { 'TabNew', new_tab, new_win }, + { 'BufWinEnter', new_tab, new_win }, + }, api.nvim_get_var('events')) + eq(curwin, api.nvim_get_current_win()) + end) + + it('handles nasty autocmds', function() + command('autocmd WinNewPre * ++once call nvim_open_tabpage(0, #{enter: 0})') + matches('E1312:', pcall_err(command, 'split')) + + command('autocmd TabNew * ++once quit') + eq('Tabpage was closed immediately', pcall_err(api.nvim_open_tabpage, 0, { enter = false })) + command('autocmd BufEnter * ++once quit') + local buf = api.nvim_create_buf(true, true) + eq('Tabpage was closed immediately', pcall_err(api.nvim_open_tabpage, buf, { enter = true })) + + -- No error if autocmds delete target buffer, if new tabpage is still valid to return. + command('autocmd BufEnter * ++once buffer # | bwipeout! #') + local new_tp = api.nvim_open_tabpage(buf, { enter = true }) + eq(false, api.nvim_buf_is_valid(buf)) + eq(new_tp, api.nvim_get_current_tabpage()) end) it('handles edge cases for positioning', function() @@ -291,28 +376,19 @@ describe('api/tabpage', function() eq({ tab1, tab2, tab3 }, initial_tabs) -- Test after=1: should become first tab - local first_tab = api.nvim_open_tabpage(0, { - enter = false, - after = 1, - }) + local first_tab = api.nvim_open_tabpage(0, { enter = false, after = 1 }) local tabs_after_first = api.nvim_list_tabpages() eq(4, #tabs_after_first) eq({ first_tab, tab1, tab2, tab3 }, tabs_after_first) -- Test after=0: should insert after current tab (tab3) - local explicit_after_current = api.nvim_open_tabpage(0, { - enter = false, - after = 0, - }) + local explicit_after_current = api.nvim_open_tabpage(0, { enter = false, after = 0 }) local tabs_after_current = api.nvim_list_tabpages() eq(5, #tabs_after_current) eq({ first_tab, tab1, tab2, tab3, explicit_after_current }, tabs_after_current) -- Test inserting before a middle tab (before tab2, which is now position 3) - local before_middle = api.nvim_open_tabpage(0, { - enter = false, - after = 3, - }) + local before_middle = api.nvim_open_tabpage(0, { enter = false, after = 3 }) local tabs_after_middle = api.nvim_list_tabpages() eq(6, #tabs_after_middle) eq({ first_tab, tab1, before_middle, tab2, tab3, explicit_after_current }, tabs_after_middle) @@ -320,9 +396,7 @@ describe('api/tabpage', function() eq(api.nvim_get_current_tabpage(), tab3) -- Test default behavior (after current) - local default_after_current = api.nvim_open_tabpage(0, { - enter = false, - }) + local default_after_current = api.nvim_open_tabpage(0, { enter = false }) local final_tabs = api.nvim_list_tabpages() eq(7, #final_tabs) eq({ @@ -334,6 +408,8 @@ describe('api/tabpage', function() default_after_current, explicit_after_current, }, final_tabs) + + eq("Invalid 'after': -1", pcall_err(api.nvim_open_tabpage, 0, { after = -1 })) end) it('handles position beyond last tab', function() @@ -361,13 +437,17 @@ describe('api/tabpage', function() end) it('works with specific buffer', function() + local curbuf = api.nvim_get_current_buf() + local new_tab = api.nvim_open_tabpage(0, { enter = false }) + api.nvim_set_current_tabpage(new_tab) + eq(curbuf, api.nvim_get_current_buf()) + local buf = api.nvim_create_buf(false, false) api.nvim_buf_set_lines(buf, 0, -1, false, { 'test content' }) local original_tab = api.nvim_get_current_tabpage() local original_buf = api.nvim_get_current_buf() - - local new_tab = api.nvim_open_tabpage(buf, { + new_tab = api.nvim_open_tabpage(buf, { enter = true, -- Enter the tab to make testing easier }) @@ -379,24 +459,11 @@ describe('api/tabpage', function() -- Switch back and check original tab still has original buffer api.nvim_set_current_tabpage(original_tab) eq(original_buf, api.nvim_get_current_buf()) - end) - it('validates buffer parameter', function() -- Test invalid buffer eq('Invalid buffer id: 999', pcall_err(api.nvim_open_tabpage, 999, {})) end) - it('works with current buffer (0)', function() - local current_buf = api.nvim_get_current_buf() - - local new_tab = api.nvim_open_tabpage(0, { - enter = false, - }) - - api.nvim_set_current_tabpage(new_tab) - eq(current_buf, api.nvim_get_current_buf()) - end) - it('handles complex positioning scenarios', function() -- Create 5 tabs total local tabs = { api.nvim_get_current_tabpage() } @@ -404,18 +471,13 @@ describe('api/tabpage', function() command('tabnew') tabs[i] = api.nvim_get_current_tabpage() end - eq(5, #api.nvim_list_tabpages()) -- Go to middle tab (tab 3) api.nvim_set_current_tabpage(tabs[3]) -- Insert after=0 (after current, which is tab 3) - local new_after_current = api.nvim_open_tabpage(0, { - enter = false, - after = 0, - }) - + local new_after_current = api.nvim_open_tabpage(0, { enter = false, after = 0 }) local result_tabs = api.nvim_list_tabpages() eq(6, #result_tabs) eq({ @@ -428,11 +490,7 @@ describe('api/tabpage', function() }, result_tabs) -- Insert at position 2 (before tab2, which becomes new position 2) - local new_at_pos2 = api.nvim_open_tabpage(0, { - enter = false, - after = 2, - }) - + local new_at_pos2 = api.nvim_open_tabpage(0, { enter = false, after = 2 }) local final_result = api.nvim_list_tabpages() eq(7, #final_result) eq({