// This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // #include #include #include #include #include "nvim/assert.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/vars.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/statusline.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" // Determines how deeply nested %{} blocks will be evaluated in statusline. #define MAX_STL_EVAL_DEPTH 100 /// Enumeration specifying the valid numeric bases that can /// be used when printing numbers in the status line. typedef enum { kNumBaseDecimal = 10, kNumBaseHexadecimal = 16, } NumberBase; /// Redraw the status line of window `wp`. /// /// If inversion is possible we use it. Else '=' characters are used. void win_redr_status(win_T *wp) { int row; int col; char_u *p; int len; int fillchar; int attr; int width; int this_ru_col; bool is_stl_global = global_stl_height() > 0; static bool busy = false; // May get here recursively when 'statusline' (indirectly) // invokes ":redrawstatus". Simply ignore the call then. if (busy // Also ignore if wildmenu is showing. || (wild_menu_showing != 0 && !ui_has(kUIWildmenu))) { return; } busy = true; wp->w_redr_status = false; if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { // no status line, either global statusline is enabled or the window is a last window redraw_cmdline = true; } else if (!redrawing()) { // Don't redraw right now, do it later. Don't update status line when // popup menu is visible and may be drawn over it wp->w_redr_status = true; } else if (*p_stl != NUL || *wp->w_p_stl != NUL) { // redraw custom status line redraw_custom_statusline(wp); } else { fillchar = fillchar_status(&attr, wp); width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); p = (char_u *)NameBuff; len = (int)STRLEN(p); if ((bt_help(wp->w_buffer) || wp->w_p_pvw || bufIsChanged(wp->w_buffer) || wp->w_buffer->b_p_ro) && len < MAXPATHL - 1) { *(p + len++) = ' '; } if (bt_help(wp->w_buffer)) { snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); len += (int)STRLEN(p + len); } if (wp->w_p_pvw) { snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); len += (int)STRLEN(p + len); } if (bufIsChanged(wp->w_buffer)) { snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); len += (int)STRLEN(p + len); } if (wp->w_buffer->b_p_ro) { snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); // len += (int)strlen(p + len); // dead assignment } this_ru_col = ru_col - (Columns - width); if (this_ru_col < (width + 1) / 2) { this_ru_col = (width + 1) / 2; } if (this_ru_col <= 1) { p = (char_u *)"<"; // No room for file name! len = 1; } else { int clen = 0, i; // Count total number of display cells. clen = (int)mb_string2cells((char *)p); // Find first character that will fit. // Going from start to end is much faster for DBCS. for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; i += utfc_ptr2len((char *)p + i)) { clen -= utf_ptr2cells((char *)p + i); } len = clen; if (i > 0) { p = p + i - 1; *p = '<'; len++; } } row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); col = is_stl_global ? 0 : wp->w_wincol; grid_puts(&default_grid, (char *)p, row, col, attr); grid_fill(&default_grid, row, row + 1, len + col, this_ru_col + col, fillchar, fillchar, attr); if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL) && this_ru_col - len > (int)(strlen(NameBuff) + 1)) { grid_puts(&default_grid, NameBuff, row, (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } win_redr_ruler(wp, true); } // May need to draw the character below the vertical separator. if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) { if (stl_connected(wp)) { fillchar = fillchar_status(&attr, wp); } else { fillchar = fillchar_vsep(wp, &attr); } grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); } busy = false; } void win_redr_winbar(win_T *wp) { static bool entered = false; // Return when called recursively. This can happen when the winbar contains an expression // that triggers a redraw. if (entered) { return; } entered = true; if (wp->w_winbar_height == 0 || !redrawing()) { // Do nothing. } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { int saved_did_emsg = did_emsg; did_emsg = false; win_redr_custom(wp, true, false); if (did_emsg) { // When there is an error disable the winbar, otherwise the // display is messed up with errors and a redraw triggers the problem // again and again. set_string_option_direct("winbar", -1, "", OPT_FREE | (*wp->w_p_stl != NUL ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); } did_emsg |= saved_did_emsg; } entered = false; } void win_redr_ruler(win_T *wp, bool always) { bool is_stl_global = global_stl_height() > 0; static bool did_show_ext_ruler = false; // If 'ruler' off, don't do anything if (!p_ru) { return; } // Check if cursor.lnum is valid, since win_redr_ruler() may be called // after deleting lines, before cursor.lnum is corrected. if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { return; } // Don't draw the ruler while doing insert-completion, it might overwrite // the (long) mode message. if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { if (edit_submode != NULL) { return; } } if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { const int called_emsg_before = called_emsg; win_redr_custom(wp, false, true); if (called_emsg > called_emsg_before) { set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); } return; } // Check if not in Insert mode and the line is empty (will show "0-1"). int empty_line = false; if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) { empty_line = true; } // Only draw the ruler when something changed. validate_virtcol_win(wp); if (redraw_cmdline || always || wp->w_cursor.lnum != wp->w_ru_cursor.lnum || wp->w_cursor.col != wp->w_ru_cursor.col || wp->w_virtcol != wp->w_ru_virtcol || wp->w_cursor.coladd != wp->w_ru_cursor.coladd || wp->w_topline != wp->w_ru_topline || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count || wp->w_topfill != wp->w_ru_topfill || empty_line != wp->w_ru_empty) { int width; int row; int fillchar; int attr; int off; bool part_of_status = false; if (wp->w_status_height) { row = W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); off = wp->w_wincol; width = wp->w_width; part_of_status = true; } else if (is_stl_global) { row = Rows - (int)p_ch - 1; fillchar = fillchar_status(&attr, wp); off = 0; width = Columns; part_of_status = true; } else { row = Rows - 1; fillchar = ' '; attr = HL_ATTR(HLF_MSG); width = Columns; off = 0; } if (!part_of_status && p_ch == 0 && !ui_has(kUIMessages)) { return; } // In list mode virtcol needs to be recomputed colnr_T virtcol = wp->w_virtcol; if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { wp->w_p_list = false; getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); wp->w_p_list = true; } #define RULER_BUF_LEN 70 char buffer[RULER_BUF_LEN]; // Some sprintfs return the length, some return a pointer. // To avoid portability problems we use strlen() here. vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum); size_t len = strlen(buffer); col_print(buffer + len, RULER_BUF_LEN - len, empty_line ? 0 : (int)wp->w_cursor.col + 1, (int)virtcol + 1); // Add a "50%" if there is room for it. // On the last line, don't print in the last column (scrolls the // screen up on some terminals). int i = (int)strlen(buffer); get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); int o = i + vim_strsize(buffer + i + 1); if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen o++; } int this_ru_col = ru_col - (Columns - width); if (this_ru_col < 0) { this_ru_col = 0; } // Never use more than half the window/screen width, leave the other half // for the filename. if (this_ru_col < (width + 1) / 2) { this_ru_col = (width + 1) / 2; } if (this_ru_col + o < width) { // Need at least 3 chars left for get_rel_pos() + NUL. while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) { i += utf_char2bytes(fillchar, buffer + i); o++; } get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); } if (ui_has(kUIMessages) && !part_of_status) { MAXSIZE_TEMP_ARRAY(content, 1); MAXSIZE_TEMP_ARRAY(chunk, 2); ADD_C(chunk, INTEGER_OBJ(attr)); ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer))); ADD_C(content, ARRAY_OBJ(chunk)); ui_call_msg_ruler(content); did_show_ext_ruler = true; } else { if (did_show_ext_ruler) { ui_call_msg_ruler((Array)ARRAY_DICT_INIT); did_show_ext_ruler = false; } // Truncate at window boundary. o = 0; for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) { o += utf_ptr2cells(buffer + i); if (this_ru_col + o > width) { buffer[i] = NUL; break; } } ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; grid_puts(grid, buffer, row, this_ru_col + off, attr); grid_fill(grid, row, row + 1, this_ru_col + off + (int)strlen(buffer), off + width, fillchar, fillchar, attr); } wp->w_ru_cursor = wp->w_cursor; wp->w_ru_virtcol = wp->w_virtcol; wp->w_ru_empty = (char)empty_line; wp->w_ru_topline = wp->w_topline; wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; wp->w_ru_topfill = wp->w_topfill; } } /// Get the character to use in a status line. Get its attributes in "*attr". int fillchar_status(int *attr, win_T *wp) { int fill; bool is_curwin = (wp == curwin); if (is_curwin) { *attr = win_hl_attr(wp, HLF_S); fill = wp->w_p_fcs_chars.stl; } else { *attr = win_hl_attr(wp, HLF_SNC); fill = wp->w_p_fcs_chars.stlnc; } // Use fill when there is highlighting, and highlighting of current // window differs, or the fillchars differ, or this is not the // current window if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) || !is_curwin || ONE_WINDOW) || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) { return fill; } if (is_curwin) { return '^'; } return '='; } /// Redraw the status line according to 'statusline' and take care of any /// errors encountered. void redraw_custom_statusline(win_T *wp) { static bool entered = false; int saved_did_emsg = did_emsg; // When called recursively return. This can happen when the statusline // contains an expression that triggers a redraw. if (entered) { return; } entered = true; did_emsg = false; win_redr_custom(wp, false, false); if (did_emsg) { // When there is an error disable the statusline, otherwise the // display is messed up with errors and a redraw triggers the problem // again and again. set_string_option_direct("statusline", -1, "", OPT_FREE | (*wp->w_p_stl != NUL ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); } did_emsg |= saved_did_emsg; entered = false; } /// Redraw the status line, window bar or ruler of window "wp". /// When "wp" is NULL redraw the tab pages line from 'tabline'. void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) { static bool entered = false; int attr; int curattr; int row; int col = 0; int maxwidth; int width; int n; int len; int fillchar; char buf[MAXPATHL]; char *stl; char *p; stl_hlrec_t *hltab; StlClickRecord *tabtab; int use_sandbox = false; win_T *ewp; int p_crb_save; bool is_stl_global = global_stl_height() > 0; ScreenGrid *grid = &default_grid; // There is a tiny chance that this gets called recursively: When // redrawing a status line triggers redrawing the ruler or tabline. // Avoid trouble by not allowing recursion. if (entered) { return; } entered = true; // setup environment for the task at hand if (wp == NULL) { // Use 'tabline'. Always at the first line of the screen. stl = p_tal; row = 0; fillchar = ' '; attr = HL_ATTR(HLF_TPF); maxwidth = Columns; use_sandbox = was_set_insecurely(wp, "tabline", 0); } else if (draw_winbar) { stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); row = -1; // row zero is first row of text col = 0; grid = &wp->w_grid; grid_adjust(&grid, &row, &col); if (row < 0) { return; } fillchar = wp->w_p_fcs_chars.wbr; attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); maxwidth = wp->w_width_inner; use_sandbox = was_set_insecurely(wp, "winbar", 0); stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); // Allocate / resize the click definitions array for winbar if needed. if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) { xfree(wp->w_winbar_click_defs); wp->w_winbar_click_defs_size = (size_t)maxwidth; wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord)); } } else { row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); maxwidth = is_stl_global ? Columns : wp->w_width; stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); // Allocate / resize the click definitions array for statusline if needed. if (wp->w_status_click_defs_size < (size_t)maxwidth) { xfree(wp->w_status_click_defs); wp->w_status_click_defs_size = (size_t)maxwidth; wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord)); } if (draw_ruler) { stl = p_ruf; // advance past any leading group spec - implicit in ru_col if (*stl == '%') { if (*++stl == '-') { stl++; } if (atoi(stl)) { while (ascii_isdigit(*stl)) { stl++; } } if (*stl++ != '(') { stl = p_ruf; } } col = ru_col - (Columns - maxwidth); if (col < (maxwidth + 1) / 2) { col = (maxwidth + 1) / 2; } maxwidth = maxwidth - col; if (!wp->w_status_height && !is_stl_global) { grid = &msg_grid_adj; row = Rows - 1; maxwidth--; // writing in last column may cause scrolling fillchar = ' '; attr = HL_ATTR(HLF_MSG); } use_sandbox = was_set_insecurely(wp, "rulerformat", 0); } else { if (*wp->w_p_stl != NUL) { stl = wp->w_p_stl; } else { stl = p_stl; } use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); } col += is_stl_global ? 0 : wp->w_wincol; } if (maxwidth <= 0) { goto theend; } // Temporarily reset 'cursorbind', we don't want a side effect from moving // the cursor away and back. ewp = wp == NULL ? curwin : wp; p_crb_save = ewp->w_p_crb; ewp->w_p_crb = false; // Make a copy, because the statusline may include a function call that // might change the option value and free the memory. stl = xstrdup(stl); width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox, fillchar, maxwidth, &hltab, &tabtab); xfree(stl); ewp->w_p_crb = p_crb_save; // Make all characters printable. p = transstr(buf, true); len = (int)STRLCPY(buf, p, sizeof(buf)); len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; xfree(p); // fill up with "fillchar" while (width < maxwidth && len < (int)sizeof(buf) - 1) { len += utf_char2bytes(fillchar, buf + len); width++; } buf[len] = NUL; // Draw each snippet with the specified highlighting. grid_puts_line_start(grid, row); curattr = attr; p = buf; for (n = 0; hltab[n].start != NULL; n++) { int textlen = (int)(hltab[n].start - p); grid_puts_len(grid, p, textlen, row, col, curattr); col += vim_strnsize(p, textlen); p = hltab[n].start; if (hltab[n].userhl == 0) { curattr = attr; } else if (hltab[n].userhl < 0) { curattr = syn_id2attr(-hltab[n].userhl); } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { curattr = highlight_stlnc[hltab[n].userhl - 1]; } else { curattr = highlight_user[hltab[n].userhl - 1]; } } // Make sure to use an empty string instead of p, if p is beyond buf + len. grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); grid_puts_line_flush(false); // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking // in the tab page line, status line or window bar StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs : draw_winbar ? wp->w_winbar_click_defs : wp->w_status_click_defs; if (click_defs == NULL) { goto theend; } col = 0; len = 0; p = buf; StlClickDefinition cur_click_def = { .type = kStlClickDisabled, }; for (n = 0; tabtab[n].start != NULL; n++) { len += vim_strnsize(p, (int)(tabtab[n].start - p)); while (col < len) { click_defs[col++] = cur_click_def; } p = (char *)tabtab[n].start; cur_click_def = tabtab[n].def; if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled || cur_click_def.type == kStlClickFuncRun)) { // window bar and status line only support click functions cur_click_def.type = kStlClickDisabled; } } while (col < maxwidth) { click_defs[col++] = cur_click_def; } theend: entered = false; } /// Build a string from the status line items in "fmt". /// Return length of string in screen cells. /// /// Normally works for window "wp", except when working for 'tabline' then it /// is "curwin". /// /// Items are drawn interspersed with the text that surrounds it /// Specials: %-(xxx%) => group, %= => separation marker, %< => truncation /// Item: %-. All but are optional /// /// If maxwidth is not zero, the string will be filled at any middle marker /// or truncated if too long, fillchar is used for all whitespace. /// /// @param wp The window to build a statusline for /// @param out The output buffer to write the statusline to /// Note: This should not be NameBuff /// @param outlen The length of the output buffer /// @param fmt The statusline format string /// @param use_sandbox Use a sandboxed environment when evaluating fmt /// @param fillchar Character to use when filling empty space in the statusline /// @param maxwidth The maximum width to make the statusline /// @param hltab HL attributes (can be NULL) /// @param tabtab Tab clicks definition (can be NULL). /// /// @return The final width of the statusline int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; static int *stl_groupitems = NULL; static stl_hlrec_t *stl_hltab = NULL; static StlClickRecord *stl_tabtab = NULL; static int *stl_separator_locations = NULL; #define TMPLEN 70 char buf_tmp[TMPLEN]; char win_tmp[TMPLEN]; char *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; if (stl_items == NULL) { stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); stl_groupitems = xmalloc(sizeof(int) * stl_items_len); // Allocate one more, because the last element is used to indicate the // end of the list. stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { typval_T tv = { .v_type = VAR_NUMBER, .vval.v_number = wp->handle, }; set_var(S_LEN("g:statusline_winid"), &tv, false); usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); if (usefmt == NULL) { usefmt = fmt; } do_unlet(S_LEN("g:statusline_winid"), true); } if (fillchar == 0) { fillchar = ' '; } // The cursor in windows other than the current one isn't always // up-to-date, esp. because of autocommands and timers. linenr_T lnum = wp->w_cursor.lnum; if (lnum > wp->w_buffer->b_ml.ml_line_count) { lnum = wp->w_buffer->b_ml.ml_line_count; wp->w_cursor.lnum = lnum; } // Get line & check if empty (cursorpos will show "0-1"). const char *line_ptr = ml_get_buf(wp->w_buffer, lnum, false); bool empty_line = (*line_ptr == NUL); // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; const size_t len = strlen(line_ptr); if (wp->w_cursor.col > (colnr_T)len) { // Line may have changed since checking the cursor column, or the lnum // was adjusted above. wp->w_cursor.col = (colnr_T)len; wp->w_cursor.coladd = 0; byteval = 0; } else { byteval = utf_ptr2char(line_ptr + wp->w_cursor.col); } int groupdepth = 0; int evaldepth = 0; int curitem = 0; bool prevchar_isflag = true; bool prevchar_isitem = false; // out_p is the current position in the output buffer char *out_p = out; // out_end_p is the last valid character in the output buffer // Note: The null termination character must occur here or earlier, // so any user-visible characters must occur before here. char *out_end_p = (out + outlen) - 1; // Proceed character by character through the statusline format string // fmt_p is the current position in the input buffer for (char *fmt_p = usefmt; *fmt_p != NUL;) { if (curitem == (int)stl_items_len) { size_t new_len = stl_items_len * 3 / 2; stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); stl_separator_locations = xrealloc(stl_separator_locations, sizeof(int) * new_len); stl_items_len = new_len; } if (*fmt_p != '%') { prevchar_isflag = prevchar_isitem = false; } // Copy the formatting verbatim until we reach the end of the string // or find a formatting item (denoted by `%`) // or run out of room in our output buffer. while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p) { *out_p++ = *fmt_p++; } // If we have processed the entire format string or run out of // room in our output buffer, exit the loop. if (*fmt_p == NUL || out_p >= out_end_p) { break; } // The rest of this loop will handle a single `%` item. // Note: We increment here to skip over the `%` character we are currently // on so we can process the item's contents. fmt_p++; // Ignore `%` at the end of the format string if (*fmt_p == NUL) { break; } // Two `%` in a row is the escape sequence to print a // single `%` in the output buffer. if (*fmt_p == '%') { *out_p++ = *fmt_p++; prevchar_isflag = prevchar_isitem = false; continue; } // STL_SEPARATE: Separation place between left and right aligned items. if (*fmt_p == STL_SEPARATE) { fmt_p++; // Ignored when we are inside of a grouping if (groupdepth > 0) { continue; } stl_items[curitem].type = Separate; stl_items[curitem++].start = out_p; continue; } // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. if (*fmt_p == STL_TRUNCMARK) { fmt_p++; stl_items[curitem].type = Trunc; stl_items[curitem++].start = out_p; continue; } // The end of a grouping if (*fmt_p == ')') { fmt_p++; // Ignore if we are not actually inside a group currently if (groupdepth < 1) { continue; } groupdepth--; // Determine how long the group is. // Note: We set the current output position to null // so `vim_strsize` will work. char *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; long group_len = vim_strsize(t); // If the group contained internal items // and the group did not have a minimum width, // and if there were no normal items in the group, // move the output pointer back to where the group started. // Note: This erases any non-item characters that were in the group. // Otherwise there would be no reason to do this step. if (curitem > stl_groupitems[groupdepth] + 1 && stl_items[stl_groupitems[groupdepth]].minwid == 0) { // remove group if all items are empty and highlight group // doesn't change int group_start_userhl = 0; int group_end_userhl = 0; int n; for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { if (stl_items[n].type == Highlight) { group_start_userhl = group_end_userhl = stl_items[n].minwid; break; } } for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { if (stl_items[n].type == Normal) { break; } if (stl_items[n].type == Highlight) { group_end_userhl = stl_items[n].minwid; } } if (n == curitem && group_start_userhl == group_end_userhl) { // empty group out_p = t; group_len = 0; for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { // do not use the highlighting from the removed group if (stl_items[n].type == Highlight) { stl_items[n].type = Empty; } // adjust the start position of TabPage to the next // item position if (stl_items[n].type == TabPage) { stl_items[n].start = out_p; } } } } // If the group is longer than it is allowed to be // truncate by removing bytes from the start of the group text. if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { // { Determine the number of bytes to remove // Find the first character that should be included. long n = 0; while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { group_len -= ptr2cells(t + n); n += utfc_ptr2len(t + n); } // } // Prepend the `<` to indicate that the output was truncated. *t = '<'; // { Move the truncated output memmove(t + 1, t + n, (size_t)(out_p - (t + n))); out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { MB_CHAR2BYTES(fillchar, out_p); } // } // correct the start of the items for the truncation for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { // Shift everything back by the number of removed bytes // Minus one for the leading '<' added above. stl_items[idx].start -= n - 1; // If the item was partially or completely truncated, set its // start to the start of the group if (stl_items[idx].start < t) { stl_items[idx].start = t; } } // If the group is shorter than the minimum width, add padding characters. } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; while (group_len++ < min_group_width && out_p < out_end_p) { MB_CHAR2BYTES(fillchar, out_p); } // If the group is right-aligned, shift everything to the right and // prepend with filler characters. } else { // { Move the group to the right group_len = (min_group_width - group_len) * utf_char2len(fillchar); memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { group_len = (long)(out_end_p - out_p); } out_p += group_len; // } // Adjust item start positions for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { stl_items[n].start += group_len; } // Prepend the fill characters for (; group_len > 0; group_len--) { MB_CHAR2BYTES(fillchar, t); } } } continue; } int minwid = 0; int maxwid = 9999; bool left_align = false; // Denotes that numbers should be left-padded with zeros bool zeropad = (*fmt_p == '0'); if (zeropad) { fmt_p++; } // Denotes that the item should be left-aligned. // This is tracked by using a negative length. if (*fmt_p == '-') { fmt_p++; left_align = true; } // The first digit group is the item's min width if (ascii_isdigit(*fmt_p)) { minwid = getdigits_int(&fmt_p, false, 0); } // User highlight groups override the min width field // to denote the styling to use. if (*fmt_p == STL_USER_HL) { stl_items[curitem].type = Highlight; stl_items[curitem].start = out_p; stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; fmt_p++; curitem++; continue; } // TABPAGE pairs are used to denote a region that when clicked will // either switch to or close a tab. // // Ex: tabline=%0Ttab\ zero%X // This tabline has a TABPAGENR item with minwid `0`, // which is then closed with a TABCLOSENR item. // Clicking on this region with mouse enabled will switch to tab 0. // Setting the minwid to a different value will switch // to that tab, if it exists // // Ex: tabline=%1Xtab\ one%X // This tabline has a TABCLOSENR item with minwid `1`, // which is then closed with a TABCLOSENR item. // Clicking on this region with mouse enabled will close tab 0. // This is determined by the following formula: // tab to close = (1 - minwid) // This is because for TABPAGENR we use `minwid` = `tab number`. // For TABCLOSENR we store the tab number as a negative value. // Because 0 is a valid TABPAGENR value, we have to // start our numbering at `-1`. // So, `-1` corresponds to us wanting to close tab `0` // // Note: These options are only valid when creating a tabline. if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) { if (*fmt_p == STL_TABCLOSENR) { if (minwid == 0) { // %X ends the close label, go back to the previous tab label nr. for (long n = curitem - 1; n >= 0; n--) { if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { minwid = stl_items[n].minwid; break; } } } else { // close nrs are stored as negative values minwid = -minwid; } } stl_items[curitem].type = TabPage; stl_items[curitem].start = out_p; stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; } if (*fmt_p == STL_CLICK_FUNC) { fmt_p++; char *t = fmt_p; while (*fmt_p != STL_CLICK_FUNC && *fmt_p) { fmt_p++; } if (*fmt_p != STL_CLICK_FUNC) { break; } stl_items[curitem].type = ClickFunc; stl_items[curitem].start = out_p; stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t)); stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; } // Denotes the end of the minwid // the maxwid may follow immediately after if (*fmt_p == '.') { fmt_p++; if (ascii_isdigit(*fmt_p)) { maxwid = getdigits_int(&fmt_p, false, 50); } } // Bound the minimum width at 50. // Make the number negative to denote left alignment of the item minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1); // Denotes the start of a new group if (*fmt_p == '(') { stl_groupitems[groupdepth++] = curitem; stl_items[curitem].type = Group; stl_items[curitem].start = out_p; stl_items[curitem].minwid = minwid; stl_items[curitem].maxwid = maxwid; fmt_p++; curitem++; continue; } // Denotes end of expanded %{} block if (*fmt_p == '}' && evaldepth > 0) { fmt_p++; evaldepth--; continue; } // An invalid item was specified. // Continue processing on the next character of the format string. if (vim_strchr(STL_ALL, *fmt_p) == NULL) { fmt_p++; continue; } // The status line item type char opt = *fmt_p++; // OK - now for the real work NumberBase base = kNumBaseDecimal; bool itemisflag = false; bool fillable = true; long num = -1; char *str = NULL; switch (opt) { case STL_FILEPATH: case STL_FULLPATH: case STL_FILENAME: // Set fillable to false so that ' ' in the filename will not // get replaced with the fillchar fillable = false; if (buf_spname(wp->w_buffer) != NULL) { STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); } else { char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname : wp->w_buffer->b_fname; home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); } trans_characters((char *)NameBuff, MAXPATHL); if (opt != STL_FILENAME) { str = (char *)NameBuff; } else { str = path_tail((char *)NameBuff); } break; case STL_VIM_EXPR: // '{' { char *block_start = fmt_p - 1; int reevaluate = (*fmt_p == '%'); itemisflag = true; if (reevaluate) { fmt_p++; } // Attempt to copy the expression to evaluate into // the output buffer as a null-terminated string. char *t = out_p; while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%')) && *fmt_p != NUL && out_p < out_end_p) { *out_p++ = *fmt_p++; } if (*fmt_p != '}') { // missing '}' or out of space break; } fmt_p++; if (reevaluate) { out_p[-1] = 0; // remove the % at the end of %{% expr %} } else { *out_p = 0; } // Move our position in the output buffer // to the beginning of the expression out_p = t; // { Evaluate the expression // Store the current buffer number as a string variable vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum); set_internal_string_var("g:actual_curbuf", buf_tmp); vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle); set_internal_string_var("g:actual_curwin", (char *)win_tmp); buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; const int save_VIsual_active = VIsual_active; curwin = wp; curbuf = wp->w_buffer; // Visual mode is only valid in the current window. if (curwin != save_curwin) { VIsual_active = false; } // Note: The result stored in `t` is unused. str = eval_to_string_safe(out_p, &t, use_sandbox); curwin = save_curwin; curbuf = save_curbuf; VIsual_active = save_VIsual_active; // Remove the variable we just stored do_unlet(S_LEN("g:actual_curbuf"), true); do_unlet(S_LEN("g:actual_curwin"), true); // } // Check if the evaluated result is a number. // If so, convert the number to an int and free the string. if (str != NULL && *str != 0) { if (*skipdigits(str) == NUL) { num = atoi(str); XFREE_CLEAR(str); itemisflag = false; } } // If the output of the expression needs to be evaluated // replace the %{} block with the result of evaluation if (reevaluate && str != NULL && *str != 0 && strchr((const char *)str, '%') != NULL && evaldepth < MAX_STL_EVAL_DEPTH) { size_t parsed_usefmt = (size_t)(block_start - usefmt); size_t str_length = strlen(str); size_t fmt_length = strlen(fmt_p); size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); char *new_fmt_p = new_fmt; new_fmt_p = (char *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + parsed_usefmt; new_fmt_p = (char *)memcpy(new_fmt_p, str, str_length) + str_length; new_fmt_p = (char *)memcpy(new_fmt_p, "%}", 2) + 2; new_fmt_p = (char *)memcpy(new_fmt_p, fmt_p, fmt_length) + fmt_length; *new_fmt_p = 0; new_fmt_p = NULL; if (usefmt != fmt) { xfree(usefmt); } XFREE_CLEAR(str); usefmt = new_fmt; fmt_p = usefmt + parsed_usefmt; evaldepth++; continue; } break; } case STL_LINE: num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); break; case STL_NUMLINES: num = wp->w_buffer->b_ml.ml_line_count; break; case STL_COLUMN: num = (State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1; break; case STL_VIRTCOL: case STL_VIRTCOL_ALT: { colnr_T virtcol = wp->w_virtcol + 1; // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col + 1))) { break; } num = (long)virtcol; break; } case STL_PERCENTAGE: num = (int)(((long)wp->w_cursor.lnum * 100L) / (long)wp->w_buffer->b_ml.ml_line_count); break; case STL_ALTPERCENT: // Store the position percentage in our temporary buffer. // Note: We cannot store the value in `num` because // `get_rel_pos` can return a named position. Ex: "Top" get_rel_pos(wp, buf_tmp, TMPLEN); str = buf_tmp; break; case STL_ARGLISTSTAT: fillable = false; // Note: This is important because `append_arg_number` starts appending // at the end of the null-terminated string. // Setting the first byte to null means it will place the argument // number string at the beginning of the buffer. buf_tmp[0] = 0; // Note: The call will only return true if it actually // appended data to the `buf_tmp` buffer. if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) { str = buf_tmp; } break; case STL_KEYMAP: fillable = false; if (get_keymap_str(wp, "<%s>", buf_tmp, TMPLEN)) { str = buf_tmp; } break; case STL_PAGENUM: num = printer_page_num; break; case STL_BUFNO: num = wp->w_buffer->b_fnum; break; case STL_OFFSET_X: base = kNumBaseHexadecimal; FALLTHROUGH; case STL_OFFSET: { long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL, false); num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ? 0 : (int)wp->w_cursor.col); break; } case STL_BYTEVAL_X: base = kNumBaseHexadecimal; FALLTHROUGH; case STL_BYTEVAL: num = byteval; if (num == NL) { num = 0; } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { num = NL; } break; case STL_ROFLAG: case STL_ROFLAG_ALT: itemisflag = true; if (wp->w_buffer->b_p_ro) { str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); } break; case STL_HELPFLAG: case STL_HELPFLAG_ALT: itemisflag = true; if (wp->w_buffer->b_help) { str = (opt == STL_HELPFLAG_ALT) ? ",HLP" : _("[Help]"); } break; case STL_FILETYPE: // Copy the filetype if it is not null and the formatted string will fit // in the temporary buffer // (including the brackets and null terminating character) if (*wp->w_buffer->b_p_ft != NUL && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 3) { vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", wp->w_buffer->b_p_ft); str = buf_tmp; } break; case STL_FILETYPE_ALT: itemisflag = true; // Copy the filetype if it is not null and the formatted string will fit // in the temporary buffer // (including the comma and null terminating character) if (*wp->w_buffer->b_p_ft != NUL && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 2) { vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); // Uppercase the file extension for (char *t = buf_tmp; *t != 0; t++) { *t = (char)TOUPPER_LOC(*t); } str = buf_tmp; } break; case STL_PREVIEWFLAG: case STL_PREVIEWFLAG_ALT: itemisflag = true; if (wp->w_p_pvw) { str = (opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]"); } break; case STL_QUICKFIX: if (bt_quickfix(wp->w_buffer)) { str = wp->w_llist_ref ? _(msg_loclist) : _(msg_qflist); } break; case STL_MODIFIED: case STL_MODIFIED_ALT: itemisflag = true; switch ((opt == STL_MODIFIED_ALT) + bufIsChanged(wp->w_buffer) * 2 + (!MODIFIABLE(wp->w_buffer)) * 4) { case 2: str = "[+]"; break; case 3: str = ",+"; break; case 4: str = "[-]"; break; case 5: str = ",-"; break; case 6: str = "[+-]"; break; case 7: str = ",+-"; break; } break; case STL_HIGHLIGHT: { // { The name of the highlight is surrounded by `#` char *t = fmt_p; while (*fmt_p != '#' && *fmt_p != NUL) { fmt_p++; } // } // Create a highlight item based on the name if (*fmt_p == '#') { stl_items[curitem].type = Highlight; stl_items[curitem].start = out_p; stl_items[curitem].minwid = -syn_name2id_len(t, (size_t)(fmt_p - t)); curitem++; fmt_p++; } continue; } } // If we made it this far, the item is normal and starts at // our current position in the output buffer. // Non-normal items would have `continued`. stl_items[curitem].start = out_p; stl_items[curitem].type = Normal; // Copy the item string into the output buffer if (str != NULL && *str) { // { Skip the leading `,` or ` ` if the item is a flag // and the proper conditions are met char *t = str; if (itemisflag) { if ((t[0] && t[1]) && ((!prevchar_isitem && *t == ',') || (prevchar_isflag && *t == ' '))) { t++; } prevchar_isflag = true; } // } long l = vim_strsize(t); // If this item is non-empty, record that the last thing // we put in the output buffer was an item if (l > 0) { prevchar_isitem = true; } // If the item is too wide, truncate it from the beginning if (l > maxwid) { while (l >= maxwid) { l -= ptr2cells(t); t += utfc_ptr2len(t); } // Early out if there isn't enough room for the truncation marker if (out_p >= out_end_p) { break; } // Add the truncation marker *out_p++ = '<'; } // If the item is right aligned and not wide enough, // pad with fill characters. if (minwid > 0) { for (; l < minwid && out_p < out_end_p; l++) { // Don't put a "-" in front of a digit. if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { *out_p++ = ' '; } else { MB_CHAR2BYTES(fillchar, out_p); } } minwid = 0; } else { // Note: The negative value denotes a left aligned item. // Here we switch the minimum width back to a positive value. minwid *= -1; } // { Copy the string text into the output buffer for (; *t && out_p < out_end_p; t++) { // Change a space by fillchar, unless fillchar is '-' and a // digit follows. if (fillable && *t == ' ' && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { MB_CHAR2BYTES(fillchar, out_p); } else { *out_p++ = *t; } } // } // For left-aligned items, fill any remaining space with the fillchar for (; l < minwid && out_p < out_end_p; l++) { MB_CHAR2BYTES(fillchar, out_p); } // Otherwise if the item is a number, copy that to the output buffer. } else if (num >= 0) { if (out_p + 20 > out_end_p) { break; // not sufficient space } prevchar_isitem = true; // { Build the formatting string char nstr[20]; char *t = nstr; if (opt == STL_VIRTCOL_ALT) { *t++ = '-'; minwid--; } *t++ = '%'; if (zeropad) { *t++ = '0'; } // Note: The `*` means we take the width as one of the arguments *t++ = '*'; *t++ = base == kNumBaseHexadecimal ? 'X' : 'd'; *t = 0; // } // { Determine how many characters the number will take up when printed // Note: We have to cast the base because the compiler uses // unsigned ints for the enum values. long num_chars = 1; for (long n = num; n >= (int)base; n /= (int)base) { num_chars++; } // VIRTCOL_ALT takes up an extra character because // of the `-` we added above. if (opt == STL_VIRTCOL_ALT) { num_chars++; } // } assert(out_end_p >= out_p); size_t remaining_buf_len = (size_t)(out_end_p - out_p) + 1; // If the number is going to take up too much room // Figure out the approximate number in "scientific" type notation. // Ex: 14532 with maxwid of 4 -> '14>3' if (num_chars > maxwid) { // Add two to the width because the power piece will take // two extra characters num_chars += 2; // How many extra characters there are long n = num_chars - maxwid; // { Reduce the number by base^n while (num_chars-- > maxwid) { num /= (long)base; } // } // { Add the format string for the exponent bit *t++ = '>'; *t++ = '%'; // Use the same base as the first number *t = t[-3]; *++t = 0; // } vim_snprintf(out_p, remaining_buf_len, nstr, 0, num, n); } else { vim_snprintf(out_p, remaining_buf_len, nstr, minwid, num); } // Advance the output buffer position to the end of the // number we just printed out_p += strlen(out_p); // Otherwise, there was nothing to print so mark the item as empty } else { stl_items[curitem].type = Empty; } // Only free the string buffer if we allocated it. // Note: This is not needed if `str` is pointing at `tmp` if (opt == STL_VIM_EXPR) { XFREE_CLEAR(str); } if (num >= 0 || (!itemisflag && str && *str)) { prevchar_isflag = false; // Item not NULL, but not a flag } // Item processed, move to the next curitem++; } *out_p = NUL; int itemcnt = curitem; // Free the format buffer if we allocated it internally if (usefmt != fmt) { xfree(usefmt); } // We have now processed the entire statusline format string. // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); if (maxwidth > 0 && width > maxwidth) { // Result is too long, must truncate somewhere. int item_idx = 0; char *trunc_p; // If there are no items, truncate from beginning if (itemcnt == 0) { trunc_p = out; // Otherwise, look for the truncation item } else { // Default to truncating at the first item trunc_p = stl_items[0].start; item_idx = 0; for (int i = 0; i < itemcnt; i++) { if (stl_items[i].type == Trunc) { // Truncate at %< stl_items. trunc_p = stl_items[i].start; item_idx = i; break; } } } // If the truncation point we found is beyond the maximum // length of the string, truncate the end of the string. if (width - vim_strsize(trunc_p) >= maxwidth) { // Walk from the beginning of the // string to find the last character that will fit. trunc_p = out; width = 0; for (;;) { width += ptr2cells(trunc_p); if (width >= maxwidth) { break; } // Note: Only advance the pointer if the next // character will fit in the available output space trunc_p += utfc_ptr2len(trunc_p); } // Ignore any items in the statusline that occur after // the truncation point for (int i = 0; i < itemcnt; i++) { if (stl_items[i].start > trunc_p) { itemcnt = i; break; } } // Truncate the output *trunc_p++ = '>'; *trunc_p = 0; // Truncate at the truncation point we found } else { // { Determine how many bytes to remove long trunc_len = 0; while (width >= maxwidth) { width -= ptr2cells(trunc_p + trunc_len); trunc_len += utfc_ptr2len(trunc_p + trunc_len); } // } // { Truncate the string char *trunc_end_p = trunc_p + trunc_len; STRMOVE(trunc_p + 1, trunc_end_p); // Put a `<` to mark where we truncated at *trunc_p = '<'; if (width + 1 < maxwidth) { // Advance the pointer to the end of the string trunc_p = trunc_p + strlen(trunc_p); } // Fill up for half a double-wide character. while (++width < maxwidth) { MB_CHAR2BYTES(fillchar, trunc_p); *trunc_p = NUL; } // } // { Change the start point for items based on // their position relative to our truncation point // Note: The offset is one less than the truncation length because // the truncation marker `<` is not counted. long item_offset = trunc_len - 1; for (int i = item_idx; i < itemcnt; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. if (stl_items[i].start >= trunc_end_p) { stl_items[i].start -= item_offset; // Anything inside the truncated area is set to start // at the `<` truncation character. } else { stl_items[i].start = trunc_p; } } // } } width = maxwidth; // If there is room left in our statusline, and room left in our buffer, // add characters at the separate marker (if there is one) to // fill up the available space. } else if (width < maxwidth && strlen(out) + (size_t)(maxwidth - width) + 1 < outlen) { // Find how many separators there are, which we will use when // figuring out how many groups there are. int num_separators = 0; for (int i = 0; i < itemcnt; i++) { if (stl_items[i].type == Separate) { // Create an array of the start location for each // separator mark. stl_separator_locations[num_separators] = i; num_separators++; } } // If we have separated groups, then we deal with it now if (num_separators) { int standard_spaces = (maxwidth - width) / num_separators; int final_spaces = (maxwidth - width) - standard_spaces * (num_separators - 1); for (int i = 0; i < num_separators; i++) { int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; dislocation *= utf_char2len(fillchar); char *start = stl_items[stl_separator_locations[i]].start; char *seploc = start + dislocation; STRMOVE(seploc, start); for (char *s = start; s < seploc;) { MB_CHAR2BYTES(fillchar, s); } for (int item_idx = stl_separator_locations[i] + 1; item_idx < itemcnt; item_idx++) { stl_items[item_idx].start += dislocation; } } width = maxwidth; } } // Store the info about highlighting. if (hltab != NULL) { *hltab = stl_hltab; stl_hlrec_t *sp = stl_hltab; for (long l = 0; l < itemcnt; l++) { if (stl_items[l].type == Highlight) { sp->start = stl_items[l].start; sp->userhl = stl_items[l].minwid; sp++; } } sp->start = NULL; sp->userhl = 0; } // Store the info about tab pages labels. if (tabtab != NULL) { *tabtab = stl_tabtab; StlClickRecord *cur_tab_rec = stl_tabtab; for (long l = 0; l < itemcnt; l++) { if (stl_items[l].type == TabPage) { cur_tab_rec->start = stl_items[l].start; if (stl_items[l].minwid == 0) { cur_tab_rec->def.type = kStlClickDisabled; cur_tab_rec->def.tabnr = 0; } else { int tabnr = stl_items[l].minwid; if (stl_items[l].minwid > 0) { cur_tab_rec->def.type = kStlClickTabSwitch; } else { cur_tab_rec->def.type = kStlClickTabClose; tabnr = -tabnr; } cur_tab_rec->def.tabnr = tabnr; } cur_tab_rec->def.func = NULL; cur_tab_rec++; } else if (stl_items[l].type == ClickFunc) { cur_tab_rec->start = stl_items[l].start; cur_tab_rec->def.type = kStlClickFuncRun; cur_tab_rec->def.tabnr = stl_items[l].minwid; cur_tab_rec->def.func = stl_items[l].cmd; cur_tab_rec++; } } cur_tab_rec->start = NULL; cur_tab_rec->def.type = kStlClickDisabled; cur_tab_rec->def.tabnr = 0; cur_tab_rec->def.func = NULL; } // When inside update_screen we do not want redrawing a statusline, ruler, // title, etc. to trigger another redraw, it may cause an endless loop. if (updating_screen) { must_redraw = save_must_redraw; curwin->w_redr_type = save_redr_type; } return width; }