diff options
author | butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> | 2020-01-02 06:06:11 -0800 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2020-01-02 06:06:11 -0800 |
commit | cbc8d72fde4b19176028490934ff7a447afe523c (patch) | |
tree | 80c962c8c65b356e55b9a55e0f67c1ba2b24b78d | |
parent | 2c62b2fc56b6b86a930356ccf48ba8fb189a7654 (diff) | |
download | rneovim-cbc8d72fde4b19176028490934ff7a447afe523c.tar.gz rneovim-cbc8d72fde4b19176028490934ff7a447afe523c.tar.bz2 rneovim-cbc8d72fde4b19176028490934ff7a447afe523c.zip |
tabpage: track last-used tabpage #11626
In a multi-window scenario, it is possible to return focus to the last
accessed window via n_CTRL-W_p. However, in the case of a multi-tab
scenario, there was previously no way to return focus to the last
accessed *tab*. Here, that ability is added via n_g<tab>.
Additionally, the index of the previous tab is exposed via
tabpagenr('#'), mirroring the existing functionality of winnr('#').
-rw-r--r-- | runtime/doc/eval.txt | 8 | ||||
-rw-r--r-- | runtime/doc/index.txt | 5 | ||||
-rw-r--r-- | src/nvim/eval.c | 4 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/normal.c | 20 | ||||
-rw-r--r-- | src/nvim/window.c | 19 | ||||
-rw-r--r-- | test/functional/autocmd/tabnewentered_spec.lua | 454 |
7 files changed, 502 insertions, 9 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 960a42ecbf..67277d19dd 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -8591,8 +8591,12 @@ tabpagebuflist([{arg}]) *tabpagebuflist()* tabpagenr([{arg}]) *tabpagenr()* The result is a Number, which is the number of the current tab page. The first tab page has number 1. - When the optional argument is "$", the number of the last tab - page is returned (the tab page count). + The optional argument {arg} supports the following values: + $ the number of the last tab page (the tab page + count). + # the number of the last accessed tab page (where + |g<Tab>| goes to). If there is no previous + tab page, 0 is returned. The number can be used with the |:tab| command. diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index ed9853a8da..3b057575a8 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -222,6 +222,8 @@ tag char note action in Normal mode ~ |CTRL-]| CTRL-] :ta to ident under cursor |CTRL-^| CTRL-^ edit Nth alternate file (equivalent to ":e #N") +|CTRL-<Tab>| CTRL-<Tab> same as `g<Tab>` : go to last accessed tab + page CTRL-_ not used |<Space>| <Space> 1 same as "l" @@ -570,6 +572,8 @@ tag command action in Normal mode ~ following the file name. |CTRL-W_gt| CTRL-W g t same as `gt`: go to next tab page |CTRL-W_gT| CTRL-W g T same as `gT`: go to previous tab page +|CTRL-W_g<Tab>| CTRL-W g <Tab> same as `g<Tab>` : go to last accessed tab + page |CTRL-W_h| CTRL-W h go to Nth left window (stop at first window) |CTRL-W_i| CTRL-W i split window and jump to declaration of identifier under the cursor @@ -788,6 +792,7 @@ tag char note action in Normal mode ~ |g<LeftMouse>| g<LeftMouse> same as <C-LeftMouse> g<MiddleMouse> same as <C-MiddleMouse> |g<RightMouse>| g<RightMouse> same as <C-RightMouse> +|g<Tab>| g<Tab> go to last accessed tab page |g<Up>| g<Up> 1 same as "gk" ============================================================================== diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 65f029649c..cf4d3af232 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18172,6 +18172,10 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (arg != NULL) { if (strcmp(arg, "$") == 0) { nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) + ? tabpage_index(lastused_tabpage) + : nr; } else { EMSG2(_(e_invexpr2), arg); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c6ab574a0f..148ab0a02b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -459,6 +459,7 @@ EXTERN frame_T *topframe; /* top of the window frame tree */ * one in the list, "curtab" is the current one. */ EXTERN tabpage_T *first_tabpage; +EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */ diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 9c707a6fdc..d0b9fd4589 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6312,9 +6312,7 @@ static void nv_gomark(cmdarg_T *cap) } } -/* - * Handle CTRL-O, CTRL-I, "g;" and "g," commands. - */ +// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. static void nv_pcmark(cmdarg_T *cap) { pos_T *pos; @@ -6322,11 +6320,16 @@ static void nv_pcmark(cmdarg_T *cap) const bool old_KeyTyped = KeyTyped; // getting file may reset it if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == 'g') + if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { + goto_tabpage_lastused(); + return; + } + if (cap->cmdchar == 'g') { pos = movechangelist((int)cap->count1); - else + } else { pos = movemark((int)cap->count1); - if (pos == (pos_T *)-1) { /* jump to other file */ + } + if (pos == (pos_T *)-1) { // jump to other file curwin->w_set_curswant = true; check_cursor(); } else if (pos != NULL) /* can jump */ @@ -7059,6 +7062,11 @@ static void nv_g_cmd(cmdarg_T *cap) if (!checkclearop(oap)) goto_tabpage(-(int)cap->count1); break; + case TAB: + if (!checkclearop(oap)) { + goto_tabpage_lastused(); + } + break; case '+': case '-': /* "g+" and "g-": undo or redo along the timeline */ diff --git a/src/nvim/window.c b/src/nvim/window.c index 79d7a8acba..4d105dd11e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -519,6 +519,10 @@ wingotofile: do_nv_ident('g', xchar); break; + case TAB: + goto_tabpage_lastused(); + break; + case 'f': /* CTRL-W gf: "gf" in a new tab page */ case 'F': /* CTRL-W gF: "gF" in a new tab page */ cmdmod.tab = tabpage_index(curtab) + 1; @@ -3691,6 +3695,10 @@ void free_tabpage(tabpage_T *tp) hash_init(&tp->tp_vars->dv_hashtab); unref_var_dict(tp->tp_vars); + if (tp == lastused_tabpage) { + lastused_tabpage = NULL; + } + xfree(tp->tp_localdir); xfree(tp); } @@ -3750,6 +3758,8 @@ int win_new_tabpage(int after, char_u *filename) tabpage_check_windows(tp); + lastused_tabpage = tp; + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); @@ -3976,6 +3986,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au if (curtab->tp_old_Columns != Columns && starting == 0) shell_new_columns(); /* update window widths */ + lastused_tabpage = old_curtab; /* Apply autocommands after updating the display, when 'rows' and * 'columns' have been set correctly. */ @@ -4095,6 +4106,14 @@ void goto_tabpage_tp(tabpage_T *tp, int trigger_enter_autocmds, int trigger_leav } } +// Go to the last accessed tab page, if there is one. +void goto_tabpage_lastused(void) +{ + if (valid_tabpage(lastused_tabpage)) { + goto_tabpage_tp(lastused_tabpage, true, true); + } +} + /* * Enter window "wp" in tab page "tp". * Also updates the GUI tab. diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index 32e341184d..6240db2042 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -1,5 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq + +local clear = helpers.clear +local command = helpers.command +local dedent = helpers.dedent +local eval = helpers.eval +local eq = helpers.eq +local feed = helpers.feed +local nvim = helpers.nvim +local redir_exec = helpers.redir_exec describe('TabNewEntered', function() describe('au TabNewEntered', function() @@ -29,3 +37,447 @@ describe('TabNewEntered', function() end) end) end) + +describe('TabEnter', function() + before_each(clear) + it('has correct previous tab when entering any new tab', function() + command('augroup TEMP') + nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')') + command('augroup END') + eq("tabenter:2:1", nvim('exec', 'tabnew', true)) + eq("tabenter:3:2", nvim('exec', 'tabnew test.x2', true)) + command('augroup! TEMP') + end) + it('has correct previous tab when entering any preexisting tab', function() + command('tabnew') + command('tabnew') + command('augroup TEMP') + nvim('command', 'au! TabEnter * echom "tabenter:".tabpagenr().":".tabpagenr(\'#\')') + command('augroup END') + eq("tabenter:1:3", nvim('exec', 'tabnext', true)) + eq("tabenter:2:1", nvim('exec', 'tabnext', true)) + command('augroup! TEMP') + end) +end) + +describe('tabpage/previous', function() + before_each(clear) + local function switches_to_previous_after_new_tab_creation_at_end(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + + -- The previous tab is now the third. + eq(3, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (third) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + > [No Name] + Tab page 4 + [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after new tab creation at end', + switches_to_previous_after_new_tab_creation_at_end('g<Tab>')) + it('switches to previous via <C-W>g<Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-W>g<Tab>')) + it('switches to previous via <C-Tab>. after new tab creation at end', switches_to_previous_after_new_tab_creation_at_end('<C-Tab>')) + + local function switches_to_previous_after_new_tab_creation_in_middle(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the second tab + command('tabnext 2') + -- Add a new tab after the second tab + command('tabnew') + + -- The previous tab is now the second. + eq(2, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (second) tab + feed(characters) + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + > [No Name] + Tab page 3 + [No Name] + Tab page 4 + [No Name] + Tab page 5 + [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the third. + eq(3, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after new tab creation in middle', + switches_to_previous_after_new_tab_creation_in_middle('g<Tab>')) + it('switches to previous via <C-W>g<Tab> after new tab creation in middle', + switches_to_previous_after_new_tab_creation_in_middle('<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after new tab creation in middle', + switches_to_previous_after_new_tab_creation_in_middle('<C-Tab>')) + + local function switches_to_previous_after_switching_to_next_tab(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the next (first) tab + command('tabnext') + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (fourth) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + [No Name] + Tab page 4 + > [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the first. + eq(1, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after switching to next tab', + switches_to_previous_after_switching_to_next_tab('g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to next tab', + switches_to_previous_after_switching_to_next_tab('<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to next tab', + switches_to_previous_after_switching_to_next_tab('<C-Tab>')) + + local function switches_to_previous_after_switching_to_last_tab(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the next (first) tab + command('tabnext') + -- Switch to the last (fourth) tab. + command('tablast') + + -- The previous tab is now the second. + eq(1, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (second) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + > [No Name] + Tab page 2 + [No Name] + Tab page 3 + [No Name] + Tab page 4 + [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous after switching to last tab', + switches_to_previous_after_switching_to_last_tab('g<Tab>')) + it('switches to previous after switching to last tab', + switches_to_previous_after_switching_to_last_tab('<C-W>g<Tab>')) + it('switches to previous after switching to last tab', + switches_to_previous_after_switching_to_last_tab('<C-Tab>')) + + local function switches_to_previous_after_switching_to_previous_tab(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the previous (third) tab + command('tabprevious') + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (fourth) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + [No Name] + Tab page 4 + > [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the third. + eq(3, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after switching to previous tab', + switches_to_previous_after_switching_to_previous_tab('g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to previous tab', + switches_to_previous_after_switching_to_previous_tab('<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to previous tab', + switches_to_previous_after_switching_to_previous_tab('<C-Tab>')) + + local function switches_to_previous_after_switching_to_first_tab(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the previous (third) tab + command('tabprevious') + -- Switch to the first tab + command('tabfirst') + + -- The previous tab is now the third. + eq(3, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (third) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + > [No Name] + Tab page 4 + [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the first. + eq(1, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after switching to first tab', + switches_to_previous_after_switching_to_first_tab('g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to first tab', + switches_to_previous_after_switching_to_first_tab('<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to first tab', + switches_to_previous_after_switching_to_first_tab('<C-Tab>')) + + local function switches_to_previous_after_numbered_tab_switch(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the second tab + command('tabnext 2') + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (fourth) tab + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + [No Name] + Tab page 4 + > [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the second. + eq(2, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after numbered tab switch', + switches_to_previous_after_numbered_tab_switch('g<Tab>')) + it('switches to previous via <C-W>g<Tab> after numbered tab switch', + switches_to_previous_after_numbered_tab_switch('<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after numbered tab switch', + switches_to_previous_after_numbered_tab_switch('<C-Tab>')) + + local function switches_to_previous_after_switching_to_previous(characters1, characters2) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Switch to the second tab + command('tabnext 2') + -- Switch to the previous (fourth) tab + feed(characters1) + + -- The previous tab is now the second. + eq(2, eval('tabpagenr(\'#\')')) + + -- Switch to the previous (second) tab + feed(characters2) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + > [No Name] + Tab page 3 + [No Name] + Tab page 4 + [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the fourth. + eq(4, eval('tabpagenr(\'#\')')) + end + end + it('switches to previous via g<Tab> after switching to previous via g<Tab>', + switches_to_previous_after_switching_to_previous('g<Tab>', 'g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to previous via g<Tab>', + switches_to_previous_after_switching_to_previous('g<Tab>', '<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to previous via g<Tab>', + switches_to_previous_after_switching_to_previous('g<Tab>', '<C-Tab>')) + it('switches to previous via g<Tab> after switching to previous via <C-W>g<Tab>', + switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', 'g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to previous via <C-W>g<Tab>', + switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to previous via <C-W>g<Tab>', + switches_to_previous_after_switching_to_previous('<C-W>g<Tab>', '<C-Tab>')) + it('switches to previous via g<Tab> after switching to previous via <C-Tab>', + switches_to_previous_after_switching_to_previous('<C-Tab>', 'g<Tab>')) + it('switches to previous via <C-W>g<Tab> after switching to previous via <C-Tab>', + switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-W>g<Tab>')) + it('switches to previous via <C-Tab> after switching to previous via <C-Tab>', + switches_to_previous_after_switching_to_previous('<C-Tab>', '<C-Tab>')) + + local function does_not_switch_to_previous_after_closing_current_tab(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + -- Close the current (fourth tab) + command('wincmd c') + + -- The previous tab is now the "zeroth" -- there isn't one. + eq(0, eval('tabpagenr(\'#\')')) + + -- At this point, switching to the "previous" (i.e. fourth) tab would mean + -- switching to either a dangling or a null pointer. + feed(characters) + + eq(dedent([=[ + + + Tab page 1 + [No Name] + Tab page 2 + [No Name] + Tab page 3 + > [No Name]]=]), + redir_exec('tabs') + ) + + -- The previous tab is now the "zero". + eq(0, eval('tabpagenr(\'#\')')) + end + end + it('does not switch to previous via g<Tab> after closing current tab', + does_not_switch_to_previous_after_closing_current_tab('g<Tab>')) + it('does not switch to previous via <C-W>g<Tab> after closing current tab', + does_not_switch_to_previous_after_closing_current_tab('<C-W>g<Tab>')) + it('does not switch to previous via <C-Tab> after closing current tab', + does_not_switch_to_previous_after_closing_current_tab('<C-Tab>')) + + local function does_not_switch_to_previous_after_entering_operator_pending(characters) + return function() + -- Add three tabs for a total of four + command('tabnew') + command('tabnew') + command('tabnew') + + -- The previous tab is now the third. + eq(3, eval('tabpagenr(\'#\')')) + + -- Enter operator pending mode. + feed('d') + eq('no', eval('mode(1)')) + + -- At this point switching to the previous tab should have no effect + -- other than leaving operator pending mode. + feed(characters) + + -- Attempting to switch tabs returns us to normal mode. + eq('n', eval('mode()')) + + -- The current tab is still the fourth. + eq(4, eval('tabpagenr()')) + + -- The previous tab is still the third. + eq(3, eval('tabpagenr(\'#\')')) + end + end + it('does not switch to previous via g<Tab> after entering operator pending', + does_not_switch_to_previous_after_entering_operator_pending('g<Tab>')) + -- NOTE: When in operator pending mode, attempting to switch to previous has + -- the following effect: + -- - Ctrl-W exits operator pending mode + -- - g<Tab> switches to the previous tab + -- In other words, the effect of "<C-W>g<Tab>" is to switch to the + -- previous tab even from operator pending mode, but only thanks to the + -- fact that the suffix after "<C-W>" in "<C-W>g<Tab>" just happens to + -- be the same as the normal mode command to switch to the previous tab. + -- it('does not switch to previous via <C-W>g<Tab> after entering operator pending', + -- does_not_switch_to_previous_after_entering_operator_pending('<C-W>g<Tab>')) + it('does not switch to previous via <C-Tab> after entering operator pending', + does_not_switch_to_previous_after_entering_operator_pending('<C-Tab>')) +end) |