diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/vim.c | 1 | ||||
-rw-r--r-- | src/nvim/buffer.c | 1194 | ||||
-rw-r--r-- | src/nvim/buffer.h | 3 | ||||
-rw-r--r-- | src/nvim/drawscreen.c | 205 | ||||
-rw-r--r-- | src/nvim/edit.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 6 | ||||
-rw-r--r-- | src/nvim/hardcopy.c | 1 | ||||
-rw-r--r-- | src/nvim/normal.c | 2 | ||||
-rw-r--r-- | src/nvim/screen.c | 460 | ||||
-rw-r--r-- | src/nvim/search.c | 2 | ||||
-rw-r--r-- | src/nvim/statusline.c | 1807 | ||||
-rw-r--r-- | src/nvim/statusline.h | 10 |
13 files changed, 1871 insertions, 1824 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e4dc219e9a..a9787787d1 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -57,6 +57,7 @@ #include "nvim/popupmenu.h" #include "nvim/runtime.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f9bce2476f..88b7cfc8ee 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -76,6 +76,7 @@ #include "nvim/runtime.h" #include "nvim/sign.h" #include "nvim/spell.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -89,11 +90,6 @@ # include "buffer.c.generated.h" #endif -// Determines how deeply nested %{} blocks will be evaluated in statusline. -#define MAX_STL_EVAL_DEPTH 100 - -static char *msg_loclist = N_("[Location List]"); -static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); @@ -3359,1192 +3355,6 @@ void free_titles(void) #endif -/// Enumeration specifying the valid numeric bases that can -/// be used when printing numbers in the status line. -typedef enum { - kNumBaseDecimal = 10, - kNumBaseHexadecimal = 16, -} NumberBase; - -/// 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: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation -/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> 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 = (char *)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; -} - /// Get relative cursor position in window into "buf[buflen]", in the form 99%, /// using "Top", "Bot" or "All" when appropriate. void get_rel_pos(win_T *wp, char *buf, int buflen) @@ -4584,7 +3394,7 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) /// @param add_file if true, add "file" before the arg number /// /// @return true if it was appended. -static bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) +bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) FUNC_ATTR_NONNULL_ALL { // Nothing to do diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 7627b6a596..d56a70dc7e 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -62,6 +62,9 @@ enum bfa_values { BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; +EXTERN char *msg_loclist INIT(= N_("[Location List]")); +EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.h.generated.h" #endif diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 8cd1bdddd8..41d7d22934 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -75,6 +75,7 @@ #include "nvim/popupmenu.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/statusline.h" #include "nvim/syntax.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" @@ -736,190 +737,10 @@ static void win_redr_border(win_T *wp) } } -/// Redraw the status line of window `wp`. -/// -/// If inversion is possible we use it. Else '=' characters are used. -static 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 = 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) { - *(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, 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; -} - -/// Redraw the status line according to 'statusline' and take care of any -/// errors encountered. -static 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; -} - -static 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; -} - -/// Show current status info in ruler and various other places +/// Show current cursor info in ruler and various other places /// /// @param always if false, only show ruler if position has changed. -void showruler(bool always) +void show_cursor_info(bool always) { if (!always && !redrawing()) { return; @@ -2303,6 +2124,26 @@ void redraw_statuslines(void) } } +/// Redraw all status lines at the bottom of frame "frp". +void win_redraw_last_status(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (frp->fr_layout == FR_LEAF) { + frp->fr_win->w_redr_status = true; + } else if (frp->fr_layout == FR_ROW) { + FOR_ALL_FRAMES(frp, frp->fr_child) { + win_redraw_last_status(frp); + } + } else { + assert(frp->fr_layout == FR_COL); + frp = frp->fr_child; + while (frp->fr_next != NULL) { + frp = frp->fr_next; + } + win_redraw_last_status(frp); + } +} + /// Changed something in the current window, at buffer line "lnum", that /// requires that line and possibly other lines to be redrawn. /// Used when entering/leaving Insert mode with the cursor on a folded line. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 05abeee550..6583ac8584 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1349,7 +1349,7 @@ void ins_redraw(bool ready) } else if (clear_cmdline || redraw_cmdline) { showmode(); // clear cmdline and show mode } - showruler(false); + show_cursor_info(false); setcursor(); emsg_on_display = false; // may remove error message now } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index bc8e823797..66176620a5 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3976,7 +3976,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); msg_no_more = false; msg_scroll = (int)i; - showruler(true); + show_cursor_info(true); ui_cursor_goto(msg_row, msg_col); RedrawingDisabled = temp; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c15d85967d..776bdd935b 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4264,7 +4264,7 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode compl_selected = findex; cmdline_pum_display(false); } else if (p_wmnu) { - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); } if (findex == -1) { return vim_strsave(orig_save); @@ -4679,7 +4679,7 @@ static int showmatches(expand_T *xp, int wildmenu) if (got_int) { got_int = false; // only int. the completion, not the cmd line } else if (wildmenu) { - win_redr_status_matches(xp, num_files, files_found, -1, showtail); + redraw_wildmenu(xp, num_files, files_found, -1, showtail); } else { // find the length of the longest file name maxlen = 0; @@ -4791,7 +4791,7 @@ static int showmatches(expand_T *xp, int wildmenu) return EXPAND_OK; } -/// Private path_tail for showmatches() (and win_redr_status_matches()): +/// Private path_tail for showmatches() (and redraw_wildmenu()): /// Find tail of file name path, but ignore trailing "/". char *sm_gettail(char *s, bool eager) { diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 6a1bbcb089..e8410d1ee7 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -33,6 +33,7 @@ #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/runtime.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c87c0cbb6e..1d8a786c9f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1322,7 +1322,7 @@ static void normal_redraw(NormalState *s) did_emsg = false; msg_didany = false; // reset lines_left in msg_start() may_clear_sb_text(); // clear scroll-back text on next msg - showruler(false); + show_cursor_info(false); setcursor(); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2419a42a2a..c9a8faa8c6 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -16,6 +16,7 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" @@ -34,6 +35,7 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/window.h" @@ -249,7 +251,7 @@ void rl_mirror(char_u *str) } /// Get the length of an item as it will be shown in the status line. -static int status_match_len(expand_T *xp, char_u *s) +static int wildmenu_match_len(expand_T *xp, char_u *s) { int len = 0; @@ -262,7 +264,7 @@ static int status_match_len(expand_T *xp, char_u *s) } while (*s != NUL) { - s += skip_status_match_char(xp, s); + s += skip_wildmenu_char(xp, s); len += ptr2cells((char *)s); MB_PTR_ADV(s); } @@ -270,29 +272,9 @@ static int status_match_len(expand_T *xp, char_u *s) return len; } -/// Redraw all status lines at the bottom of frame "frp". -void win_redraw_last_status(const frame_T *frp) - FUNC_ATTR_NONNULL_ARG(1) -{ - if (frp->fr_layout == FR_LEAF) { - frp->fr_win->w_redr_status = true; - } else if (frp->fr_layout == FR_ROW) { - FOR_ALL_FRAMES(frp, frp->fr_child) { - win_redraw_last_status(frp); - } - } else { - assert(frp->fr_layout == FR_COL); - frp = frp->fr_child; - while (frp->fr_next != NULL) { - frp = frp->fr_next; - } - win_redraw_last_status(frp); - } -} - -/// Return the number of characters that should be skipped in a status match. +/// Return the number of characters that should be skipped in the wildmenu /// These are backslashes used for escaping. Do show backslashes in help tags. -static int skip_status_match_char(expand_T *xp, char_u *s) +static int skip_wildmenu_char(expand_T *xp, char_u *s) { if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) || ((xp->xp_context == EXPAND_MENUS @@ -300,6 +282,10 @@ static int skip_status_match_char(expand_T *xp, char_u *s) && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) { #ifndef BACKSLASH_IN_FILENAME + // TODO(bfredl): Why in the actual fuck are we special casing the + // shell variety deep in the redraw logic? Shell special snowflakiness + // should already be eliminated multiple layers before reaching the + // screen infracstructure. if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { return 2; } @@ -316,7 +302,7 @@ static int skip_status_match_char(expand_T *xp, char_u *s) /// If inversion is possible we use it. Else '=' characters are used. /// /// @param matches list of matches -void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int match, int showtail) +void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) { #define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) int row; @@ -347,7 +333,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int highlight = false; } // count 1 for the ending ">" - clen = status_match_len(xp, (char_u *)L_MATCH(match)) + 3; + clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3; if (match == 0) { first_match = 0; } else if (match < first_match) { @@ -357,7 +343,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } else { // check if match fits on the screen for (i = first_match; i < match; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; } if (first_match > 0) { clen += 2; @@ -368,7 +354,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int // if showing the last match, we can add some on the left clen = 2; for (i = match; i < num_matches; i++) { - clen += status_match_len(xp, (char_u *)L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; if ((long)clen >= Columns) { break; } @@ -380,7 +366,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int } if (add_left) { while (first_match > 0) { - clen += status_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; + clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; if ((long)clen >= Columns) { break; } @@ -400,7 +386,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int clen = len; i = first_match; - while (clen + status_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { + while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; @@ -417,7 +403,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char **matches, int clen += l; } else { for (; *s != NUL; s++) { - s += skip_status_match_char(xp, s); + s += skip_wildmenu_char(xp, s); clen += ptr2cells((char *)s); if ((l = utfc_ptr2len((char *)s)) > 1) { STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) @@ -561,223 +547,6 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) return buf[0] != NUL; } -/// 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_u *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 = (char_u *)((*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((char *)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 = vim_strsave(stl); - width = - build_stl_str_hl(ewp, buf, sizeof(buf), (char *)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, (char_u *)p, textlen, row, col, curattr); - col += vim_strnsize((char_u *)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 ? (char_u *)"" : (char_u *)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((char_u *)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; -} - /// Prepare for 'hlsearch' highlighting. void start_search_hl(void) { @@ -1378,32 +1147,6 @@ void get_trans_bufname(buf_T *buf) trans_characters((char *)NameBuff, MAXPATHL); } -/// 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 '='; -} - /// Get the character to use in a separator between vertically split windows. /// Get its attributes in "*attr". int fillchar_vsep(win_T *wp, int *attr) @@ -1433,175 +1176,6 @@ bool messaging(void) return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages(); } -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 && !ui_has_messages()) { - 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, (char_u *)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; - } -} - #define COL_RULER 17 // columns needed by standard ruler /// Compute columns for ruler and shown command. 'sc_col' is also used to diff --git a/src/nvim/search.c b/src/nvim/search.c index c820817a71..c53d955974 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2423,7 +2423,7 @@ void showmatch(int c) curwin->w_cursor = mpos; // move to matching char *so = 0; // don't use 'scrolloff' here *siso = 0; // don't use 'sidescrolloff' here - showruler(false); + show_cursor_info(false); setcursor(); ui_flush(); // Restore dollar_vcol(), because setcursor() may call curs_rows() diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c new file mode 100644 index 0000000000..c3260fd496 --- /dev/null +++ b/src/nvim/statusline.c @@ -0,0 +1,1807 @@ +// 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 <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#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/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 = 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) { + *(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, 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 && !ui_has_messages()) { + 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, (char_u *)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_u *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 = (char_u *)((*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((char *)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 = vim_strsave(stl); + width = + build_stl_str_hl(ewp, buf, sizeof(buf), (char *)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, (char_u *)p, textlen, row, col, curattr); + col += vim_strnsize((char_u *)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 ? (char_u *)"" : (char_u *)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((char_u *)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: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation +/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> 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 = (char *)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; +} diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h new file mode 100644 index 0000000000..357a9a821f --- /dev/null +++ b/src/nvim/statusline.h @@ -0,0 +1,10 @@ +#ifndef NVIM_STATUSLINE_H +#define NVIM_STATUSLINE_H + +#include "nvim/buffer_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "statusline.h.generated.h" +#endif + +#endif // NVIM_STATUSLINE_H |