diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index d1db1a0936..b7ad960377 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1165,6 +1165,14 @@ TabEnter Just after entering a |tabpage|. TabLeave Just before leaving a |tabpage|. After WinLeave. + *TabMoved* +TabMoved After a |tabpage| is moved. + See |:tabmove|. expands to the + affected |tabpage-number| before the move. + The |event-data| has these keys (type: `vim.event.tabmoved.data`) + - tabnr_old: |tabpage-number| before the move. + - tabnr_new: |tabpage-number| after the move. + *TabNew* TabNew When creating a new |tabpage|. After WinEnter. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 1510d3745d..f984be6552 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -148,6 +148,7 @@ EVENTS • |:delmarks| now triggers the |MarkSet| autocommand with line==col==0, same as |nvim_buf_del_mark()| • |TextPutPre| and |TextPutPost| are triggered before/after putting text. +• |TabMoved| is triggered when tabs are reordered. HIGHLIGHTS diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 0b7c1fbf64..d2b695f023 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2593,6 +2593,7 @@ A jump table for the options with a short description can be found at |Q_op|. |TabClosedPre|, |TabEnter|, |TabLeave|, + |TabMoved|, |TabNew|, |TabNewEntered|, |TermClose|, diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 32fb4a49c4..70deda3a96 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -343,6 +343,7 @@ Events (autocommands): - |RecordingLeave| - |SearchWrapped| - |Signal| +- |TabMoved| - |TabNewEntered| - |TermClose| - |TermOpen| diff --git a/runtime/lua/vim/_meta/api_keysets.gen.lua b/runtime/lua/vim/_meta/api_keysets.gen.lua index dea2a70507..9c0137de66 100644 --- a/runtime/lua/vim/_meta/api_keysets.gen.lua +++ b/runtime/lua/vim/_meta/api_keysets.gen.lua @@ -194,6 +194,7 @@ error('Cannot require a meta file') --- |'TabClosedPre' --- |'TabEnter' --- |'TabLeave' +--- |'TabMoved' --- |'TabNew' --- |'TabNewEntered' --- |'TermChanged' diff --git a/runtime/lua/vim/_meta/events.lua b/runtime/lua/vim/_meta/events.lua index c7def2e7c3..364d706bf6 100644 --- a/runtime/lua/vim/_meta/events.lua +++ b/runtime/lua/vim/_meta/events.lua @@ -50,6 +50,10 @@ error('Cannot require a meta file') --- @field status? string --- @field title? string +--- @class vim.event.tabmoved.data +--- @field tabnr_old integer +--- @field tabnr_new integer + --- @class vim.event.termrequest.data --- @field sequence string --- @field terminator string diff --git a/runtime/lua/vim/_meta/options.gen.lua b/runtime/lua/vim/_meta/options.gen.lua index 6d39a37747..32f31bfdf7 100644 --- a/runtime/lua/vim/_meta/options.gen.lua +++ b/runtime/lua/vim/_meta/options.gen.lua @@ -2240,6 +2240,7 @@ vim.go.ei = vim.go.eventignore --- `TabClosedPre`, --- `TabEnter`, --- `TabLeave`, +--- `TabMoved`, --- `TabNew`, --- `TabNewEntered`, --- `TermClose`, diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index eca8256833..7be105b960 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -113,6 +113,7 @@ return { TabClosedPre = false, -- before closing a tab page TabEnter = false, -- after entering a tab page TabLeave = false, -- before leaving a tab page + TabMoved = false, -- after a tab was moved TabNew = false, -- when creating a new tab TabNewEntered = false, -- after entering a new tab TermChanged = false, -- after changing 'term' @@ -171,6 +172,7 @@ return { RecordingEnter = true, RecordingLeave = true, Signal = true, + TabMoved = true, TabNewEntered = true, TermClose = true, TermOpen = true, diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index adc41c7645..328d17c232 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1843,6 +1843,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX || event == EVENT_TABCLOSED + || event == EVENT_TABMOVED || event == EVENT_USER || event == EVENT_WINCLOSED || event == EVENT_WINRESIZED diff --git a/src/nvim/window.c b/src/nvim/window.c index 43c1cf332a..1de0a021e3 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5005,6 +5005,7 @@ void tabpage_move(int nr) return; } + int old_nr = tabpage_index(curtab); tabpage_T *tp_dst = tp; // Remove the current tab page from the list of tab pages. @@ -5035,6 +5036,15 @@ void tabpage_move(int nr) // Need to redraw the tabline. Tab page contents doesn't change. redraw_tabline = true; + + if (has_event(EVENT_TABMOVED)) { + char prev_idx[NUMBUFLEN]; + vim_snprintf(prev_idx, NUMBUFLEN, "%i", old_nr); + MAXSIZE_TEMP_DICT(data, 2); + PUT_C(data, "tabnr_old", INTEGER_OBJ(old_nr)); + PUT_C(data, "tabnr_new", INTEGER_OBJ(tabpage_index(curtab))); + aucmd_defer(EVENT_TABMOVED, prev_idx, NULL, AUGROUP_ALL, curbuf, NULL, &DICT_OBJ(data)); + } } /// Go to another window. diff --git a/test/functional/autocmd/tabmove_spec.lua b/test/functional/autocmd/tabmove_spec.lua new file mode 100644 index 0000000000..ca5ed02b1d --- /dev/null +++ b/test/functional/autocmd/tabmove_spec.lua @@ -0,0 +1,114 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local clear = n.clear +local command = n.command +local exec_lua = n.exec_lua +local insert = n.insert +local poke_eventloop = n.poke_eventloop +local api = n.api +local eval = n.eval + +local eq = t.eq + +describe('TabMoved', function() + before_each(function() + clear() + end) + + it('sets and event-data fields', function() + -- start on tabpage 1, create 2 more so tabpage 3 is "current tab" + command('tabnew') + command('tabnew') + + exec_lua([[ + _G.tm_events = {} + vim.api.nvim_create_autocmd('TabMoved', { + callback = function(ev) + table.insert(_G.tm_events, ev) + end, + }) + ]]) + + command('tabmove 0') + poke_eventloop() + local events = exec_lua('return _G.tm_events') + eq('3', events[1].match) + eq(3, events[1].data.tabnr_old) + eq(1, events[1].data.tabnr_new) + + command('tabnext 2') + command('tabmove $') + poke_eventloop() + events = exec_lua('return _G.tm_events') + eq('2', events[2].match) + eq(2, events[2].data.tabnr_old) + eq(3, events[2].data.tabnr_new) + + command('tabmove -1') + poke_eventloop() + events = exec_lua('return _G.tm_events') + eq('3', events[3].match) + eq(3, events[3].data.tabnr_old) + eq(2, events[3].data.tabnr_new) + end) + + it('does not trigger when a tab is moved to same position', function() + command('tabnew') + command('tabnew') + command('let g:test = 0') + command('au! TabMoved * let g:test += 1') + + command('tabmove $') -- already at last position, no-op + poke_eventloop() + eq(0, eval('g:test')) + end) + + it('is not triggered when creating or closing tabs', function() + command('let g:test = 0') + command('au! TabMoved * let g:test += 1') + command('file Xtestfile1') + command('0tabedit Xtestfile2') + command('tabclose') + poke_eventloop() + eq(0, eval('g:test')) + end) + + it('responds correctly to mouse interactions on the tabline', function() + local function setup() + Screen.new(25, 5) + command('set mouse=a') + command('tabnew') + command('tabnew') + exec_lua([[ + _G.tm_events = {} + vim.api.nvim_create_autocmd('TabMoved', { + callback = function(ev) + table.insert(_G.tm_events, ev) + end, + }) + ]]) + end + + -- clicking tab 2 shouldn't trigger the event. + setup() + api.nvim_input_mouse('left', 'press', '', 0, 0, 5) + api.nvim_input_mouse('left', 'release', '', 0, 0, 5) + poke_eventloop() + eq(0, #exec_lua('return _G.tm_events')) + clear() + + -- dragging tab 1 to the right should trigger the event. + setup() + api.nvim_input_mouse('left', 'press', '', 0, 0, 4) + poke_eventloop() + eq(0, #exec_lua('return _G.tm_events')) + api.nvim_input_mouse('left', 'drag', '', 0, 0, 14) + poke_eventloop() + local events = exec_lua('return _G.tm_events') + eq(1, #events) + eq(1, events[1].data.tabnr_old) + eq(2, events[1].data.tabnr_new) + end) +end)