aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c1194
-rw-r--r--src/nvim/buffer.h3
-rw-r--r--src/nvim/drawscreen.c205
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_getln.c6
-rw-r--r--src/nvim/hardcopy.c1
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/screen.c460
-rw-r--r--src/nvim/search.c2
-rw-r--r--src/nvim/statusline.c1807
-rw-r--r--src/nvim/statusline.h10
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