diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
commit | 9243becbedbb6a1592208051f8fa2b090dcc5e7d (patch) | |
tree | 607c2a862ec3f4399b8766383f6f8e04c4aa43b4 /src/nvim/statusline.c | |
parent | 9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 (diff) | |
parent | 3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff) | |
download | rneovim-usermarks.tar.gz rneovim-usermarks.tar.bz2 rneovim-usermarks.zip |
Merge remote-tracking branch 'upstream/master' into usermarksusermarks
Diffstat (limited to 'src/nvim/statusline.c')
-rw-r--r-- | src/nvim/statusline.c | 900 |
1 files changed, 634 insertions, 266 deletions
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index e868a79b9a..6ad1f31143 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -5,22 +5,43 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> -#include "nvim/assert.h" -#include "nvim/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/sign_defs.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -43,11 +64,10 @@ void win_redr_status(win_T *wp) { int row; int col; - char_u *p; + char *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; @@ -74,53 +94,54 @@ void win_redr_status(win_T *wp) redraw_custom_statusline(wp); } else { fillchar = fillchar_status(&attr, wp); - width = is_stl_global ? Columns : wp->w_width; + const int stl_width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); p = NameBuff; - len = (int)STRLEN(p); + len = (int)strlen(p); - if (bt_help(wp->w_buffer) - || wp->w_p_pvw - || bufIsChanged(wp->w_buffer) - || wp->w_buffer->b_p_ro) { + if ((bt_help(wp->w_buffer) + || wp->w_p_pvw + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) + && len < MAXPATHL - 1) { *(p + len++) = ' '; } if (bt_help(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); - len += (int)STRLEN(p + len); + snprintf(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); + snprintf(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); + snprintf(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 + snprintf(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; + this_ru_col = ru_col - (Columns - stl_width); + if (this_ru_col < (stl_width + 1) / 2) { + this_ru_col = (stl_width + 1) / 2; } if (this_ru_col <= 1) { - p = (char_u *)"<"; // No room for file name! + p = "<"; // No room for file name! len = 1; } else { int clen = 0, i; // Count total number of display cells. - clen = (int)mb_string2cells((char *)p); + clen = (int)mb_string2cells(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); + i += utfc_ptr2len(p + i)) { + clen -= utf_ptr2cells(p + i); } len = clen; if (i > 0) { @@ -136,13 +157,23 @@ void win_redr_status(win_T *wp) 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)) { + if (get_keymap_str(wp, "<%s>", 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); + (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } win_redr_ruler(wp, true); + + // Draw the 'showcmd' information if 'showcmdloc' == "statusline". + if (p_sc && *p_sloc == 's') { + const int sc_width = MIN(10, this_ru_col - len - 2); + + if (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, row, + wp->w_wincol + this_ru_col - sc_width - 1, attr); + } + } } // May need to draw the character below the vertical separator. @@ -157,6 +188,252 @@ void win_redr_status(win_T *wp) busy = false; } +/// Clear status line, window bar or tab page line click definition table +/// +/// @param[out] tpcd Table to clear. +/// @param[in] tpcd_size Size of the table. +void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t click_defs_size) +{ + if (click_defs != NULL) { + for (size_t i = 0; i < click_defs_size; i++) { + if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { + xfree(click_defs[i].func); + } + } + memset(click_defs, 0, click_defs_size * sizeof(click_defs[0])); + } +} + +/// Allocate or resize the click definitions array if needed. +StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size) +{ + if (*size < (size_t)width) { + xfree(cdp); + *size = (size_t)width; + cdp = xcalloc(*size, sizeof(StlClickDefinition)); + } + return cdp; +} + +/// Fill the click definitions array if needed. +void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf, + int width, bool tabline) +{ + if (click_defs == NULL) { + return; + } + + int col = 0; + int len = 0; + + StlClickDefinition cur_click_def = { + .type = kStlClickDisabled, + }; + for (int i = 0; click_recs[i].start != NULL; i++) { + len += vim_strnsize(buf, (int)(click_recs[i].start - buf)); + if (col < len) { + while (col < len) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } + buf = (char *)click_recs[i].start; + cur_click_def = click_recs[i].def; + if (!tabline && !(cur_click_def.type == kStlClickDisabled + || cur_click_def.type == kStlClickFuncRun)) { + // window bar and status line only support click functions + cur_click_def.type = kStlClickDisabled; + } + } + if (col < width) { + while (col < width) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } +} + +/// Redraw the status line, window bar or ruler of window "wp". +/// When "wp" is NULL redraw the tab pages line from 'tabline'. +static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +{ + static bool entered = false; + int attr; + int curattr; + int row; + int col = 0; + int maxwidth; + int width; + int n; + int len; + int fillchar; + char buf[MAXPATHL]; + char *stl; + char *p; + char *opt_name; + int opt_scope = 0; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; + 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; + opt_name = "tabline"; + } else if (draw_winbar) { + opt_name = "winbar"; + stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); + opt_scope = ((*wp->w_p_wbr != NUL) ? OPT_LOCAL : 0); + 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; + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); + wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth, + &wp->w_winbar_click_defs_size); + } 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, wp->w_status_click_defs_size); + wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth, + &wp->w_status_click_defs_size); + + if (draw_ruler) { + stl = p_ruf; + opt_name = "rulerformat"; + // advance past any leading group spec - implicit in ru_col + if (*stl == '%') { + if (*++stl == '-') { + stl++; + } + if (atoi(stl)) { + while (ascii_isdigit(*stl)) { + stl++; + } + } + if (*stl++ != '(') { + stl = p_ruf; + } + } + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; + } + maxwidth = maxwidth - col; + if (!wp->w_status_height && !is_stl_global) { + grid = &msg_grid_adj; + row = Rows - 1; + maxwidth--; // writing in last column may cause scrolling + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + } + } else { + opt_name = "statusline"; + stl = ((*wp->w_p_stl != NUL) ? wp->w_p_stl : p_stl); + opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0); + } + + col += is_stl_global ? 0 : wp->w_wincol; + } + + if (maxwidth <= 0) { + goto theend; + } + + // Temporarily reset 'cursorbind', we don't want a side effect from moving + // the cursor away and back. + ewp = wp == NULL ? curwin : wp; + p_crb_save = ewp->w_p_crb; + ewp->w_p_crb = false; + + // Make a copy, because the statusline may include a function call that + // might change the option value and free the memory. + stl = xstrdup(stl); + width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope, + fillchar, maxwidth, &hltab, &tabtab, NULL); + + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // Make all characters printable. + p = transstr(buf, true); + len = (int)xstrlcpy(buf, p, sizeof(buf)); + len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; + xfree(p); + + // fill up with "fillchar" + while (width < maxwidth && len < (int)sizeof(buf) - 1) { + len += utf_char2bytes(fillchar, buf + len); + width++; + } + buf[len] = NUL; + + // Draw each snippet with the specified highlighting. + grid_puts_line_start(grid, row); + + curattr = attr; + p = buf; + for (n = 0; hltab[n].start != NULL; n++) { + int textlen = (int)(hltab[n].start - p); + grid_puts_len(grid, p, textlen, row, col, curattr); + col += vim_strnsize(p, textlen); + p = hltab[n].start; + + if (hltab[n].userhl == 0) { + curattr = attr; + } else if (hltab[n].userhl < 0) { + curattr = syn_id2attr(-hltab[n].userhl); + } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { + curattr = highlight_stlnc[hltab[n].userhl - 1]; + } else { + curattr = highlight_user[hltab[n].userhl - 1]; + } + } + // Make sure to use an empty string instead of p, if p is beyond buf + len. + grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + + grid_puts_line_flush(false); + + // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking + // in the tab page line, status line or window bar + StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs + : draw_winbar ? wp->w_winbar_click_defs + : wp->w_status_click_defs; + + stl_fill_click_defs(click_defs, tabtab, buf, maxwidth, wp == NULL); + +theend: + entered = false; +} + void win_redr_winbar(win_T *wp) { static bool entered = false; @@ -171,19 +448,7 @@ void win_redr_winbar(win_T *wp) 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; } @@ -213,11 +478,7 @@ void win_redr_ruler(win_T *wp, bool always) } 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; } @@ -266,7 +527,7 @@ void win_redr_ruler(win_T *wp, bool always) off = 0; } - if (!part_of_status && !ui_has_messages()) { + if (!part_of_status && p_ch == 0 && !ui_has(kUIMessages)) { return; } @@ -286,7 +547,7 @@ void win_redr_ruler(win_T *wp, bool always) 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); + 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); @@ -294,7 +555,7 @@ void win_redr_ruler(win_T *wp, bool always) // 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); + 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 @@ -342,9 +603,9 @@ void win_redr_ruler(win_T *wp, bool always) } ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr); + grid_puts(grid, buffer, row, this_ru_col + off, attr); grid_fill(grid, row, row + 1, - this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, + this_ru_col + off + (int)strlen(buffer), off + width, fillchar, fillchar, attr); } @@ -388,7 +649,6 @@ int fillchar_status(int *attr, win_T *wp) 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. @@ -397,235 +657,262 @@ void redraw_custom_statusline(win_T *wp) } 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 void ui_ext_tabline_update(void) { - static bool entered = false; - int attr; - int curattr; - int row; + Arena arena = ARENA_EMPTY; + + size_t n_tabs = 0; + FOR_ALL_TABS(tp) { + n_tabs++; + } + + Array tabs = arena_array(&arena, n_tabs); + FOR_ALL_TABS(tp) { + Dictionary tab_info = arena_dict(&arena, 2); + PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); + + win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; + get_trans_bufname(cwp->w_buffer); + PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(tabs, DICTIONARY_OBJ(tab_info)); + } + + size_t n_buffers = 0; + FOR_ALL_BUFFERS(buf) { + n_buffers += buf->b_p_bl ? 1 : 0; + } + + Array buffers = arena_array(&arena, n_buffers); + FOR_ALL_BUFFERS(buf) { + // Do not include unlisted buffers + if (!buf->b_p_bl) { + continue; + } + + Dictionary buffer_info = arena_dict(&arena, 2); + PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); + + get_trans_bufname(buf); + PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(buffers, DICTIONARY_OBJ(buffer_info)); + } + + ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); + arena_mem_free(arena_finish(&arena)); +} + +/// Draw the tab pages line at the top of the Vim window. +void draw_tabline(void) +{ + int tabcount = 0; + int tabwidth = 0; int col = 0; - int maxwidth; - int width; - int n; + int scol = 0; + int attr; + win_T *wp; + win_T *cwp; + int wincount; + int modified; + int c; int len; - int fillchar; - char buf[MAXPATHL]; - char_u *stl; + int attr_nosel = HL_ATTR(HLF_TP); + int attr_fill = HL_ATTR(HLF_TPF); 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; + int room; + int use_sep_chars = (t_colors < 8); - ScreenGrid *grid = &default_grid; + if (default_grid.chars == NULL) { + return; + } + redraw_tabline = false; - // 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) { + if (ui_has(kUITabline)) { + ui_ext_tabline_update(); 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 (tabline_height() < 1) { + return; + } - if (row < 0) { - return; - } + // Clear tab_page_click_defs: Clicking outside of tabs has no effect. + assert(tab_page_click_defs_size >= (size_t)Columns); + stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - 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); + // Use the 'tabline' option if it's set. + if (*p_tal != NUL) { + win_redr_custom(NULL, false, false); + } else { + FOR_ALL_TABS(tp) { + tabcount++; + } - 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)); + if (tabcount > 0) { + tabwidth = (Columns - 1 + tabcount / 2) / tabcount; } - } 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 (tabwidth < 6) { + tabwidth = 6; } - 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); + attr = attr_nosel; + tabcount = 0; + + FOR_ALL_TABS(tp) { + if (col >= Columns - 4) { + break; } - use_sandbox = was_set_insecurely(wp, "rulerformat", 0); - } else { - if (*wp->w_p_stl != NUL) { - stl = wp->w_p_stl; + scol = col; + + if (tp == curtab) { + cwp = curwin; + wp = firstwin; } else { - stl = p_stl; + cwp = tp->tp_curwin; + wp = tp->tp_firstwin; } - use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); - } - col += is_stl_global ? 0 : wp->w_wincol; - } + if (tp->tp_topframe == topframe) { + attr = win_hl_attr(cwp, HLF_TPS); + } + if (use_sep_chars && col > 0) { + grid_putchar(&default_grid, '|', 0, col++, attr); + } - if (maxwidth <= 0) { - goto theend; - } + if (tp->tp_topframe != topframe) { + attr = win_hl_attr(cwp, HLF_TP); + } - // 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; + grid_putchar(&default_grid, ' ', 0, col++, attr); - // 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; + modified = false; - // 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); + for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { + if (bufIsChanged(wp->w_buffer)) { + modified = true; + } + } - // fill up with "fillchar" - while (width < maxwidth && len < (int)sizeof(buf) - 1) { - len += utf_char2bytes(fillchar, buf + len); - width++; - } - buf[len] = NUL; + if (modified || wincount > 1) { + if (wincount > 1) { + vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); + len = (int)strlen(NameBuff); + if (col + len >= Columns - 3) { + break; + } + grid_puts_len(&default_grid, NameBuff, len, 0, col, + hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); + col += len; + } + if (modified) { + grid_puts_len(&default_grid, "+", 1, 0, col++, attr); + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + } - // Draw each snippet with the specified highlighting. - grid_puts_line_start(grid, row); + room = scol - col + tabwidth - 1; + if (room > 0) { + // Get buffer name in NameBuff[] + get_trans_bufname(cwp->w_buffer); + shorten_dir(NameBuff); + len = vim_strsize(NameBuff); + p = NameBuff; + while (len > room) { + len -= ptr2cells(p); + MB_PTR_ADV(p); + } + if (len > Columns - col - 1) { + len = Columns - col - 1; + } - 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; + grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr); + col += len; + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + + // Store the tab page number in tab_page_click_defs[], so that + // jump_to_mouse() knows where each one is. + tabcount++; + while (scol < col) { + tab_page_click_defs[scol++] = (StlClickDefinition) { + .type = kStlClickTabSwitch, + .tabnr = tabcount, + .func = NULL, + }; + } + } - 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]; + if (use_sep_chars) { + c = '_'; } else { - curattr = highlight_user[hltab[n].userhl - 1]; + c = ' '; } - } - // 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_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); - grid_puts_line_flush(false); + // Draw the 'showcmd' information if 'showcmdloc' == "tabline". + if (p_sc && *p_sloc == 't') { + const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); - // 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 (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, 0, + Columns - sc_width - (tabcount > 1) * 2, attr_nosel); + } + } - if (click_defs == NULL) { - goto theend; + // Put an "X" for closing the current tab if there are several. + if (tabcount > 1) { + grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); + tab_page_click_defs[Columns - 1] = (StlClickDefinition) { + .type = kStlClickTabClose, + .tabnr = 999, + .func = NULL, + }; + } } - 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; - } + // Reset the flag here again, in case evaluating 'tabline' causes it to be + // set. + redraw_tabline = false; +} + +/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1, +/// the v:lnum and v:relnum variables don't have to be updated. +/// +/// @param hlrec HL attributes (can be NULL) +/// @param stcp Status column attributes (can be NULL) +/// @return The width of the built status column string for line "lnum" +int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar, + char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp) +{ + bool fillclick = relnum >= 0 && lnum == wp->w_topline; + + if (relnum >= 0) { + set_vim_var_nr(VV_LNUM, lnum); + set_vim_var_nr(VV_RELNUM, relnum); } - while (col < maxwidth) { - click_defs[col++] = cur_click_def; + + StlClickRecord *clickrec; + char *stc = xstrdup(wp->w_p_stc); + int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar, + maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp); + xfree(stc); + + // Only update click definitions once per window per redraw + if (fillclick) { + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width, + &wp->w_statuscol_click_defs_size); + stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false); } -theend: - entered = false; + return width; } /// Build a string from the status line items in "fmt". @@ -646,15 +933,18 @@ theend: /// 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 opt_name The option name corresponding to "fmt" +/// @param opt_scope The scope corresponding to "opt_name" /// @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). +/// @param tabtab Tab clicks definition (can be NULL) +/// @param stcp Status column attributes (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) +int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_name, int opt_scope, + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab, + statuscol_T *stcp) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -669,6 +959,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san char *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; + const bool save_KeyTyped = KeyTyped; + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // const int called_emsg_before = called_emsg; + const int did_emsg_before = did_emsg; if (stl_items == NULL) { stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); @@ -682,6 +977,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } + // if "fmt" was set insecurely it needs to be evaluated in the sandbox + // "opt_name" will be NULL when caller is nvim_eval_statusline() + const int use_sandbox = opt_name ? was_set_insecurely(wp, opt_name, opt_scope) + : false; + // 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] == '!') { @@ -712,13 +1012,13 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // Get line & check if empty (cursorpos will show "0-1"). - const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); + const char *line_ptr = ml_get_buf(wp->w_buffer, lnum, false); bool empty_line = (*line_ptr == NUL); // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; - const size_t len = STRLEN(line_ptr); + 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. @@ -829,7 +1129,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // so `vim_strsize` will work. char *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; - long group_len = vim_strsize(t); + ptrdiff_t group_len = vim_strsize(t); // If the group contained internal items // and the group did not have a minimum width, @@ -915,7 +1215,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // 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; + ptrdiff_t 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; @@ -929,7 +1229,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san 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); + group_len = out_end_p - out_p; } out_p += group_len; // } @@ -1037,7 +1337,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } 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].cmd = tabtab ? xmemdupz(t, (size_t)(fmt_p - t)) : NULL; stl_items[curitem].minwid = minwid; fmt_p++; curitem++; @@ -1078,7 +1378,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // An invalid item was specified. // Continue processing on the next character of the format string. - if (vim_strchr(STL_ALL, *fmt_p) == NULL) { + if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) { fmt_p++; continue; } @@ -1100,17 +1400,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // get replaced with the fillchar fillable = false; if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); + xstrlcpy(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); + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); } - trans_characters((char *)NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); if (opt != STL_FILENAME) { - str = (char *)NameBuff; + str = NameBuff; } else { - str = path_tail((char *)NameBuff); + str = path_tail(NameBuff); } break; case STL_VIM_EXPR: // '{' @@ -1191,8 +1491,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san && 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 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; @@ -1217,8 +1517,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); + // Overload %l with v:lnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_LNUM); + } + } else { + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); + } break; case STL_NUMLINES: @@ -1255,6 +1561,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san str = buf_tmp; break; + case STL_SHOWCMD: + if (p_sc && (opt_name == NULL || strcmp(opt_name, p_sloc) == 0)) { + str = showcmd_buf; + } + break; + case STL_ARGLISTSTAT: fillable = false; @@ -1278,7 +1590,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; case STL_PAGENUM: - num = printer_page_num; + num = 0; break; case STL_BUFNO: @@ -1310,9 +1622,16 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san case STL_ROFLAG: case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + // Overload %r with v:relnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_RELNUM); + } + } else { + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } } break; @@ -1324,12 +1643,39 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; + case STL_FOLDCOL: // 'C' for 'statuscolumn' + case STL_SIGNCOL: { // 's' for 'statuscolumn' + if (stcp == NULL) { + break; + } + + bool fold = opt == STL_FOLDCOL; + *buf_tmp = NUL; + for (int i = 0; i <= SIGN_SHOW_MAX; i++) { + char *p = fold ? stcp->fold_text : stcp->sign_text[i]; + if ((!p || !*p) && *buf_tmp == NUL) { + break; + } + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p + strlen(buf_tmp); + stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr + : stcp->sign_attr[i]; + curitem++; + if (!p || (fold && i)) { + str = buf_tmp; + break; + } + STRCAT(buf_tmp, p); + } + 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) { + && 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; @@ -1342,11 +1688,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // 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) { + && 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); + *t = (char)TOUPPER_LOC((uint8_t)(*t)); } str = buf_tmp; } @@ -1562,7 +1908,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // Advance the output buffer position to the end of the // number we just printed - out_p += STRLEN(out_p); + out_p += strlen(out_p); // Otherwise, there was nothing to print so mark the item as empty } else { @@ -1595,6 +1941,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); + // Return truncated width for 'statuscolumn' + if (stcp != NULL && width > maxwidth) { + stcp->truncate = width - maxwidth; + } if (maxwidth > 0 && width > maxwidth) { // Result is too long, must truncate somewhere. int item_idx = 0; @@ -1642,6 +1992,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // the truncation point for (int i = 0; i < itemcnt; i++) { if (stl_items[i].start > trunc_p) { + for (int j = i; j < itemcnt; j++) { + if (stl_items[j].type == ClickFunc) { + XFREE_CLEAR(stl_items[j].cmd); + } + } itemcnt = i; break; } @@ -1670,7 +2025,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san if (width + 1 < maxwidth) { // Advance the pointer to the end of the string - trunc_p = trunc_p + STRLEN(trunc_p); + trunc_p = trunc_p + strlen(trunc_p); } // Fill up for half a double-wide character. @@ -1706,7 +2061,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // 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) { + && 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; @@ -1804,5 +2159,18 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san curwin->w_redr_type = save_redr_type; } + // Check for an error. If there is one the display will be messed up and + // might loop redrawing. Avoid that by making the corresponding option + // empty. + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // if (called_emsg > called_emsg_before) + if (opt_name && did_emsg > did_emsg_before) { + set_string_option_direct(opt_name, -1, "", OPT_FREE | opt_scope, SID_ERROR); + } + + // A user function may reset KeyTyped, restore it. + KeyTyped = save_KeyTyped; + return width; } |