blob: bb7a8742b2ca335033efce5f338d7cff6b646aa0 [file] [log] [blame]
/* 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) || defined(PROTO)
static void do_by_tplmode(int tplmode, int col_start, int col_end,
int *pcurtab_row, int *ptabpagenr);
// 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
// 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 int tpl_is_vert = FALSE;
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;
int new_columns = 20;
int new_is_vert = FALSE;
int do_equal = 0;
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);
}
else if (STRNCMP(p, "vert", 4) == 0)
{
p += 4;
new_is_vert = TRUE;
}
if (*p != ',' && *p != NUL)
return FAIL;
if (*p == ',')
++p;
}
// Whether all the windows are automatically made the same size
// when tabpanel size is changed.
do_equal = p_ea && tpl_columns != new_columns;
tpl_align = new_align;
tpl_columns = new_columns;
tpl_is_vert = new_is_vert;
shell_new_columns();
redraw_tabpanel = TRUE;
if (do_equal)
win_equal(curwin, FALSE, 0);
return OK;
}
/*
* Return the width of tabpanel.
*/
int
tabpanel_width(void)
{
if (msg_scrolled != 0)
return 0;
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(win_T *wp)
{
if (cmdline_pum_active() || (wp != NULL && WIN_IS_POPUP(wp)))
return 0;
else
return tpl_align == ALIGN_RIGHT ? 0 : tabpanel_width();
}
/*
* 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;
#ifndef MSWIN
int row = 0;
int off = 0;
#endif
int vsrow = 0;
int is_right = tpl_align == ALIGN_RIGHT;
if (maxwidth == 0)
return;
#ifndef MSWIN
// We need this section only for the Vim running on WSL.
for (row = 0; row < cmdline_row; row++)
{
if (is_right)
off = LineOffset[row] + Columns - maxwidth;
else
off = LineOffset[row];
vim_memset(ScreenLines + off, ' ', (size_t)maxwidth * sizeof(schar_T));
if (enc_utf8)
vim_memset(ScreenLinesUC + off, -1,
(size_t)maxwidth * sizeof(u8char_T));
}
#endif
// Reset got_int to avoid build_stl_str_hl() isn't evaluted.
got_int = FALSE;
if (tpl_is_vert)
{
if (is_right)
{
// draw main contents in tabpanel
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN,
maxwidth - VERT_LEN, &curtab_row, NULL);
do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth, &curtab_row,
NULL);
// clear for multi-byte vert separater
screen_fill(0, cmdline_row, COLUMNS_WITHOUT_TPL(),
COLUMNS_WITHOUT_TPL() + VERT_LEN,
TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
// draw vert separater in tabpanel
for (vsrow = 0; vsrow < cmdline_row; vsrow++)
screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
COLUMNS_WITHOUT_TPL(), vs_attr);
}
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,
&curtab_row, NULL);
// clear for multi-byte vert separater
screen_fill(0, cmdline_row, maxwidth - VERT_LEN,
maxwidth, TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
// draw vert separater in tabpanel
for (vsrow = 0; vsrow < cmdline_row; vsrow++)
screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
maxwidth - VERT_LEN, vs_attr);
}
}
else
{
do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth, &curtab_row, NULL);
}
got_int |= saved_got_int;
// A user function may reset KeyTyped, restore it.
KeyTyped = saved_KeyTyped;
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 ? COLUMNS_WITHOUT_TPL() : 0) + col_start,
(is_right ? COLUMNS_WITHOUT_TPL() : 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, k;
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 (p[j] == '\n' || p[j] == '\r')
{
// 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->prow)++;
*pargs->pcol = pargs->col_start;
j++;
}
else
{
if (has_mbyte)
chlen = (*mb_ptr2len)(p + j);
else
chlen = (int)STRLEN(p + j);
for (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)
? COLUMNS_WITHOUT_TPL()
: 0;
if (TPLMODE_REDRAW == tplmode
&& (*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;
}
/*
* default tabpanel drawing behavior if 'tabpanel' option is NOT empty.
*/
static void
draw_tabpanel_userdefined(int tplmode, tabpanel_T *pargs)
{
char_u *p;
int p_crb_save;
char_u buf[IOSIZE];
stl_hlrec_T *hltab;
stl_hlrec_T *tabtab;
int curattr;
int n;
// Temporarily reset 'cursorbind', we don't want a side effect from moving
// the cursor away and back.
p_crb_save = pargs->cwp->w_p_crb;
pargs->cwp->w_p_crb = FALSE;
// Make a copy, because the statusline may include a function call that
// might change the option value and free the memory.
p = vim_strsave(pargs->user_defined);
build_stl_str_hl(pargs->cwp, buf, sizeof(buf),
p, opt_name, opt_scope,
TPL_FILLCHAR, pargs->col_end - pargs->col_start, &hltab, &tabtab);
vim_free(p);
pargs->cwp->w_p_crb = p_crb_save;
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;
}
static char_u *
starts_with_percent_and_bang(tabpanel_T *pargs)
{
int len = 0;
char_u *usefmt = p_tpl;
int did_emsg_before = did_emsg;
if (usefmt == NULL)
return NULL;
len = (int)STRLEN(usefmt);
if (len == 0)
return NULL;
#ifdef FEAT_EVAL
// if "fmt" was set insecurely it needs to be evaluated in the sandbox
int use_sandbox = was_set_insecurely(opt_name, opt_scope);
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
if (len > 1 && usefmt[0] == '%' && usefmt[1] == '!')
{
typval_T tv;
char_u *p = NULL;
tv.v_type = VAR_NUMBER;
tv.vval.v_number = pargs->cwp->w_id;
set_var((char_u *)"g:tabpanel_winid", &tv, FALSE);
p = eval_to_string_safe(usefmt + 2, use_sandbox, FALSE, FALSE);
if (p != NULL)
usefmt = p;
do_unlet((char_u *)"g:tabpanel_winid", TRUE);
if (did_emsg > did_emsg_before)
{
usefmt = NULL;
set_string_option_direct((char_u *)"tabpanel", -1, (char_u *)"",
OPT_FREE | OPT_GLOBAL, SID_ERROR);
}
}
#endif
return usefmt;
}
/*
* 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 = cmdline_row;
args.offsetrow = 0;
args.col_start = col_start;
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;
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)
{
*pcurtab_row = row;
break;
}
}
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 = starts_with_percent_and_bang(&args);
if (usefmt != NULL)
{
char_u buf[IOSIZE];
char_u *p = usefmt;
size_t i = 0;
while (p[i] != NUL)
{
while (p[i] == '\n' || p[i] == '\r')
{
// fill the tailing area of current row.
if (row - args.offsetrow >= 0
&& row - args.offsetrow < args.maxrow)
screen_fill_tailing_area(tplmode,
row - args.offsetrow,
row - args.offsetrow + 1,
col, args.col_end, args.attr);
row++;
col = col_start;
p++;
}
while (p[i] != '\n' && p[i] != '\r' && (p[i] != NUL))
{
if (i + 1 >= sizeof(buf))
break;
buf[i] = p[i];
i++;
}
buf[i] = NUL;
args.user_defined = buf;
args.prow = &row;
args.pcol = &col;
draw_tabpanel_userdefined(tplmode, &args);
p += i;
i = 0;
}
if (usefmt != p_tpl)
VIM_CLEAR(usefmt);
}
else
{
args.user_defined = NULL;
args.prow = &row;
args.pcol = &col;
draw_tabpanel_default(tplmode, &args);
}
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, row - args.offsetrow, args.maxrow,
args.col_start, args.col_end, attr_tplf);
}
#endif // FEAT_TABPANEL