]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0386: No scroll/scrollbar support in the tabpanel v9.2.0386
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Tue, 21 Apr 2026 20:20:30 +0000 (20:20 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 21 Apr 2026 20:39:22 +0000 (20:39 +0000)
Problem:  No scroll/scrollbar support in the tabpanel
Solution: Add support for it (Yasuhiro Matsumoto)

closes: #19979

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/options.txt
runtime/doc/tabpage.txt
runtime/doc/tags
runtime/doc/version9.txt
src/buffer.c
src/mouse.c
src/proto/tabpanel.pro
src/tabpanel.c
src/testdir/test_tabpanel.vim
src/version.c

index c02d40eaa8933df68800118b2ad461f890e82c84..d2611db0959ec5fc0e1124544da7af6b416e9caf 100644 (file)
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.2.  Last change: 2026 Apr 20
+*options.txt*  For Vim version 9.2.  Last change: 2026 Apr 21
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -9065,6 +9065,23 @@ A jump table for the options with a short description can be found at |Q_op|.
                                tab panel 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".
+
                vert            Use a vertical separator for tabpanel.
                                The vertical separator character is taken from
                                "tpl_vert" in 'fillchars'.
index 3e307b648a5dc7fb960f5219fbf431ccf0036ca1..3130c185f87a7b3af504f8c04a0238f1ce042d29 100644 (file)
@@ -1,4 +1,4 @@
-*tabpage.txt*  For Vim version 9.2.  Last change: 2026 Feb 14
+*tabpage.txt*  For Vim version 9.2.  Last change: 2026 Apr 21
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -482,6 +482,37 @@ The vertical separator character is taken from "tpl_vert" in 'fillchars'.
 You can customize the appearance of the tab page labels using the highlight
 groups: |hl-TabPanel| |hl-TabPanelSel| |hl-TabPanelFill|
 
+SCROLLING IN THE TABPANEL:                             *tabpanel-scroll*
+
+When the total height of the tab list exceeds the visible screen height, the
+tabpanel by default displays the "page" that contains the current tab and
+offers no way to view tabs outside that page.
+
+To make the tabpanel scrollable, add "scroll" to 'tabpanelopt': >
+       :set tabpanelopt+=scroll
+
+With "scroll" enabled, mouse wheel events over the tabpanel area scroll the
+tab 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": >
+       :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
+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.
+
+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.
+
 ==============================================================================
 6. Setting 'guitablabel'                               *setting-guitablabel*
 
index 2b0e6cab39d9b0a51dc47550a33abf1fdced50ad..c4cc01bdc96c709079a6b35626446bdc7c6c3deb 100644 (file)
@@ -10889,6 +10889,7 @@ tabpagebuflist()        builtin.txt     /*tabpagebuflist()*
 tabpagenr()    builtin.txt     /*tabpagenr()*
 tabpagewinnr() builtin.txt     /*tabpagewinnr()*
 tabpanel       tabpage.txt     /*tabpanel*
+tabpanel-scroll        tabpage.txt     /*tabpanel-scroll*
 tag    tagsrch.txt     /*tag*
 tag-!  tagsrch.txt     /*tag-!*
 tag-binary-search      tagsrch.txt     /*tag-binary-search*
index 90d563c90275e01b36db615954a78d7b17bc20df..90cd1a3efba75231b82d74a3fdee2943ba2f155d 100644 (file)
@@ -52616,6 +52616,9 @@ 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 list exceeds the visible screen
+  height.
 
 Platform specific ~
 -----------------
index 7ea9e93511f6e1e83bd50fa2e9100e0c1ce843d5..44e504c5373bf7f5a5d04930c7aa743408374ccf 100644 (file)
@@ -4946,6 +4946,9 @@ build_stl_str_hl_local(
                    maxwid = 50;
            }
        }
+       // Keep the uncapped value for %N[FuncName] click-region IDs; the 50
+       // cap below applies only when minwid is used as a padding width.
+       int raw_minwid = minwid * l;
        minwid = (minwid > 50 ? 50 : minwid) * l;
        if (*s == '(')
        {
@@ -5306,7 +5309,11 @@ build_stl_str_hl_local(
                {
                    stl_items[curitem].stl_type = ClickFunc;
                    stl_items[curitem].stl_start = p;
-                   stl_items[curitem].stl_minwid = minwid;
+                   // The stl_minwid field is overloaded: it may be the
+                   // "min" part of %<min>.<max> used for padding, or an
+                   // identifier passed to the %N[FuncName] callback.  Store
+                   // the uncapped value so IDs above 50 are preserved.
+                   stl_items[curitem].stl_minwid = raw_minwid;
                    stl_items[curitem].stl_clickfunc =
                                              vim_strnsave(s, rb - s);
                    s = rb + 1;
index f6ab7700405b15d3b94cd7d8c350ac5c8eaf5267..93fdd31824ceb5557c71f6a4b411b9fcb712bc0c 100644 (file)
@@ -240,6 +240,9 @@ do_mouse(
     int                in_status_line; // mouse in status line
     static int in_tab_line = FALSE; // mouse clicked in tab line
     static int in_tabpanel = FALSE; // mouse clicked in tabpanel
+#ifdef FEAT_TABPANEL
+    static bool        in_tabpanel_scrollbar = false; // dragging tabpanel scrollbar
+#endif
     int                in_sep_line;    // mouse in vertical separator line
     int                c1, c2;
 #if defined(FEAT_FOLDING)
@@ -346,6 +349,9 @@ do_mouse(
        got_click = TRUE;
        in_tab_line = FALSE;
        in_tabpanel = FALSE;
+#ifdef FEAT_TABPANEL
+       in_tabpanel_scrollbar = false;
+#endif
     }
     else
     {
@@ -354,15 +360,31 @@ do_mouse(
        if (!is_drag)                   // release, reset got_click
        {
            got_click = FALSE;
-           if (in_tab_line || in_tabpanel)
+           if (in_tab_line || in_tabpanel
+#ifdef FEAT_TABPANEL
+                   || in_tabpanel_scrollbar
+#endif
+                   )
            {
                in_tab_line = FALSE;
                in_tabpanel = FALSE;
+#ifdef FEAT_TABPANEL
+               in_tabpanel_scrollbar = false;
+#endif
                return FALSE;
            }
        }
     }
 
+#ifdef FEAT_TABPANEL
+    // Continue a scrollbar drag before any tab-selection handling.
+    if (is_drag && in_tabpanel_scrollbar)
+    {
+       tabpanel_drag_scrollbar(mouse_row);
+       return FALSE;
+    }
+#endif
+
     // CTRL right mouse button does CTRL-T
     if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT)
     {
@@ -494,6 +516,15 @@ do_mouse(
     if (mouse_col < firstwin->w_wincol
                || mouse_col >= firstwin->w_wincol + topframe->fr_width)
     {
+       // A click on the scrollbar column starts a drag interaction and
+       // preempts tab-selection.
+       if (is_click && !is_drag && mouse_on_tabpanel_scrollbar())
+       {
+           in_tabpanel_scrollbar = TRUE;
+           tabpanel_drag_scrollbar(mouse_row);
+           return FALSE;
+       }
+
        // Dispatch 'tabpanel' %[FuncName] click regions before falling through
        // to tab-page selection.  On drag events fall through to the normal
        // tab-drag handling.
@@ -1276,6 +1307,17 @@ ins_mousescroll(int dir)
     cap.oap = &oa;
     cap.arg = dir;
 
+#ifdef FEAT_TABPANEL
+    if (mouse_row >= 0 && mouse_col >= 0
+           && (dir == MSCR_UP || dir == MSCR_DOWN)
+           && mouse_on_tabpanel())
+    {
+       (void)tabpanel_scroll(dir == MSCR_UP ? 1 : -1,
+               mouse_vert_step > 0 ? mouse_vert_step : 3);
+       return;
+    }
+#endif
+
     switch (dir)
     {
        case MSCR_UP:
@@ -2409,6 +2451,17 @@ nv_mousescroll(cmdarg_T *cap)
 {
     win_T   *old_curwin = curwin;
 
+#ifdef FEAT_TABPANEL
+    if (mouse_row >= 0 && mouse_col >= 0
+           && (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN)
+           && mouse_on_tabpanel())
+    {
+       (void)tabpanel_scroll(cap->arg == MSCR_UP ? 1 : -1,
+               mouse_vert_step > 0 ? mouse_vert_step : 3);
+       return;
+    }
+#endif
+
     if (mouse_row >= 0 && mouse_col >= 0)
     {
        // Find the window at the mouse pointer coordinates.
index d62d13f3a1099b48e3a7e947eeff6969fb31547d..72726d89ea4fbd9c09ac093b797512b40cb19c5d 100644 (file)
@@ -4,4 +4,8 @@ int tabpanel_width(void);
 int tabpanel_leftcol(void);
 void draw_tabpanel(void);
 int get_tabpagenr_on_tabpanel(void);
+bool mouse_on_tabpanel(void);
+bool mouse_on_tabpanel_scrollbar(void);
+bool tabpanel_drag_scrollbar(int screen_row);
+bool tabpanel_scroll(int dir, int count);
 /* vim: set ft=c : */
index 4138f9760a7795a9b7f264a91f20416e43fad37d..07be48e44c7c02d071fb5536a0d54386b29e0bf4 100644 (file)
@@ -20,6 +20,7 @@ static void do_by_tplmode(int tplmode, int col_start, int col_end,
 static void tabpanel_free_click_regions(void);
 static void tabpanel_append_click_regions(stl_clickrec_T *clicktab,
        char_u *buf, int row, int col_start, int col_end, int tabnr);
+static void draw_tabpanel_scrollbar(int screen_col);
 
 // set pcurtab_row. don't redraw tabpanel.
 #define TPLMODE_GET_CURTAB_ROW 0
@@ -31,6 +32,7 @@ static void tabpanel_append_click_regions(stl_clickrec_T *clicktab,
 #define TPL_FILLCHAR           ' '
 
 #define VERT_LEN               1
+#define SCROLL_LEN             1
 
 // tpl_align's values
 #define ALIGN_LEFT             0
@@ -40,7 +42,12 @@ static char_u *opt_name = (char_u *)"tabpanel";
 static int opt_scope = OPT_LOCAL;
 static int tpl_align = ALIGN_LEFT;
 static int tpl_columns = 20;
-static int tpl_is_vert = FALSE;
+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
 
 typedef struct {
     win_T   *wp;
@@ -61,7 +68,9 @@ tabpanelopt_changed(void)
     char_u     *p;
     int                new_align = ALIGN_LEFT;
     long       new_columns = 20;
-    int                new_is_vert = FALSE;
+    bool       new_is_vert = false;
+    bool       new_scroll = false;
+    bool       new_scrollbar = false;
 
     p = p_tplo;
     while (*p != NUL)
@@ -92,7 +101,18 @@ tabpanelopt_changed(void)
        else if (STRNCMP(p, "vert", 4) == 0)
        {
            p += 4;
-           new_is_vert = TRUE;
+           new_is_vert = true;
+       }
+       else if (STRNCMP(p, "scrollbar", 9) == 0)
+       {
+           p += 9;
+           new_scrollbar = true;
+           new_scroll = true;
+       }
+       else if (STRNCMP(p, "scroll", 6) == 0)
+       {
+           p += 6;
+           new_scroll = true;
        }
 
        if (*p != ',' && *p != NUL)
@@ -104,6 +124,10 @@ 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;
 
     shell_new_columns();
     return OK;
@@ -266,39 +290,65 @@ draw_tabpanel(void)
     // Reset got_int to avoid build_stl_str_hl() isn't evaluated.
     got_int = FALSE;
 
+    int sb_len = tpl_scrollbar ? SCROLL_LEN : 0;
+    int sb_screen_col = -1;
+
     if (tpl_is_vert)
     {
        if (is_right)
        {
            // draw main contents in tabpanel
-           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN,
+           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN + sb_len,
                    maxwidth - VERT_LEN, &curtab_row, NULL);
-           do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth, &curtab_row,
-                   NULL);
+           do_by_tplmode(TPLMODE_REDRAW, VERT_LEN + sb_len, maxwidth,
+                   &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;
        }
        else
        {
            // draw main contents in tabpanel
-           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - VERT_LEN,
-                   &curtab_row, NULL);
-           do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - VERT_LEN,
+           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0,
+                   maxwidth - VERT_LEN - sb_len, &curtab_row, NULL);
+           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);
+           if (tpl_scrollbar)
+               sb_screen_col = maxwidth - VERT_LEN - SCROLL_LEN;
        }
     }
     else
     {
-       do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
-       do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth, &curtab_row, NULL);
+       if (is_right)
+       {
+           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, sb_len, maxwidth,
+                   &curtab_row, NULL);
+           do_by_tplmode(TPLMODE_REDRAW, sb_len, maxwidth, &curtab_row, NULL);
+           if (tpl_scrollbar)
+               sb_screen_col = topframe->fr_width;
+       }
+       else
+       {
+           do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - sb_len,
+                   &curtab_row, NULL);
+           do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - sb_len,
+                   &curtab_row, NULL);
+           if (tpl_scrollbar)
+               sb_screen_col = maxwidth - SCROLL_LEN;
+       }
     }
 
