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)
got_click = TRUE;
in_tab_line = FALSE;
in_tabpanel = FALSE;
+#ifdef FEAT_TABPANEL
+ in_tabpanel_scrollbar = false;
+#endif
}
else
{
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)
{
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.
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:
{
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.
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
#define TPL_FILLCHAR ' '
#define VERT_LEN 1
+#define SCROLL_LEN 1
// tpl_align's values
#define ALIGN_LEFT 0
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;
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)
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)
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;
// 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.
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;
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
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;
// 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
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