Merge #27223 nvim_open_tabpage

This commit is contained in:
Justin M. Keyes
2026-03-16 09:51:27 -04:00
committed by GitHub
10 changed files with 534 additions and 47 deletions

View File

@@ -3544,6 +3544,25 @@ nvim_set_option_value({name}, {value}, {opts})
==============================================================================
Tabpage Functions *api-tabpage*
nvim_open_tabpage({buffer}, {enter}, {config}) *nvim_open_tabpage()*
Opens a new tabpage.
Attributes: ~
not allowed when |textlock| is active
Since: 0.12.0
Parameters: ~
• {buffer} (`integer`) Buffer to open in the first window of the new
tabpage. Use 0 for current buffer.
• {enter} (`boolean`) Enter the tabpage (make it the current tabpage).
• {config} (`vim.api.keyset.tabpage_config`) Configuration for the new
tabpage. Keys:
• after: Position to insert tabpage (default: -1; after
current). 0 = first, N = after Nth.
Return: ~
(`integer`) Tabpage handle of the created tabpage
nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()*
Removes a tab-scoped (t:) variable

View File

@@ -165,6 +165,7 @@ API
• |nvim_echo()| can create |Progress| messages
• |nvim_get_commands()| returns `preview` and `callback` as Lua functions if
they were so specified in `nvim_create_user_command()`.
• |nvim_open_tabpage()| can open a new |tab-page|.
• |nvim_open_win()| floating windows can show a 'statusline'. Plugins can use
`style='minimal'` or `:setlocal statusline=` to hide the statusline.
• |nvim_win_set_config()| can move windows to other tab pages as floats.

View File

@@ -1673,6 +1673,17 @@ function vim.api.nvim_load_context(dict) end
--- @return any
function vim.api.nvim_notify(msg, log_level, opts) end
--- Opens a new tabpage.
---
--- @param buffer integer Buffer to open in the first window of the new tabpage.
--- Use 0 for current buffer.
--- @param enter boolean Enter the tabpage (make it the current tabpage).
--- @param config vim.api.keyset.tabpage_config Configuration for the new tabpage. Keys:
--- - after: Position to insert tabpage (default: -1; after current).
--- 0 = first, N = after Nth.
--- @return integer # Tabpage handle of the created tabpage
function vim.api.nvim_open_tabpage(buffer, enter, config) end
--- Open a terminal instance in a buffer
---
--- By default (and currently the only option) the terminal will not be

View File

@@ -437,6 +437,9 @@ error('Cannot require a meta file')
--- @field scoped? boolean
--- @field _subpriority? integer
--- @class vim.api.keyset.tabpage_config
--- @field after? integer
--- @class vim.api.keyset.user_command
--- @field addr? any
--- @field bang? boolean

View File

