blob: f4bed4b7c3c7c2f6376a49ae6df85f71d0d38b68 [file] [log] [blame]
Naruhiko Nishinobe5bd4d2025-05-14 21:20:28 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * tabpanel.c:
12 */
13
14#include "vim.h"
15
16#if defined(FEAT_TABPANEL) || defined(PROTO)
17
18static void do_by_tplmode(int tplmode, int col_start, int col_end,
19 int* pcurtab_row, int* ptabpagenr);
20
21// set pcurtab_row. don't redraw tabpanel.
22#define TPLMODE_GET_CURTAB_ROW 0
23// set ptabpagenr. don't redraw tabpanel.
24#define TPLMODE_GET_TABPAGENR 1
25// redraw tabpanel.
26#define TPLMODE_REDRAW 2
27
28#define TPL_FILLCHAR ' '
29
30#define VERT_LEN 1
31
32// tpl_vert's values
33#define VERT_OFF 0
34#define VERT_ON 1
35
36// tpl_align's values
37#define ALIGN_LEFT 0
38#define ALIGN_RIGHT 1
39
40static char_u *opt_name = (char_u *)"tabpanel";
41static int opt_scope = OPT_LOCAL;
42static int tpl_align = ALIGN_LEFT;
43static int tpl_columns = 20;
44static int tpl_vert = VERT_OFF;
45
46typedef struct {
47 win_T *wp;
48 win_T *cwp;
49 char_u *user_defined;
50 int maxrow;
51 int offsetrow;
52 int *prow;
53 int *pcol;
54 int attr;
55 int col_start;
56 int col_end;
57} tabpanel_T;
58
59 int
60tabpanelopt_changed(void)
61{
62 char_u *p;
63 int new_align = ALIGN_LEFT;
64 int new_columns = 20;
65 int new_vert = VERT_OFF;
66
67 p = p_tplo;
68 while (*p != NUL)
69 {
70 if (STRNCMP(p, "align:left", 10) == 0)
71 {
72 p += 10;
73 new_align = ALIGN_LEFT;
74 }
75 else if (STRNCMP(p, "align:right", 11) == 0)
76 {
77 p += 11;
78 new_align = ALIGN_RIGHT;
79 }
80 else if (STRNCMP(p, "columns:", 8) == 0 && VIM_ISDIGIT(p[8]))
81 {
82 p += 8;
83 new_columns = getdigits(&p);
84 }
85 else if (STRNCMP(p, "vert", 4) == 0)
86 {
87 p += 4;
88 new_vert = VERT_ON;
89 }
90
91 if (*p != ',' && *p != NUL)
92 return FAIL;
93 if (*p == ',')
94 ++p;
95 }
96
97 tpl_align = new_align;
98 tpl_columns = new_columns;
99 tpl_vert = new_vert;
100
101 return OK;
102}
103
104/*
105 * Return the width of tabpanel.
106 */
107 int
108tabpanel_width(void)
109{
110 if (msg_scrolled != 0)
111 return 0;
112
113 switch (p_stpl)
114 {
115 case 0:
116 return 0;
117 case 1:
118 if (first_tabpage->tp_next == NULL)
119 return 0;
120 }
121 if (Columns < tpl_columns)
122 return 0;
123 else
124 return tpl_columns;
125}
126
127/*
128 * Return the offset of a window considering the width of tabpanel.
129 */
130 int
131tabpanel_leftcol(win_T *wp)
132{
133 if (cmdline_pum_active())
134 return 0;
135 else if (wp != NULL && WIN_IS_POPUP(wp))
136 return 0;
137 else
138 return tpl_align == ALIGN_RIGHT ? 0 : tabpanel_width();
139}
140
141/*
142 * draw the tabpanel.
143 */
144 void
145draw_tabpanel(void)
146{
147 int saved_KeyTyped = KeyTyped;
148 int saved_got_int = got_int;
149 int maxwidth = tabpanel_width();
150 int vs_attr = HL_ATTR(HLF_C);
151 int curtab_row = 0;
152#ifndef MSWIN
153 int row = 0;
154 int off = 0;
155#endif
156int vsrow = 0;
157 int is_right = tpl_align == ALIGN_RIGHT;
158
159 if (0 == maxwidth)
160 return;
161
162#ifndef MSWIN
163 // We need this section only for the Vim running on WSL.
164 for (row = 0; row < cmdline_row; row++)
165 {
166 if (is_right)
167 off = LineOffset[row] + Columns - maxwidth;
168 else
169 off = LineOffset[row];
170
171 vim_memset(ScreenLines + off, ' ',
172 (size_t)maxwidth * sizeof(schar_T));
173 if (enc_utf8)
174 vim_memset(ScreenLinesUC + off, -1,
175 (size_t)maxwidth * sizeof(u8char_T));
176 }
177#endif
178
179 // Reset got_int to avoid build_stl_str_hl() isn't evaluted.
180 got_int = FALSE;
181
182 if (tpl_vert == VERT_ON)
183 {
184 if (is_right)
185 {
186 // draw main contents in tabpanel
187 do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN,
188 maxwidth - VERT_LEN, &curtab_row, NULL);
189 do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth, &curtab_row,
190 NULL);
191 // clear for multi-byte vert separater
192 screen_fill(0, cmdline_row, COLUMNS_WITHOUT_TPL(),
193 COLUMNS_WITHOUT_TPL() + VERT_LEN,
194 TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
195 // draw vert separater in tabpanel
196 for (vsrow = 0; vsrow < cmdline_row; vsrow++)
197 screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
198 COLUMNS_WITHOUT_TPL(), vs_attr);
199 }
200 else
201 {
202 // draw main contents in tabpanel
203 do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - VERT_LEN,
204 &curtab_row, NULL);
205 do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - VERT_LEN,
206 &curtab_row, NULL);
207 // clear for multi-byte vert separater
208 screen_fill(0, cmdline_row, maxwidth - VERT_LEN,
209 maxwidth, TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
210 // draw vert separater in tabpanel
211 for (vsrow = 0; vsrow < cmdline_row; vsrow++)
212 screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
213 maxwidth - VERT_LEN, vs_attr);
214 }
215 }
216 else
217 {
218 do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
219 do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth, &curtab_row, NULL);
220 }
221
222 got_int |= saved_got_int;
223
224 // A user function may reset KeyTyped, restore it.
225 KeyTyped = saved_KeyTyped;
226
227 redraw_tabpanel = FALSE;
228}
229
230/*
231 * Return tabpagenr when clicking and dragging in tabpanel.
232 */
233 int
234get_tabpagenr_on_tabpanel(void)
235{
236 int maxwidth = tabpanel_width();
237 int curtab_row = 0;
238 int tabpagenr = 0;
239
240 if (0 == maxwidth)
241 return -1;
242
243 do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
244 do_by_tplmode(TPLMODE_GET_TABPAGENR, 0, maxwidth, &curtab_row,
245 &tabpagenr);
246
247 return tabpagenr;
248}
249
250/*
251 * Fill tailing area between {start_row} and {end_row - 1}.
252 */
253 static void
254screen_fill_tailing_area(
255 int tplmode,
256 int row_start,
257 int row_end,
258 int col_start,
259 int col_end,
260 int attr)
261{
262 int is_right = tpl_align == ALIGN_RIGHT;
263 if (TPLMODE_REDRAW == tplmode)
264 screen_fill(row_start, row_end,
265 (is_right ? COLUMNS_WITHOUT_TPL() : 0) + col_start,
266 (is_right ? COLUMNS_WITHOUT_TPL() : 0) + col_end,
267 TPL_FILLCHAR, TPL_FILLCHAR, attr);
268}
269
270/*
271 * screen_puts_len() for tabpanel.
272 */
273 static void
274screen_puts_len_for_tabpanel(
275 int tplmode,
276 char_u *p,
277 int len,
278 int attr,
279 tabpanel_T *pargs)
280{
281 int j, k;
282 int chlen;
283 int chcells;
284 char_u buf[IOSIZE];
285 char_u* temp;
286
287 for (j = 0; j < len;)
288 {
289 if ((TPLMODE_GET_CURTAB_ROW != tplmode)
290 && (pargs->maxrow <= (*pargs->prow - pargs->offsetrow)))
291 break;
292
293 if ((p[j] == '\n') || (p[j] == '\r'))
294 {
295 // fill the tailing area of current row.
296 if (0 <= (*pargs->prow - pargs->offsetrow)
297 && (*pargs->prow - pargs->offsetrow) < pargs->maxrow)
298 screen_fill_tailing_area(tplmode,
299 *pargs->prow - pargs->offsetrow,
300 *pargs->prow - pargs->offsetrow + 1,
301 *pargs->pcol, pargs->col_end, attr);
302 (*pargs->prow)++;
303 *pargs->pcol = pargs->col_start;
304 j++;
305 }
306 else
307 {
308 if (has_mbyte)
309 chlen = (*mb_ptr2len)(p + j);
310 else
311 chlen = (int)STRLEN(p + j);
312
313 for (k = 0; k < chlen; k++)
314 buf[k] = p[j + k];
315 buf[chlen] = NUL;
316 j += chlen;
317
318 // Make all characters printable.
319 temp = transstr(buf);
320 if (temp != NULL)
321 {
322 vim_strncpy(buf, temp, sizeof(buf) - 1);
323 vim_free(temp);
324 }
325
326 if (has_mbyte)
327 chcells = (*mb_ptr2cells)(buf);
328 else
329 chcells = 1;
330
331 if (pargs->col_end < (*pargs->pcol) + chcells)
332 {
333 // fill the tailing area of current row.
334 if (0 <= (*pargs->prow - pargs->offsetrow)
335 && (*pargs->prow - pargs->offsetrow) < pargs->maxrow)
336 screen_fill_tailing_area(tplmode,
337 *pargs->prow - pargs->offsetrow,
338 *pargs->prow - pargs->offsetrow + 1,
339 *pargs->pcol, pargs->col_end, attr);
340 *pargs->pcol = pargs->col_end;
341
342 if (pargs->col_end < chcells)
343 break;
344 }
345
346 if ((*pargs->pcol) + chcells <= pargs->col_end)
347 {
348 int off = (tpl_align == ALIGN_RIGHT)
349 ? COLUMNS_WITHOUT_TPL()
350 : 0;
351 if ((TPLMODE_REDRAW == tplmode)
352 && (0 <= (*pargs->prow - pargs->offsetrow)
353 && (*pargs->prow - pargs->offsetrow) < pargs->maxrow))
354 screen_puts(buf, *pargs->prow - pargs->offsetrow,
355 *pargs->pcol + off, attr);
356 (*pargs->pcol) += chcells;
357 }
358 }
359 }
360}
361
362/*
363 * default tabpanel drawing behavior if 'tabpanel' option is empty.
364 */
365 static void
366draw_tabpanel_default(int tplmode, tabpanel_T *pargs)
367{
368 int modified;
369 int wincount;
370 int len = 0;
371 char_u buf[2] = { NUL, NUL };
372
373 modified = FALSE;
374 for (wincount = 0; pargs->wp != NULL;
375 pargs->wp = pargs->wp->w_next, ++wincount)
376 if (bufIsChanged(pargs->wp->w_buffer))
377 modified = TRUE;
378
379 if (modified || 1 < wincount)
380 {
381 if (1 < wincount)
382 {
383 vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount);
384 len = (int)STRLEN(NameBuff);
385 screen_puts_len_for_tabpanel(tplmode, NameBuff, len,
386#if defined(FEAT_SYN_HL)
387 hl_combine_attr(pargs->attr, HL_ATTR(HLF_T)),
388#else
389 pargs->attr,
390#endif
391 pargs);
392 }
393 if (modified)
394 {
395 buf[0] = '+';
396 screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
397 }
398
399 buf[0] = TPL_FILLCHAR;
400 screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
401 }
402
403 get_trans_bufname(pargs->cwp->w_buffer);
404 shorten_dir(NameBuff);
405 len = (int)STRLEN(NameBuff);
406 screen_puts_len_for_tabpanel(tplmode, NameBuff, len, pargs->attr, pargs);
407
408 // fill the tailing area of current row.
409 if (0 <= (*pargs->prow - pargs->offsetrow)
410 && (*pargs->prow - pargs->offsetrow) < pargs->maxrow)
411 screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
412 *pargs->prow - pargs->offsetrow + 1,
413 *pargs->pcol, pargs->col_end, pargs->attr);
414 *pargs->pcol = pargs->col_end;
415}
416
417/*
418 * default tabpanel drawing behavior if 'tabpanel' option is NOT empty.
419 */
420 static void
421draw_tabpanel_userdefined(int tplmode, tabpanel_T *pargs)
422{
423 char_u *p;
424 int p_crb_save;
425 char_u buf[IOSIZE];
426 stl_hlrec_T *hltab;
427 stl_hlrec_T *tabtab;
428 int curattr;
429 int n;
430
431 // Temporarily reset 'cursorbind', we don't want a side effect from moving
432 // the cursor away and back.
433 p_crb_save = pargs->cwp->w_p_crb;
434 pargs->cwp->w_p_crb = FALSE;
435
436 // Make a copy, because the statusline may include a function call that
437 // might change the option value and free the memory.
438 p = vim_strsave(pargs->user_defined);
439
440 build_stl_str_hl(pargs->cwp, buf, sizeof(buf),
441 p, opt_name, opt_scope,
442 TPL_FILLCHAR, pargs->col_end - pargs->col_start, &hltab, &tabtab);
443
444 vim_free(p);
445 pargs->cwp->w_p_crb = p_crb_save;
446
447 curattr = pargs->attr;
448 p = buf;
449 for (n = 0; hltab[n].start != NULL; n++)
450 {
451 screen_puts_len_for_tabpanel(tplmode, p, (int)(hltab[n].start - p),
452 curattr, pargs);
453 p = hltab[n].start;
454 if (hltab[n].userhl == 0)
455 curattr = pargs->attr;
456 else if (hltab[n].userhl < 0)
457 curattr = syn_id2attr(-hltab[n].userhl);
458#ifdef FEAT_TERMINAL
459 else if (pargs->wp != NULL && pargs->wp != curwin
460 && bt_terminal(pargs->wp->w_buffer)
461 && pargs->wp->w_status_height != 0)
462 curattr = highlight_stltermnc[hltab[n].userhl - 1];
463 else if (pargs->wp != NULL && bt_terminal(pargs->wp->w_buffer)
464 && pargs->wp->w_status_height != 0)
465 curattr = highlight_stlterm[hltab[n].userhl - 1];
466#endif
467 else if (pargs->wp != NULL && pargs->wp != curwin
468 && pargs->wp->w_status_height != 0)
469 curattr = highlight_stlnc[hltab[n].userhl - 1];
470 else
471 curattr = highlight_user[hltab[n].userhl - 1];
472 }
473 screen_puts_len_for_tabpanel(tplmode, p, (int)STRLEN(p), curattr, pargs);
474
475 // fill the tailing area of current row.
476 if (0 <= (*pargs->prow - pargs->offsetrow)
477 && (*pargs->prow - pargs->offsetrow) < pargs->maxrow)
478 screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
479 *pargs->prow - pargs->offsetrow + 1, *pargs->pcol,
480 pargs->col_end, curattr);
481 *pargs->pcol = pargs->col_end;
482}
483
484 static char_u *
485starts_with_percent_and_bang(tabpanel_T *pargs)
486{
487 int len = 0;
488 char_u *usefmt = p_tpl;
489
490 if (usefmt == NULL)
491 return NULL;
492
493 len = (int)STRLEN(usefmt);
494
495 if (len == 0)
496 return NULL;
497
498#ifdef FEAT_EVAL
499 // if "fmt" was set insecurely it needs to be evaluated in the sandbox
500 int use_sandbox = was_set_insecurely(opt_name, opt_scope);
501
502 // When the format starts with "%!" then evaluate it as an expression and
503 // use the result as the actual format string.
504 if (1 < len && usefmt[0] == '%' && usefmt[1] == '!')
505 {
506 typval_T tv;
507 char_u *p = NULL;
508
509 tv.v_type = VAR_NUMBER;
510 tv.vval.v_number = pargs->cwp->w_id;
511 set_var((char_u *)"g:tabpanel_winid", &tv, FALSE);
512
513 p = eval_to_string_safe(usefmt + 2, use_sandbox, FALSE, FALSE);
514 if (p != NULL)
515 usefmt = p;
516
517 do_unlet((char_u *)"g:tabpanel_winid", TRUE);
518 }
519#endif
520
521 return usefmt;
522}
523
524/*
525 * do something by tplmode for drawing tabpanel.
526 */
527 static void
528do_by_tplmode(
529 int tplmode,
530 int col_start,
531 int col_end,
532 int *pcurtab_row,
533 int *ptabpagenr)
534{
535 int attr_tplf = HL_ATTR(HLF_TPLF);
536 int attr_tpls = HL_ATTR(HLF_TPLS);
537 int attr_tpl = HL_ATTR(HLF_TPL);
538 int col = col_start;
539 int row = 0;
540 tabpage_T *tp = NULL;
541 typval_T v;
542 tabpanel_T args;
543
544 args.maxrow = cmdline_row;
545 args.offsetrow = 0;
546 args.col_start = col_start;
547 args.col_end = col_end;
548
549 if (TPLMODE_GET_CURTAB_ROW != tplmode)
550 if (0 < args.maxrow)
551 while (args.offsetrow + args.maxrow <= *pcurtab_row)
552 args.offsetrow += args.maxrow;
553
554 tp = first_tabpage;
555
556 for (row = 0; tp != NULL; row++)
557 {
558 if ((TPLMODE_GET_CURTAB_ROW != tplmode)
559 && (args.maxrow <= (row - args.offsetrow)))
560 break;
561
562 col = col_start;
563
564 v.v_type = VAR_NUMBER;
565 v.vval.v_number = tabpage_index(tp);
566 set_var((char_u *)"g:actual_curtabpage", &v, TRUE);
567
568 if (tp->tp_topframe == topframe)
569 {
570 args.attr = attr_tpls;
571 if (TPLMODE_GET_CURTAB_ROW == tplmode)
572 {
573 *pcurtab_row = row;
574 break;
575 }
576 }
577 else
578 args.attr = attr_tpl;
579
580 if (tp == curtab)
581 {
582 args.cwp = curwin;
583 args.wp = firstwin;
584 }
585 else
586 {
587 args.cwp = tp->tp_curwin;
588 args.wp = tp->tp_firstwin;
589 }
590
591 char_u* usefmt = starts_with_percent_and_bang(&args);
592 if (usefmt != NULL)
593 {
594 char_u buf[IOSIZE];
595 char_u *p = usefmt;
596 size_t i = 0;
597
598 while (p[i] != '\0')
599 {
600 while ((p[i] == '\n') || (p[i] == '\r'))
601 {
602 // fill the tailing area of current row.
603 if (0 <= (row - args.offsetrow)
604 && (row - args.offsetrow) < args.maxrow)
605 screen_fill_tailing_area(tplmode,
606 row - args.offsetrow,
607 row - args.offsetrow + 1,
608 col, args.col_end, args.attr);
609 row++;
610 col = col_start;
611 p++;
612 }
613
614 while ((p[i] != '\n') && (p[i] != '\r')
615 && (p[i] != '\0'))
616 {
617 if (i + 1 >= sizeof(buf))
618 break;
619 buf[i] = p[i];
620 i++;
621 }
622 buf[i] = '\0';
623
624 args.user_defined = buf;
625 args.prow = &row;
626 args.pcol = &col;
627 draw_tabpanel_userdefined(tplmode, &args);
628
629 p += i;
630 i = 0;
631 }
632 if (usefmt != p_tpl)
633 VIM_CLEAR(usefmt);
634 }
635 else
636 {
637 args.user_defined = NULL;
638 args.prow = &row;
639 args.pcol = &col;
640 draw_tabpanel_default(tplmode, &args);
641 }
642
643 do_unlet((char_u *)"g:actual_curtabpage", TRUE);
644
645 tp = tp->tp_next;
646
647 if ((TPLMODE_GET_TABPAGENR == tplmode)
648 && (mouse_row <= (row - args.offsetrow)))
649 {
650 *ptabpagenr = v.vval.v_number;
651 break;
652 }
653 }
654
655 // fill the area of TabPanelFill.
656 screen_fill_tailing_area(tplmode, row - args.offsetrow, args.maxrow,
657 args.col_start, args.col_end, attr_tplf);
658}
659
660#endif // FEAT_TABPANEL