mirror of
https://github.com/vim/vim.git
synced 2026-06-14 15:37:28 +02:00
5ef1eec5c5
Problem: In a multi-line statusline (and 'tabpanel'), %#XX# / %N*
set on one row do not persist on subsequent rows.
build_stl_str_hl_local() rebuilds stl_items[] from scratch
on every line break ("%@" or "\n"), so the highlight is
reset at each row boundary even though within a row it
stays until %* (or another %# / %*).
Solution: Carry the last Highlight item's stl_minwid across line
breaks via a new in/out int* parameter "carry_hl". At the
start of each row, pre-insert a Highlight item from the
carried value so the row begins under the same highlight;
before returning, update the carried value with the row's
final Highlight item. Apply the same carry to the
tabpanel rendering loop (Hirohito Higashi).
related: #19123
closes: #20180
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
1018 lines
25 KiB
C
1018 lines
25 KiB
C
/* vi:set ts=8 sts=4 sw=4 noet:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
/*
|
|
* tabpanel.c:
|
|
*/
|
|
|
|
#include "vim.h"
|
|
|
|
#if defined(FEAT_TABPANEL)
|
|
|
|
static void do_by_tplmode(int tplmode, int col_start, int col_end,
|
|
int *pcurtab_row, int *ptabpagenr);
|
|
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
|
|
// set ptabpagenr. don't redraw tabpanel.
|
|
#define TPLMODE_GET_TABPAGENR 1
|
|
// redraw tabpanel.
|
|
#define TPLMODE_REDRAW 2
|
|
|
|
#define TPL_FILLCHAR ' '
|
|
|
|
#define VERT_LEN 1
|
|
#define SCROLL_LEN 1
|
|
|
|
// tpl_align's values
|
|
#define ALIGN_LEFT 0
|
|
#define ALIGN_RIGHT 1
|
|
|
|
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 bool tpl_is_vert = 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 tabpage_T *tpl_last_curtab = NULL; // last curtab seen by draw_tabpanel
|
|
|
|
typedef struct {
|
|
win_T *wp;
|
|
win_T *cwp;
|
|
char_u *user_defined;
|
|
int maxrow;
|
|
int offsetrow;
|
|
int *prow;
|
|
int *pcol;
|
|
int attr;
|
|
int col_start;
|
|
int col_end;
|
|
} tabpanel_T;
|
|
|
|
int
|
|
tabpanelopt_changed(void)
|
|
{
|
|
char_u *p;
|
|
int new_align = ALIGN_LEFT;
|
|
long new_columns = 20;
|
|
bool new_is_vert = false;
|
|
bool new_scrollbar = false;
|
|
|
|
p = p_tplo;
|
|
while (*p != NUL)
|
|
{
|
|
if (STRNCMP(p, "align:", 6) == 0)
|
|
{
|
|
p += 6;
|
|
if (STRNCMP(p, "left", 4) == 0)
|
|
{
|
|
p += 4;
|
|
new_align = ALIGN_LEFT;
|
|
}
|
|
else if (STRNCMP(p, "right", 5) == 0)
|
|
{
|
|
p += 5;
|
|
new_align = ALIGN_RIGHT;
|
|
}
|
|
else
|
|
return FAIL;
|
|
}
|
|
else if (STRNCMP(p, "columns:", 8) == 0 && VIM_ISDIGIT(p[8]))
|
|
{
|
|
p += 8;
|
|
new_columns = getdigits(&p);
|
|
if (new_columns < 0 || new_columns > 1000)
|
|
return FAIL;
|
|
}
|
|
else if (STRNCMP(p, "vert", 4) == 0)
|
|
{
|
|
p += 4;
|
|
new_is_vert = true;
|
|
}
|
|
else if (STRNCMP(p, "scrollbar", 9) == 0)
|
|
{
|
|
p += 9;
|
|
new_scrollbar = true;
|
|
}
|
|
|
|
if (*p != ',' && *p != NUL)
|
|
return FAIL;
|
|
if (*p == ',')
|
|
++p;
|
|
}
|
|
|
|
tpl_align = new_align;
|
|
tpl_columns = new_columns;
|
|
tpl_is_vert = new_is_vert;
|
|
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.
|
|
*/
|
|
int
|
|
tabpanel_width(void)
|
|
{
|
|
switch (p_stpl)
|
|
{
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
if (first_tabpage->tp_next == NULL)
|
|
return 0;
|
|
}
|
|
if (Columns < tpl_columns)
|
|
return 0;
|
|
else
|
|
return tpl_columns;
|
|
}
|
|
|
|
/*
|
|
* Return the offset of a window considering the width of tabpanel.
|
|
*/
|
|
int
|
|
tabpanel_leftcol(void)
|
|
{
|
|
return tpl_align == ALIGN_RIGHT ? 0 : tabpanel_width();
|
|
}
|
|
|
|
/*
|
|
* Free previously resolved 'tabpanel' click regions.
|
|
*/
|
|
static void
|
|
tabpanel_free_click_regions(void)
|
|
{
|
|
int n;
|
|
|
|
if (tabpanel_stl_click != NULL)
|
|
{
|
|
for (n = 0; n < tabpanel_stl_click_count; n++)
|
|
vim_free(tabpanel_stl_click[n].funcname);
|
|
VIM_CLEAR(tabpanel_stl_click);
|
|
}
|
|
tabpanel_stl_click_count = 0;
|
|
}
|
|
|
|
/*
|
|
* Convert click records produced by build_stl_str_hl() for one line of
|
|
* 'tabpanel' into screen-column based regions and append them to the global
|
|
* tabpanel_stl_click array. The caller keeps ownership of the funcname
|
|
* strings inside "clicktab" — this function makes its own copies.
|
|
*/
|
|
static void
|
|
tabpanel_append_click_regions(
|
|
stl_clickrec_T *clicktab,
|
|
char_u *buf,
|
|
int row,
|
|
int col_start,
|
|
int col_end,
|
|
int tabnr)
|
|
{
|
|
int count = 0;
|
|
int n;
|
|
int base_col;
|
|
int acc_width = 0;
|
|
int max_w = col_end - col_start;
|
|
char_u *p;
|
|
char_u *cur_funcname = NULL;
|
|
int cur_minwid = 0;
|
|
int region_start_col;
|
|
stl_click_region_T *new_arr;
|
|
int limit;
|
|
|
|
if (clicktab == NULL)
|
|
return;
|
|
|
|
for (n = 0; clicktab[n].start != NULL; n++)
|
|
count++;
|
|
if (count == 0)
|
|
return;
|
|
|
|
base_col = (tpl_align == ALIGN_RIGHT ? topframe->fr_width : 0) + col_start;
|
|
region_start_col = base_col;
|
|
|
|
// Grow the global array to make room for up to "count" more regions
|
|
// (one close for each record plus a possible trailing region).
|
|
new_arr = vim_realloc(tabpanel_stl_click,
|
|
sizeof(stl_click_region_T) * (tabpanel_stl_click_count + count + 1));
|
|
if (new_arr == NULL)
|
|
return;
|
|
tabpanel_stl_click = new_arr;
|
|
|
|
p = buf;
|
|
for (n = 0; clicktab[n].start != NULL; n++)
|
|
{
|
|
acc_width += vim_strnsize(p, (int)(clicktab[n].start - p));
|
|
p = clicktab[n].start;
|
|
limit = acc_width < max_w ? acc_width : max_w;
|
|
|
|
if (cur_funcname != NULL)
|
|
{
|
|
stl_click_region_T *r =
|
|
&tabpanel_stl_click[tabpanel_stl_click_count];
|
|
r->row = row;
|
|
r->col_start = region_start_col;
|
|
r->col_end = base_col + limit;
|
|
r->funcname = vim_strsave(cur_funcname);
|
|
r->minwid = cur_minwid;
|
|
r->tabnr = tabnr;
|
|
tabpanel_stl_click_count++;
|
|
}
|
|
|
|
cur_funcname = clicktab[n].funcname;
|
|
cur_minwid = clicktab[n].minwid;
|
|
region_start_col = base_col + limit;
|
|
}
|
|
|
|
// Close the final region if it extends to the end.
|
|
if (cur_funcname != NULL)
|
|
{
|
|
stl_click_region_T *r = &tabpanel_stl_click[tabpanel_stl_click_count];
|
|
r->row = row;
|
|
r->col_start = region_start_col;
|
|
r->col_end = base_col + max_w;
|
|
r->funcname = vim_strsave(cur_funcname);
|
|
r->minwid = cur_minwid;
|
|
r->tabnr = tabnr;
|
|
tabpanel_stl_click_count++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
void
|
|
draw_tabpanel(void)
|
|
{
|
|
int saved_KeyTyped = KeyTyped;
|
|
int saved_got_int = got_int;
|
|
int maxwidth = tabpanel_width();
|
|
int vs_attr = HL_ATTR(HLF_C);
|
|
int curtab_row = 0;
|
|
int vsrow = 0;
|
|
int is_right = tpl_align == ALIGN_RIGHT;
|
|
|
|
if (maxwidth == 0)
|
|
{
|
|
tabpanel_free_click_regions();
|
|
return;
|
|
}
|
|
|
|
// Discard old click regions — they'll be rebuilt during redraw below.
|
|
tabpanel_free_click_regions();
|
|
|
|
// 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;
|
|
|
|
// 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)
|
|
{
|
|
// 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);
|
|
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 + maxwidth - SCROLL_LEN;
|
|
}
|
|
else
|
|
{
|
|
// 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);
|
|
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
|
|
{
|
|
if (is_right)
|
|
{
|
|
// 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);
|
|
if (tpl_scrollbar)
|
|
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)
|
|
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.
|
|
KeyTyped = saved_KeyTyped;
|
|
|
|
tpl_last_curtab = curtab;
|
|
redraw_tabpanel = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return tabpagenr when clicking and dragging in tabpanel.
|
|
*/
|
|
int
|
|
get_tabpagenr_on_tabpanel(void)
|
|
{
|
|
int maxwidth = tabpanel_width();
|
|
int curtab_row = 0;
|
|
int tabpagenr = 0;
|
|
|
|
if (maxwidth == 0)
|
|
return -1;
|
|
|
|
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
|
|
do_by_tplmode(TPLMODE_GET_TABPAGENR, 0, maxwidth, &curtab_row,
|
|
&tabpagenr);
|
|
|
|
return tabpagenr;
|
|
}
|
|
|
|
/*
|
|
* Fill tailing area between {start_row} and {end_row - 1}.
|
|
*/
|
|
static void
|
|
screen_fill_tailing_area(
|
|
int tplmode,
|
|
int row_start,
|
|
int row_end,
|
|
int col_start,
|
|
int col_end,
|
|
int attr)
|
|
{
|
|
int is_right = tpl_align == ALIGN_RIGHT;
|
|
if (tplmode == TPLMODE_REDRAW)
|
|
screen_fill(row_start, row_end,
|
|
(is_right ? topframe->fr_width : 0) + col_start,
|
|
(is_right ? topframe->fr_width : 0) + col_end,
|
|
TPL_FILLCHAR, TPL_FILLCHAR, attr);
|
|
}
|
|
|
|
/*
|
|
* screen_puts_len() for tabpanel.
|
|
*/
|
|
static void
|
|
screen_puts_len_for_tabpanel(
|
|
int tplmode,
|
|
char_u *p,
|
|
int len,
|
|
int attr,
|
|
tabpanel_T *pargs)
|
|
{
|
|
int j;
|
|
int chlen;
|
|
int chcells;
|
|
char_u buf[IOSIZE];
|
|
char_u *temp;
|
|
|
|
for (j = 0; j < len;)
|
|
{
|
|
if (tplmode != TPLMODE_GET_CURTAB_ROW
|
|
&& pargs->maxrow <= *pargs->prow - pargs->offsetrow)
|
|
break;
|
|
|
|
if (has_mbyte)
|
|
chlen = (*mb_ptr2len)(p + j);
|
|
else
|
|
chlen = 1;
|
|
|
|
for (int k = 0; k < chlen; k++)
|
|
buf[k] = p[j + k];
|
|
buf[chlen] = NUL;
|
|
j += chlen;
|
|
|
|
// Make all characters printable.
|
|
temp = transstr(buf);
|
|
if (temp != NULL)
|
|
{
|
|
vim_strncpy(buf, temp, sizeof(buf) - 1);
|
|
vim_free(temp);
|
|
}
|
|
|
|
if (has_mbyte)
|
|
chcells = (*mb_ptr2cells)(buf);
|
|
else
|
|
chcells = 1;
|
|
|
|
if (pargs->col_end < (*pargs->pcol) + chcells)
|
|
{
|
|
// fill the tailing area of current row.
|
|
if (*pargs->prow - pargs->offsetrow >= 0
|
|
&& *pargs->prow - pargs->offsetrow < pargs->maxrow)
|
|
screen_fill_tailing_area(tplmode,
|
|
*pargs->prow - pargs->offsetrow,
|
|
*pargs->prow - pargs->offsetrow + 1,
|
|
*pargs->pcol, pargs->col_end, attr);
|
|
*pargs->pcol = pargs->col_end;
|
|
|
|
if (pargs->col_end < chcells)
|
|
break;
|
|
}
|
|
|
|
if (*pargs->pcol + chcells <= pargs->col_end)
|
|
{
|
|
int off = (tpl_align == ALIGN_RIGHT)
|
|
? topframe->fr_width
|
|
: 0;
|
|
if (tplmode == TPLMODE_REDRAW
|
|
&& (*pargs->prow - pargs->offsetrow >= 0
|
|
&& *pargs->prow - pargs->offsetrow < pargs->maxrow))
|
|
screen_puts(buf, *pargs->prow - pargs->offsetrow,
|
|
*pargs->pcol + off, attr);
|
|
*pargs->pcol += chcells;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* default tabpanel drawing behavior if 'tabpanel' option is empty.
|
|
*/
|
|
static void
|
|
draw_tabpanel_default(int tplmode, tabpanel_T *pargs)
|
|
{
|
|
int modified;
|
|
int wincount;
|
|
int len = 0;
|
|
char_u buf[2] = { NUL, NUL };
|
|
|
|
modified = FALSE;
|
|
for (wincount = 0; pargs->wp != NULL;
|
|
pargs->wp = pargs->wp->w_next, ++wincount)
|
|
if (bufIsChanged(pargs->wp->w_buffer))
|
|
modified = TRUE;
|
|
|
|
if (modified || wincount > 1)
|
|
{
|
|
if (wincount > 1)
|
|
{
|
|
vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount);
|
|
len = (int)STRLEN(NameBuff);
|
|
screen_puts_len_for_tabpanel(tplmode, NameBuff, len,
|
|
#if defined(FEAT_SYN_HL)
|
|
hl_combine_attr(pargs->attr, HL_ATTR(HLF_T)),
|
|
#else
|
|
pargs->attr,
|
|
#endif
|
|
pargs);
|
|
}
|
|
if (modified)
|
|
{
|
|
buf[0] = '+';
|
|
screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
|
|
}
|
|
|
|
buf[0] = TPL_FILLCHAR;
|
|
screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
|
|
}
|
|
|
|
get_trans_bufname(pargs->cwp->w_buffer);
|
|
shorten_dir(NameBuff);
|
|
len = (int)STRLEN(NameBuff);
|
|
screen_puts_len_for_tabpanel(tplmode, NameBuff, len, pargs->attr, pargs);
|
|
|
|
// fill the tailing area of current row.
|
|
if (*pargs->prow - pargs->offsetrow >= 0
|
|
&& *pargs->prow - pargs->offsetrow < pargs->maxrow)
|
|
screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
|
|
*pargs->prow - pargs->offsetrow + 1,
|
|
*pargs->pcol, pargs->col_end, pargs->attr);
|
|
*pargs->pcol = pargs->col_end;
|
|
}
|
|
|
|
/*
|
|
* Draw tabpanel content with highlight handling.
|
|
* Processes hltab entries and fills tailing area.
|
|
*/
|
|
static void
|
|
draw_tabpanel_with_highlight(
|
|
int tplmode,
|
|
char_u *buf,
|
|
stl_hlrec_T *hltab,
|
|
tabpanel_T *pargs)
|
|
{
|
|
char_u *p;
|
|
int curattr;
|
|
int n;
|
|
|
|
curattr = pargs->attr;
|
|
p = buf;
|
|
for (n = 0; hltab[n].start != NULL; n++)
|
|
{
|
|
screen_puts_len_for_tabpanel(tplmode, p, (int)(hltab[n].start - p),
|
|
curattr, pargs);
|
|
p = hltab[n].start;
|
|
if (hltab[n].userhl == 0)
|
|
curattr = pargs->attr;
|
|
else if (hltab[n].userhl < 0)
|
|
curattr = syn_id2attr(-hltab[n].userhl);
|
|
#ifdef FEAT_TERMINAL
|
|
else if (pargs->wp != NULL && pargs->wp != curwin
|
|
&& bt_terminal(pargs->wp->w_buffer)
|
|
&& pargs->wp->w_status_height != 0)
|
|
curattr = highlight_stltermnc[hltab[n].userhl - 1];
|
|
else if (pargs->wp != NULL && bt_terminal(pargs->wp->w_buffer)
|
|
&& pargs->wp->w_status_height != 0)
|
|
curattr = highlight_stlterm[hltab[n].userhl - 1];
|
|
#endif
|
|
else if (pargs->wp != NULL && pargs->wp != curwin
|
|
&& pargs->wp->w_status_height != 0)
|
|
curattr = highlight_stlnc[hltab[n].userhl - 1];
|
|
else
|
|
curattr = highlight_user[hltab[n].userhl - 1];
|
|
}
|
|
screen_puts_len_for_tabpanel(tplmode, p, (int)STRLEN(p), curattr, pargs);
|
|
|
|
// fill the tailing area of current row.
|
|
if (*pargs->prow - pargs->offsetrow >= 0
|
|
&& *pargs->prow - pargs->offsetrow < pargs->maxrow)
|
|
screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
|
|
*pargs->prow - pargs->offsetrow + 1, *pargs->pcol,
|
|
pargs->col_end, curattr);
|
|
*pargs->pcol = pargs->col_end;
|
|
}
|
|
|
|
/*
|
|
* do something by tplmode for drawing tabpanel.
|
|
*/
|
|
static void
|
|
do_by_tplmode(
|
|
int tplmode,
|
|
int col_start,
|
|
int col_end,
|
|
int *pcurtab_row,
|
|
int *ptabpagenr)
|
|
{
|
|
int attr_tplf = HL_ATTR(HLF_TPLF);
|
|
int attr_tpls = HL_ATTR(HLF_TPLS);
|
|
int attr_tpl = HL_ATTR(HLF_TPL);
|
|
int col = col_start;
|
|
int row = 0;
|
|
tabpage_T *tp = NULL;
|
|
typval_T v;
|
|
tabpanel_T args;
|
|
|
|
args.maxrow = Rows;
|
|
args.offsetrow = 0;
|
|
args.col_start = col_start;
|
|
args.col_end = col_end;
|
|
|
|
if (tplmode != TPLMODE_GET_CURTAB_ROW && args.maxrow > 0)
|
|
args.offsetrow = tpl_scroll_offset;
|
|
|
|
tp = first_tabpage;
|
|
|
|
for (row = 0; tp != NULL; row++)
|
|
{
|
|
if (tplmode != TPLMODE_GET_CURTAB_ROW
|
|
&& args.maxrow <= row - args.offsetrow)
|
|
break;
|
|
|
|
col = col_start;
|
|
|
|
v.v_type = VAR_NUMBER;
|
|
v.vval.v_number = tabpage_index(tp);
|
|
set_var((char_u *)"g:actual_curtabpage", &v, TRUE);
|
|
|
|
if (tp->tp_topframe == topframe)
|
|
{
|
|
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;
|
|
}
|
|
else
|
|
args.attr = attr_tpl;
|
|
|
|
if (tp == curtab)
|
|
{
|
|
args.cwp = curwin;
|
|
args.wp = firstwin;
|
|
}
|
|
else
|
|
{
|
|
args.cwp = tp->tp_curwin;
|
|
args.wp = tp->tp_firstwin;
|
|
}
|
|
|
|
char_u *usefmt = vim_strsave(p_tpl);
|
|
|
|
if (usefmt != NULL && *usefmt != NUL)
|
|
{
|
|
int carry_hl = 0;
|
|
|
|
while (*usefmt != NUL)
|
|
{
|
|
char_u buf[IOSIZE];
|
|
stl_hlrec_T *hltab;
|
|
stl_hlrec_T *tabtab;
|
|
stl_clickrec_T *clicktab = NULL;
|
|
|
|
if (tplmode != TPLMODE_GET_CURTAB_ROW
|
|
&& args.maxrow <= row - args.offsetrow)
|
|
break;
|
|
|
|
buf[0] = NUL;
|
|
#ifdef ENABLE_STL_MODE_MULTI_NL
|
|
(void)build_stl_str_hl_mline_nl
|
|
#else
|
|
(void)build_stl_str_hl_mline
|
|
#endif
|
|
(args.cwp, buf, sizeof(buf),
|
|
&usefmt, opt_name, opt_scope, TPL_FILLCHAR,
|
|
args.col_end - args.col_start, &hltab, &tabtab,
|
|
tplmode == TPLMODE_REDRAW ? &clicktab : NULL,
|
|
&carry_hl);
|
|
|
|
args.prow = &row;
|
|
args.pcol = &col;
|
|
|
|
draw_tabpanel_with_highlight(tplmode, buf, hltab, &args);
|
|
|
|
// Record any %[FuncName] click regions for this line once
|
|
// the text has been drawn. Only visible rows participate.
|
|
if (tplmode == TPLMODE_REDRAW && clicktab != NULL)
|
|
{
|
|
int screen_row = row - args.offsetrow;
|
|
int m;
|
|
|
|
if (screen_row >= 0 && screen_row < args.maxrow)
|
|
tabpanel_append_click_regions(clicktab, buf,
|
|
screen_row, args.col_start, args.col_end,
|
|
(int)v.vval.v_number);
|
|
// We took ownership of the click records — free the
|
|
// function names (matches the non-NULL clicktab path in
|
|
// build_stl_str_hl()).
|
|
for (m = 0; clicktab[m].start != NULL; m++)
|
|
vim_free(clicktab[m].funcname);
|
|
}
|
|
|
|
// Move to next line for %@
|
|
if (*usefmt != NUL)
|
|
{
|
|
row++;
|
|
col = col_start;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
args.user_defined = NULL;
|
|
args.prow = &row;
|
|
args.pcol = &col;
|
|
draw_tabpanel_default(tplmode, &args);
|
|
}
|
|
|
|
vim_free(usefmt);
|
|
do_unlet((char_u *)"g:actual_curtabpage", TRUE);
|
|
|
|
tp = tp->tp_next;
|
|
|
|
if ((tplmode == TPLMODE_GET_TABPAGENR)
|
|
&& (mouse_row <= (row - args.offsetrow)))
|
|
{
|
|
*ptabpagenr = v.vval.v_number;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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_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)
|
|
{
|
|
int max_offset = tpl_total_rows - Rows;
|
|
int track_range;
|
|
|
|
thumb_height = Rows * Rows / tpl_total_rows;
|
|
if (thumb_height < 1)
|
|
thumb_height = 1;
|
|
|
|
// 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)
|
|
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.
|
|
*/
|
|
bool
|
|
tabpanel_scroll(int dir, int count)
|
|
{
|
|
int max_offset;
|
|
int new_offset;
|
|
|
|
if (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;
|
|
}
|
|
|
|
/*
|
|
* Set the tabpanel scroll offset to "offset" (clamped to the valid range).
|
|
* Returns true if the offset changed and a redraw was scheduled.
|
|
*/
|
|
bool
|
|
tabpanel_set_offset(int offset)
|
|
{
|
|
int max_offset;
|
|
|
|
if (tabpanel_width() == 0)
|
|
return false;
|
|
|
|
max_offset = tpl_total_rows - Rows;
|
|
if (max_offset < 0)
|
|
max_offset = 0;
|
|
|
|
if (offset < 0)
|
|
offset = 0;
|
|
if (offset > max_offset)
|
|
offset = max_offset;
|
|
if (offset == tpl_scroll_offset)
|
|
return false;
|
|
|
|
tpl_scroll_offset = offset;
|
|
redraw_tabpanel = TRUE;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* "tabpanel_getinfo()" function
|
|
*/
|
|
void
|
|
f_tabpanel_getinfo(typval_T *argvars UNUSED, typval_T *rettv)
|
|
{
|
|
dict_T *d;
|
|
int max_offset;
|
|
|
|
if (rettv_dict_alloc(rettv) == FAIL)
|
|
return;
|
|
d = rettv->vval.v_dict;
|
|
|
|
max_offset = tpl_total_rows - Rows;
|
|
if (max_offset < 0)
|
|
max_offset = 0;
|
|
|
|
dict_add_string(d, "align",
|
|
(char_u *)(tpl_align == ALIGN_RIGHT ? "right" : "left"));
|
|
dict_add_number(d, "columns", tabpanel_width());
|
|
dict_add_bool(d, "scrollbar", tpl_scrollbar);
|
|
dict_add_number(d, "offset", tpl_scroll_offset);
|
|
dict_add_number(d, "total", tpl_total_rows);
|
|
dict_add_number(d, "max_offset", max_offset);
|
|
}
|
|
|
|
/*
|
|
* "tabpanel_scroll()" function
|
|
*/
|
|
void
|
|
f_tabpanel_scroll(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
varnumber_T n;
|
|
int absolute = 0;
|
|
bool changed;
|
|
|
|
rettv->v_type = VAR_BOOL;
|
|
rettv->vval.v_number = VVAL_FALSE;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_number_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_dict_arg(argvars, 1) == FAIL))
|
|
return;
|
|
|
|
n = tv_get_number_chk(&argvars[0], NULL);
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
{
|
|
if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
|
|
{
|
|
emsg(_(e_dictionary_required));
|
|
return;
|
|
}
|
|
absolute = dict_get_bool(argvars[1].vval.v_dict, "absolute", FALSE);
|
|
}
|
|
|
|
// Clamp to int range to avoid signed overflow when casting and negating.
|
|
if (n > INT_MAX)
|
|
n = INT_MAX;
|
|
else if (n < -INT_MAX)
|
|
n = -INT_MAX;
|
|
|
|
if (absolute)
|
|
changed = tabpanel_set_offset((int)n);
|
|
else
|
|
changed = tabpanel_scroll(n >= 0 ? 1 : -1,
|
|
(int)(n >= 0 ? n : -n));
|
|
|
|
rettv->vval.v_number = changed ? VVAL_TRUE : VVAL_FALSE;
|
|
}
|
|
|
|
#endif // FEAT_TABPANEL
|