neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }