statusline.c (69069B)
1 #include <assert.h> 2 #include <inttypes.h> 3 #include <stdbool.h> 4 #include <stddef.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 9 #include "nvim/api/private/defs.h" 10 #include "nvim/api/private/helpers.h" 11 #include "nvim/ascii_defs.h" 12 #include "nvim/buffer.h" 13 #include "nvim/buffer_defs.h" 14 #include "nvim/charset.h" 15 #include "nvim/digraph.h" 16 #include "nvim/drawline.h" 17 #include "nvim/drawscreen.h" 18 #include "nvim/eval.h" 19 #include "nvim/eval/typval_defs.h" 20 #include "nvim/eval/vars.h" 21 #include "nvim/gettext_defs.h" 22 #include "nvim/globals.h" 23 #include "nvim/grid.h" 24 #include "nvim/grid_defs.h" 25 #include "nvim/highlight.h" 26 #include "nvim/highlight_defs.h" 27 #include "nvim/highlight_group.h" 28 #include "nvim/macros_defs.h" 29 #include "nvim/mbyte.h" 30 #include "nvim/memline.h" 31 #include "nvim/memline_defs.h" 32 #include "nvim/memory.h" 33 #include "nvim/memory_defs.h" 34 #include "nvim/message.h" 35 #include "nvim/normal.h" 36 #include "nvim/option.h" 37 #include "nvim/option_vars.h" 38 #include "nvim/os/os.h" 39 #include "nvim/os/os_defs.h" 40 #include "nvim/path.h" 41 #include "nvim/plines.h" 42 #include "nvim/pos_defs.h" 43 #include "nvim/sign.h" 44 #include "nvim/sign_defs.h" 45 #include "nvim/state_defs.h" 46 #include "nvim/statusline.h" 47 #include "nvim/strings.h" 48 #include "nvim/types_defs.h" 49 #include "nvim/ui.h" 50 #include "nvim/ui_defs.h" 51 #include "nvim/undo.h" 52 #include "nvim/window.h" 53 54 // Determines how deeply nested %{} blocks will be evaluated in statusline. 55 #define MAX_STL_EVAL_DEPTH 100 56 57 /// Enumeration specifying the valid numeric bases that can 58 /// be used when printing numbers in the status line. 59 typedef enum { 60 kNumBaseDecimal = 10, 61 kNumBaseHexadecimal = 16, 62 } NumberBase; 63 64 /// Redraw the status line of window `wp`. 65 /// 66 /// If inversion is possible we use it. Else '=' characters are used. 67 void win_redr_status(win_T *wp) 68 { 69 bool is_stl_global = global_stl_height() > 0; 70 static bool busy = false; 71 72 // May get here recursively when 'statusline' (indirectly) 73 // invokes ":redrawstatus". Simply ignore the call then. 74 if (busy 75 // Also ignore if wildmenu is showing. 76 || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { 77 return; 78 } 79 busy = true; 80 wp->w_redr_status = false; 81 if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { 82 // no status line, either global statusline is enabled or the window is a last window 83 redraw_cmdline = true; 84 } else if (!redrawing()) { 85 // Don't redraw right now, do it later. Don't update status line when 86 // popup menu is visible and may be drawn over it 87 wp->w_redr_status = true; 88 } else if (*wp->w_p_stl != NUL || !wp->w_floating || (is_stl_global && wp == curwin)) { 89 // redraw custom status line 90 redraw_custom_statusline(wp); 91 } 92 93 hlf_T group = HLF_C; 94 // May need to draw the character below the vertical separator. 95 if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { 96 schar_T fillchar; 97 if (stl_connected(wp)) { 98 fillchar = fillchar_status(&group, wp); 99 } else { 100 fillchar = wp->w_p_fcs_chars.vert; 101 } 102 int attr = win_hl_attr(wp, (int)group); 103 grid_line_start(&default_gridview, W_ENDROW(wp)); 104 grid_line_put_schar(W_ENDCOL(wp), fillchar, attr); 105 grid_line_flush(); 106 } 107 busy = false; 108 } 109 110 void get_trans_bufname(buf_T *buf) 111 { 112 if (buf_spname(buf) != NULL) { 113 xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); 114 } else { 115 home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); 116 } 117 trans_characters(NameBuff, MAXPATHL); 118 } 119 120 /// Only call if (wp->w_vsep_width != 0). 121 /// 122 /// @return true if the status line of window "wp" is connected to the status 123 /// line of the window right of it. If not, then it's a vertical separator. 124 bool stl_connected(win_T *wp) 125 { 126 frame_T *fr = wp->w_frame; 127 while (fr->fr_parent != NULL) { 128 if (fr->fr_parent->fr_layout == FR_COL) { 129 if (fr->fr_next != NULL) { 130 break; 131 } 132 } else { 133 if (fr->fr_next != NULL) { 134 return true; 135 } 136 } 137 fr = fr->fr_parent; 138 } 139 return false; 140 } 141 142 /// Clear status line, window bar or tab page line click definition table 143 /// 144 /// @param[out] tpcd Table to clear. 145 /// @param[in] tpcd_size Size of the table. 146 void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t click_defs_size) 147 { 148 if (click_defs != NULL) { 149 for (size_t i = 0; i < click_defs_size; i++) { 150 if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { 151 xfree(click_defs[i].func); 152 } 153 } 154 memset(click_defs, 0, click_defs_size * sizeof(click_defs[0])); 155 } 156 } 157 158 /// Allocate or resize the click definitions array if needed. 159 StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, int width, size_t *size) 160 { 161 if (*size < (size_t)width) { 162 xfree(cdp); 163 *size = (size_t)width; 164 cdp = xcalloc(*size, sizeof(StlClickDefinition)); 165 } 166 return cdp; 167 } 168 169 /// Fill the click definitions array if needed. 170 void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, 171 const char *buf, int width, bool tabline) 172 { 173 if (click_defs == NULL) { 174 return; 175 } 176 177 int col = 0; 178 int len = 0; 179 180 StlClickDefinition cur_click_def = { 181 .type = kStlClickDisabled, 182 }; 183 for (int i = 0; click_recs[i].start != NULL; i++) { 184 len += vim_strnsize(buf, (int)(click_recs[i].start - buf)); 185 assert(len <= width); 186 if (col < len) { 187 while (col < len) { 188 click_defs[col++] = cur_click_def; 189 } 190 } else { 191 xfree(cur_click_def.func); 192 } 193 buf = click_recs[i].start; 194 cur_click_def = click_recs[i].def; 195 if (!tabline && !(cur_click_def.type == kStlClickDisabled 196 || cur_click_def.type == kStlClickFuncRun)) { 197 // window bar and status line only support click functions 198 cur_click_def.type = kStlClickDisabled; 199 } 200 } 201 if (col < width) { 202 while (col < width) { 203 click_defs[col++] = cur_click_def; 204 } 205 } else { 206 xfree(cur_click_def.func); 207 } 208 } 209 210 static bool did_show_ext_ruler = false; 211 /// Redraw the status line, window bar, ruler or tabline. 212 /// @param wp target window, NULL for 'tabline' 213 /// @param draw_winbar redraw 'winbar' 214 /// @param draw_ruler redraw 'rulerformat' 215 /// @param ui_event emit UI-event instead of drawing 216 static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler, bool ui_event) 217 { 218 static bool entered = false; 219 int col = 0; 220 int attr, row, maxwidth; 221 hlf_T group; 222 schar_T fillchar; 223 char buf[MAXPATHL]; 224 char transbuf[MAXPATHL]; 225 char *stl; 226 OptIndex opt_idx = kOptInvalid; 227 int opt_scope = 0; 228 stl_hlrec_t *hltab; 229 StlClickRecord *tabtab; 230 bool is_stl_global = global_stl_height() > 0; 231 232 ScreenGrid *grid = wp && wp->w_floating && !is_stl_global ? &wp->w_grid_alloc : &default_grid; 233 234 // There is a tiny chance that this gets called recursively: When 235 // redrawing a status line triggers redrawing the ruler or tabline. 236 // Avoid trouble by not allowing recursion. 237 if (entered) { 238 return; 239 } 240 entered = true; 241 242 // setup environment for the task at hand 243 if (wp == NULL) { 244 // Use 'tabline'. Always at the first line of the screen. 245 stl = p_tal; 246 row = 0; 247 fillchar = schar_from_ascii(' '); 248 group = HLF_TPF; 249 attr = HL_ATTR(group); 250 maxwidth = Columns; 251 opt_idx = kOptTabline; 252 } else if (draw_winbar) { 253 opt_idx = kOptWinbar; 254 stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); 255 opt_scope = ((*wp->w_p_wbr != NUL) ? OPT_LOCAL : 0); 256 row = -1; // row zero is first row of text 257 col = 0; 258 grid = grid_adjust(&wp->w_grid, &row, &col); 259 260 if (row < 0) { 261 goto theend; 262 } 263 264 fillchar = wp->w_p_fcs_chars.wbr; 265 group = (wp == curwin) ? HLF_WBR : HLF_WBRNC; 266 attr = win_hl_attr(wp, (int)group); 267 maxwidth = wp->w_view_width; 268 stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); 269 wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth, 270 &wp->w_winbar_click_defs_size); 271 } else { 272 const bool in_status_line = wp->w_status_height != 0 || is_stl_global; 273 if (wp->w_floating && !is_stl_global && !draw_ruler) { 274 row = wp->w_winrow_off + wp->w_view_height; 275 col = wp->w_wincol_off; 276 maxwidth = wp->w_view_width; 277 } else { 278 row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); 279 maxwidth = in_status_line && !is_stl_global ? wp->w_width : Columns; 280 } 281 fillchar = fillchar_status(&group, wp); 282 stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); 283 wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth, 284 &wp->w_status_click_defs_size); 285 286 if (draw_ruler) { 287 stl = p_ruf; 288 opt_idx = kOptRulerformat; 289 // advance past any leading group spec - implicit in ru_col 290 if (*stl == '%') { 291 if (*++stl == '-') { 292 stl++; 293 } 294 if (atoi(stl)) { 295 while (ascii_isdigit(*stl)) { 296 stl++; 297 } 298 } 299 if (*stl++ != '(') { 300 stl = p_ruf; 301 } 302 } 303 col = MAX(ru_col - (Columns - maxwidth), (maxwidth + 1) / 2); 304 maxwidth -= col; 305 if (!in_status_line) { 306 row = Rows - 1; 307 grid = grid_adjust(&msg_grid_adj, &row, &col); 308 maxwidth--; // writing in last column may cause scrolling 309 fillchar = schar_from_ascii(' '); 310 group = HLF_MSG; 311 } 312 } else { 313 opt_idx = kOptStatusline; 314 stl = ((*wp->w_p_stl != NUL) ? wp->w_p_stl : p_stl); 315 opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0); 316 } 317 318 attr = win_hl_attr(wp, (int)group); 319 if (!wp->w_floating && in_status_line && !is_stl_global) { 320 col += wp->w_wincol; 321 } 322 } 323 324 if (maxwidth <= 0) { 325 goto theend; 326 } 327 328 // Temporarily reset 'cursorbind', we don't want a side effect from moving 329 // the cursor away and back. 330 win_T *ewp = wp == NULL ? curwin : wp; 331 int p_crb_save = ewp->w_p_crb; 332 ewp->w_p_crb = false; 333 334 // Make a copy, because the statusline may include a function call that 335 // might change the option value and free the memory. 336 stl = xstrdup(stl); 337 build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_idx, opt_scope, 338 fillchar, maxwidth, &hltab, NULL, &tabtab, NULL); 339 340 xfree(stl); 341 ewp->w_p_crb = p_crb_save; 342 343 int len = (int)strlen(buf); 344 int start_col = col; 345 346 if (!ui_event) { 347 // Draw each snippet with the specified highlighting. 348 screengrid_line_start(grid, row, 0); 349 } 350 351 char *p = buf; 352 int curattr = attr; 353 int curgroup = (int)group; 354 Array content = ARRAY_DICT_INIT; 355 for (stl_hlrec_t *sp = hltab;; sp++) { 356 int textlen = (int)(sp->start ? sp->start - p : buf + len - p); 357 // Make all characters printable. Use an empty string instead of p, if p is beyond buf + len. 358 size_t tsize = transstr_buf(p >= buf + len ? "" : p, textlen, transbuf, sizeof transbuf, true); 359 if (!ui_event) { 360 col += grid_line_puts(col, transbuf, (int)tsize, curattr); 361 } else { 362 Array chunk = ARRAY_DICT_INIT; 363 ADD(chunk, INTEGER_OBJ(curattr)); 364 ADD(chunk, STRING_OBJ(cbuf_as_string(xmemdupz(transbuf, tsize), tsize))); 365 ADD(chunk, INTEGER_OBJ(curgroup)); 366 ADD(content, ARRAY_OBJ(chunk)); 367 } 368 p = sp->start; 369 370 if (p == NULL) { 371 break; 372 } else if (sp->userhl == 0) { 373 curattr = attr; 374 curgroup = (int)group; 375 } else if (sp->userhl < 0) { 376 int new_attr = syn_id2attr(-sp->userhl); 377 if (sp->item == STL_HIGHLIGHT_COMB) { 378 curattr = hl_combine_attr(curattr, new_attr); 379 } else { 380 curattr = new_attr; 381 } 382 curgroup = -sp->userhl; 383 } else { 384 int *userhl = (wp != NULL && wp != curwin && wp->w_status_height != 0) 385 ? highlight_stlnc : highlight_user; 386 char userbuf[5] = "User"; 387 userbuf[4] = (char)sp->userhl + '0'; 388 curattr = userhl[sp->userhl - 1]; 389 curgroup = syn_name2id_len(userbuf, 5); 390 } 391 if (curattr != attr) { 392 curattr = hl_combine_attr(attr, curattr); 393 } 394 } 395 396 if (ui_event) { 397 ui_call_msg_ruler(content); 398 did_show_ext_ruler = true; 399 api_free_array(content); 400 goto theend; 401 } 402 403 int maxcol = start_col + maxwidth; 404 405 // fill up with "fillchar" 406 grid_line_fill(col, maxcol, fillchar, curattr); 407 grid_line_flush(); 408 409 // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking 410 // in the tab page line, status line or window bar 411 StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs 412 : draw_winbar ? wp->w_winbar_click_defs 413 : wp->w_status_click_defs; 414 415 stl_fill_click_defs(click_defs, tabtab, buf, maxwidth, wp == NULL); 416 417 theend: 418 entered = false; 419 } 420 421 void win_redr_winbar(win_T *wp) 422 { 423 static bool entered = false; 424 425 // Return when called recursively. This can happen when the winbar contains an expression 426 // that triggers a redraw. 427 if (entered) { 428 return; 429 } 430 entered = true; 431 432 if (wp->w_winbar_height == 0 || !redrawing()) { 433 // Do nothing. 434 } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { 435 win_redr_custom(wp, true, false, false); 436 } 437 entered = false; 438 } 439 440 void redraw_ruler(void) 441 { 442 static int did_ruler_col = -1; 443 win_T *wp = curwin->w_status_height == 0 ? curwin : lastwin_nofloating(); 444 bool is_stl_global = global_stl_height() > 0; 445 446 // Check if ruler should be drawn, clear if it was drawn before. 447 if (!p_ru || wp->w_status_height > 0 || is_stl_global || (p_ch == 0 && !ui_has(kUIMessages))) { 448 if (did_show_ext_ruler && ui_has(kUIMessages)) { 449 ui_call_msg_ruler((Array)ARRAY_DICT_INIT); 450 did_show_ext_ruler = false; 451 } else if (did_ruler_col > 0) { 452 msg_col = did_ruler_col; 453 msg_row = Rows - 1; 454 msg_clr_eos(); 455 } 456 did_ruler_col = -1; 457 return; 458 } 459 460 // Check if cursor.lnum is valid, since redraw_ruler() may be called 461 // after deleting lines, before cursor.lnum is corrected. 462 if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { 463 return; 464 } 465 466 // Don't draw the ruler while doing insert-completion, it might overwrite 467 // the (long) mode message. 468 if (wp->w_status_height == 0 && !is_stl_global && edit_submode != NULL) { 469 return; 470 } 471 472 bool part_of_status = wp->w_status_height || is_stl_global; 473 if (*p_ruf && (p_ch > 0 || (ui_has(kUIMessages) && !part_of_status))) { 474 win_redr_custom(wp, false, true, ui_has(kUIMessages)); 475 return; 476 } 477 478 hlf_T group = HLF_MSG; 479 int off = wp->w_status_height ? wp->w_wincol : 0; 480 int width = wp->w_status_height ? wp->w_width : Columns; 481 schar_T fillchar = part_of_status ? fillchar_status(&group, wp) : schar_from_ascii(' '); 482 int attr = win_hl_attr(wp, (int)group); 483 484 // In list mode virtcol needs to be recomputed 485 colnr_T virtcol = wp->w_virtcol; 486 if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { 487 wp->w_p_list = false; 488 getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); 489 wp->w_p_list = true; 490 } 491 492 // Check if not in Insert mode and the line is empty (will show "0-1"). 493 int empty_line = (State & MODE_INSERT) == 0 494 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL; 495 496 #define RULER_BUF_LEN 70 497 char buffer[RULER_BUF_LEN]; 498 499 // row number, column number is appended 500 // l10n: leave as-is unless a space after the comma is preferred 501 // l10n: do not add any row/column label, due to the limited space 502 int bufferlen = vim_snprintf(buffer, RULER_BUF_LEN, _("%" PRId64 ","), 503 (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) 504 ? 0 505 : (int64_t)wp->w_cursor.lnum); 506 bufferlen += col_print(buffer + bufferlen, RULER_BUF_LEN - (size_t)bufferlen, 507 empty_line ? 0 : (int)wp->w_cursor.col + 1, 508 (int)virtcol + 1); 509 510 // Add a "50%" if there is room for it. 511 // On the last line, don't print in the last column (scrolls the 512 // screen up on some terminals). 513 char rel_pos[RULER_BUF_LEN]; 514 int rel_poslen = get_rel_pos(wp, rel_pos, RULER_BUF_LEN); 515 int n1 = bufferlen + vim_strsize(rel_pos); 516 if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen 517 n1++; 518 } 519 520 int this_ru_col = ru_col - (Columns - width); 521 // Never use more than half the window/screen width, leave the other half 522 // for the filename. 523 int n2 = (width + 1) / 2; 524 this_ru_col = MAX(this_ru_col, n2); 525 if (this_ru_col + n1 < width) { 526 // need at least space for rel_pos + NUL 527 while (this_ru_col + n1 < width 528 && RULER_BUF_LEN > bufferlen + rel_poslen + 1) { // +1 for NUL 529 bufferlen += (int)schar_get(buffer + bufferlen, fillchar); 530 n1++; 531 } 532 bufferlen += vim_snprintf(buffer + bufferlen, RULER_BUF_LEN - (size_t)bufferlen, 533 "%s", rel_pos); 534 } 535 (void)bufferlen; 536 537 if (ui_has(kUIMessages) && !part_of_status) { 538 MAXSIZE_TEMP_ARRAY(content, 1); 539 MAXSIZE_TEMP_ARRAY(chunk, 3); 540 ADD_C(chunk, INTEGER_OBJ(attr)); 541 ADD_C(chunk, CSTR_AS_OBJ(buffer)); 542 ADD_C(chunk, INTEGER_OBJ(HLF_MSG)); 543 assert(attr == HL_ATTR(HLF_MSG)); 544 ADD_C(content, ARRAY_OBJ(chunk)); 545 ui_call_msg_ruler(content); 546 did_show_ext_ruler = true; 547 did_ruler_col = 1; 548 } else { 549 if (did_show_ext_ruler) { 550 ui_call_msg_ruler((Array)ARRAY_DICT_INIT); 551 did_show_ext_ruler = false; 552 } 553 // Truncate at window boundary. 554 for (n1 = 0, n2 = 0; buffer[n1] != NUL; n1 += utfc_ptr2len(buffer + n1)) { 555 n2 += utf_ptr2cells(buffer + n1); 556 if (this_ru_col + n2 > width) { 557 bufferlen = n1; 558 buffer[bufferlen] = NUL; 559 break; 560 } 561 } 562 563 grid_line_start(&msg_grid_adj, Rows - 1); 564 did_ruler_col = off + this_ru_col; 565 int w = grid_line_puts(did_ruler_col, buffer, -1, attr); 566 grid_line_fill(did_ruler_col + w, off + width, fillchar, attr); 567 grid_line_flush(); 568 } 569 } 570 571 /// Get the character to use in a status line. Get its attributes in "*attr". 572 schar_T fillchar_status(hlf_T *group, win_T *wp) 573 { 574 if (wp == curwin) { 575 *group = HLF_S; 576 return wp->w_p_fcs_chars.stl; 577 } else { 578 *group = HLF_SNC; 579 return wp->w_p_fcs_chars.stlnc; 580 } 581 } 582 583 /// Redraw the status line according to 'statusline' and take care of any 584 /// errors encountered. 585 void redraw_custom_statusline(win_T *wp) 586 { 587 static bool entered = false; 588 589 // When called recursively return. This can happen when the statusline 590 // contains an expression that triggers a redraw. 591 if (entered) { 592 return; 593 } 594 entered = true; 595 596 win_redr_custom(wp, false, false, false); 597 entered = false; 598 } 599 600 static void ui_ext_tabline_update(void) 601 { 602 Arena arena = ARENA_EMPTY; 603 604 size_t n_tabs = 0; 605 FOR_ALL_TABS(tp) { 606 n_tabs++; 607 } 608 609 Array tabs = arena_array(&arena, n_tabs); 610 FOR_ALL_TABS(tp) { 611 Dict tab_info = arena_dict(&arena, 2); 612 PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); 613 614 win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; 615 get_trans_bufname(cwp->w_buffer); 616 PUT_C(tab_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff)); 617 618 ADD_C(tabs, DICT_OBJ(tab_info)); 619 } 620 621 size_t n_buffers = 0; 622 FOR_ALL_BUFFERS(buf) { 623 n_buffers += buf->b_p_bl ? 1 : 0; 624 } 625 626 Array buffers = arena_array(&arena, n_buffers); 627 FOR_ALL_BUFFERS(buf) { 628 // Do not include unlisted buffers 629 if (!buf->b_p_bl) { 630 continue; 631 } 632 633 Dict buffer_info = arena_dict(&arena, 2); 634 PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); 635 636 get_trans_bufname(buf); 637 PUT_C(buffer_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff)); 638 639 ADD_C(buffers, DICT_OBJ(buffer_info)); 640 } 641 642 ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); 643 arena_mem_free(arena_finish(&arena)); 644 } 645 646 /// Draw the tab pages line at the top of the Vim window. 647 void draw_tabline(void) 648 { 649 win_T *wp; 650 int attr_nosel = HL_ATTR(HLF_TP); 651 int attr_fill = HL_ATTR(HLF_TPF); 652 bool use_sep_chars = (t_colors < 8); 653 654 if (default_grid.chars == NULL) { 655 return; 656 } 657 redraw_tabline = false; 658 659 if (ui_has(kUITabline)) { 660 ui_ext_tabline_update(); 661 return; 662 } 663 664 if (tabline_height() < 1) { 665 return; 666 } 667 668 // Clear tab_page_click_defs: Clicking outside of tabs has no effect. 669 assert(tab_page_click_defs_size >= (size_t)Columns); 670 stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); 671 672 // Use the 'tabline' option if it's set. 673 if (*p_tal != NUL) { 674 win_redr_custom(NULL, false, false, false); 675 } else { 676 int tabcount = 0; 677 int col = 0; 678 win_T *cwp; 679 int wincount; 680 grid_line_start(&default_gridview, 0); 681 FOR_ALL_TABS(tp) { 682 tabcount++; 683 } 684 685 int tabwidth = MAX(tabcount > 0 ? (Columns - 1 + tabcount / 2) / tabcount : 0, 6); 686 687 int attr = attr_nosel; 688 tabcount = 0; 689 690 FOR_ALL_TABS(tp) { 691 if (col >= Columns - 4) { 692 break; 693 } 694 695 int scol = col; 696 697 if (tp == curtab) { 698 cwp = curwin; 699 wp = firstwin; 700 } else { 701 cwp = tp->tp_curwin; 702 wp = tp->tp_firstwin; 703 } 704 705 if (tp->tp_topframe == topframe) { 706 attr = win_hl_attr(cwp, HLF_TPS); 707 } 708 if (use_sep_chars && col > 0) { 709 grid_line_put_schar(col++, schar_from_ascii('|'), attr); 710 } 711 712 if (tp->tp_topframe != topframe) { 713 attr = win_hl_attr(cwp, HLF_TP); 714 } 715 716 grid_line_put_schar(col++, schar_from_ascii(' '), attr); 717 718 bool modified = false; 719 720 for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { 721 if (!wp->w_config.focusable || wp->w_config.hide) { 722 wincount--; 723 } else if (bufIsChanged(wp->w_buffer)) { 724 modified = true; 725 } 726 } 727 728 if (modified || wincount > 1) { 729 if (wincount > 1) { 730 int len = vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); 731 if (col + len >= Columns - 3) { 732 break; 733 } 734 grid_line_puts(col, NameBuff, len, 735 hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); 736 col += len; 737 } 738 if (modified) { 739 grid_line_put_schar(col++, schar_from_ascii('+'), attr); 740 } 741 grid_line_put_schar(col++, schar_from_ascii(' '), attr); 742 } 743 744 int room = scol - col + tabwidth - 1; 745 if (room > 0) { 746 // Get buffer name in NameBuff[] 747 get_trans_bufname(cwp->w_buffer); 748 shorten_dir(NameBuff); 749 int len = vim_strsize(NameBuff); 750 char *p = NameBuff; 751 while (len > room) { 752 len -= ptr2cells(p); 753 MB_PTR_ADV(p); 754 } 755 int n = Columns - col - 1; 756 len = MIN(len, n); 757 758 grid_line_puts(col, p, -1, attr); 759 col += len; 760 } 761 grid_line_put_schar(col++, schar_from_ascii(' '), attr); 762 763 // Store the tab page number in tab_page_click_defs[], so that 764 // jump_to_mouse() knows where each one is. 765 tabcount++; 766 while (scol < col) { 767 tab_page_click_defs[scol++] = (StlClickDefinition) { 768 .type = kStlClickTabSwitch, 769 .tabnr = tabcount, 770 .func = NULL, 771 }; 772 } 773 } 774 775 for (int scol = col; scol < Columns; scol++) { 776 // Use 0 as tabpage number here, so that double-click opens a tabpage 777 // after the last one, and single-click goes to the next tabpage. 778 tab_page_click_defs[scol] = (StlClickDefinition) { 779 .type = kStlClickTabSwitch, 780 .tabnr = 0, 781 .func = NULL, 782 }; 783 } 784 785 char c = use_sep_chars ? '_' : ' '; 786 grid_line_fill(col, Columns, schar_from_ascii(c), attr_fill); 787 788 // Draw the 'showcmd' information if 'showcmdloc' == "tabline". 789 if (p_sc && *p_sloc == 't') { 790 int n = Columns - col - (tabcount > 1) * 3; 791 const int sc_width = MIN(10, n); 792 793 if (sc_width > 0) { 794 grid_line_puts(Columns - sc_width - (tabcount > 1) * 2, 795 showcmd_buf, sc_width, attr_nosel); 796 } 797 } 798 799 // Put an "X" for closing the current tab if there are several. 800 if (tabcount > 1) { 801 grid_line_put_schar(Columns - 1, schar_from_ascii('X'), attr_nosel); 802 tab_page_click_defs[Columns - 1] = (StlClickDefinition) { 803 .type = kStlClickTabClose, 804 .tabnr = 999, 805 .func = NULL, 806 }; 807 } 808 809 grid_line_flush(); 810 } 811 812 // Reset the flag here again, in case evaluating 'tabline' causes it to be 813 // set. 814 redraw_tabline = false; 815 } 816 817 /// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1, 818 /// the v:lnum and v:relnum variables don't have to be updated. 819 /// 820 /// @return The width of the built status column string for line "lnum" 821 int build_statuscol_str(win_T *wp, linenr_T lnum, linenr_T relnum, char *buf, statuscol_T *stcp) 822 { 823 // Only update click definitions once per window per redraw. 824 // Don't update when current width is 0, since it will be redrawn again if not empty. 825 const bool fillclick = relnum >= 0 && stcp->width > 0 && lnum == wp->w_topline; 826 827 if (relnum >= 0) { 828 set_vim_var_nr(VV_LNUM, lnum); 829 set_vim_var_nr(VV_RELNUM, relnum); 830 } 831 832 StlClickRecord *clickrec; 833 char *stc = xstrdup(wp->w_p_stc); 834 int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, kOptStatuscolumn, OPT_LOCAL, 0, 835 stcp->width, &stcp->hlrec, NULL, fillclick ? &clickrec : NULL, stcp); 836 xfree(stc); 837 838 if (fillclick) { 839 stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); 840 wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width, 841 &wp->w_statuscol_click_defs_size); 842 stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false); 843 } 844 845 return width; 846 } 847 848 /// Build a string from the status line items in "fmt". 849 /// Return length of string in screen cells. 850 /// 851 /// Normally works for window "wp", except when working for 'tabline' then it 852 /// is "curwin". 853 /// 854 /// Items are drawn interspersed with the text that surrounds it 855 /// Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation 856 /// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional 857 /// 858 /// If maxwidth is not zero, the string will be filled at any middle marker 859 /// or truncated if too long, fillchar is used for all whitespace. 860 /// 861 /// @param wp The window to build a statusline for 862 /// @param out The output buffer to write the statusline to 863 /// Note: This should not be NameBuff 864 /// @param outlen The length of the output buffer 865 /// @param fmt The statusline format string 866 /// @param opt_idx Index of the option corresponding to "fmt" 867 /// @param opt_scope The scope corresponding to "opt_idx" 868 /// @param fillchar Character to use when filling empty space in the statusline 869 /// @param maxwidth The maximum width to make the statusline 870 /// @param hltab HL attributes (can be NULL) 871 /// @param tabtab Tab clicks definition (can be NULL) 872 /// @param stcp Status column attributes (can be NULL) 873 /// 874 /// @return The final width of the statusline 875 int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex opt_idx, 876 int opt_scope, schar_T fillchar, int maxwidth, stl_hlrec_t **hltab, 877 size_t *hltab_len, StlClickRecord **tabtab, statuscol_T *stcp) 878 { 879 static size_t stl_items_len = 20; // Initial value, grows as needed. 880 static stl_item_t *stl_items = NULL; 881 static int *stl_groupitems = NULL; 882 static stl_hlrec_t *stl_hltab = NULL; 883 static StlClickRecord *stl_tabtab = NULL; 884 static int *stl_separator_locations = NULL; 885 static int curitem = 0; 886 887 #define TMPLEN 70 888 char buf_tmp[TMPLEN]; 889 char *usefmt = fmt; 890 const bool save_redraw_not_allowed = redraw_not_allowed; 891 const bool save_KeyTyped = KeyTyped; 892 // TODO(Bram): find out why using called_emsg_before makes tests fail, does it 893 // matter? 894 // const int called_emsg_before = called_emsg; 895 const int did_emsg_before = did_emsg; 896 897 // When inside update_screen() we do not want redrawing a statusline, 898 // ruler, title, etc. to trigger another redraw, it may cause an endless 899 // loop. 900 if (updating_screen) { 901 redraw_not_allowed = true; 902 } 903 904 if (stl_items == NULL) { 905 stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); 906 stl_groupitems = xmalloc(sizeof(int) * stl_items_len); 907 908 // Allocate one more, because the last element is used to indicate the 909 // end of the list. 910 stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); 911 stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); 912 913 stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); 914 } 915 916 // If "fmt" was set insecurely it needs to be evaluated in the sandbox. 917 // "opt_idx" will be kOptInvalid when caller is nvim_eval_statusline(). 918 const bool use_sandbox = (opt_idx != kOptInvalid) ? was_set_insecurely(wp, opt_idx, opt_scope) 919 : false; 920 921 // When the format starts with "%!" then evaluate it as an expression and 922 // use the result as the actual format string. 923 if (fmt[0] == '%' && fmt[1] == '!') { 924 typval_T tv = { 925 .v_type = VAR_NUMBER, 926 .vval.v_number = wp->handle, 927 }; 928 set_var(S_LEN("g:statusline_winid"), &tv, false); 929 930 usefmt = eval_to_string_safe(fmt + 2, use_sandbox, false); 931 if (usefmt == NULL) { 932 usefmt = fmt; 933 } 934 935 do_unlet(S_LEN("g:statusline_winid"), true); 936 } 937 938 if (fillchar == 0) { 939 fillchar = schar_from_ascii(' '); 940 } 941 942 // The cursor in windows other than the current one isn't always 943 // up-to-date, esp. because of autocommands and timers. 944 linenr_T lnum = wp->w_cursor.lnum; 945 if (lnum > wp->w_buffer->b_ml.ml_line_count) { 946 lnum = wp->w_buffer->b_ml.ml_line_count; 947 wp->w_cursor.lnum = lnum; 948 } 949 950 // Get line & check if empty (cursorpos will show "0-1"). 951 const char *line_ptr = ml_get_buf(wp->w_buffer, lnum); 952 bool empty_line = (*line_ptr == NUL); 953 954 // Get the byte value now, in case we need it below. This is more 955 // efficient than making a copy of the line. 956 int byteval; 957 const colnr_T len = ml_get_buf_len(wp->w_buffer, lnum); 958 if (wp->w_cursor.col > len) { 959 // Line may have changed since checking the cursor column, or the lnum 960 // was adjusted above. 961 wp->w_cursor.col = len; 962 wp->w_cursor.coladd = 0; 963 byteval = 0; 964 } else { 965 byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); 966 } 967 968 int groupdepth = 0; 969 int evaldepth = 0; 970 971 // nvim_eval_statusline() can be called from inside a {-expression item so 972 // this may be a recursive call. Keep track of the start index into "stl_items". 973 // During post-processing only treat items filled in a certain recursion level. 974 int evalstart = curitem; 975 976 bool prevchar_isflag = true; 977 bool prevchar_isitem = false; 978 979 // out_p is the current position in the output buffer 980 char *out_p = out; 981 982 // out_end_p is the last valid character in the output buffer 983 // Note: The null termination character must occur here or earlier, 984 // so any user-visible characters must occur before here. 985 char *out_end_p = (out + outlen) - 1; 986 987 // Proceed character by character through the statusline format string 988 // fmt_p is the current position in the input buffer 989 for (char *fmt_p = usefmt; *fmt_p != NUL;) { 990 if (curitem == (int)stl_items_len) { 991 size_t new_len = stl_items_len * 3 / 2; 992 993 stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); 994 stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); 995 stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); 996 stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); 997 stl_separator_locations = 998 xrealloc(stl_separator_locations, sizeof(int) * new_len); 999 1000 stl_items_len = new_len; 1001 } 1002 1003 if (*fmt_p != '%') { 1004 prevchar_isflag = prevchar_isitem = false; 1005 } 1006 1007 // Copy the formatting verbatim until we reach the end of the string 1008 // or find a formatting item (denoted by `%`) 1009 // or run out of room in our output buffer. 1010 while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { 1011 *out_p++ = *fmt_p++; 1012 } 1013 1014 // If we have processed the entire format string or run out of 1015 // room in our output buffer, exit the loop. 1016 if (*fmt_p == NUL || out_p >= out_end_p) { 1017 break; 1018 } 1019 1020 // The rest of this loop will handle a single `%` item. 1021 // Note: We increment here to skip over the `%` character we are currently 1022 // on so we can process the item's contents. 1023 fmt_p++; 1024 1025 // Ignore `%` at the end of the format string 1026 if (*fmt_p == NUL) { 1027 break; 1028 } 1029 1030 // Two `%` in a row is the escape sequence to print a 1031 // single `%` in the output buffer. 1032 if (*fmt_p == '%') { 1033 *out_p++ = *fmt_p++; 1034 prevchar_isflag = prevchar_isitem = false; 1035 continue; 1036 } 1037 1038 // STL_SEPARATE: Separation between items, filled with white space. 1039 if (*fmt_p == STL_SEPARATE) { 1040 fmt_p++; 1041 // Ignored when we are inside of a grouping 1042 if (groupdepth > 0) { 1043 continue; 1044 } 1045 stl_items[curitem].type = Separate; 1046 stl_items[curitem++].start = out_p; 1047 continue; 1048 } 1049 1050 // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. 1051 if (*fmt_p == STL_TRUNCMARK) { 1052 fmt_p++; 1053 stl_items[curitem].type = Trunc; 1054 stl_items[curitem++].start = out_p; 1055 continue; 1056 } 1057 1058 // The end of a grouping 1059 if (*fmt_p == ')') { 1060 fmt_p++; 1061 // Ignore if we are not actually inside a group currently 1062 if (groupdepth < 1) { 1063 continue; 1064 } 1065 groupdepth--; 1066 1067 // Determine how long the group is. 1068 // Note: We set the current output position to null 1069 // so `vim_strsize` will work. 1070 char *t = stl_items[stl_groupitems[groupdepth]].start; 1071 *out_p = NUL; 1072 ptrdiff_t group_len = vim_strsize(t); 1073 1074 // If the group contained internal items 1075 // and the group did not have a minimum width, 1076 // and if there were no normal items in the group, 1077 // move the output pointer back to where the group started. 1078 // Note: This erases any non-item characters that were in the group. 1079 // Otherwise there would be no reason to do this step. 1080 if (curitem > stl_groupitems[groupdepth] + 1 1081 && stl_items[stl_groupitems[groupdepth]].minwid == 0) { 1082 int group_start_userhl = 0; 1083 int group_end_userhl = 0; 1084 int n; 1085 // remove group if all items are empty and highlight group 1086 // doesn't change 1087 for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { 1088 if (stl_items[n].type == Highlight || stl_items[n].type == HighlightCombining) { 1089 group_start_userhl = group_end_userhl = stl_items[n].minwid; 1090 break; 1091 } 1092 } 1093 for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { 1094 if (stl_items[n].type == Normal) { 1095 break; 1096 } 1097 if (stl_items[n].type == Highlight || stl_items[n].type == HighlightCombining) { 1098 group_end_userhl = stl_items[n].minwid; 1099 } 1100 } 1101 if (n == curitem && group_start_userhl == group_end_userhl) { 1102 // empty group 1103 out_p = t; 1104 group_len = 0; 1105 for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { 1106 // do not use the highlighting from the removed group 1107 if (stl_items[n].type == Highlight || stl_items[n].type == HighlightCombining) { 1108 stl_items[n].type = Empty; 1109 } 1110 // adjust the start position of TabPage to the next 1111 // item position 1112 if (stl_items[n].type == TabPage) { 1113 stl_items[n].start = out_p; 1114 } 1115 } 1116 } 1117 } 1118 1119 // If the group is longer than it is allowed to be truncate by removing 1120 // bytes from the start of the group text. Don't truncate when item is a 1121 // 'statuscolumn' fold item to ensure correctness of the mouse clicks. 1122 if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid 1123 && stl_items[stl_groupitems[groupdepth]].type != HighlightFold) { 1124 // { Determine the number of bytes to remove 1125 1126 // Find the first character that should be included. 1127 int n = 0; 1128 while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { 1129 group_len -= ptr2cells(t + n); 1130 n += utfc_ptr2len(t + n); 1131 } 1132 // } 1133 1134 // Prepend the `<` to indicate that the output was truncated. 1135 *t = '<'; 1136 1137 // { Move the truncated output 1138 memmove(t + 1, t + n, (size_t)(out_p - (t + n))); 1139 out_p = out_p - n + 1; 1140 // Fill up space left over by half a double-wide char. 1141 while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { 1142 schar_get_adv(&out_p, fillchar); 1143 } 1144 // } 1145 1146 // correct the start of the items for the truncation 1147 for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { 1148 // Shift everything back by the number of removed bytes 1149 // Minus one for the leading '<' added above. 1150 stl_items[idx].start -= n - 1; 1151 1152 // If the item was partially or completely truncated, set its 1153 // start to the start of the group 1154 stl_items[idx].start = MAX(stl_items[idx].start, t); 1155 } 1156 // If the group is shorter than the minimum width, add padding characters. 1157 } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { 1158 ptrdiff_t min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; 1159 // If the group is left-aligned, add characters to the right. 1160 if (min_group_width < 0) { 1161 min_group_width = 0 - min_group_width; 1162 while (group_len++ < min_group_width && out_p < out_end_p) { 1163 schar_get_adv(&out_p, fillchar); 1164 } 1165 // If the group is right-aligned, shift everything to the right and 1166 // prepend with filler characters. 1167 } else { 1168 // { Move the group to the right 1169 group_len = (min_group_width - group_len) * (int)schar_len(fillchar); 1170 memmove(t + group_len, t, (size_t)(out_p - t)); 1171 if (out_p + group_len >= (out_end_p + 1)) { 1172 group_len = out_end_p - out_p; 1173 } 1174 out_p += group_len; 1175 // } 1176 1177 // Adjust item start positions 1178 for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { 1179 stl_items[n].start += group_len; 1180 } 1181 1182 // Prepend the fill characters 1183 for (; group_len > 0; group_len--) { 1184 schar_get_adv(&t, fillchar); 1185 } 1186 } 1187 } 1188 continue; 1189 } 1190 int minwid = 0; 1191 int maxwid = 9999; 1192 int foldsignitem = -1; // Start of fold or sign item 1193 bool left_align_num = false; // Number item for should be left-aligned 1194 bool left_align = false; 1195 1196 // Denotes that numbers should be left-padded with zeros 1197 bool zeropad = (*fmt_p == '0'); 1198 if (zeropad) { 1199 fmt_p++; 1200 } 1201 1202 // Denotes that the item should be left-aligned. 1203 // This is tracked by using a negative length. 1204 if (*fmt_p == '-') { 1205 fmt_p++; 1206 left_align = true; 1207 } 1208 1209 // The first digit group is the item's min width 1210 if (ascii_isdigit(*fmt_p)) { 1211 minwid = getdigits_int(&fmt_p, false, 0); 1212 } 1213 1214 // User highlight groups override the min width field 1215 // to denote the styling to use. 1216 if (*fmt_p == STL_USER_HL) { 1217 stl_items[curitem].type = Highlight; 1218 stl_items[curitem].start = out_p; 1219 stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; 1220 fmt_p++; 1221 curitem++; 1222 continue; 1223 } 1224 1225 // TABPAGE pairs are used to denote a region that when clicked will 1226 // either switch to or close a tab. 1227 // 1228 // Ex: tabline=%1Ttab\ one%X 1229 // This tabline has a TABPAGENR item with minwid `1`, 1230 // which is then closed with a TABCLOSENR item. 1231 // Clicking on this region with mouse enabled will switch to tab 1. 1232 // Setting the minwid to a different value will switch 1233 // to that tab, if it exists 1234 // 1235 // Ex: tabline=%1Xtab\ one%X 1236 // This tabline has a TABCLOSENR item with minwid `1`, 1237 // which is then closed with a TABCLOSENR item. 1238 // Clicking on this region with mouse enabled will close tab 1. 1239 // 1240 // Note: These options are only valid when creating a tabline. 1241 if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { 1242 if (*fmt_p == STL_TABCLOSENR) { 1243 if (minwid == 0) { 1244 // %X ends the close label, go back to the previous tab label nr. 1245 for (int n = curitem - 1; n >= 0; n--) { 1246 if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { 1247 minwid = stl_items[n].minwid; 1248 break; 1249 } 1250 } 1251 } else { 1252 // close nrs are stored as negative values 1253 minwid = -minwid; 1254 } 1255 } 1256 stl_items[curitem].type = TabPage; 1257 stl_items[curitem].start = out_p; 1258 stl_items[curitem].minwid = minwid; 1259 fmt_p++; 1260 curitem++; 1261 continue; 1262 } 1263 1264 if (*fmt_p == STL_CLICK_FUNC) { 1265 fmt_p++; 1266 char *t = fmt_p; 1267 while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { 1268 fmt_p++; 1269 } 1270 if (*fmt_p != STL_CLICK_FUNC) { 1271 break; 1272 } 1273 stl_items[curitem].type = ClickFunc; 1274 stl_items[curitem].start = out_p; 1275 stl_items[curitem].cmd = tabtab ? xmemdupz(t, (size_t)(fmt_p - t)) : NULL; 1276 stl_items[curitem].minwid = minwid; 1277 fmt_p++; 1278 curitem++; 1279 continue; 1280 } 1281 1282 // Denotes the end of the minwid 1283 // the maxwid may follow immediately after 1284 if (*fmt_p == '.') { 1285 fmt_p++; 1286 if (ascii_isdigit(*fmt_p)) { 1287 maxwid = getdigits_int(&fmt_p, false, 50); 1288 } 1289 } 1290 1291 // Bound the minimum width at 50. 1292 // Make the number negative to denote left alignment of the item 1293 minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); 1294 1295 // Denotes the start of a new group 1296 if (*fmt_p == '(') { 1297 stl_groupitems[groupdepth++] = curitem; 1298 stl_items[curitem].type = Group; 1299 stl_items[curitem].start = out_p; 1300 stl_items[curitem].minwid = minwid; 1301 stl_items[curitem].maxwid = maxwid; 1302 fmt_p++; 1303 curitem++; 1304 continue; 1305 } 1306 1307 // Denotes end of expanded %{} block 1308 if (*fmt_p == '}' && evaldepth > 0) { 1309 fmt_p++; 1310 evaldepth--; 1311 continue; 1312 } 1313 1314 // An invalid item was specified. 1315 // Continue processing on the next character of the format string. 1316 if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) { 1317 if (*fmt_p == NUL) { // can happen with "%0" 1318 break; 1319 } 1320 fmt_p++; 1321 continue; 1322 } 1323 1324 // The status line item type 1325 char opt = *fmt_p++; 1326 1327 // OK - now for the real work 1328 NumberBase base = kNumBaseDecimal; 1329 bool itemisflag = false; 1330 bool fillable = true; 1331 int num = -1; 1332 char *str = NULL; 1333 switch (opt) { 1334 case STL_FILEPATH: 1335 case STL_FULLPATH: 1336 case STL_FILENAME: { 1337 // Set fillable to false so that ' ' in the filename will not 1338 // get replaced with the fillchar 1339 fillable = false; 1340 char *name = buf_spname(wp->w_buffer); 1341 if (name != NULL) { 1342 xstrlcpy(NameBuff, name, MAXPATHL); 1343 } else { 1344 char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname 1345 : wp->w_buffer->b_fname; 1346 home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); 1347 } 1348 trans_characters(NameBuff, MAXPATHL); 1349 if (opt != STL_FILENAME) { 1350 str = NameBuff; 1351 } else { 1352 str = path_tail(NameBuff); 1353 } 1354 break; 1355 } 1356 1357 case STL_VIM_EXPR: // '{' 1358 { 1359 char *block_start = fmt_p - 1; 1360 bool reevaluate = (*fmt_p == '%'); 1361 itemisflag = true; 1362 1363 if (reevaluate) { 1364 fmt_p++; 1365 } 1366 1367 // Attempt to copy the expression to evaluate into 1368 // the output buffer as a null-terminated string. 1369 char *t = out_p; 1370 while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) 1371 && *fmt_p != NUL && out_p < out_end_p) { 1372 *out_p++ = *fmt_p++; 1373 } 1374 if (*fmt_p != '}') { // missing '}' or out of space 1375 break; 1376 } 1377 fmt_p++; 1378 if (reevaluate) { 1379 out_p[-1] = NUL; // remove the % at the end of %{% expr %} 1380 } else { 1381 *out_p = NUL; 1382 } 1383 1384 // Move our position in the output buffer 1385 // to the beginning of the expression 1386 out_p = t; 1387 1388 // { Evaluate the expression 1389 1390 // Store the current buffer number as a string variable 1391 vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); 1392 set_internal_string_var("g:actual_curbuf", buf_tmp); 1393 vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curwin->handle); 1394 set_internal_string_var("g:actual_curwin", buf_tmp); 1395 1396 buf_T *const save_curbuf = curbuf; 1397 win_T *const save_curwin = curwin; 1398 const int save_VIsual_active = VIsual_active; 1399 curwin = wp; 1400 curbuf = wp->w_buffer; 1401 // Visual mode is only valid in the current window. 1402 if (curwin != save_curwin) { 1403 VIsual_active = false; 1404 } 1405 1406 // Note: The result stored in `t` is unused. 1407 str = eval_to_string_safe(out_p, use_sandbox, false); 1408 1409 curwin = save_curwin; 1410 curbuf = save_curbuf; 1411 VIsual_active = save_VIsual_active; 1412 1413 // Remove the variable we just stored 1414 do_unlet(S_LEN("g:actual_curbuf"), true); 1415 do_unlet(S_LEN("g:actual_curwin"), true); 1416 1417 // } 1418 1419 // Check if the evaluated result is a number. 1420 // If so, convert the number to an int and free the string. 1421 if (str != NULL && *str != NUL) { 1422 if (*skipdigits(str) == NUL) { 1423 num = atoi(str); 1424 XFREE_CLEAR(str); 1425 itemisflag = false; 1426 } 1427 } 1428 1429 // If the output of the expression needs to be evaluated 1430 // replace the %{} block with the result of evaluation 1431 if (reevaluate && str != NULL && *str != NUL 1432 && strchr(str, '%') != NULL 1433 && evaldepth < MAX_STL_EVAL_DEPTH) { 1434 size_t parsed_usefmt = (size_t)(block_start - usefmt); 1435 size_t str_length = strlen(str); 1436 size_t fmt_length = strlen(fmt_p); 1437 size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; 1438 char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); 1439 char *new_fmt_p = new_fmt; 1440 1441 new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; 1442 new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; 1443 new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; 1444 new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; 1445 *new_fmt_p = 0; 1446 new_fmt_p = NULL; 1447 1448 if (usefmt != fmt) { 1449 xfree(usefmt); 1450 } 1451 XFREE_CLEAR(str); 1452 usefmt = new_fmt; 1453 fmt_p = usefmt + parsed_usefmt; 1454 evaldepth++; 1455 continue; 1456 } 1457 break; 1458 } 1459 1460 case STL_LINE: 1461 // Overload %l with v:(re)lnum for 'statuscolumn'. Place a sign when 'signcolumn' 1462 // is set to "number". Take care of alignment for 'number' + 'relativenumber'. 1463 if (stcp != NULL && (wp->w_p_nu || wp->w_p_rnu) && get_vim_var_nr(VV_VIRTNUM) == 0) { 1464 if (wp->w_maxscwidth == SCL_NUM && stcp->sattrs[0].text[0]) { 1465 goto stcsign; 1466 } 1467 int relnum = (int)get_vim_var_nr(VV_RELNUM); 1468 num = (!wp->w_p_rnu || (wp->w_p_nu && relnum == 0)) ? (int)get_vim_var_nr(VV_LNUM) : relnum; 1469 left_align_num = wp->w_p_rnu && wp->w_p_nu && relnum == 0; 1470 if (!left_align_num) { 1471 stl_items[curitem].type = Separate; 1472 stl_items[curitem++].start = out_p; 1473 } 1474 } else if (stcp == NULL) { 1475 num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0 : wp->w_cursor.lnum; 1476 } 1477 break; 1478 1479 case STL_NUMLINES: 1480 num = wp->w_buffer->b_ml.ml_line_count; 1481 break; 1482 1483 case STL_COLUMN: 1484 num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; 1485 break; 1486 1487 case STL_VIRTCOL: 1488 case STL_VIRTCOL_ALT: { 1489 colnr_T virtcol = wp->w_virtcol + 1; 1490 // Don't display %V if it's the same as %c. 1491 if (opt == STL_VIRTCOL_ALT 1492 && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line 1493 ? 0 : (int)wp->w_cursor.col + 1))) { 1494 break; 1495 } 1496 num = virtcol; 1497 break; 1498 } 1499 1500 case STL_PERCENTAGE: 1501 num = calc_percentage(wp->w_cursor.lnum, wp->w_buffer->b_ml.ml_line_count); 1502 break; 1503 1504 case STL_ALTPERCENT: 1505 // Store the position percentage in our temporary buffer. 1506 // Note: We cannot store the value in `num` because 1507 // `get_rel_pos` can return a named position. Ex: "Top" 1508 (void)get_rel_pos(wp, buf_tmp, TMPLEN); 1509 str = buf_tmp; 1510 break; 1511 1512 case STL_SHOWCMD: 1513 if (p_sc && (opt_idx == kOptInvalid || find_option(p_sloc) == opt_idx)) { 1514 str = showcmd_buf; 1515 } 1516 break; 1517 1518 case STL_ARGLISTSTAT: 1519 fillable = false; 1520 buf_tmp[0] = NUL; 1521 if (append_arg_number(wp, buf_tmp, sizeof(buf_tmp)) > 0) { 1522 str = buf_tmp; 1523 } 1524 break; 1525 1526 case STL_KEYMAP: 1527 fillable = false; 1528 if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN) > 0) { 1529 str = buf_tmp; 1530 } 1531 break; 1532 case STL_PAGENUM: 1533 num = 0; 1534 break; 1535 1536 case STL_BUFNO: 1537 num = wp->w_buffer->b_fnum; 1538 break; 1539 1540 case STL_OFFSET_X: 1541 base = kNumBaseHexadecimal; 1542 FALLTHROUGH; 1543 case STL_OFFSET: { 1544 int l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, 1545 false); 1546 num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 1547 ? 0 : l + 1 + ((State & MODE_INSERT) == 0 && empty_line 1548 ? 0 : (int)wp->w_cursor.col); 1549 break; 1550 } 1551 case STL_BYTEVAL_X: 1552 base = kNumBaseHexadecimal; 1553 FALLTHROUGH; 1554 case STL_BYTEVAL: 1555 num = byteval; 1556 if (num == NL) { 1557 num = 0; 1558 } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { 1559 num = NL; 1560 } 1561 break; 1562 1563 case STL_ROFLAG: 1564 case STL_ROFLAG_ALT: 1565 itemisflag = true; 1566 if (wp->w_buffer->b_p_ro) { 1567 str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); 1568 } 1569 break; 1570 1571 case STL_HELPFLAG: 1572 case STL_HELPFLAG_ALT: 1573 itemisflag = true; 1574 if (wp->w_buffer->b_help) { 1575 str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); 1576 } 1577 break; 1578 1579 case STL_FOLDCOL: // 'C' for 'statuscolumn' 1580 case STL_SIGNCOL: { // 's' for 'statuscolumn' 1581 stcsign: 1582 if (stcp == NULL) { 1583 break; 1584 } 1585 int fdc = opt == STL_FOLDCOL ? compute_foldcolumn(wp, 0) : 0; 1586 int width = opt == STL_FOLDCOL ? fdc > 0 : opt == STL_SIGNCOL ? wp->w_scwidth : 1; 1587 1588 if (width <= 0) { 1589 break; 1590 } 1591 foldsignitem = curitem; 1592 lnum = (linenr_T)get_vim_var_nr(VV_LNUM); 1593 1594 if (fdc > 0) { 1595 schar_T fold_buf[9]; 1596 fill_foldcolumn(wp, stcp->foldinfo, lnum, 0, fdc, NULL, stcp->fold_vcol, fold_buf); 1597 stl_items[curitem].minwid = -(use_cursor_line_highlight(wp, lnum) ? HLF_CLF : HLF_FC); 1598 size_t buflen = 0; 1599 // TODO(bfredl): this is very backwards. we must support schar_T 1600 // being used directly in 'statuscolumn' 1601 for (int i = 0; i < fdc; i++) { 1602 buflen += schar_get(buf_tmp + buflen, fold_buf[i]); 1603 } 1604 } 1605 1606 size_t signlen = 0; 1607 for (int i = 0; i < width; i++) { 1608 stl_items[curitem].start = out_p + signlen; 1609 if (fdc == 0) { 1610 SignTextAttrs sattr = stcp->sattrs[i]; 1611 if (sattr.text[0] && get_vim_var_nr(VV_VIRTNUM) == 0) { 1612 signlen += describe_sign_text(buf_tmp + signlen, sattr.text); 1613 stl_items[curitem].minwid = -(stcp->sign_cul_id ? stcp->sign_cul_id : sattr.hl_id); 1614 } else { 1615 buf_tmp[signlen++] = ' '; 1616 buf_tmp[signlen++] = ' '; 1617 buf_tmp[signlen] = NUL; 1618 stl_items[curitem].minwid = 0; 1619 } 1620 } 1621 stl_items[curitem++].type = fdc > 0 ? HighlightFold : HighlightSign; 1622 } 1623 str = buf_tmp; 1624 break; 1625 } 1626 1627 case STL_FILETYPE: 1628 // Copy the filetype if it is not null and the formatted string will fit 1629 // in the temporary buffer 1630 // (including the brackets and null terminating character) 1631 if (*wp->w_buffer->b_p_ft != NUL 1632 && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 3) { 1633 vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", 1634 wp->w_buffer->b_p_ft); 1635 str = buf_tmp; 1636 } 1637 break; 1638 1639 case STL_FILETYPE_ALT: 1640 itemisflag = true; 1641 // Copy the filetype if it is not null and the formatted string will fit 1642 // in the temporary buffer 1643 // (including the comma and null terminating character) 1644 if (*wp->w_buffer->b_p_ft != NUL 1645 && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 2) { 1646 vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); 1647 // Uppercase the file extension 1648 for (char *t = buf_tmp; *t != 0; t++) { 1649 *t = (char)TOUPPER_LOC((uint8_t)(*t)); 1650 } 1651 str = buf_tmp; 1652 } 1653 break; 1654 case STL_PREVIEWFLAG: 1655 case STL_PREVIEWFLAG_ALT: 1656 itemisflag = true; 1657 if (wp->w_p_pvw) { 1658 str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); 1659 } 1660 break; 1661 1662 case STL_QUICKFIX: 1663 if (bt_quickfix(wp->w_buffer)) { 1664 str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); 1665 } 1666 break; 1667 1668 case STL_MODIFIED: 1669 case STL_MODIFIED_ALT: 1670 itemisflag = true; 1671 switch ((opt == STL_MODIFIED_ALT) 1672 + bufIsChanged(wp->w_buffer) * 2 1673 + (!MODIFIABLE(wp->w_buffer)) * 4) { 1674 case 2: 1675 str = "[+]"; break; 1676 case 3: 1677 str = ",+"; break; 1678 case 4: 1679 str = "[-]"; break; 1680 case 5: 1681 str = ",-"; break; 1682 case 6: 1683 str = "[+-]"; break; 1684 case 7: 1685 str = ",+-"; break; 1686 } 1687 break; 1688 1689 case STL_HIGHLIGHT_COMB: 1690 case STL_HIGHLIGHT: { 1691 // { The name of the highlight is surrounded by `#` or `$` 1692 char *t = fmt_p; 1693 while (*fmt_p != opt && *fmt_p != NUL) { 1694 fmt_p++; 1695 } 1696 // } 1697 1698 // Create a highlight item based on the name 1699 if (*fmt_p == opt) { 1700 stl_items[curitem].type = opt == STL_HIGHLIGHT_COMB ? HighlightCombining : Highlight; 1701 stl_items[curitem].start = out_p; 1702 stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); 1703 curitem++; 1704 fmt_p++; 1705 } 1706 continue; 1707 } 1708 } 1709 1710 // If we made it this far, the item is normal and starts at 1711 // our current position in the output buffer. 1712 // Non-normal items would have `continued`. 1713 stl_items[curitem].start = out_p; 1714 stl_items[curitem].type = Normal; 1715 1716 // Copy the item string into the output buffer 1717 if (str != NULL && *str) { 1718 // { Skip the leading `,` or ` ` if the item is a flag 1719 // and the proper conditions are met 1720 char *t = str; 1721 if (itemisflag) { 1722 if ((t[0] && t[1]) 1723 && ((!prevchar_isitem && *t == ',') 1724 || (prevchar_isflag && *t == ' '))) { 1725 t++; 1726 } 1727 prevchar_isflag = true; 1728 } 1729 // } 1730 1731 int l = vim_strsize(t); 1732 1733 // If this item is non-empty, record that the last thing 1734 // we put in the output buffer was an item 1735 if (l > 0) { 1736 prevchar_isitem = true; 1737 } 1738 1739 // If the item is too wide, truncate it from the beginning 1740 if (l > maxwid) { 1741 while (l >= maxwid) { 1742 l -= ptr2cells(t); 1743 t += utfc_ptr2len(t); 1744 } 1745 1746 // Early out if there isn't enough room for the truncation marker 1747 if (out_p >= out_end_p) { 1748 break; 1749 } 1750 1751 // Add the truncation marker 1752 *out_p++ = '<'; 1753 } 1754 1755 // If the item is right aligned and not wide enough, 1756 // pad with fill characters. 1757 if (minwid > 0) { 1758 for (; l < minwid && out_p < out_end_p; l++) { 1759 // Don't put a "-" in front of a digit. 1760 if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { 1761 *out_p++ = ' '; 1762 } else { 1763 schar_get_adv(&out_p, fillchar); 1764 } 1765 } 1766 minwid = 0; 1767 // For a 'statuscolumn' sign or fold item, shift the added items 1768 if (foldsignitem >= 0) { 1769 ptrdiff_t offset = out_p - stl_items[foldsignitem].start; 1770 for (int i = foldsignitem; i < curitem; i++) { 1771 stl_items[i].start += offset; 1772 } 1773 } 1774 } else { 1775 // Note: The negative value denotes a left aligned item. 1776 // Here we switch the minimum width back to a positive value. 1777 minwid *= -1; 1778 } 1779 1780 // { Copy the string text into the output buffer 1781 for (; *t && out_p < out_end_p; t++) { 1782 // Change a space by fillchar, unless fillchar is '-' and a 1783 // digit follows. 1784 if (fillable && *t == ' ' 1785 && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { 1786 schar_get_adv(&out_p, fillchar); 1787 } else { 1788 *out_p++ = *t; 1789 } 1790 } 1791 // } 1792 1793 // For a 'statuscolumn' sign or fold item, add an item to reset the highlight group 1794 if (foldsignitem >= 0) { 1795 stl_items[curitem].type = Highlight; 1796 stl_items[curitem].start = out_p; 1797 stl_items[curitem].minwid = 0; 1798 } 1799 1800 // For left-aligned items, fill any remaining space with the fillchar 1801 for (; l < minwid && out_p < out_end_p; l++) { 1802 schar_get_adv(&out_p, fillchar); 1803 } 1804 1805 // Otherwise if the item is a number, copy that to the output buffer. 1806 } else if (num >= 0) { 1807 if (out_p + 20 > out_end_p) { 1808 break; // not sufficient space 1809 } 1810 prevchar_isitem = true; 1811 1812 // { Build the formatting string 1813 char nstr[20]; 1814 char *t = nstr; 1815 if (opt == STL_VIRTCOL_ALT) { 1816 *t++ = '-'; 1817 minwid--; 1818 } 1819 *t++ = '%'; 1820 if (zeropad) { 1821 *t++ = '0'; 1822 } 1823 1824 // Note: The `*` means we take the width as one of the arguments 1825 *t++ = '*'; 1826 *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; 1827 *t = NUL; 1828 // } 1829 1830 // { Determine how many characters the number will take up when printed 1831 // Note: We have to cast the base because the compiler uses 1832 // unsigned ints for the enum values. 1833 int num_chars = 1; 1834 for (int n = num; n >= (int)base; n /= (int)base) { 1835 num_chars++; 1836 } 1837 1838 // VIRTCOL_ALT takes up an extra character because 1839 // of the `-` we added above. 1840 if (opt == STL_VIRTCOL_ALT) { 1841 num_chars++; 1842 } 1843 // } 1844 1845 assert(out_end_p >= out_p); 1846 size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; 1847 1848 // If the number is going to take up too much room 1849 // Figure out the approximate number in "scientific" type notation. 1850 // Ex: 14532 with maxwid of 4 -> '14>3' 1851 if (num_chars > maxwid) { 1852 // Add two to the width because the power piece will take 1853 // two extra characters 1854 num_chars += 2; 1855 1856 // How many extra characters there are 1857 int n = num_chars - maxwid; 1858 1859 // { Reduce the number by base^n 1860 while (num_chars-- > maxwid) { 1861 num /= (int)base; 1862 } 1863 // } 1864 1865 // { Add the format string for the exponent bit 1866 *t++ = '>'; 1867 *t++ = '%'; 1868 // Use the same base as the first number 1869 *t = t[-3]; 1870 *++t = NUL; 1871 // } 1872 1873 out_p += vim_snprintf_safelen(out_p, remaining_buf_len, nstr, 0, num, n); 1874 } else { 1875 out_p += vim_snprintf_safelen(out_p, remaining_buf_len, nstr, minwid, num); 1876 } 1877 1878 // Otherwise, there was nothing to print so mark the item as empty 1879 } else { 1880 stl_items[curitem].type = Empty; 1881 } 1882 1883 if (num >= 0 || (!itemisflag && str && *str)) { 1884 prevchar_isflag = false; // Item not NULL, but not a flag 1885 } 1886 1887 // Only free the string buffer if we allocated it. 1888 // Note: This is not needed if `str` is pointing at `tmp` 1889 if (opt == STL_VIM_EXPR) { 1890 XFREE_CLEAR(str); 1891 } 1892 1893 // Item processed, move to the next 1894 curitem++; 1895 // For a 'statuscolumn' number item that is left aligned, add a separator item. 1896 if (left_align_num) { 1897 stl_items[curitem].type = Separate; 1898 stl_items[curitem++].start = out_p; 1899 } 1900 } 1901 1902 *out_p = NUL; 1903 // Length of out[] used (excluding the NUL) 1904 size_t outputlen = (size_t)(out_p - out); 1905 // Subtract offset from `itemcnt` and restore `curitem` to previous recursion level. 1906 int itemcnt = curitem - evalstart; 1907 curitem = evalstart; 1908 1909 // Free the format buffer if we allocated it internally 1910 if (usefmt != fmt) { 1911 xfree(usefmt); 1912 } 1913 1914 // We have now processed the entire statusline format string. 1915 // What follows is post-processing to handle alignment and highlighting. 1916 1917 int width = vim_strsize(out); 1918 if (maxwidth > 0 && width > maxwidth && (!stcp || width > MAX_STCWIDTH)) { 1919 // Result is too long, must truncate somewhere. 1920 int item_idx = evalstart; 1921 char *trunc_p; 1922 1923 // If there are no items, truncate from beginning 1924 if (itemcnt == 0) { 1925 trunc_p = out; 1926 1927 // Otherwise, look for the truncation item 1928 } else { 1929 // Default to truncating at the first item 1930 trunc_p = stl_items[item_idx].start; 1931 1932 for (int i = evalstart; i < itemcnt + evalstart; i++) { 1933 if (stl_items[i].type == Trunc) { 1934 // Truncate at %< stl_items. 1935 trunc_p = stl_items[i].start; 1936 item_idx = i; 1937 break; 1938 } 1939 } 1940 } 1941 1942 // If the truncation point we found is beyond the maximum 1943 // length of the string, truncate the end of the string. 1944 if (width - vim_strsize(trunc_p) >= maxwidth) { 1945 // Walk from the beginning of the 1946 // string to find the last character that will fit. 1947 trunc_p = out; 1948 width = 0; 1949 while (true) { 1950 width += ptr2cells(trunc_p); 1951 if (width >= maxwidth) { 1952 break; 1953 } 1954 1955 // Note: Only advance the pointer if the next 1956 // character will fit in the available output space 1957 trunc_p += utfc_ptr2len(trunc_p); 1958 } 1959 1960 // Ignore any items in the statusline that occur after 1961 // the truncation point 1962 for (int i = evalstart; i < itemcnt + evalstart; i++) { 1963 if (stl_items[i].start > trunc_p) { 1964 for (int j = i; j < itemcnt + evalstart; j++) { 1965 if (stl_items[j].type == ClickFunc) { 1966 XFREE_CLEAR(stl_items[j].cmd); 1967 } 1968 } 1969 itemcnt = i; 1970 break; 1971 } 1972 } 1973 1974 // Truncate the output 1975 *trunc_p++ = '>'; 1976 *trunc_p = NUL; 1977 1978 // Truncate at the truncation point we found 1979 } else { 1980 char *end = out + outputlen; 1981 1982 // { Determine how many bytes to remove 1983 int trunc_len = 0; 1984 while (width >= maxwidth) { 1985 width -= ptr2cells(trunc_p + trunc_len); 1986 trunc_len += utfc_ptr2len(trunc_p + trunc_len); 1987 } 1988 // } 1989 1990 // { Truncate the string 1991 char *trunc_end_p = trunc_p + trunc_len; 1992 memmove(trunc_p + 1, trunc_end_p, (size_t)(end - trunc_end_p) + 1); // +1 for NUL 1993 end -= (size_t)(trunc_end_p - (trunc_p + 1)); 1994 1995 // Put a `<` to mark where we truncated at 1996 *trunc_p = '<'; 1997 // } 1998 1999 // { Change the start point for items based on 2000 // their position relative to our truncation point 2001 2002 // Note: The offset is one less than the truncation length because 2003 // the truncation marker `<` is not counted. 2004 int item_offset = trunc_len - 1; 2005 2006 for (int i = item_idx; i < itemcnt + evalstart; i++) { 2007 // Items starting at or after the end of the truncated section need 2008 // to be moved backwards. 2009 if (stl_items[i].start >= trunc_end_p) { 2010 stl_items[i].start -= item_offset; 2011 } else { 2012 // Anything inside the truncated area is set to start 2013 // at the `<` truncation character. 2014 stl_items[i].start = trunc_p; 2015 } 2016 } 2017 // } 2018 2019 if (width + 1 < maxwidth) { 2020 // Advance the pointer to the end of the string 2021 trunc_p = end; 2022 } 2023 2024 // Fill up for half a double-wide character. 2025 while (++width < maxwidth) { 2026 schar_get_adv(&trunc_p, fillchar); 2027 end = trunc_p; 2028 } 2029 (void)end; 2030 } 2031 width = maxwidth; 2032 2033 // If there is room left in our statusline, and room left in our buffer, 2034 // add characters at the separate marker (if there is one) to 2035 // fill up the available space. 2036 } else if (width < maxwidth 2037 && outputlen + (size_t)(maxwidth - width) * schar_len(fillchar) + 1 < outlen) { 2038 // Find how many separators there are, which we will use when 2039 // figuring out how many groups there are. 2040 int num_separators = 0; 2041 for (int i = evalstart; i < itemcnt + evalstart; i++) { 2042 if (stl_items[i].type == Separate) { 2043 // Create an array of the start location for each separator mark. 2044 stl_separator_locations[num_separators] = i; 2045 num_separators++; 2046 } 2047 } 2048 2049 // If we have separated groups, then we deal with it now 2050 if (num_separators) { 2051 int standard_spaces = (maxwidth - width) / num_separators; 2052 int final_spaces = (maxwidth - width) - 2053 standard_spaces * (num_separators - 1); 2054 2055 for (int l = 0; l < num_separators; l++) { 2056 int dislocation = (l == (num_separators - 1)) ? final_spaces : standard_spaces; 2057 dislocation *= (int)schar_len(fillchar); 2058 char *start = stl_items[stl_separator_locations[l]].start; 2059 char *seploc = start + dislocation; 2060 STRMOVE(seploc, start); 2061 for (char *s = start; s < seploc;) { 2062 schar_get_adv(&s, fillchar); 2063 } 2064 2065 for (int item_idx = stl_separator_locations[l] + 1; 2066 item_idx < itemcnt + evalstart; 2067 item_idx++) { 2068 stl_items[item_idx].start += dislocation; 2069 } 2070 } 2071 2072 width = maxwidth; 2073 } 2074 } 2075 2076 // Store the info about highlighting. 2077 if (hltab != NULL) { 2078 *hltab = stl_hltab; 2079 stl_hlrec_t *sp = stl_hltab; 2080 for (int l = evalstart; l < itemcnt + evalstart; l++) { 2081 if (stl_items[l].type == Highlight || stl_items[l].type == HighlightCombining 2082 || stl_items[l].type == HighlightFold || stl_items[l].type == HighlightSign) { 2083 sp->start = stl_items[l].start; 2084 sp->userhl = stl_items[l].minwid; 2085 unsigned type = stl_items[l].type; 2086 sp->item = type == HighlightSign ? STL_SIGNCOL : type == 2087 HighlightFold ? STL_FOLDCOL : type == 2088 HighlightCombining ? STL_HIGHLIGHT_COMB : 0; 2089 sp++; 2090 } 2091 } 2092 sp->start = NULL; 2093 sp->userhl = 0; 2094 } 2095 if (hltab_len) { 2096 *hltab_len = (size_t)itemcnt; 2097 } 2098 2099 // Store the info about tab pages labels. 2100 if (tabtab != NULL) { 2101 *tabtab = stl_tabtab; 2102 StlClickRecord *cur_tab_rec = stl_tabtab; 2103 for (int l = evalstart; l < itemcnt + evalstart; l++) { 2104 if (stl_items[l].type == TabPage) { 2105 cur_tab_rec->start = stl_items[l].start; 2106 if (stl_items[l].minwid == 0) { 2107 cur_tab_rec->def.type = kStlClickDisabled; 2108 cur_tab_rec->def.tabnr = 0; 2109 } else { 2110 int tabnr = stl_items[l].minwid; 2111 if (stl_items[l].minwid > 0) { 2112 cur_tab_rec->def.type = kStlClickTabSwitch; 2113 } else { 2114 cur_tab_rec->def.type = kStlClickTabClose; 2115 tabnr = -tabnr; 2116 } 2117 cur_tab_rec->def.tabnr = tabnr; 2118 } 2119 cur_tab_rec->def.func = NULL; 2120 cur_tab_rec++; 2121 } else if (stl_items[l].type == ClickFunc) { 2122 cur_tab_rec->start = stl_items[l].start; 2123 cur_tab_rec->def.type = kStlClickFuncRun; 2124 cur_tab_rec->def.tabnr = stl_items[l].minwid; 2125 cur_tab_rec->def.func = stl_items[l].cmd; 2126 cur_tab_rec++; 2127 } 2128 } 2129 cur_tab_rec->start = NULL; 2130 cur_tab_rec->def.type = kStlClickDisabled; 2131 cur_tab_rec->def.tabnr = 0; 2132 cur_tab_rec->def.func = NULL; 2133 } 2134 2135 redraw_not_allowed = save_redraw_not_allowed; 2136 2137 // Check for an error. If there is one the display will be messed up and 2138 // might loop redrawing. Avoid that by setting the option to its default. 2139 // TODO(Bram): find out why using called_emsg_before makes tests fail, does it 2140 // matter? 2141 // if (called_emsg > called_emsg_before) 2142 if (opt_idx != kOptInvalid && did_emsg > did_emsg_before) { 2143 set_option_direct(opt_idx, get_option_default(opt_idx, opt_scope), opt_scope, SID_ERROR); 2144 } 2145 2146 // A user function may reset KeyTyped, restore it. 2147 KeyTyped = save_KeyTyped; 2148 2149 return width; 2150 }