From 2d43240659868e91e7c371017793091082bfda74 Mon Sep 17 00:00:00 2001 From: Hirohito Higashi Date: Mon, 27 Apr 2026 21:14:46 +0000 Subject: [PATCH] patch 9.2.0407: tabpanel: A few issues with the tabpanel Problem: Several issues around the tabpanel scrollbar: 1. :set tabpanelopt= completion did not offer "scroll" and "scrollbar". 2. gt/gT and other tab switches did not update the scrollbar thumb; the current tab could move outside the visible panel range without the view following. 3. When tpl_scroll_offset was at its maximum, the thumb's bottom did not reach the last screen row due to integer truncation in thumb_top (e.g. 31 tabs on 24 rows + :tablast left a one-row gap). 4. For align:right the scrollbar was drawn on the panel's left edge (adjacent to the buffer area), which breaks the common convention that a vertical scrollbar sits on the right. Solution: - Add "scroll" and "scrollbar" to the 'tabpanelopt' expansion list. Cover the completion in test_options.vim and extend util/gen_opt_test.vim with the new valid/invalid values; drop the now-redundant acceptance test from test_tabpanel.vim. - In draw_tabpanel(), remember the last-drawn curtab and, when it changes, adjust tpl_scroll_offset so curtab_row falls inside [offset, offset + Rows). Mouse wheel and drag leave curtab unchanged, so the user's chosen offset is preserved. - In draw_tabpanel_scrollbar(), compute thumb_top as (Rows - thumb_height) * tpl_scroll_offset / (tpl_total_rows - Rows), mirroring the mapping already used by tabpanel_drag_scrollbar(). This guarantees the thumb's bottom reaches the last row at the maximum offset. - In draw_tabpanel(), place the scrollbar at the tabpanel's right edge for both align:left and align:right (previously align:right put it on the panel's left edge next to the vertical separator). For align:right this means the scrollbar now sits at the screen's right edge. - Update :h tabpanel-scroll to describe the new, align- independent placement. - Add Test_tabpanel_scrollbar_follows_curtab() and Test_tabpanel_scrollbar_reaches_bottom() to exercise the regressions fixed by items 2 and 3. closes: #20052 Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- runtime/doc/options.txt | 28 ++-- runtime/doc/tabpage.txt | 36 ++--- runtime/doc/version9.txt | 6 +- src/optionstr.c | 2 +- src/proto/tabpanel.pro | 1 + src/tabpanel.c | 123 ++++++++++++------ .../dumps/Test_tabpanel_many_tabpages_4.dump | 16 +-- src/testdir/test_options.vim | 6 + src/testdir/test_tabpanel.vim | 119 +++++++++++++---- src/testdir/util/gen_opt_test.vim | 6 +- src/version.c | 2 + src/window.c | 3 + 12 files changed, 228 insertions(+), 120 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d2611db095..b3edc22e35 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.2. Last change: 2026 Apr 21 +*options.txt* For Vim version 9.2. Last change: 2026 Apr 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -9062,25 +9062,17 @@ A jump table for the options with a short description can be found at |Q_op|. columns:{n} Number of columns of the tabpanel. If this value is 0 or less than 'columns', the - tab panel will not be displayed. + tabpanel will not be displayed. (default 20) - scroll Enable mouse wheel scrolling over the tabpanel - area when the tab list exceeds the visible - screen height. The scroll step is controlled - by 'mousescroll'. When disabled (the default), - the tabpanel shows the page containing the - current tab, with no way to view tabs outside - that page. - - scrollbar Reserve a one-column scrollbar in the tabpanel - showing the current scroll position. The - scrollbar uses the |hl-PmenuSbar| and - |hl-PmenuThumb| highlight groups for the track - and thumb respectively. Clicking on the - scrollbar column jumps the thumb to that - position; the thumb can also be dragged. - Implies "scroll". + scrollbar Reserve a one-column scrollbar at the right + edge of the tabpanel showing the current + scroll position. The scrollbar uses the + |hl-PmenuSbar| and |hl-PmenuThumb| highlight + groups for the track and thumb respectively. + Clicking on the scrollbar column jumps the + thumb to that position; the thumb can also be + dragged. See |tabpanel-scroll|. vert Use a vertical separator for tabpanel. The vertical separator character is taken from diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt index 7fc09c5451..88aeb4ad34 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -1,4 +1,4 @@ -*tabpage.txt* For Vim version 9.2. Last change: 2026 Apr 26 +*tabpage.txt* For Vim version 9.2. Last change: 2026 Apr 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -485,33 +485,33 @@ groups: |hl-TabPanel| |hl-TabPanelSel| |hl-TabPanelFill| SCROLLING IN THE TABPANEL: *tabpanel-scroll* When the total height of the tab page list exceeds the visible screen height, -the tabpanel by default displays the "page" that contains the current tab page -and offers no way to view tab pages outside that page. +mouse wheel events over the tabpanel area scroll the tab page list up or +down. The scroll step follows the 'mousescroll' setting. Wheel events +inside the tabpanel area are consumed by the tabpanel and do not trigger +|| or || mappings. -To make the tabpanel scrollable, add "scroll" to 'tabpanelopt': > - :set tabpanelopt+=scroll +The current tab page is always brought into view: when the selected tab +page changes (by |gt|, |gT|, |:tabnext| etc.), the panel scrolls so the +current entry is visible. -With "scroll" enabled, mouse wheel events over the tabpanel area scroll the -tab page list up or down. The scroll step follows the 'mousescroll' setting. -Wheel events inside the tabpanel area are consumed by the tabpanel and do not -trigger || or || mappings. - -To additionally show a vertical scrollbar indicating the current scroll -position, use "scrollbar": > +To show a vertical scrollbar indicating the current scroll position, add +"scrollbar" to 'tabpanelopt': > :set tabpanelopt+=scrollbar -The "scrollbar" value implies "scroll". A one-column scrollbar is reserved at -the edge of the tabpanel; clicking on the scrollbar column moves the thumb to +A one-column scrollbar is always reserved at the right edge of the +tabpanel, regardless of 'align'. For |'tabpanelopt'|=align:left this is +the edge adjacent to the buffer windows; for align:right it is the right +edge of the screen. Clicking on the scrollbar column moves the thumb to the click position, and the thumb can be dragged to scroll continuously. -When "vert" is combined with "scrollbar", the scrollbar is drawn next to the -vertical separator, on the panel side. +When "vert" is combined with "scrollbar", the vertical separator is drawn +at the tabpanel's boundary with the buffer area and the scrollbar stays at +the tabpanel's right edge. The scrollbar uses the |hl-PmenuSbar| highlight group for the track and |hl-PmenuThumb| for the thumb. -The scroll offset is remembered across redraws but is reset when "scroll" or -"scrollbar" is toggled off and back on. +The scroll offset is remembered across redraws. MOUSE CLICKS IN THE TABPANEL: *tabpanel-mouse* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 43106ef30a..725ba3326d 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.2. Last change: 2026 Apr 26 +*version9.txt* For Vim version 9.2. Last change: 2026 Apr 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -52616,8 +52616,8 @@ Other ~ - Allow mouse clickable regions in the 'statusline', 'tabline' and the 'tabpanel' using the |stl-%[FuncName]| atom. - Enable reflow support in the |:terminal|. -- Added "scroll" and "scrollbar" sub-options to 'tabpanelopt' so the tabpanel - can scroll when the tab page list exceeds the visible screen height. +- Added "scrollbar" sub-option to 'tabpanelopt' so the tabpanel can scroll + when the tab page list exceeds the visible screen height. Platform specific ~ ----------------- diff --git a/src/optionstr.c b/src/optionstr.c index ab7c5ffff1..72f5dd3ced 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -30,7 +30,7 @@ static char *(p_briopt_values[]) = {"shift:", "min:", "sbr", "list:", "column:", #endif #if defined(FEAT_TABPANEL) // Note: Keep this in sync with tabpanelopt_changed() -static char *(p_tplo_values[]) = {"align:", "columns:", "vert", NULL}; +static char *(p_tplo_values[]) = {"align:", "columns:", "scrollbar", "vert", NULL}; static char *(p_tplo_align_values[]) = {"left", "right", NULL}; #endif #if defined(FEAT_DIFF) diff --git a/src/proto/tabpanel.pro b/src/proto/tabpanel.pro index 72726d89ea..e6328b1774 100644 --- a/src/proto/tabpanel.pro +++ b/src/proto/tabpanel.pro @@ -1,5 +1,6 @@ /* tabpanel.c */ int tabpanelopt_changed(void); +void tabpanel_forget_tabpage(const tabpage_T *tp); int tabpanel_width(void); int tabpanel_leftcol(void); void draw_tabpanel(void); diff --git a/src/tabpanel.c b/src/tabpanel.c index 07be48e44c..34372fe60c 100644 --- a/src/tabpanel.c +++ b/src/tabpanel.c @@ -43,11 +43,11 @@ static int opt_scope = OPT_LOCAL; static int tpl_align = ALIGN_LEFT; static int tpl_columns = 20; static bool tpl_is_vert = false; -static bool tpl_scroll = false; static bool tpl_scrollbar = false; static int tpl_scroll_offset = 0; static int tpl_total_rows = 0; -static int tpl_scrollbar_col = -1; // screen column of scrollbar, -1 if none +static int tpl_scrollbar_col = -1; // screen column of scrollbar, -1 if none +static tabpage_T *tpl_last_curtab = NULL; // last curtab seen by draw_tabpanel typedef struct { win_T *wp; @@ -69,7 +69,6 @@ tabpanelopt_changed(void) int new_align = ALIGN_LEFT; long new_columns = 20; bool new_is_vert = false; - bool new_scroll = false; bool new_scrollbar = false; p = p_tplo; @@ -107,12 +106,6 @@ tabpanelopt_changed(void) { p += 9; new_scrollbar = true; - new_scroll = true; - } - else if (STRNCMP(p, "scroll", 6) == 0) - { - p += 6; - new_scroll = true; } if (*p != ',' && *p != NUL) @@ -124,15 +117,26 @@ tabpanelopt_changed(void) tpl_align = new_align; tpl_columns = new_columns; tpl_is_vert = new_is_vert; - if (tpl_scroll != new_scroll) - tpl_scroll_offset = 0; - tpl_scroll = new_scroll; tpl_scrollbar = new_scrollbar; + // Re-center the current tab on the next redraw. + tpl_last_curtab = NULL; + shell_new_columns(); return OK; } +/* + * Drop any internal reference to "tp", so draw_tabpanel() never compares + * against a dangling pointer after the tabpage has been freed. + */ + void +tabpanel_forget_tabpage(const tabpage_T *tp) +{ + if (tpl_last_curtab == tp) + tpl_last_curtab = NULL; +} + /* * Return the width of tabpanel. */ @@ -264,6 +268,31 @@ tabpanel_append_click_regions( } } +/* + * Ensure the current tab is visible by adjusting tpl_scroll_offset when + * the selected tab has changed since the previous redraw. Mouse wheel or + * scrollbar drag operations leave curtab unchanged, so the user's chosen + * offset is preserved in those cases. + */ + static void +follow_curtab_if_needed(int curtab_row) +{ + if (Rows <= 0 || curtab == tpl_last_curtab) + return; + + if (curtab_row < tpl_scroll_offset) + tpl_scroll_offset = curtab_row; + else if (curtab_row >= tpl_scroll_offset + Rows) + tpl_scroll_offset = curtab_row - Rows + 1; + + int max_offset = tpl_total_rows > Rows ? tpl_total_rows - Rows : 0; + + if (tpl_scroll_offset < 0) + tpl_scroll_offset = 0; + else if (tpl_scroll_offset > max_offset) + tpl_scroll_offset = max_offset; +} + /* * draw the tabpanel. */ @@ -293,30 +322,36 @@ draw_tabpanel(void) int sb_len = tpl_scrollbar ? SCROLL_LEN : 0; int sb_screen_col = -1; + // The scrollbar is always placed at the right edge of the tabpanel, + // regardless of 'align'. The vertical separator sits at the panel's + // boundary with the buffer area (left edge for align:right, right edge + // for align:left). if (tpl_is_vert) { if (is_right) { - // draw main contents in tabpanel - do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN + sb_len, - maxwidth - VERT_LEN, &curtab_row, NULL); - do_by_tplmode(TPLMODE_REDRAW, VERT_LEN + sb_len, maxwidth, + // Panel on the right: vert at panel's left edge, scrollbar at + // panel's right edge (= screen's right edge). + do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN, + maxwidth - sb_len, &curtab_row, NULL); + follow_curtab_if_needed(curtab_row); + do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth - sb_len, &curtab_row, NULL); - // draw vert separator in tabpanel for (vsrow = 0; vsrow < Rows; vsrow++) screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow, topframe->fr_width, vs_attr); if (tpl_scrollbar) - sb_screen_col = topframe->fr_width + VERT_LEN; + sb_screen_col = topframe->fr_width + maxwidth - SCROLL_LEN; } else { - // draw main contents in tabpanel + // Panel on the left: scrollbar just left of vert, vert at + // panel's right edge (boundary with buffer). do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - VERT_LEN - sb_len, &curtab_row, NULL); + follow_curtab_if_needed(curtab_row); do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - VERT_LEN - sb_len, &curtab_row, NULL); - // draw vert separator in tabpanel for (vsrow = 0; vsrow < Rows; vsrow++) screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow, maxwidth - VERT_LEN, vs_attr); @@ -328,16 +363,20 @@ draw_tabpanel(void) { if (is_right) { - do_by_tplmode(TPLMODE_GET_CURTAB_ROW, sb_len, maxwidth, + // Panel on the right, no vert: scrollbar at screen's right edge. + do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - sb_len, + &curtab_row, NULL); + follow_curtab_if_needed(curtab_row); + do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - sb_len, &curtab_row, NULL); - do_by_tplmode(TPLMODE_REDRAW, sb_len, maxwidth, &curtab_row, NULL); if (tpl_scrollbar) - sb_screen_col = topframe->fr_width; + sb_screen_col = topframe->fr_width + maxwidth - SCROLL_LEN; } else { do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - sb_len, &curtab_row, NULL); + follow_curtab_if_needed(curtab_row); do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - sb_len, &curtab_row, NULL); if (tpl_scrollbar) @@ -354,6 +393,7 @@ draw_tabpanel(void) // A user function may reset KeyTyped, restore it. KeyTyped = saved_KeyTyped; + tpl_last_curtab = curtab; redraw_tabpanel = FALSE; } @@ -606,13 +646,7 @@ do_by_tplmode( args.col_end = col_end; if (tplmode != TPLMODE_GET_CURTAB_ROW && args.maxrow > 0) - { - if (tpl_scroll) - args.offsetrow = tpl_scroll_offset; - else - while (args.offsetrow + args.maxrow <= *pcurtab_row) - args.offsetrow += args.maxrow; - } + args.offsetrow = tpl_scroll_offset; tp = first_tabpage; @@ -632,16 +666,9 @@ do_by_tplmode( { args.attr = attr_tpls; if (tplmode == TPLMODE_GET_CURTAB_ROW) - { + // Capture the row of the current tab and keep iterating so + // tpl_total_rows receives the true content height below. *pcurtab_row = row; - // When scroll mode is active keep iterating so tpl_total_rows - // receives the true content height; otherwise bail out early. - if (!tpl_scroll) - { - do_unlet((char_u *)"g:actual_curtabpage", TRUE); - break; - } - } } else args.attr = attr_tpl; @@ -742,7 +769,7 @@ do_by_tplmode( // Capture the true content height during the GET_CURTAB_ROW pass, which // ignores maxrow and therefore walks every tab. REDRAW stops at the // visible edge so its "row" is clamped and unusable here. - if (tplmode == TPLMODE_GET_CURTAB_ROW && tpl_scroll) + if (tplmode == TPLMODE_GET_CURTAB_ROW) tpl_total_rows = row; } @@ -761,10 +788,21 @@ draw_tabpanel_scrollbar(int screen_col) if (tpl_total_rows > Rows && Rows > 0) { + int max_offset = tpl_total_rows - Rows; + int track_range; + thumb_height = Rows * Rows / tpl_total_rows; if (thumb_height < 1) thumb_height = 1; - thumb_top = Rows * tpl_scroll_offset / tpl_total_rows; + + // Map tpl_scroll_offset onto the track: at offset 0 the thumb's top + // is at row 0, at the maximum offset its bottom reaches the last + // row. This is the exact inverse of tabpanel_drag_scrollbar(). + track_range = Rows - thumb_height; + if (track_range > 0 && max_offset > 0) + thumb_top = track_range * tpl_scroll_offset / max_offset; + else + thumb_top = 0; if (thumb_top + thumb_height > Rows) thumb_top = Rows - thumb_height; if (thumb_top < 0) @@ -848,7 +886,6 @@ tabpanel_drag_scrollbar(int screen_row) /* * Scroll the tabpanel by 'count' rows in direction 'dir' (1 = down, -1 = up). * Returns true if the offset changed and a redraw was scheduled. - * Has no effect unless 'tabpanelopt' contains "scroll". */ bool tabpanel_scroll(int dir, int count) @@ -856,7 +893,7 @@ tabpanel_scroll(int dir, int count) int max_offset; int new_offset; - if (!tpl_scroll || tabpanel_width() == 0) + if (tabpanel_width() == 0) return false; max_offset = tpl_total_rows - Rows; diff --git a/src/testdir/dumps/Test_tabpanel_many_tabpages_4.dump b/src/testdir/dumps/Test_tabpanel_many_tabpages_4.dump index aa1ae3e18b..3f462354c4 100644 --- a/src/testdir/dumps/Test_tabpanel_many_tabpages_4.dump +++ b/src/testdir/dumps/Test_tabpanel_many_tabpages_4.dump @@ -1,10 +1,10 @@ -|1+2&#ffffff0@1|:|t|a|b| @3> +0&&@34 +|5+8#0000001#e0e0e08|:|t|a|b| @4> +0#0000000#ffffff0@34 +|6+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33 +|7+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33 +|8+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33 +|9+8#0000001#e0e0e08|:|t|a|b| @4|~+0#4040ff13#ffffff0| @33 +|1+8#0000001#e0e0e08|0|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 +|1+2#0000000&@1|:|t|a|b| @3|~+0#4040ff13&| @33 |1+8#0000001#e0e0e08|2|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 |1+8#0000001#e0e0e08|3|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|4|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|5|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|6|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|7|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|8|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|1+8#0000001#e0e0e08|9|:|t|a|b| @3|~+0#4040ff13#ffffff0| @33 -|2+8#0000001#e0e0e08|0|:|t|a|b| @3|:+0#0000000#ffffff0|t|a|b|n|e|x|t| |-|3| @6|0|,|0|-|1| @7|A|l@1| +|1+8#0000001#e0e0e08|4|:|t|a|b| @3|:+0#0000000#ffffff0|t|a|b|n|e|x|t| |-|3| @6|0|,|0|-|1| @7|A|l@1| diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim index b1be224467..91610e1289 100644 --- a/src/testdir/test_options.vim +++ b/src/testdir/test_options.vim @@ -593,6 +593,12 @@ func Test_set_completion_string_values() if exists('+tabclose') call assert_equal('left uselast', join(sort(getcompletion('set tabclose=', 'cmdline'))), ' ') endif + if has('tabpanel') + call assert_equal(['align:', 'columns:', 'scrollbar', 'vert'], + \ getcompletion('set tabpanelopt=', 'cmdline')) + call assert_equal(['left', 'right'], + \ getcompletion('set tabpanelopt=align:', 'cmdline')) + endif if exists('+termwintype') call assert_equal('conpty', getcompletion('set termwintype=', 'cmdline')[1]) endif diff --git a/src/testdir/test_tabpanel.vim b/src/testdir/test_tabpanel.vim index c510d078fd..f7e6bdb082 100644 --- a/src/testdir/test_tabpanel.vim +++ b/src/testdir/test_tabpanel.vim @@ -963,49 +963,26 @@ func Test_tabpanel_large_columns() call assert_fails(':set tabpanelopt=columns:-1', 'E474:') endfunc -func Test_tabpanel_scrollopt_accepted() - " 'scroll' / 'scrollbar' must be accepted in 'tabpanelopt'. - set tabpanelopt=scroll - call assert_equal('scroll', &tabpanelopt) - set tabpanelopt=scrollbar - call assert_equal('scrollbar', &tabpanelopt) - - " Combination with other values. - set tabpanelopt=align:right,scroll - call assert_equal('align:right,scroll', &tabpanelopt) - set tabpanelopt=columns:15,vert,scrollbar - call assert_equal('columns:15,vert,scrollbar', &tabpanelopt) - set tabpanelopt=align:right,columns:12,vert,scrollbar - call assert_equal('align:right,columns:12,vert,scrollbar', &tabpanelopt) - - " Unknown values must still fail. - call assert_fails(':set tabpanelopt=scrol', 'E474:') - call assert_fails(':set tabpanelopt=scrollbarx', 'E474:') - - call s:reset() -endfunc - func Test_tabpanel_scroll_many_tabs() let save_lines = &lines let save_showtabpanel = &showtabpanel let save_tabpanelopt = &tabpanelopt - " Make the screen short so the tab list exceeds the visible height. + " Make the screen short so the tab page list exceeds the visible height. set lines=8 set showtabpanel=2 - set tabpanelopt=scroll + set tabpanelopt= for i in range(20) tabnew endfor - " Should not crash with many tabs and scroll enabled. + " Should not crash with many tabs (scroll behaviour is always on). redraw! - " Switching to scrollbar resets the offset but must also not crash. + " Toggling scrollbar must also not crash. set tabpanelopt=scrollbar redraw! - " Disabling scroll returns to normal behavior. set tabpanelopt= redraw! @@ -1024,6 +1001,94 @@ func Test_tabpanel_scroll_many_tabs() let &lines = save_lines endfunc +" The scrollbar thumb must follow the current tab when it is moved by +" gt/gT/:tabnext/:tablast, so that the selected tab is always visible. +func Test_tabpanel_scrollbar_follows_curtab() + let save_lines = &lines + let save_columns = &columns + let save_showtabpanel = &showtabpanel + let save_tabpanelopt = &tabpanelopt + + set lines=10 columns=40 + set showtabpanel=2 tabpanelopt=scrollbar,columns:8 + for i in range(49) + tabnew + endfor + let sb_col = 8 + + " With curtab at the top of the list, row 1 shows the thumb and the + " last row shows the track. Record the two attrs for comparison. + tabfirst + redraw + let attr_thumb = screenattr(1, sb_col) + let attr_track = screenattr(&lines, sb_col) + call assert_notequal(attr_thumb, attr_track) + + " Jump to a tab far outside the visible range: thumb must leave the top. + 30tabnext + redraw + call assert_equal(attr_track, screenattr(1, sb_col)) + + " Back to the first tab: thumb returns to the top. + tabfirst + redraw + call assert_equal(attr_thumb, screenattr(1, sb_col)) + call assert_equal(attr_track, screenattr(&lines, sb_col)) + + " gT from the first tab wraps to the last: thumb moves to the bottom. + normal! gT + redraw + call assert_equal(attr_track, screenattr(1, sb_col)) + call assert_equal(attr_thumb, screenattr(&lines, sb_col)) + + " gt from the last tab wraps to the first: thumb returns to the top. + normal! gt + redraw + call assert_equal(attr_thumb, screenattr(1, sb_col)) + call assert_equal(attr_track, screenattr(&lines, sb_col)) + + %bwipeout! + let &tabpanelopt = save_tabpanelopt + let &showtabpanel = save_showtabpanel + let &lines = save_lines + let &columns = save_columns +endfunc + +" With 31 tabs on 24 rows, :tablast must place the scrollbar thumb's +" bottom at the last screen row. Before the fix, integer truncation in +" thumb_top left a one-row gap at the bottom. +func Test_tabpanel_scrollbar_reaches_bottom() + let save_lines = &lines + let save_columns = &columns + let save_showtabpanel = &showtabpanel + let save_tabpanelopt = &tabpanelopt + + set lines=24 columns=40 + set showtabpanel=2 tabpanelopt=scrollbar,columns:8 + for i in range(30) + tabnew + endfor + let sb_col = 8 + + " Identify the thumb attr while the thumb is at the top. + tabfirst + redraw + let attr_thumb = screenattr(1, sb_col) + let attr_track = screenattr(&lines, sb_col) + call assert_notequal(attr_thumb, attr_track) + + " :tablast must push the thumb all the way to the bottom. + tablast + redraw + call assert_equal(attr_thumb, screenattr(&lines, sb_col)) + + %bwipeout! + let &tabpanelopt = save_tabpanelopt + let &showtabpanel = save_showtabpanel + let &lines = save_lines + let &columns = save_columns +endfunc + func Test_tabpanel_variable_height() let save_lines = &lines diff --git a/src/testdir/util/gen_opt_test.vim b/src/testdir/util/gen_opt_test.vim index bc54d272d8..02f7fdf34e 100644 --- a/src/testdir/util/gen_opt_test.vim +++ b/src/testdir/util/gen_opt_test.vim @@ -330,9 +330,11 @@ let test_values = { \ 'tabline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']], \ 'tabpanel': [['', 'aaa', 'bbb'], []], \ 'tabpanelopt': [['', 'align:left', 'align:right', 'vert', 'columns:0', - \ 'columns:20', 'columns:999'], + \ 'columns:20', 'columns:999', 'scrollbar', + \ 'columns:15,vert,scrollbar', + \ 'align:right,columns:12,vert,scrollbar'], \ ['xxx', 'align:', 'align:middle', 'colomns:', 'cols:10', - \ 'cols:-1']], + \ 'cols:-1', 'scroll', 'scrol', 'scrollbarx']], \ 'tagcase': [['followic', 'followscs', 'ignore', 'match', 'smart'], \ ['', 'xxx', 'smart,match']], \ 'termencoding': [has('gui_gtk') ? [] : ['', 'utf-8'], ['xxx']], diff --git a/src/version.c b/src/version.c index 0d4d8eac2f..b6cbb51aa8 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 407, /**/ 406, /**/ diff --git a/src/window.c b/src/window.c index 478ec03ae1..c3acd7af33 100644 --- a/src/window.c +++ b/src/window.c @@ -4784,6 +4784,9 @@ free_tabpage(tabpage_T *tp) if (tp == lastused_tabpage) lastused_tabpage = NULL; +#ifdef FEAT_TABPANEL + tabpanel_forget_tabpage(tp); +#endif vim_free(tp->tp_localdir); vim_free(tp->tp_prevdir); -- 2.47.3