patch 9.1.1391: Vim does not have a vertical tabpanel
Problem: Vim does not have a tabpanel
Solution: include the tabpanel feature
(Naruhiko Nishino, thinca)
closes: #17263
Co-authored-by: thinca <thinca@gmail.com>
Signed-off-by: Naruhiko Nishino <naru123456789@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/tabpanel.c b/src/tabpanel.c
new file mode 100644
index 0000000..f4bed4b
--- /dev/null
+++ b/src/tabpanel.c
@@ -0,0 +1,660 @@
+/* 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_vert's values
+#define VERT_OFF 0
+#define VERT_ON 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_vert = VERT_OFF;
+
+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_vert = VERT_OFF;
+
+ p = p_tplo;
+ while (*p != NUL)
+ {
+ if (STRNCMP(p, "align:left", 10) == 0)
+ {
+ p += 10;
+ new_align = ALIGN_LEFT;
+ }
+ else if (STRNCMP(p, "align:right", 11) == 0)
+ {
+ p += 11;
+ new_align = ALIGN_RIGHT;
+ }
+ 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_vert = VERT_ON;
+ }
+
+ if (*p != ',' && *p != NUL)
+ return FAIL;
+ if (*p == ',')
+ ++p;
+ }
+
+ tpl_align = new_align;
+ tpl_columns = new_columns;
+ tpl_vert = new_vert;
+
+ 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())
+ return 0;
+ else if (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 (0 == maxwidth)
+ 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_vert == VERT_ON)
+ {
+ 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 (0 == maxwidth)
+ 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_REDRAW == tplmode)
+ 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_GET_CURTAB_ROW != tplmode)
+ && (pargs->maxrow <= (*pargs->prow - pargs->offsetrow)))
+ break;
+
+ if ((p[j] == '\n') || (p[j] == '\r'))
+ {
+ // fill the tailing area of current row.
+ if (0 <= (*pargs->prow - pargs->offsetrow)
+ && (*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 (0 <= (*pargs->prow - pargs->offsetrow)
+ && (*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)
+ && (0 <= (*pargs->prow - pargs->offsetrow)
+ && (*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 || 1 < wincount)
+ {
+ if (1 < wincount)
+ {
+ 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 (0 <= (*pargs->prow - pargs->offsetrow)
+ && (*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 (0 <= (*pargs->prow - pargs->offsetrow)
+ && (*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;
+
+ 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 (1 < len && 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);
+ }
+#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_GET_CURTAB_ROW != tplmode)
+ if (0 < args.maxrow)
+ while (args.offsetrow + args.maxrow <= *pcurtab_row)
+ args.offsetrow += args.maxrow;
+
+ tp = first_tabpage;
+
+ for (row = 0; tp != NULL; row++)
+ {
+ if ((TPLMODE_GET_CURTAB_ROW != tplmode)
+ && (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_GET_CURTAB_ROW == tplmode)
+ {
+ *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] != '\0')
+ {
+ while ((p[i] == '\n') || (p[i] == '\r'))
+ {
+ // fill the tailing area of current row.
+ if (0 <= (row - args.offsetrow)
+ && (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] != '\0'))
+ {
+ if (i + 1 >= sizeof(buf))
+ break;
+ buf[i] = p[i];
+ i++;
+ }
+ buf[i] = '\0';
+
+ 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_GET_TABPAGENR == tplmode)
+ && (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