+    tpl_scrollbar_col = sb_screen_col;
+    if (sb_screen_col >= 0)
+       draw_tabpanel_scrollbar(sb_screen_col);
+
     got_int |= saved_got_int;
 
     // A user function may reset KeyTyped, restore it.
@@ -556,8 +606,13 @@ do_by_tplmode(
     args.col_end = col_end;
 
     if (tplmode != TPLMODE_GET_CURTAB_ROW && args.maxrow > 0)
-       while (args.offsetrow + args.maxrow <= *pcurtab_row)
-           args.offsetrow += args.maxrow;
+    {
+       if (tpl_scroll)
+           args.offsetrow = tpl_scroll_offset;
+       else
+           while (args.offsetrow + args.maxrow <= *pcurtab_row)
+               args.offsetrow += args.maxrow;
+    }
 
     tp = first_tabpage;
 
@@ -579,8 +634,13 @@ do_by_tplmode(
            if (tplmode == TPLMODE_GET_CURTAB_ROW)
            {
                *pcurtab_row = row;
-               do_unlet((char_u *)"g:actual_curtabpage", TRUE);
-               break;
+               // 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
@@ -608,7 +668,8 @@ do_by_tplmode(
                stl_hlrec_T     *tabtab;
                stl_clickrec_T  *clicktab = NULL;
 
-               if (args.maxrow <= row - args.offsetrow)
+               if (tplmode != TPLMODE_GET_CURTAB_ROW
+                       && args.maxrow <= row - args.offsetrow)
                    break;
 
                buf[0] = NUL;
@@ -677,6 +738,142 @@ do_by_tplmode(
     // fill the area of TabPanelFill.
     screen_fill_tailing_area(tplmode, MAX(row - args.offsetrow, 0), args.maxrow,
            args.col_start, args.col_end, attr_tplf);
+
+    // 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)
+       tpl_total_rows = row;
+}
+
+/*
+ * Draw the tabpanel scrollbar (track + thumb) at screen column 'screen_col'.
+ * The scrollbar spans the full screen height.  The thumb position and size
+ * are derived from tpl_scroll_offset, tpl_total_rows and Rows.
+ */
+    static void
+draw_tabpanel_scrollbar(int screen_col)
+{
+    int attr_sb = HL_ATTR(HLF_PSB);
+    int attr_thumb = HL_ATTR(HLF_PST);
+    int thumb_top = 0;
+    int thumb_height = 0;
+
+    if (tpl_total_rows > Rows && Rows > 0)
+    {
+       thumb_height = Rows * Rows / tpl_total_rows;
+       if (thumb_height < 1)
+           thumb_height = 1;
+       thumb_top = Rows * tpl_scroll_offset / tpl_total_rows;
+       if (thumb_top + thumb_height > Rows)
+           thumb_top = Rows - thumb_height;
+       if (thumb_top < 0)
+           thumb_top = 0;
+    }
+
+    for (int r = 0; r < Rows; r++)
+    {
+       bool on_thumb = thumb_height > 0
+           && r >= thumb_top && r < thumb_top + thumb_height;
+       screen_putchar(TPL_FILLCHAR, r, screen_col,
+               on_thumb ? attr_thumb : attr_sb);
+    }
+}
+
+/*
+ * Return true if the mouse is currently positioned over the tabpanel area.
+ */
+    bool
+mouse_on_tabpanel(void)
+{
+    if (tabpanel_width() == 0)
+       return false;
+    return mouse_col < firstwin->w_wincol
+       || mouse_col >= firstwin->w_wincol + topframe->fr_width;
+}
+
+/*
+ * Return true if the mouse is currently on the scrollbar column.
+ * The scrollbar column is tracked by draw_tabpanel() and is -1 when the
+ * scrollbar is not enabled or not yet drawn.
+ */
+    bool
+mouse_on_tabpanel_scrollbar(void)
+{
+    return tpl_scrollbar && tpl_scrollbar_col >= 0
+       && mouse_col == tpl_scrollbar_col;
+}
+
+/*
+ * Move the scrollbar thumb so it is vertically centred on screen row
+ * 'screen_row', updating tpl_scroll_offset accordingly.  Used for both
+ * initial clicks and subsequent drag events.
+ * Returns true if the event was consumed (offset changed or not).
+ */
+    bool
+tabpanel_drag_scrollbar(int screen_row)
+{
+    int thumb_height;
+    int max_offset;
+    int track_range;
+    int thumb_top;
+    int new_offset;
+
+    if (!tpl_scrollbar || Rows <= 0 || tpl_total_rows <= Rows)
+       return false;
+
+    thumb_height = Rows * Rows / tpl_total_rows;
+    if (thumb_height < 1)
+       thumb_height = 1;
+    track_range = Rows - thumb_height;
+    if (track_range <= 0)
+       return true;
+
+    max_offset = tpl_total_rows - Rows;
+    thumb_top = screen_row - thumb_height / 2;
+    if (thumb_top < 0)
+       thumb_top = 0;
+    if (thumb_top > track_range)
+       thumb_top = track_range;
+
+    new_offset = thumb_top * max_offset / track_range;
+    if (new_offset != tpl_scroll_offset)
+    {
+       tpl_scroll_offset = new_offset;
+       redraw_tabpanel = TRUE;
+    }
+    return true;
+}
+
+/*
+ * 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)
+{
+    int max_offset;
+    int new_offset;
+
+    if (!tpl_scroll || tabpanel_width() == 0)
+       return false;
+
+    max_offset = tpl_total_rows - Rows;
+    if (max_offset < 0)
+       max_offset = 0;
+
+    new_offset = tpl_scroll_offset + (dir > 0 ? count : -count);
+    if (new_offset < 0)
+       new_offset = 0;
+    if (new_offset > max_offset)
+       new_offset = max_offset;
+    if (new_offset == tpl_scroll_offset)
+       return false;
+
+    tpl_scroll_offset = new_offset;
+    redraw_tabpanel = TRUE;
+    return true;
 }
 
 #endif // FEAT_TABPANEL
index 7803a0eb4dc3cf3ceb936c5dc47310029205cf23..427360aeda388ffc3f0e9a4c94aaa552a5a63147 100644 (file)
@@ -962,8 +962,68 @@ 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.
+  set lines=8
+  set showtabpanel=2
+  set tabpanelopt=scroll
+  for i in range(20)
+    tabnew
+  endfor
+
+  " Should not crash with many tabs and scroll enabled.
+  redraw!
+
+  " Switching to scrollbar resets the offset but must also not crash.
+  set tabpanelopt=scrollbar
+  redraw!
+
+  " Disabling scroll returns to normal behavior.
+  set tabpanelopt=
+  redraw!
+
+  " Right alignment with scrollbar.
+  set tabpanelopt=align:right,scrollbar
+  redraw!
+
+  " Vertical separator with scrollbar.
+  set tabpanelopt=columns:10,vert,scrollbar
+  redraw!
+
+  " Cleanup.
+  %bwipeout!
+  let &tabpanelopt = save_tabpanelopt
+  let &showtabpanel = save_showtabpanel
+  let &lines = save_lines
+endfunc
+
 func Test_tabpanel_variable_height()
-  CheckFeature tabpanel
 
   let save_lines = &lines
   let save_showtabpanel = &showtabpanel
index aa38e1a35c9d388893b35d4ad59db24a8eef4053..36a8da0782f83b940cf2906d2cd6ac3186e04b42 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    386,
 /**/
     385,
 /**/