mirror of
https://github.com/neovim/neovim.git
synced 2026-05-06 08:26:45 -04:00
fix: nvim_open_tabpage cleanup, fixes, more tests
- Cleanup, remove redundant comments, add more tests.
- Enhance win_new_tabpage rather than create a new function for !enter, and use
a different approach that minimizes side-effects. Return the tabpage_T * and
first win_T * it allocated.
- Disallow during textlock, like other APIs that open windows.
- Remove existing win_alloc_firstwin error handling from win_new_tabpage; it's
not needed, and looks incorrect. (enter_tabpage is called for curtab, which is
not the old tabpage! Plus newtp is not freed)
- Fix checks after creating the tabpage:
- Don't fail if buf wasn't set successfully; the tab page may still be valid
regardless. Set buffer like nvim_open_win, possibly blocking Enter/Leave
events. (except BufWinEnter)
- tp_curwin may not be the initial window opened by win_new_tabpage. Use the
win_T * it returns instead, which is the real first window it allocated,
regardless of autocmd shenanigans.
- Properly check whether tab page was freed; it may have also been freed
before win_set_buf. Plus, it may not be safe to read its handle!
This commit is contained in:
+2
-1
@@ -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: ~
|
||||
|
||||
Generated
+1
-1
@@ -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.
|
||||
|
||||
+45
-53
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
+78
-132
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user