diff options
Diffstat (limited to 'src/nvim/statusline.c')
-rw-r--r-- | src/nvim/statusline.c | 564 |
1 files changed, 415 insertions, 149 deletions
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 039fe5d586..93334a1d75 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -158,6 +158,243 @@ 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)); + while (col < len) { + click_defs[col++] = cur_click_def; + } + 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; + } + } + while (col < width) { + click_defs[col++] = cur_click_def; + } +} + +/// 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); + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // Make all characters printable. + p = transstr(buf, true); + len = (int)STRLCPY(buf, p, sizeof(buf)); + len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; + xfree(p); + + // fill up with "fillchar" + while (width < maxwidth && len < (int)sizeof(buf) - 1) { + len += utf_char2bytes(fillchar, buf + len); + width++; + } + buf[len] = NUL; + + // Draw each snippet with the specified highlighting. + grid_puts_line_start(grid, row); + + curattr = attr; + p = buf; + for (n = 0; hltab[n].start != NULL; n++) { + int textlen = (int)(hltab[n].start - p); + grid_puts_len(grid, p, textlen, row, col, curattr); + col += vim_strnsize(p, textlen); + p = hltab[n].start; + + if (hltab[n].userhl == 0) { + curattr = attr; + } else if (hltab[n].userhl < 0) { + curattr = syn_id2attr(-hltab[n].userhl); + } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { + curattr = highlight_stlnc[hltab[n].userhl - 1]; + } else { + curattr = highlight_user[hltab[n].userhl - 1]; + } + } + // Make sure to use an empty string instead of p, if p is beyond buf + len. + grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + + grid_puts_line_flush(false); + + // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking + // in the tab page line, status line or window bar + StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs + : draw_winbar ? wp->w_winbar_click_defs + : wp->w_status_click_defs; + + 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; @@ -385,182 +622,211 @@ void redraw_custom_statusline(win_T *wp) 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((char *)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((char *)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 *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; + int attr_nosel = HL_ATTR(HLF_TP); + int attr_fill = HL_ATTR(HLF_TPF); + char_u *p; + 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; - 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 (tabline_height() < 1) { + return; + } - if (row < 0) { - return; + // Use the 'tabline' option if it's set. + if (*p_tal != NUL) { + win_redr_custom(NULL, false, false); + } else { + FOR_ALL_TABS(tp) { + tabcount++; } - 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, (long)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, (long)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 (tabcount > 0) { + tabwidth = (Columns - 1 + tabcount / 2) / tabcount; + } - 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; + if (tabwidth < 6) { + tabwidth = 6; + } + + attr = attr_nosel; + tabcount = 0; + + FOR_ALL_TABS(tp) { + if (col >= Columns - 4) { + break; } - 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); + + scol = col; + + if (tp == curtab) { + cwp = curwin; + wp = firstwin; + } else { + cwp = tp->tp_curwin; + wp = tp->tp_firstwin; } - } 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 (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 = xstrdup(stl); - width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, - opt_scope, 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((char *)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((char *)NameBuff); + p = (char_u *)NameBuff; + while (len > room) { + len -= ptr2cells((char *)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, p, textlen, row, col, curattr); - col += vim_strnsize(p, textlen); - p = hltab[n].start; + grid_puts_len(&default_grid, (char *)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 = ' '; + } + grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); + + // Put an "X" for closing the current tab if there are several. + if (first_tabpage->tp_next != NULL) { + grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); + tab_page_click_defs[Columns - 1] = (StlClickDefinition) { + .type = kStlClickTabClose, + .tabnr = 999, + .func = NULL, + }; } } - // 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; + // Reset the flag here again, in case evaluating 'tabline' causes it to be + // set. + redraw_tabline = false; } /// Build a string from the status line items in "fmt". |