patch 9.2.0407: tabpanel: A few issues with the tabpanel

Commit: 
https://github.com/vim/vim/commit/2d43240659868e91e7c371017793091082bfda74
Author: Hirohito Higashi <[email protected]>
Date:   Mon Apr 27 21:14:46 2026 +0000

    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) <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index d2611db09..b3edc22e3 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 7fc09c545..88aeb4ad3 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
+|<ScrollWheelUp>| or |<ScrollWheelDown>| 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 |<ScrollWheelUp>| or |<ScrollWheelDown>| 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 43106ef30..725ba3326 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 ab7c5ffff..72f5dd3ce 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 72726d89e..e6328b177 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 07be48e44..34372fe60 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 aa1ae3e18..3f462354c 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 b1be22446..91610e128 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 c510d078f..f7e6bdb08 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 bc54d272d..02f7fdf34 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 0d4d8eac2..b6cbb51aa 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 478ec03ae..c3acd7af3 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);

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wHTWn-004Zub-5E%40256bit.org.

Raspunde prin e-mail lui