diff options
Diffstat (limited to 'src/nvim/buffer.c')
-rw-r--r-- | src/nvim/buffer.c | 986 |
1 files changed, 645 insertions, 341 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 10106bcb1d..6fc08643af 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2803,58 +2803,59 @@ 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, + kNumBaseOctal = 8, + 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, %= => middle 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. - */ -int -build_stl_str_hl ( + +/// 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, %= => middle 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 page nrs (can be NULL) +/// +/// @return The final width of the statusline +int build_stl_str_hl( win_T *wp, - char_u *out, /* buffer to write into != NameBuff */ - size_t outlen, /* length of out[] */ + char_u *out, + size_t outlen, char_u *fmt, - int use_sandbox, /* "fmt" was set insecurely, use sandbox */ + int use_sandbox, int fillchar, int maxwidth, - struct stl_hlrec *hltab, /* return: HL attributes (can be NULL) */ - struct stl_hlrec *tabtab /* return: tab page nrs (can be NULL) */ + struct stl_hlrec *hltab, + struct stl_hlrec *tabtab ) { - char_u *p; - char_u *s; - char_u *t; - int byteval; - win_T *o_curwin; - buf_T *o_curbuf; - int empty_line; - colnr_T virtcol; - long l; - long n; - int prevchar_isflag; - int prevchar_isitem; - int itemisflag; - int fillable; - char_u *str; - long num; - int width; - int itemcnt; - int curitem; int groupitem[STL_MAX_ITEM]; - int groupdepth; struct stl_item { + // Where the item starts in the status line output buffer char_u *start; + // The minimum width of the item int minwid; + // The maximum width of the item int maxwid; enum { Normal, @@ -2866,20 +2867,13 @@ build_stl_str_hl ( Trunc } type; } item[STL_MAX_ITEM]; - int minwid; - int maxwid; - int zeropad; - char_u base; - char_u opt; + #define TMPLEN 70 char_u tmp[TMPLEN]; char_u *usefmt = fmt; - struct stl_hlrec *sp; - /* - * When the format starts with "%!" then evaluate it as an expression and - * use the result as the actual format string. - */ + // 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] == '!') { usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); if (usefmt == NULL) @@ -2888,175 +2882,280 @@ build_stl_str_hl ( if (fillchar == 0) fillchar = ' '; - /* Can't handle a multi-byte fill character yet. */ + // Can't handle a multi-byte fill character yet. else if (mb_char2len(fillchar) > 1) fillchar = '-'; - /* Get line & check if empty (cursorpos will show "0-1"). Note that - * p will become invalid when getting another buffer line. */ - p = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE); - empty_line = (*p == NUL); + // Get line & check if empty (cursorpos will show "0-1"). + char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.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. */ - if (wp->w_cursor.col > (colnr_T)STRLEN(p)) + // Get the byte value now, in case we need it below. This is more + // efficient than making a copy of the line. + int byteval; + if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) byteval = 0; else - byteval = (*mb_ptr2char)(p + wp->w_cursor.col); - - groupdepth = 0; - p = out; - curitem = 0; - prevchar_isflag = TRUE; - prevchar_isitem = FALSE; - for (s = usefmt; *s; ) { + byteval = (*mb_ptr2char)(line_ptr + wp->w_cursor.col); + + int groupdepth = 0; + + int curitem = 0; + bool prevchar_isflag = true; + bool prevchar_isitem = false; + + // out_p is the current position in the output buffer + char_u *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_u *out_end_p = (out + outlen) - 1; + + + // Proceed character by character through the statusline format string + // fmt_p is the current positon in the input buffer + for (char_u *fmt_p = usefmt; *fmt_p; ) { if (curitem == STL_MAX_ITEM) { - /* There are too many items. Add the error code to the statusline - * to give the user a hint about what went wrong. */ - if (p + 6 < out + outlen) { - memmove(p, " E541", (size_t)5); - p += 5; + // There are too many items. Add the error code to the statusline + // to give the user a hint about what went wrong. + if (out_p + 5 < out_end_p) { + memmove(out_p, " E541", (size_t)5); + out_p += 5; } break; } - if (*s != NUL && *s != '%') - prevchar_isflag = prevchar_isitem = FALSE; + if (*fmt_p != NUL && *fmt_p != '%') { + prevchar_isflag = prevchar_isitem = false; + } - /* - * Handle up to the next '%' or the end. - */ - while (*s != NUL && *s != '%' && p + 1 < out + outlen) - *p++ = *s++; - if (*s == NUL || p + 1 >= out + outlen) + // 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; - /* - * Handle one '%' item. - */ - s++; - if (*s == NUL) /* ignore trailing % */ + // 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; - if (*s == '%') { - if (p + 1 >= out + outlen) + } + + // Two `%` in a row is the escape sequence to print a + // single `%` in the output buffer. + if (*fmt_p == '%') { + // Ignore the character if we're out of room in the output buffer. + if (out_p >= out_end_p) break; - *p++ = *s++; - prevchar_isflag = prevchar_isitem = FALSE; + *out_p++ = *fmt_p++; + prevchar_isflag = prevchar_isitem = false; continue; } - if (*s == STL_MIDDLEMARK) { - s++; - if (groupdepth > 0) + + // STL_MIDDLEMARK: Separation place between left and right aligned items. + if (*fmt_p == STL_MIDDLEMARK) { + fmt_p++; + // Ignored when we are inside of a grouping + if (groupdepth > 0) { continue; + } item[curitem].type = Middle; - item[curitem++].start = p; + item[curitem++].start = out_p; continue; } - if (*s == STL_TRUNCMARK) { - s++; + + // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. + if (*fmt_p == STL_TRUNCMARK) { + fmt_p++; item[curitem].type = Trunc; - item[curitem++].start = p; + item[curitem++].start = out_p; continue; } - if (*s == ')') { - s++; - if (groupdepth < 1) + + // 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--; - t = item[groupitem[groupdepth]].start; - *p = NUL; - l = vim_strsize(t); + // Determine how long the group is. + // Note: We set the current output position to null + // so `vim_strsize` will work. + char_u *t = item[groupitem[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 > groupitem[groupdepth] + 1 && item[groupitem[groupdepth]].minwid == 0) { - /* remove group if all items are empty */ - for (n = groupitem[groupdepth] + 1; n < curitem; n++) - if (item[n].type == Normal) + bool has_normal_items = false; + for (long n = groupitem[groupdepth] + 1; n < curitem; n++) { + if (item[n].type == Normal) { + has_normal_items = true; break; - if (n == curitem) { - p = t; - l = 0; + } + } + + if (!has_normal_items) { + out_p = t; + group_len = 0; } } - if (l > item[groupitem[groupdepth]].maxwid) { - /* truncate, remove n bytes of text at the start */ + + // 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 > item[groupitem[groupdepth]].maxwid) { + // { Determine the number of bytes to remove + long n; if (has_mbyte) { /* Find the first character that should be included. */ n = 0; - while (l >= item[groupitem[groupdepth]].maxwid) { - l -= ptr2cells(t + n); + while (group_len >= item[groupitem[groupdepth]].maxwid) { + group_len -= ptr2cells(t + n); n += (*mb_ptr2len)(t + n); } - } else - n = (long)(p - t) - item[groupitem[groupdepth]].maxwid + 1; + } else { + n = (long)(out_p - t) - item[groupitem[groupdepth]].maxwid + 1; + } + // } + // Prepend the `<` to indicate that the output was truncated. *t = '<'; - memmove(t + 1, t + n, (size_t)(p - (t + n))); - p = p - n + 1; + + // { 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 (++l < item[groupitem[groupdepth]].minwid) - *p++ = fillchar; + while (++group_len < item[groupitem[groupdepth]].minwid) + *out_p++ = fillchar; + // } /* correct the start of the items for the truncation */ - for (l = groupitem[groupdepth] + 1; l < curitem; l++) { - item[l].start -= n; - if (item[l].start < t) - item[l].start = t; + for (int idx = groupitem[groupdepth] + 1; idx < curitem; idx++) { + // Shift everything back by the number of removed bytes + item[idx].start -= n; + + // If the item was partially or completely truncated, set its + // start to the start of the group + if (item[idx].start < t) { + item[idx].start = t; + } } - } else if (abs(item[groupitem[groupdepth]].minwid) > l) { - /* fill */ - n = item[groupitem[groupdepth]].minwid; - if (n < 0) { - /* fill by appending characters */ - n = 0 - n; - while (l++ < n && p + 1 < out + outlen) - *p++ = fillchar; + // If the group is shorter than the minimum width, add padding characters. + } else if (abs(item[groupitem[groupdepth]].minwid) > group_len) { + long min_group_width = item[groupitem[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) + *out_p++ = fillchar; + // If the group is right-aligned, shift everything to the right and + // prepend with filler characters. } else { - /* fill by inserting characters */ - memmove(t + n - l, t, (size_t)(p - t)); - l = n - l; - if (p + l >= out + outlen) - l = (long)((out + outlen) - p - 1); - p += l; - for (n = groupitem[groupdepth] + 1; n < curitem; n++) - item[n].start += l; - for (; l > 0; l--) + // { Move the group to the right + memmove(t + min_group_width - group_len, t, (size_t)(out_p - t)); + group_len = min_group_width - group_len; + 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 = groupitem[groupdepth] + 1; n < curitem; n++) { + item[n].start += group_len; + } + + // Prepend the fill characters + for (; group_len > 0; group_len--) { *t++ = fillchar; + } } } continue; } - minwid = 0; - maxwid = 9999; - zeropad = FALSE; - l = 1; - if (*s == '0') { - s++; - zeropad = TRUE; + 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++; } - if (*s == '-') { - s++; - l = -1; + + // Denotes that the item should be left-aligned. + // This is tracked by using a negative length. + if (*fmt_p == '-') { + fmt_p++; + left_align = true; } - if (ascii_isdigit(*s)) { - minwid = getdigits_int(&s); + + // The first digit group is the item's min width + if (ascii_isdigit(*fmt_p)) { + minwid = getdigits_int(&fmt_p); if (minwid < 0) /* overflow */ minwid = 0; } - if (*s == STL_USER_HL) { + + // User highlight groups override the min width field + // to denote the styling to use. + if (*fmt_p == STL_USER_HL) { item[curitem].type = Highlight; - item[curitem].start = p; + item[curitem].start = out_p; item[curitem].minwid = minwid > 9 ? 1 : minwid; - s++; + fmt_p++; curitem++; continue; } - if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) { - if (*s == STL_TABCLOSENR) { + + // 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 previously * define tab label nr. */ - for (n = curitem - 1; n >= 0; --n) + for (long n = curitem - 1; n >= 0; --n) if (item[n].type == TabPage && item[n].minwid >= 0) { minwid = item[n].minwid; break; @@ -3066,54 +3165,70 @@ build_stl_str_hl ( minwid = -minwid; } item[curitem].type = TabPage; - item[curitem].start = p; + item[curitem].start = out_p; item[curitem].minwid = minwid; - s++; + fmt_p++; curitem++; continue; } - if (*s == '.') { - s++; - if (ascii_isdigit(*s)) { - maxwid = getdigits_int(&s); + + // 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); if (maxwid <= 0) /* overflow */ maxwid = 50; } } - minwid = (minwid > 50 ? 50 : minwid) * l; - if (*s == '(') { + + // 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 == '(') { groupitem[groupdepth++] = curitem; item[curitem].type = Group; - item[curitem].start = p; + item[curitem].start = out_p; item[curitem].minwid = minwid; item[curitem].maxwid = maxwid; - s++; + fmt_p++; curitem++; continue; } - if (vim_strchr(STL_ALL, *s) == NULL) { - s++; + + // 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; } - opt = *s++; - - /* OK - now for the real work */ - base = 'D'; - itemisflag = FALSE; - fillable = TRUE; - num = -1; - str = NULL; + + // The status line item type + char_u opt = *fmt_p++; + + // OK - now for the real work + NumberBase base = kNumBaseDecimal; + bool itemisflag = false; + bool fillable = true; + long num = -1; + char_u *str = NULL; switch (opt) { case STL_FILEPATH: case STL_FULLPATH: case STL_FILENAME: - fillable = FALSE; /* don't change ' ' to fillchar */ - if (buf_spname(wp->w_buffer) != NULL) + { + // Set fillable to false to 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 { - t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname - : wp->w_buffer->b_fname; - home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE); + } else { + char_u *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname + : wp->w_buffer->b_fname; + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); } trans_characters(NameBuff, MAXPATHL); if (opt != STL_FILENAME) @@ -3121,42 +3236,59 @@ build_stl_str_hl ( else str = path_tail(NameBuff); break; - + } case STL_VIM_EXPR: /* '{' */ - itemisflag = TRUE; - t = p; - while (*s != '}' && *s != NUL && p + 1 < out + outlen) - *p++ = *s++; - if (*s != '}') /* missing '}' or out of space */ + { + itemisflag = true; + + // Attempt to copy the expression to evaluate into + // the output buffer as a null-terminated string. + char_u *t = out_p; + while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p) + *out_p++ = *fmt_p++; + if (*fmt_p != '}') /* missing '}' or out of space */ break; - s++; - *p = 0; - p = t; + fmt_p++; + *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((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); set_internal_string_var((char_u *)"actual_curbuf", tmp); - o_curbuf = curbuf; - o_curwin = curwin; + buf_T *o_curbuf = curbuf; + win_T *o_curwin = curwin; curwin = wp; curbuf = wp->w_buffer; - str = eval_to_string_safe(p, &t, use_sandbox); + // Note: The result stored in `t` is unused. + str = eval_to_string_safe(out_p, &t, use_sandbox); curwin = o_curwin; curbuf = o_curbuf; - do_unlet((char_u *)"g:actual_curbuf", TRUE); + // Remove the variable we just stored + do_unlet((char_u *)"g:actual_curbuf", 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((char *)str); xfree(str); str = NULL; - itemisflag = FALSE; + itemisflag = false; } } break; - + } case STL_LINE: num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); @@ -3173,21 +3305,23 @@ build_stl_str_hl ( case STL_VIRTCOL: case STL_VIRTCOL_ALT: - /* In list mode virtcol needs to be recomputed */ - virtcol = wp->w_virtcol; + { + // In list mode virtcol needs to be recomputed + colnr_T virtcol = wp->w_virtcol; if (wp->w_p_list && lcs_tab1 == NUL) { wp->w_p_list = FALSE; getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); wp->w_p_list = TRUE; } ++virtcol; - /* Don't display %V if it's the same as %c. */ + // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)(!(State & INSERT) && 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) / @@ -3195,19 +3329,31 @@ build_stl_str_hl ( 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, tmp, TMPLEN); str = tmp; - get_rel_pos(wp, str, TMPLEN); break; case STL_ARGLISTSTAT: - fillable = FALSE; + 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. tmp[0] = 0; - if (append_arg_number(wp, tmp, (int)sizeof(tmp), FALSE)) + + // Note: The call will only return true if it actually + // appended data to the `tmp` buffer. + if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) { str = tmp; + } break; case STL_KEYMAP: - fillable = FALSE; + fillable = false; if (get_keymap_str(wp, tmp, TMPLEN)) str = tmp; break; @@ -3220,16 +3366,17 @@ build_stl_str_hl ( break; case STL_OFFSET_X: - base = 'X'; + base = kNumBaseHexadecimal; case STL_OFFSET: - l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); + { + long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? 0L : l + 1 + (!(State & INSERT) && empty_line ? 0 : (int)wp->w_cursor.col); break; - + } case STL_BYTEVAL_X: - base = 'X'; + base = kNumBaseHexadecimal; case STL_BYTEVAL: num = byteval; if (num == NL) @@ -3240,20 +3387,23 @@ build_stl_str_hl ( case STL_ROFLAG: case STL_ROFLAG_ALT: - itemisflag = TRUE; + itemisflag = true; if (wp->w_buffer->b_p_ro) str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]")); break; case STL_HELPFLAG: case STL_HELPFLAG_ALT: - itemisflag = TRUE; + itemisflag = true; if (wp->w_buffer->b_help) str = (char_u *)((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((char *)tmp, sizeof(tmp), "[%s]", @@ -3263,20 +3413,26 @@ build_stl_str_hl ( break; case STL_FILETYPE_ALT: - itemisflag = TRUE; + { + 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((char *)tmp, sizeof(tmp), ",%s", wp->w_buffer->b_p_ft); - for (t = tmp; *t != 0; t++) + // Uppercase the file extension + for (char_u *t = tmp; *t != 0; t++) { *t = TOUPPER_LOC(*t); + } str = tmp; } break; - + } case STL_PREVIEWFLAG: case STL_PREVIEWFLAG_ALT: - itemisflag = TRUE; + itemisflag = true; if (wp->w_p_pvw) str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV" : _("[Preview]")); @@ -3291,7 +3447,7 @@ build_stl_str_hl ( case STL_MODIFIED: case STL_MODIFIED_ALT: - itemisflag = TRUE; + itemisflag = true; switch ((opt == STL_MODIFIED_ALT) + bufIsChanged(wp->w_buffer) * 2 + (!MODIFIABLE(wp->w_buffer)) * 4) { @@ -3305,212 +3461,360 @@ build_stl_str_hl ( break; case STL_HIGHLIGHT: - t = s; - while (*s != '#' && *s != NUL) - ++s; - if (*s == '#') { + { + // { The name of the highlight is surrounded by `#` + char_u *t = fmt_p; + while (*fmt_p != '#' && *fmt_p != NUL) { + ++fmt_p; + } + // } + + // Create a highlight item based on the name + if (*fmt_p == '#') { item[curitem].type = Highlight; - item[curitem].start = p; - item[curitem].minwid = -syn_namen2id(t, (int)(s - t)); + item[curitem].start = out_p; + item[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); curitem++; + fmt_p++; } - if (*s != NUL) - ++s; continue; } + } - item[curitem].start = p; + // 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`. + item[curitem].start = out_p; item[curitem].type = Normal; + + // Copy the item string into the output buffer if (str != NULL && *str) { - t = str; + // { Skip the leading `,` or ` ` if the item is a flag + // and the proper conditions are met + char_u *t = str; if (itemisflag) { if ((t[0] && t[1]) && ((!prevchar_isitem && *t == ',') || (prevchar_isflag && *t == ' '))) t++; - prevchar_isflag = TRUE; + 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; } - l = vim_strsize(t); - if (l > 0) - prevchar_isitem = TRUE; + + // If the item is too wide, truncate it from the beginning if (l > maxwid) { while (l >= maxwid) if (has_mbyte) { l -= ptr2cells(t); t += (*mb_ptr2len)(t); - } else + } else { l -= byte2cells(*t++); - if (p + 1 >= out + outlen) + } + + // Early out if there isn't enough room for the truncation marker + if (out_p >= out_end_p) break; - *p++ = '<'; + + // 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 && p + 1 < out + outlen; l++) { - /* Don't put a "-" in front of a digit. */ + 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)) - *p++ = ' '; + *out_p++ = ' '; else - *p++ = fillchar; + *out_p++ = fillchar; } minwid = 0; - } else + } else { + // Note: The negative value denotes a left aligned item. + // Here we switch the minimum width back to a positive value. minwid *= -1; - while (*t && p + 1 < out + outlen) { - *p++ = *t++; - /* Change a space by fillchar, unless fillchar is '-' and a - * digit follows. */ - if (fillable && p[-1] == ' ' + } + + // { Copy the string text into the output buffer + while (*t && out_p < out_end_p) { + *out_p++ = *t++; + // Change a space by fillchar, unless fillchar is '-' and a + // digit follows. + if (fillable && out_p[-1] == ' ' && (!ascii_isdigit(*t) || fillchar != '-')) - p[-1] = fillchar; + out_p[-1] = fillchar; + } + // } + + // For left-aligned items, fill any remaining space with the fillchar + for (; l < minwid && out_p < out_end_p; l++) { + *out_p++ = fillchar; } - for (; l < minwid && p + 1 < out + outlen; l++) - *p++ = fillchar; - } else if (num >= 0) { - int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16)); - char_u nstr[20]; - if (p + 20 >= out + outlen) + // 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; - t = nstr; + prevchar_isitem = true; + + // { Build the formatting string + char_u nstr[20]; + char_u *t = nstr; if (opt == STL_VIRTCOL_ALT) { *t++ = '-'; minwid--; } *t++ = '%'; - if (zeropad) + if (zeropad) { *t++ = '0'; + } + + // Note: The `*` means we take the width as one of the arguments *t++ = '*'; - *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd'); + *t++ = (char_u) (base == kNumBaseHexadecimal ? 'X' + : (base == kNumBaseOctal ? 'o' + : '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++; + } - for (n = num, l = 1; n >= nbase; n /= nbase) - l++; - if (opt == STL_VIRTCOL_ALT) - l++; - if (l > maxwid) { - l += 2; - n = l - maxwid; - while (l-- > maxwid) - num /= nbase; + // VIRTCOL_ALT takes up an extra character because + // of the `-` we added above. + if (opt == STL_VIRTCOL_ALT) { + num_chars++; + } + // } + + size_t remaining_buf_len = (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 /= 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((char *)p, outlen - (p - out), (char *)nstr, + // } + + vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr, 0, num, n); - } else - vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + } else { + vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr, minwid, num); - p += STRLEN(p); - } else + } + + // 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 { item[curitem].type = Empty; + } - if (opt == STL_VIM_EXPR) + // 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(str); + } if (num >= 0 || (!itemisflag && str && *str)) - prevchar_isflag = FALSE; /* Item not NULL, but not a flag */ + prevchar_isflag = false; /* Item not NULL, but not a flag */ + + // Item processed, move to the next curitem++; } - *p = NUL; - itemcnt = curitem; - if (usefmt != fmt) + *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 factors. - width = vim_strsize(out); + int width = vim_strsize(out); if (maxwidth > 0 && width > maxwidth) { - /* Result is too long, must truncate somewhere. */ - l = 0; - if (itemcnt == 0) - s = out; - else { - for (; l < itemcnt; l++) - if (item[l].type == Trunc) { - /* Truncate at %< item. */ - s = item[l].start; + // Result is too long, must truncate somewhere. + int item_idx = 0; + char_u *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 = item[0].start; + item_idx = 0; + + for (int i = 0; i < itemcnt; i++) + if (item[i].type == Trunc) { + // Truncate at %< item. + trunc_p = item[i].start; + item_idx = i; break; } - if (l == itemcnt) { - /* No %< item, truncate first item. */ - s = item[0].start; - l = 0; - } } - if (width - vim_strsize(s) >= maxwidth) { - /* Truncation mark is beyond max length */ + // 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) { + // If we are using a multi-byte encoding, walk from the beginning of the + // string to find the last character that will fit. if (has_mbyte) { - s = out; + trunc_p = out; width = 0; for (;; ) { - width += ptr2cells(s); + width += ptr2cells(trunc_p); if (width >= maxwidth) break; - s += (*mb_ptr2len)(s); + + // Note: Only advance the pointer if the next + // character will fit in the available output space + trunc_p += (*mb_ptr2len)(trunc_p); } - /* Fill up for half a double-wide character. */ - while (++width < maxwidth) - *s++ = fillchar; - } else - s = out + maxwidth - 1; - for (l = 0; l < itemcnt; l++) - if (item[l].start > s) + + // Otherwise put the truncation point at the end, leaving enough room + // for a single-character truncation marker + } else { + trunc_p = out + maxwidth - 1; + } + + // Ignore any items in the statusline that occur after + // the truncation point + for (int i = 0; i < itemcnt; i++) { + if (item[i].start > trunc_p) { + itemcnt = i; break; - itemcnt = l; - *s++ = '>'; - *s = 0; + } + } + + // 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; if (has_mbyte) { - n = 0; + trunc_len = 0; while (width >= maxwidth) { - width -= ptr2cells(s + n); - n += (*mb_ptr2len)(s + n); + width -= ptr2cells(trunc_p + trunc_len); + trunc_len += (*mb_ptr2len)(trunc_p + trunc_len); } - } else - n = width - maxwidth + 1; - p = s + n; - STRMOVE(s + 1, p); - *s = '<'; + } else { + // Truncate an extra character so we can insert our `<`. + trunc_len = (width - maxwidth) + 1; + } + // } - /* Fill up for half a double-wide character. */ + // { Truncate the string + char_u *trunc_end_p = trunc_p + trunc_len; + STRMOVE(trunc_p + 1, trunc_end_p); + + // Put a `<` to mark where we truncated at + *trunc_p = '<'; + + // 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) { - s = s + STRLEN(s); - *s++ = fillchar; - *s = NUL; + *trunc_p++ = fillchar; + *trunc_p = NUL; } - - --n; /* count the '<' */ - for (; l < itemcnt; l++) { - if (item[l].start - n >= s) - item[l].start -= n; - else - item[l].start = s; + // } + + // { 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 (item[i].start >= trunc_end_p) { + item[i].start -= item_offset; + // Anything inside the truncated area is set to start + // at the `<` truncation character. + } else { + item[i].start = trunc_p; + } } + // } } width = maxwidth; - } else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < - outlen) { - /* Apply STL_MIDDLE if any */ - for (l = 0; l < itemcnt; l++) - if (item[l].type == Middle) + + // If there is room left in our statusline, and room left in our buffer, + // add characters at the middle marker (if there is one) to + // fill up the available space. + } else if (width < maxwidth + && STRLEN(out) + maxwidth - width + 1 < outlen) { + for (int item_idx = 0; item_idx < itemcnt; item_idx++) { + if (item[item_idx].type == Middle) { + // Move the statusline to make room for the middle characters + char_u *middle_end = item[item_idx].start + (maxwidth - width); + STRMOVE(middle_end, item[item_idx].start); + + // Fill the middle section with our fill character + for (char_u *s = item[item_idx].start; s < middle_end; s++) + *s = fillchar; + + // Adjust the offset of any items after the middle + for (item_idx++; item_idx < itemcnt; item_idx++) + item[item_idx].start += maxwidth - width; + + width = maxwidth; break; - if (l < itemcnt) { - p = item[l].start + maxwidth - width; - STRMOVE(p, item[l].start); - for (s = item[l].start; s < p; s++) - *s = fillchar; - for (l++; l < itemcnt; l++) - item[l].start += maxwidth - width; - width = maxwidth; + } } } - /* Store the info about highlighting. */ + // Store the info about highlighting. if (hltab != NULL) { - sp = hltab; - for (l = 0; l < itemcnt; l++) { + struct stl_hlrec *sp = hltab; + for (long l = 0; l < itemcnt; l++) { if (item[l].type == Highlight) { sp->start = item[l].start; sp->userhl = item[l].minwid; @@ -3521,10 +3825,10 @@ build_stl_str_hl ( sp->userhl = 0; } - /* Store the info about tab pages labels. */ + // Store the info about tab pages labels. if (tabtab != NULL) { - sp = tabtab; - for (l = 0; l < itemcnt; l++) { + struct stl_hlrec *sp = tabtab; + for (long l = 0; l < itemcnt; l++) { if (item[l].type == TabPage) { sp->start = item[l].start; sp->userhl = item[l].minwid; |