diff options
Diffstat (limited to 'src/nvim/drawline.c')
| -rw-r--r-- | src/nvim/drawline.c | 3190 |
1 files changed, 1695 insertions, 1495 deletions
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 01ff207c2b..e0887ed1d0 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1,18 +1,15 @@ -// 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 - // drawline.c: Functions for drawing window lines on the screen. -// This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level. +// This is the middle level, drawscreen.c is the top and grid.c the lower level. #include <assert.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "nvim/arabic.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -21,10 +18,10 @@ #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawline.h" +#include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/extmark_defs.h" #include "nvim/fold.h" -#include "nvim/garray.h" +#include "nvim/fold_defs.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" @@ -37,10 +34,10 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/plines.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/quickfix.h" -#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" @@ -48,12 +45,11 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" -#define MB_FILLER_CHAR '<' // character used when a double-width character - // doesn't fit. +#define MB_FILLER_CHAR '<' // character used when a double-width character doesn't fit. /// possible draw states in win_line(), drawn in sequence. typedef enum { @@ -68,19 +64,104 @@ typedef enum { WL_LINE, // text in the line } LineDrawState; -/// for line_putchar. Contains the state that needs to be remembered from -/// putting one character to the next. +/// structure with variables passed between win_line() and other functions typedef struct { - const char *p; - int prev_c; ///< previous Arabic character - int prev_c1; ///< first composing char for prev_c -} LineState; -#define LINE_STATE(p) { p, 0, 0 } + LineDrawState draw_state; ///< what to draw next + + linenr_T lnum; ///< line number to be drawn + foldinfo_T foldinfo; ///< fold info for this line + + int startrow; ///< first row in the window to be drawn + int row; ///< row in the window, excl w_winrow + + colnr_T vcol; ///< virtual column, before wrapping + int col; ///< visual column on screen, after wrapping + int boguscols; ///< nonexistent columns added to "col" to force wrapping + int vcol_off; ///< offset for concealed characters + + int off; ///< offset relative start of line + + int cul_attr; ///< set when 'cursorline' active + int line_attr; ///< attribute for the whole line + int line_attr_lowprio; ///< low-priority attribute for the line + + int fromcol; ///< start of inverting + int tocol; ///< end of inverting + + colnr_T vcol_sbr; ///< virtual column after showbreak + bool need_showbreak; ///< overlong line, skipping first x chars + + int char_attr; ///< attributes for next character + + int n_extra; ///< number of extra bytes + int n_attr; ///< chars with special attr + char *p_extra; ///< string of extra chars, plus NUL, only used + ///< when c_extra and c_final are NUL + int extra_attr; ///< attributes for p_extra + int c_extra; ///< extra chars, all the same + int c_final; ///< final char, mandatory if set + + int n_closing; ///< number of chars in fdc which will be closing + + bool extra_for_extmark; ///< n_extra set for inline virtual text + + // saved "extra" items for when draw_state becomes WL_LINE (again) + int saved_n_extra; + char *saved_p_extra; + bool saved_extra_for_extmark; + int saved_c_extra; + int saved_c_final; + int saved_char_attr; + + char extra[57]; ///< sign, line number and 'fdc' must fit in here + + hlf_T diff_hlf; ///< type of diff highlighting + + int n_virt_lines; ///< nr of virtual lines + int filler_lines; ///< nr of filler lines to be drawn + int filler_todo; ///< nr of filler lines still to do + 1 + SignTextAttrs sattrs[SIGN_SHOW_MAX]; ///< sign attributes for the sign column + /// do consider wrapping in linebreak mode only after encountering + /// a non whitespace char + bool need_lbr; + + VirtText virt_inline; + size_t virt_inline_i; + HlMode virt_inline_hl_mode; + + bool reset_extra_attr; + + int skip_cells; ///< nr of cells to skip for w_leftcol + ///< or w_skipcol or concealing + int skipped_cells; ///< nr of skipped cells for virtual text + ///< to be added to wlv.vcol later +} winlinevars_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "drawline.c.generated.h" #endif +static char *extra_buf = NULL; +static size_t extra_buf_size = 0; + +static char *get_extra_buf(size_t size) +{ + size = MAX(size, 64); + if (extra_buf_size < size) { + xfree(extra_buf); + extra_buf = xmalloc(size); + extra_buf_size = size; + } + return extra_buf; +} + +#ifdef EXITFREE +void drawline_free_all_mem(void) +{ + xfree(extra_buf); +} +#endif + /// Advance **color_cols /// /// @return true when there are columns to draw. @@ -114,7 +195,7 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col) return; } - width1 = wp->w_width - cur_col_off; + width1 = wp->w_width_inner - cur_col_off; width2 = width1 + win_col_off2(wp); *left_col = 0; @@ -137,110 +218,96 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col) /// Put a single char from an UTF-8 buffer into a line buffer. /// -/// Handles composing chars and arabic shaping state. -static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol) +/// If `*pp` is a double-width char and only one cell is left, emit a space, +/// and don't advance *pp +/// +/// Handles composing chars +static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells, int vcol) { - const char *p = s->p; + const char *p = *pp; int cells = utf_ptr2cells(p); int c_len = utfc_ptr2len(p); - int u8c, u8cc[MAX_MCO]; + assert(maxcells > 0); if (cells > maxcells) { - return -1; + dest[0] = schar_from_ascii(' '); + return 1; } - u8c = utfc_ptr2char(p, u8cc); + if (*p == TAB) { cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells); + } + + if (cells < maxcells && dest[cells] == 0) { + dest[cells] = schar_from_ascii(' '); + } + if (*p == TAB) { for (int c = 0; c < cells; c++) { - schar_from_ascii(dest[c], ' '); + dest[c] = schar_from_ascii(' '); } - goto done; - } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], *p); - s->prev_c = u8c; } else { - if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { - // Do Arabic shaping. - int pc, pc1, nc; - int pcc[MAX_MCO]; - int firstbyte = (uint8_t)(*p); - - // The idea of what is the previous and next - // character depends on 'rightleft'. - if (rl) { - pc = s->prev_c; - pc1 = s->prev_c1; - nc = utf_ptr2char(p + c_len); - s->prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(p + c_len, pcc); - nc = s->prev_c; - pc1 = pcc[0]; - } - s->prev_c = u8c; - - u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc); - } else { - s->prev_c = u8c; + int u8c; + dest[0] = utfc_ptr2schar(p, &u8c); + if (cells > 1) { + dest[1] = 0; } - schar_from_cc(dest[0], u8c, u8cc); - } - if (cells > 1) { - dest[1][0] = 0; } -done: - s->p += c_len; - return cells; -} -static inline void provider_err_virt_text(linenr_T lnum, char *err) -{ - Decoration err_decor = DECORATION_INIT; - int hl_err = syn_check_group(S_LEN("ErrorMsg")); - kv_push(err_decor.virt_text, - ((VirtTextChunk){ .text = err, - .hl_id = hl_err })); - err_decor.virt_text_width = (int)mb_string2cells(err); - decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0); + *pp += c_len; + return cells; } -static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, - int win_row) +static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row) { DecorState *state = &decor_state; + const int max_col = wp->w_grid.cols; int right_pos = max_col; bool do_eol = state->eol_col > -1; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange *item = &kv_A(state->active, i); - if (!(item->start_row == state->row - && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) { + if (!(item->start_row == state->row && decor_virt_pos(item))) { continue; } - if (item->win_col == -1) { - if (item->decor.virt_text_pos == kVTRightAlign) { - right_pos -= item->decor.virt_text_width; - item->win_col = right_pos; - } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { - item->win_col = state->eol_col; - } else if (item->decor.virt_text_pos == kVTWinCol) { - item->win_col = MAX(item->decor.col + col_off, 0); + + DecorVirtText *vt = NULL; + if (item->kind == kDecorKindVirtText) { + assert(item->data.vt); + vt = item->data.vt; + } + if (decor_virt_pos(item) && item->draw_col == -1) { + bool updated = true; + VirtTextPos pos = decor_virt_pos_kind(item); + if (pos == kVPosRightAlign) { + right_pos -= vt->width; + item->draw_col = right_pos; + } else if (pos == kVPosEndOfLine && do_eol) { + item->draw_col = state->eol_col; + } else if (pos == kVPosWinCol) { + item->draw_col = MAX(col_off + vt->col, 0); + } else { + updated = false; + } + if (updated && (item->draw_col < 0 || item->draw_col >= wp->w_grid.cols)) { + // Out of window, don't draw at all. + item->draw_col = INT_MIN; } } - if (item->win_col < 0) { + if (item->draw_col < 0) { continue; } - int col; - if (item->decor.ui_watched) { + int col = 0; + if (item->kind == kDecorKindUIWatched) { // send mark position to UI - col = item->win_col; - WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col }; + col = item->draw_col; + WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, col }; kv_push(win_extmark_arr, m); } - if (kv_size(item->decor.virt_text)) { - col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text, - item->decor.hl_mode, max_col, item->win_col - col_off); + if (vt) { + int vcol = item->draw_col - col_off; + col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, + vt->hl_mode, max_col, vcol); } - item->win_col = -2; // deactivate - if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + item->draw_col = INT_MIN; // deactivate + if (vt && vt->pos == kVPosEndOfLine && do_eol) { state->eol_col = col + 1; } @@ -251,28 +318,22 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, int vcol) { - LineState s = LINE_STATE(""); + const char *p = ""; int virt_attr = 0; size_t virt_pos = 0; while (col < max_col) { - if (!*s.p) { + if (!*p) { if (virt_pos >= kv_size(vt)) { break; } virt_attr = 0; - do { - s.p = kv_A(vt, virt_pos).text; - int hl_id = kv_A(vt, virt_pos).hl_id; - virt_attr = hl_combine_attr(virt_attr, - hl_id > 0 ? syn_id2attr(hl_id) : 0); - virt_pos++; - } while (!s.p && virt_pos < kv_size(vt)); - if (!s.p) { + p = next_virt_text_chunk(vt, &virt_pos, &virt_attr); + if (p == NULL) { break; } } - if (!*s.p) { + if (*p == NUL) { continue; } int attr; @@ -280,19 +341,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, if (hl_mode == kHlModeCombine) { attr = hl_combine_attr(linebuf_attr[col], virt_attr); } else if (hl_mode == kHlModeBlend) { - through = (*s.p == ' '); + through = (*p == ' '); attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); } else { attr = virt_attr; } schar_T dummy[2]; - int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col], - max_col - col, false, vcol); - // if we failed to emit a char, we still need to advance - cells = MAX(cells, 1); - + int maxcells = max_col - col; + int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col], + maxcells, vcol); for (int c = 0; c < cells; c++) { - linebuf_attr[col++] = attr; + linebuf_attr[col] = attr; + col++; } vcol += cells; } @@ -300,102 +360,243 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, } /// Return true if CursorLineSign highlight is to be used. -static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum) { return wp->w_p_cul - && lnum == wp->w_cursor.lnum + && lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR); } -// Get information needed to display the sign in line 'lnum' in window 'wp'. -// If 'nrcol' is true, the sign is going to be displayed in the number column. -// Otherwise the sign is going to be displayed in the sign column. -// -// @param count max number of signs -// @param[out] n_extrap number of characters from pp_extra to display -// @param sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[], - int row, int startrow, int filler_lines, int filler_todo, - int *c_extrap, int *c_finalp, char *extra, size_t extra_size, - char **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, - int cul_attr) +static char fdc_buf[MB_MAXCHAR * 10 + 1]; + +/// Setup for drawing the 'foldcolumn', if there is one. +static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv) { - // Draw cells with the sign value or blank. - *c_extrap = ' '; - *c_finalp = NUL; - if (nrcol) { - *n_extrap = number_width(wp) + 1; + int fdc = compute_foldcolumn(wp, 0); + if (fdc <= 0) { + return; + } + + // Use a separate buffer as `extra_buf` might be in use. + wlv->n_extra = (int)fill_foldcolumn(fdc_buf, wp, wlv->foldinfo, wlv->lnum, + &wlv->n_closing); + fdc_buf[wlv->n_extra] = NUL; + wlv->p_extra = fdc_buf; + wlv->c_extra = NUL; + wlv->c_final = NUL; + if (use_cursor_line_highlight(wp, wlv->lnum)) { + wlv->char_attr = win_hl_attr(wp, HLF_CLF); } else { - if (use_cursor_line_sign(wp, lnum)) { - *char_attrp = win_hl_attr(wp, HLF_CLS); + wlv->char_attr = win_hl_attr(wp, HLF_FC); + } +} + +/// Fills the foldcolumn at "p" for window "wp". +/// Only to be called when 'foldcolumn' > 0. +/// +/// @param[out] p Char array to write into +/// @param lnum Absolute current line number +/// @param closed Whether it is in 'foldcolumn' mode +/// +/// Assume monocell characters +/// @return number of chars added to \param p +size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing) +{ + int i = 0; + int fdc = compute_foldcolumn(wp, 0); // available cell width + size_t char_counter = 0; + int symbol = 0; + int len = 0; + bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; + // Init to all spaces. + memset(p, ' ', MB_MAXCHAR * (size_t)fdc + 1); + + int level = foldinfo.fi_level; + + // If the column is too narrow, we start at the lowest level that + // fits and use numbers to indicate the depth. + int first_level = level - fdc - closed + 1; + if (first_level < 1) { + first_level = 1; + } + + for (i = 0; i < MIN(fdc, level); i++) { + if (foldinfo.fi_lnum == lnum + && first_level + i >= foldinfo.fi_low_level) { + symbol = wp->w_p_fcs_chars.foldopen; + } else if (first_level == 1) { + symbol = wp->w_p_fcs_chars.foldsep; + } else if (first_level + i <= 9) { + symbol = '0' + first_level + i; } else { - *char_attrp = win_hl_attr(wp, HLF_SC); + symbol = '>'; + } + + len = utf_char2bytes(symbol, &p[char_counter]); + char_counter += (size_t)len; + if (first_level + i >= level) { + i++; + break; } - *n_extrap = win_signcol_width(wp); } - if (row == startrow + filler_lines && filler_todo <= 0) { - SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth); - if (sattr != NULL) { - *pp_extra = sattr->text; - if (*pp_extra != NULL) { - *c_extrap = NUL; - *c_finalp = NUL; - - if (nrcol) { - int n, width = number_width(wp) - 2; - for (n = 0; n < width; n++) { - extra[n] = ' '; - } - extra[n] = NUL; - STRCAT(extra, *pp_extra); - STRCAT(extra, " "); - *pp_extra = extra; - *n_extrap = (int)strlen(*pp_extra); - } else { - size_t symbol_blen = strlen(*pp_extra); + int n_closing_val = i; - // TODO(oni-link): Is sign text already extended to - // full cell width? - assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra))); - // symbol(s) bytes + (filling spaces) (one byte each) - *n_extrap = (int)symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells(*pp_extra); + if (closed) { + if (symbol != 0) { + // rollback previous write + char_counter -= (size_t)len; + memset(&p[char_counter], ' ', (size_t)len); + n_closing_val--; + } + len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); + char_counter += (size_t)len; + } - assert(extra_size > symbol_blen); - memset(extra, ' ', extra_size); - memcpy(extra, *pp_extra, symbol_blen); + if (n_closing) { + *n_closing = n_closing_val; + } - *pp_extra = extra; - (*pp_extra)[*n_extrap] = NUL; - } - } + return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc); +} - if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) { - *char_attrp = cul_attr; - } else { - *char_attrp = sattr->hl_attr_id; - } +/// Get information needed to display the sign in line "wlv->lnum" in window "wp". +/// If "nrcol" is true, the sign is going to be displayed in the number column. +/// Otherwise the sign is going to be displayed in the sign column. If there is no +/// sign, draw blank cells instead. +static void get_sign_display_info(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, + int sign_cul_attr) +{ + SignTextAttrs sattr = wlv->sattrs[sign_idx]; + wlv->c_final = NUL; + + if (sattr.text && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { + size_t fill = nrcol ? (size_t)number_width(wp) - SIGN_WIDTH : 0; + size_t sign_len = strlen(sattr.text); + + // Spaces + sign: " " + ">>" + ' ' + wlv->n_extra = (int)(fill + sign_len + nrcol); + if (nrcol) { + memset(wlv->extra, ' ', (size_t)wlv->n_extra); + } + memcpy(wlv->extra + fill, sattr.text, sign_len); + wlv->p_extra = wlv->extra; + wlv->c_extra = NUL; + wlv->char_attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr) + ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0; + } else { + wlv->c_extra = ' '; + wlv->n_extra = nrcol ? number_width(wp) + 1 : SIGN_WIDTH; + if (!nrcol) { + wlv->char_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); } } } -static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr, - int *num_attr, int *cul_attr) +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) { - HlPriAttr line_attrs = { *line_attr, 0 }; - HlPriAttr num_attrs = { *num_attr, 0 }; - HlPriAttr cul_attrs = { *cul_attr, 0 }; + linenr_T num; + char *fmt = "%*" PRIdLINENR " "; - // TODO(bfredl, vigoux): line_attr should not take priority over decoration! - int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs); - decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs); + if (wp->w_p_nu && !wp->w_p_rnu) { + // 'number' + 'norelativenumber' + num = lnum; + } else { + // 'relativenumber', don't use negative numbers + num = abs(get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { + // 'number' + 'relativenumber' + num = lnum; + fmt = "%-*" PRIdLINENR " "; + } + } - *line_attr = line_attrs.attr_id; - *num_attr = num_attrs.attr_id; - *cul_attr = cul_attrs.attr_id; + snprintf(buf, buf_len, fmt, number_width(wp), num); +} - return num_signs; +/// Return true if CursorLineNr highlight is to be used for the number column. +/// - 'cursorline' must be set +/// - "wlv->lnum" must be the cursor line +/// - 'cursorlineopt' has "number" +/// - don't highlight filler lines (when in diff mode) +/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number +/// itself on the first screenline of the wrapped line, otherwise highlight the number column of +/// all screenlines of the wrapped line. +static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv) +{ + return wp->w_p_cul + && wlv->lnum == wp->w_cursorline + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (wlv->row == wlv->startrow + wlv->filler_lines + || (wlv->row > wlv->startrow + wlv->filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE))); +} + +static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) +{ + if (use_cursor_line_nr(wp, wlv)) { + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + return win_hl_attr(wp, HLF_CLN); + } + + if (wp->w_p_rnu) { + if (wlv->lnum < wp->w_cursor.lnum) { + // Use LineNrAbove + return win_hl_attr(wp, HLF_LNA); + } + if (wlv->lnum > wp->w_cursor.lnum) { + // Use LineNrBelow + return win_hl_attr(wp, HLF_LNB); + } + } + + return win_hl_attr(wp, HLF_N); +} + +/// Display the absolute or relative line number. After the first row fill with +/// blanks when the 'n' flag isn't in 'cpo'. +static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr) +{ + bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; + + if ((wp->w_p_nu || wp->w_p_rnu) + && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n) + // there is no line number in a wrapped line when "n" is in + // 'cpoptions', but 'breakindent' assumes it anyway. + && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) { + // If 'signcolumn' is set to 'number' and a sign is present in "lnum", + // then display the sign instead of the line number. + if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text) { + get_sign_display_info(true, wp, wlv, 0, sign_cul_attr); + } else { + // Draw the line number (empty space after wrapping). + if (wlv->row == wlv->startrow + wlv->filler_lines + && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) { + get_line_number_str(wp, wlv->lnum, wlv->extra, sizeof(wlv->extra)); + if (wp->w_skipcol > 0 && wlv->startrow == 0) { + for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; wlv->p_extra++) { + *wlv->p_extra = '-'; + } + } + if (wp->w_p_rl) { // reverse line numbers + char *num = skipwhite(wlv->extra); + rl_mirror_ascii(num, skiptowhite(num)); + } + wlv->p_extra = wlv->extra; + wlv->c_extra = NUL; + } else { + wlv->c_extra = ' '; + } + wlv->c_final = NUL; + wlv->n_extra = number_width(wp) + 1; + if (sign_num_attr > 0) { + wlv->char_attr = sign_num_attr; + } else { + wlv->char_attr = get_line_number_attr(wp, wlv); + } + } + } } /// Prepare and build the 'statuscolumn' string for line "lnum" in window "wp". @@ -404,37 +605,10 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int /// the start of the buffer line "lnum" and once for the wrapped lines. /// /// @param[out] stcp Status column attributes -static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, - int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp, - foldinfo_T foldinfo, SignTextAttrs *sattrs) +static void get_statuscol_str(win_T *wp, linenr_T lnum, int virtnum, statuscol_T *stcp) { - long relnum = -1; - bool use_cul = use_cursor_line_sign(wp, lnum); - int virtnum = row - startrow - filler_lines; - - // When called the first time for line "lnum" set num_attr - if (stcp->num_attr == 0) { - stcp->num_attr = sign_num_attr ? sign_num_attr - : get_line_number_attr(wp, lnum, row, startrow, filler_lines); - } - // When called for the first non-filler row of line "lnum" set num v:vars and fold column - if (virtnum == 0) { - relnum = labs(get_cursor_rel_lnum(wp, lnum)); - if (compute_foldcolumn(wp, 0)) { - size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum); - stcp->fold_text[n] = NUL; - stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC); - } - } - // Make sure to clear->set->clear sign column for filler->first->wrapped lines - int i = 0; - for (; i < wp->w_scwidth; i++) { - SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth); - stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " "; - stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id) - : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC); - } - stcp->sign_text[i] = NULL; + // When called for the first non-filler row of line "lnum" set num v:vars + linenr_T relnum = virtnum == 0 ? abs(get_cursor_rel_lnum(wp, lnum)) : -1; // When a buffer's line count has changed, make a best estimate for the full // width of the status column by building with "w_nrwidth_line_count". Add @@ -442,17 +616,23 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) { wp->w_statuscol_line_count = wp->w_nrwidth_line_count; set_vim_var_nr(VV_VIRTNUM, 0); - build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp->width, - ' ', stcp->text, &stcp->hlrec, stcp); - stcp->width += stcp->truncate; + build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp); + if (stcp->truncate > 0) { + // Add truncated width to avoid unnecessary redraws + int addwidth = MIN(stcp->truncate, MAX_NUMBERWIDTH - wp->w_nrwidth); + stcp->truncate = 0; + stcp->width += addwidth; + wp->w_nrwidth += addwidth; + wp->w_nrwidth_width = wp->w_nrwidth; + wp->w_valid &= ~VALID_WCOL; + } } set_vim_var_nr(VV_VIRTNUM, virtnum); - int width = build_statuscol_str(wp, lnum, relnum, stcp->width, - ' ', stcp->text, &stcp->hlrec, stcp); + int width = build_statuscol_str(wp, lnum, relnum, stcp); // Force a redraw in case of error or when truncated if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) { - if (stcp->truncate) { // Avoid truncating 'statuscolumn' + if (stcp->truncate > 0) { // Avoid truncating 'statuscolumn' wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate); wp->w_nrwidth_width = wp->w_nrwidth; } else { // 'statuscolumn' reset due to error @@ -478,201 +658,374 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i } /// Get information needed to display the next segment in the 'statuscolumn'. -/// If not yet at the end, prepare for next segment and decrement "draw_state". +/// If not yet at the end, prepare for next segment and decrement "wlv->draw_state". /// /// @param stcp Status column attributes -/// @param[out] draw_state Current draw state in win_line() -static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_state, int *char_attr, - int *n_extrap, int *c_extrap, int *c_finalp, char **pp_extra) +/// @param[in,out] wlv +static void get_statuscol_display_info(statuscol_T *stcp, winlinevars_T *wlv) { - *c_extrap = NUL; - *c_finalp = NUL; + wlv->c_extra = NUL; + wlv->c_final = NUL; do { - *draw_state = WL_STC; - *char_attr = stcp->cur_attr; - *pp_extra = stcp->textp; - *n_extrap = (int)((stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end) - stcp->textp); + wlv->draw_state = WL_STC; + wlv->char_attr = stcp->cur_attr; + wlv->p_extra = stcp->textp; + char *const section_end = stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end; + wlv->n_extra = (int)(section_end - stcp->textp); // Prepare for next highlight section if not yet at the end - if (stcp->textp + *n_extrap < stcp->text_end) { + if (section_end < stcp->text_end) { int hl = stcp->hlrecp->userhl; stcp->textp = stcp->hlrecp->start; - stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl) - : hl > 0 ? hl : stcp->num_attr; + stcp->cur_attr = hl < 0 ? syn_id2attr(-hl) : stcp->num_attr; stcp->hlrecp++; - *draw_state = WL_STC - 1; + wlv->draw_state = WL_STC - 1; } // Skip over empty highlight sections - } while (*n_extrap == 0 && stcp->textp < stcp->text_end); -} - -/// Return true if CursorLineNr highlight is to be used for the number column. -/// -/// - 'cursorline' must be set -/// - lnum must be the cursor line -/// - 'cursorlineopt' has "number" -/// - don't highlight filler lines (when in diff mode) -/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number -/// itself on the first screenline of the wrapped line, otherwise highlight the number column of -/// all screenlines of the wrapped line. -static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) -{ - return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR) - && (row == startrow + filler_lines - || (row > startrow + filler_lines - && (wp->w_p_culopt_flags & CULOPT_LINE))); + } while (wlv->n_extra == 0 && stcp->textp < stcp->text_end); + if (wlv->n_extra > 0) { + static char transbuf[(MAX_NUMBERWIDTH + 9 + 9 * 2) * MB_MAXBYTES + 1]; + wlv->n_extra = (int)transstr_buf(wlv->p_extra, wlv->n_extra, transbuf, sizeof transbuf, true); + wlv->p_extra = transbuf; + } } -static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) +static void handle_breakindent(win_T *wp, winlinevars_T *wlv) { - long num; - char *fmt = "%*ld "; + if (wp->w_briopt_sbr && wlv->draw_state == WL_BRI - 1 + && *get_showbreak_value(wp) != NUL) { + // draw indent after showbreak value + wlv->draw_state = WL_BRI; + } else if (wp->w_briopt_sbr && wlv->draw_state == WL_SBR) { + // after the showbreak, draw the breakindent + wlv->draw_state = WL_BRI - 1; + } - if (wp->w_p_nu && !wp->w_p_rnu) { - // 'number' + 'norelativenumber' - num = (long)lnum; - } else { - // 'relativenumber', don't use negative numbers - num = labs((long)get_cursor_rel_lnum(wp, lnum)); - if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { - // 'number' + 'relativenumber' - num = lnum; - fmt = "%-*ld "; + // draw 'breakindent': indent wrapped text accordingly + if (wlv->draw_state == WL_BRI - 1 && wlv->n_extra == 0) { + wlv->draw_state = WL_BRI; + // if wlv->need_showbreak is set, breakindent also applies + if (wp->w_p_bri && (wlv->row != wlv->startrow || wlv->need_showbreak) + && wlv->filler_lines == 0) { + wlv->char_attr = 0; + if (wlv->diff_hlf != (hlf_T)0) { + wlv->char_attr = win_hl_attr(wp, (int)wlv->diff_hlf); + } + wlv->p_extra = NULL; + wlv->c_extra = ' '; + wlv->c_final = NUL; + wlv->n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum)); + if (wlv->row == wlv->startrow) { + wlv->n_extra -= win_col_off2(wp); + if (wlv->n_extra < 0) { + wlv->n_extra = 0; + } + } + if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) { + wlv->need_showbreak = false; + } + // Correct end of highlighted area for 'breakindent', + // required wen 'linebreak' is also set. + if (wlv->tocol == wlv->vcol) { + wlv->tocol += wlv->n_extra; + } } } - - snprintf(buf, buf_len, fmt, number_width(wp), num); } -static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv) { - if (wp->w_p_rnu) { - if (lnum < wp->w_cursor.lnum) { - // Use LineNrAbove - return win_hl_attr(wp, HLF_LNA); - } - if (lnum > wp->w_cursor.lnum) { - // Use LineNrBelow - return win_hl_attr(wp, HLF_LNB); + if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) { + // TODO(bfredl): check this doesn't inhibit TUI-style + // clear-to-end-of-line. + wlv->c_extra = ' '; + wlv->c_final = NUL; + wlv->n_extra = wp->w_grid.cols - wlv->col; + wlv->char_attr = 0; + } else if (wlv->filler_todo > 0) { + // Draw "deleted" diff line(s) + if (char2cells(wp->w_p_fcs_chars.diff) > 1) { + wlv->c_extra = '-'; + wlv->c_final = NUL; + } else { + wlv->c_extra = wp->w_p_fcs_chars.diff; + wlv->c_final = NUL; } + wlv->n_extra = wp->w_grid.cols - wlv->col; + wlv->char_attr = win_hl_attr(wp, HLF_DED); } - if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - return win_hl_attr(wp, HLF_CLN); - } + char *const sbr = get_showbreak_value(wp); + if (*sbr != NUL && wlv->need_showbreak) { + // Draw 'showbreak' at the start of each broken line. + wlv->p_extra = sbr; + wlv->c_extra = NUL; |