@@ -142,6 +142,11 @@ typedef struct {
Integer _cmdline_offset;
} Dict(win_config);
typedef struct {
OptionalKeys is_set__tabpage_config_;
Integer after;
} Dict(tabpage_config);
typedef struct {
Boolean is_lua;
Boolean do_source;

View File

@@ -1,11 +1,16 @@
#include <stdbool.h>
#include <stdlib.h>
#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.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"
#include "nvim/globals.h"
#include "nvim/memory_defs.h"
#include "nvim/types_defs.h"
@@ -183,3 +188,73 @@ Boolean nvim_tabpage_is_valid(Tabpage tabpage)
api_clear_error(&stub);
return ret;
}
/// Opens a new tabpage.
///
/// @param buffer Buffer to open in the first window of the new tabpage.
/// Use 0 for current buffer.
/// @param enter Enter the tabpage (make it the current tabpage).
/// @param config Configuration for the new tabpage. Keys:
/// - after: Position to insert tabpage (default: -1; after current).
/// 0 = first, N = after Nth.
/// @param[out] err Error details, if any
/// @return Tabpage handle of the created tabpage
Tabpage nvim_open_tabpage(Buffer buffer, Boolean enter, Dict(tabpage_config) *config, Error *err)
FUNC_API_SINCE(14) FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
#define HAS_KEY_X(d, key) HAS_KEY(d, tabpage_config, key)
buf_T *buf = find_buffer_by_handle(buffer, err);
if (buf == NULL) {
return 0;
}
if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return 0;
}
int after = -1; // Default to after current tabpage
if (HAS_KEY_X(config, after)) {
after = (int)config->after;
}
tabpage_T *tp;
win_T *wp;
TRY_WRAP(err, {
tp = win_new_tabpage(after + 1, 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;
}
// 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 tp->handle;
#undef HAS_KEY_X
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "api/tabpage.h.generated.h"

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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, 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, false, {}))
eq(1, fn.tabpagenr('$'))
matches('E11:', pcall_err(api.nvim_open_tabpage, other_buf, true, {}))
eq(1, fn.tabpagenr('$'))
local tp = api.nvim_open_tabpage(other_buf, 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:')
@@ -173,4 +192,325 @@ describe('api/tabpage', function()
ok(not api.nvim_tabpage_is_valid(tab))
end)
end)
describe('open_tabpage', function()
it('works', function()
local tabs = api.nvim_list_tabpages()
eq(1, #tabs)
local curtab = api.nvim_get_current_tabpage()
local tab = api.nvim_open_tabpage(0, 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, true, {})
local newtabs2 = api.nvim_list_tabpages()
eq(3, #newtabs2)
eq({
tabs[1],
tab2, -- new tabs open after the current tab
tab,
}, newtabs2)
eq(tab2, newtabs2[2])
eq(tab, newtabs2[3])
eq(tab2, api.nvim_get_current_tabpage())
end)
it('respects the `after` option', function()
local tab1 = api.nvim_get_current_tabpage()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
command('tabnew')
local tab3 = api.nvim_get_current_tabpage()
local newtabs = api.nvim_list_tabpages()
eq(3, #newtabs)
eq(newtabs, {
tab1,
tab2,
-- new_tab,
tab3,
})
local new_tab = api.nvim_open_tabpage(0, false, { after = api.nvim_tabpage_get_number(tab2) })
local newtabs2 = api.nvim_list_tabpages()
eq(4, #newtabs2)
eq({
tab1,
tab2,
new_tab,
tab3,
}, newtabs2)
eq(api.nvim_get_current_tabpage(), tab3)
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, false, {})
local newtabs = api.nvim_list_tabpages()
eq(2, #newtabs)
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, true, {})
local newtabs2 = api.nvim_list_tabpages()
eq(3, #newtabs2)
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, 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 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, 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())
api.nvim_set_var('events', {})
new_tab = api.nvim_open_tabpage(api.nvim_create_buf(true, true), 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'))
local curwin = new_win
api.nvim_set_var('events', {})
new_tab = api.nvim_open_tabpage(0, 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), 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, 0, {})')
matches('E1312:', pcall_err(command, 'split'))
command('autocmd TabNew * ++once quit')
eq('Tabpage was closed immediately', pcall_err(api.nvim_open_tabpage, 0, 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, 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, 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()
-- Start with 3 tabs
local tab1 = api.nvim_get_current_tabpage()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
command('tabnew')
local tab3 = api.nvim_get_current_tabpage()
local initial_tabs = api.nvim_list_tabpages()
eq(3, #initial_tabs)
eq({ tab1, tab2, tab3 }, initial_tabs)
-- Test after=0: should become first tab
local first_tab = api.nvim_open_tabpage(0, false, { after = 0 })
local tabs_after_first = api.nvim_list_tabpages()
eq(4, #tabs_after_first)
eq({ first_tab, tab1, tab2, tab3 }, tabs_after_first)
-- Test after=-1: should insert after current tab (tab3)
local explicit_after_current = api.nvim_open_tabpage(0, false, { after = -1 })
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 number 3)
local before_middle = api.nvim_open_tabpage(0, false, { after = 2 })
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)
eq(api.nvim_get_current_tabpage(), tab3)
-- Test default behavior (after current)
local default_after_current = api.nvim_open_tabpage(0, false, {})
local final_tabs = api.nvim_list_tabpages()
eq(7, #final_tabs)
eq({
first_tab,
tab1,
before_middle,
tab2,
tab3,
default_after_current,
explicit_after_current,
}, final_tabs)
end)
it('handles position beyond last tab', function()
-- Create a few tabs first
local tab1 = api.nvim_get_current_tabpage()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
command('tabnew')
local tab3 = api.nvim_get_current_tabpage()
eq(3, #api.nvim_list_tabpages())
eq({ tab1, tab2, tab3 }, api.nvim_list_tabpages())
-- Test that requesting position beyond last tab still works
-- (should place it at the end)
local new_tab = api.nvim_open_tabpage(0, false, { after = 10 }) -- Way beyond the last tab
local final_tabs = api.nvim_list_tabpages()
eq(4, #final_tabs)
-- Should append at the end
eq({ tab1, tab2, tab3, new_tab }, final_tabs)
end)
it('works with specific buffer', function()
local curbuf = api.nvim_get_current_buf()
local new_tab = api.nvim_open_tabpage(0, 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()
new_tab = api.nvim_open_tabpage(buf, true, {}) -- Enter the tab to make testing easier
-- Check that new tab has the specified buffer
eq(new_tab, api.nvim_get_current_tabpage())
eq(buf, api.nvim_get_current_buf())
eq({ 'test content' }, api.nvim_buf_get_lines(buf, 0, -1, false))
-- 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())
-- Test invalid buffer
eq('Invalid buffer id: 999', pcall_err(api.nvim_open_tabpage, 999, true, {}))
end)
it('handles complex positioning scenarios', function()
-- Create 5 tabs total
local tabs = { api.nvim_get_current_tabpage() }
for i = 2, 5 do
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, false, {})
local result_tabs = api.nvim_list_tabpages()
eq(6, #result_tabs)
eq({
tabs[1],
tabs[2],
tabs[3],
new_after_current,
tabs[4],
tabs[5],
}, result_tabs)
-- Insert as number 2 (before tab2, which will become number 3)
local new_at_pos2 = api.nvim_open_tabpage(0, false, { after = 1 })
local final_result = api.nvim_list_tabpages()
eq(7, #final_result)
eq({
tabs[1],
new_at_pos2,
tabs[2],
tabs[3],
new_after_current,
tabs[4],
tabs[5],
}, final_result)
end)
it('preserves tab order when entering new tabs', function()
local tab1 = api.nvim_get_current_tabpage()
command('tabnew')
local tab2 = api.nvim_get_current_tabpage()
-- Create new tab with enter=true, should insert after current (tab2)
local tab3 = api.nvim_open_tabpage(0, true, { after = -1 })
local tabs = api.nvim_list_tabpages()
eq(3, #tabs)
eq({ tab1, tab2, tab3 }, tabs)
eq(tab3, api.nvim_get_current_tabpage())
-- Create another with enter=true and specific position
api.nvim_set_current_tabpage(tab1)
local tab4 = api.nvim_open_tabpage(0, true, { after = 0 }) -- Should become first tab
local final_tabs = api.nvim_list_tabpages()
eq(4, #final_tabs)
eq({ tab4, tab1, tab2, tab3 }, final_tabs)
eq(tab4, api.nvim_get_current_tabpage())
end)
end)
end)