diff options
| author | Josh Rahm <rahm@google.com> | 2022-08-19 12:26:08 -0600 |
|---|---|---|
| committer | Josh Rahm <rahm@google.com> | 2022-08-19 13:06:41 -0600 |
| commit | a7237662f96933efe29eed8212464571e3778cd0 (patch) | |
| tree | 27930202726b4251437c8cfa53069f65b4db90dc /src/nvim/drawline.c | |
| parent | 02292344929069ea63c0bb872cc22d552d86b67f (diff) | |
| parent | b2f979b30beac67906b2dd717fcb6a34f46f5e54 (diff) | |
| download | rneovim-tmp.tar.gz rneovim-tmp.tar.bz2 rneovim-tmp.zip | |
Merge branch 'master' of https://github.com/neovim/neovim into rahmtmp
Diffstat (limited to 'src/nvim/drawline.c')
| -rw-r--r-- | src/nvim/drawline.c | 2706 |
1 files changed, 2706 insertions, 0 deletions
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c new file mode 100644 index 0000000000..95026ff8ed --- /dev/null +++ b/src/nvim/drawline.c @@ -0,0 +1,2706 @@ +// 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. + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/arabic.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/cursor_shape.h" +#include "nvim/diff.h" +#include "nvim/drawline.h" +#include "nvim/fold.h" +#include "nvim/grid.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/indent.h" +#include "nvim/match.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/plines.h" +#include "nvim/quickfix.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/state.h" +#include "nvim/syntax.h" +#include "nvim/undo.h" +#include "nvim/window.h" + +#define MB_FILLER_CHAR '<' // character used when a double-width character + // doesn't fit. + +/// for line_putchar. Contains the state that needs to be remembered from +/// putting one character to the next. +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 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "drawline.c.generated.h" +#endif + +/// Advance **color_cols +/// +/// @return true when there are columns to draw. +static bool advance_color_col(int vcol, int **color_cols) +{ + while (**color_cols >= 0 && vcol > **color_cols) { + (*color_cols)++; + } + return **color_cols >= 0; +} + +/// Used when 'cursorlineopt' contains "screenline": compute the margins between +/// which the highlighting is used. +static void margin_columns_win(win_T *wp, int *left_col, int *right_col) +{ + // cache previous calculations depending on w_virtcol + static int saved_w_virtcol; + static win_T *prev_wp; + static int prev_left_col; + static int prev_right_col; + static int prev_col_off; + + int cur_col_off = win_col_off(wp); + int width1; + int width2; + + if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp + && prev_col_off == cur_col_off) { + *right_col = prev_right_col; + *left_col = prev_left_col; + return; + } + + width1 = wp->w_width - cur_col_off; + width2 = width1 + win_col_off2(wp); + + *left_col = 0; + *right_col = width1; + + if (wp->w_virtcol >= (colnr_T)width1) { + *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; + } + if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { + *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + } + + // cache values + prev_left_col = *left_col; + prev_right_col = *right_col; + prev_wp = wp; + saved_w_virtcol = wp->w_virtcol; + prev_col_off = cur_col_off; +} + +/// 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) +{ + const char_u *p = (char_u *)s->p; + int cells = utf_ptr2cells((char *)p); + int c_len = utfc_ptr2len((char *)p); + int u8c, u8cc[MAX_MCO]; + if (cells > maxcells) { + 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); + for (int c = 0; c < cells; c++) { + schar_from_ascii(dest[c], ' '); + } + goto done; + } else if (*p < 0x80 && u8cc[0] == 0) { + schar_from_ascii(dest[0], (char)(*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 = *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((char *)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; + } + 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); +} + +static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col, + int win_row) +{ + DecorState *state = &decor_state; + 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))) { + 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); + } + } + if (item->win_col < 0) { + continue; + } + int col; + if (item->decor.ui_watched) { + // send mark position to UI + col = item->win_col; + WinExtmark m = { (NS)item->ns_id, item->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); + } + item->win_col = -2; // deactivate + if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) { + state->eol_col = col + 1; + } + + *end_col = MAX(*end_col, col); + } +} + +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(""); + int virt_attr = 0; + size_t virt_pos = 0; + + while (col < max_col) { + if (!*s.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) { + break; + } + } + if (!*s.p) { + continue; + } + int attr; + bool through = false; + if (hl_mode == kHlModeCombine) { + attr = hl_combine_attr(linebuf_attr[col], virt_attr); + } else if (hl_mode == kHlModeBlend) { + through = (*s.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); + + for (int c = 0; c < cells; c++) { + linebuf_attr[col++] = attr; + } + vcol += cells; + } + return col; +} + +/// Return true if CursorLineSign highlight is to be used. +static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (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_u *extra, size_t extra_size, + char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, + int cul_attr) +{ + // Draw cells with the sign value or blank. + *c_extrap = ' '; + *c_finalp = NUL; + if (nrcol) { + *n_extrap = number_width(wp) + 1; + } else { + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } + *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); + + // 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((char *)(*pp_extra)); + + assert(extra_size > symbol_blen); + memset(extra, ' ', extra_size); + memcpy(extra, *pp_extra, symbol_blen); + + *pp_extra = extra; + (*pp_extra)[*n_extrap] = NUL; + } + } + + if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) { + *char_attrp = cul_attr; + } else { + *char_attrp = sattr->hl_attr_id; + } + } + } +} + +static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr, + int *num_attr, int *cul_attr) +{ + HlPriAttr line_attrs = { *line_attr, 0 }; + HlPriAttr num_attrs = { *num_attr, 0 }; + HlPriAttr cul_attrs = { *cul_attr, 0 }; + + // 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); + + *line_attr = line_attrs.attr_id; + *num_attr = num_attrs.attr_id; + *cul_attr = cul_attrs.attr_id; + + return num_signs; +} + +/// 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))); +} + +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +{ + long num; + char *fmt = "%*ld "; + + 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 "; + } + } + + snprintf((char *)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) +{ + 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 (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); + } + + return win_hl_attr(wp, HLF_N); +} + +static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr, + int *line_attr_lowprio) +{ + *cul_attr = win_hl_attr(wp, HLF_CUL); + HlAttrs ae = syn_attr2entry(*cul_attr); + // We make a compromise here (#7383): + // * low-priority CursorLine if fg is not set + // * high-priority ("same as Vim" priority) CursorLine if fg is set + if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { + *line_attr_lowprio = *cul_attr; + } else { + if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) + && qf_current_entry(wp) == lnum) { + *line_attr = hl_combine_attr(*cul_attr, *line_attr); + } else { + *line_attr = *cul_attr; + } + } +} + +/// Display line "lnum" of window 'wp' on the screen. +/// wp->w_virtcol needs to be valid. +/// +/// @param lnum line to display +/// @param startrow first row relative to window grid +/// @param endrow last grid row to be redrawn +/// @param nochange not updating for changed text +/// @param number_only only update the number column +/// @param foldinfo fold info for this line +/// @param[in, out] providers decoration providers active this line +/// items will be disables if they cause errors +/// or explicitly return `false`. +/// +/// @return the number of last row the line occupies. +int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only, + foldinfo_T foldinfo, DecorProviders *providers, char **provider_err) +{ + int c = 0; // init for GCC + long vcol = 0; // virtual column (for tabs) + long vcol_sbr = -1; // virtual column after showbreak + long vcol_prev = -1; // "vcol" of previous character + char_u *line; // current line + char_u *ptr; // current position in "line" + int row; // row in the window, excl w_winrow + ScreenGrid *grid = &wp->w_grid; // grid specific to the window + + char_u extra[57]; // sign, line number and 'fdc' must + // fit in here + int n_extra = 0; // number of extra chars + char_u *p_extra = NULL; // string of extra chars, plus NUL + char_u *p_extra_free = NULL; // p_extra needs to be freed + int c_extra = NUL; // extra chars, all the same + int c_final = NUL; // final char, mandatory if set + int extra_attr = 0; // attributes when n_extra != 0 + static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying + // curwin->w_p_lcs_chars.eol at + // end-of-line + int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used + int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used + bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; + + // saved "extra" items for when draw_state becomes WL_LINE (again) + int saved_n_extra = 0; + char_u *saved_p_extra = NULL; + int saved_c_extra = 0; + int saved_c_final = 0; + int saved_char_attr = 0; + + int n_attr = 0; // chars with special attr + int saved_attr2 = 0; // char_attr saved for n_attr + int n_attr3 = 0; // chars with overruling special attr + int saved_attr3 = 0; // char_attr saved for n_attr3 + + int n_skip = 0; // nr of chars to skip for 'nowrap' + + int fromcol = -10; // start of inverting + int tocol = MAXCOL; // end of inverting + int fromcol_prev = -2; // start of inverting after cursor + bool noinvcur = false; // don't invert the cursor + bool lnum_in_visual_area = false; + pos_T pos; + long v; + + int char_attr = 0; // attributes for next character + bool attr_pri = false; // char_attr has priority + bool area_highlighting = false; // Visual or incsearch highlighting in this line + int attr = 0; // attributes for area highlighting + int area_attr = 0; // attributes desired by highlighting + int search_attr = 0; // attributes desired by 'hlsearch' + int vcol_save_attr = 0; // saved attr for 'cursorcolumn' + int syntax_attr = 0; // attributes desired by syntax + bool has_syntax = false; // this buffer has syntax highl. + int save_did_emsg; + int eol_hl_off = 0; // 1 if highlighted char after EOL + bool draw_color_col = false; // highlight colorcolumn + int *color_cols = NULL; // pointer to according columns array + bool has_spell = false; // this buffer has spell checking +#define SPWORDLEN 150 + char_u nextline[SPWORDLEN * 2]; // text with start of the next line + int nextlinecol = 0; // column where nextline[] starts + int nextline_idx = 0; // index in nextline[] where next line + // starts + int spell_attr = 0; // attributes desired by spelling + int word_end = 0; // last byte with same spell_attr + static linenr_T checked_lnum = 0; // line number for "checked_col" + static int checked_col = 0; // column in "checked_lnum" up to which + // there are no spell errors + static int cap_col = -1; // column to check for Cap word + static linenr_T capcol_lnum = 0; // line number where "cap_col" + int cur_checked_col = 0; // checked column for current line + int extra_check = 0; // has syntax or linebreak + int multi_attr = 0; // attributes desired by multibyte + int mb_l = 1; // multi-byte byte length + int mb_c = 0; // decoded multi-byte character + bool mb_utf8 = false; // screen char is UTF-8 char + int u8cc[MAX_MCO]; // composing UTF-8 chars + int filler_lines; // nr of filler lines to be drawn + int filler_todo; // nr of filler lines still to do + 1 + hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting + int change_start = MAXCOL; // first col of changed area + int change_end = -1; // last col of changed area + colnr_T trailcol = MAXCOL; // start of trailing spaces + colnr_T leadcol = 0; // start of leading spaces + bool in_multispace = false; // in multiple consecutive spaces + int multispace_pos = 0; // position in lcs-multispace string + bool need_showbreak = false; // overlong line, skip first x chars + int line_attr = 0; // attribute for the whole line + int line_attr_save; + int line_attr_lowprio = 0; // low-priority attribute for the line + int line_attr_lowprio_save; + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c + + bool search_attr_from_match = false; // if search_attr is from :match + bool has_decor = false; // this buffer has decoration + int win_col_offset = 0; // offset for window columns + + char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext + + bool area_active = false; + + int cul_attr = 0; // set when 'cursorline' active + // 'cursorlineopt' has "screenline" and cursor is in this line + bool cul_screenline = false; + // margin columns for the screen line, needed for when 'cursorlineopt' + // contains "screenline" + int left_curline_col = 0; + int right_curline_col = 0; + + // draw_state: items that are drawn in sequence: +#define WL_START 0 // nothing done yet +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#define WL_SIGN (WL_FOLD + 1) // column for signs +#define WL_NR (WL_SIGN + 1) // line number +#define WL_BRI (WL_NR + 1) // 'breakindent' +#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#define WL_LINE (WL_SBR + 1) // text in the line + int draw_state = WL_START; // what to draw next + + int syntax_flags = 0; + int syntax_seqnr = 0; + int prev_syntax_id = 0; + int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); + bool is_concealing = false; + int boguscols = 0; ///< nonexistent columns added to + ///< force wrapping + int vcol_off = 0; ///< offset for concealed characters + int did_wcol = false; + int match_conc = 0; ///< cchar for match functions + int old_boguscols = 0; +#define VCOL_HLC (vcol - vcol_off) +#define FIX_FOR_BOGUSCOLS \ + { \ + n_extra += vcol_off; \ + vcol -= vcol_off; \ + vcol_off = 0; \ + col -= boguscols; \ + old_boguscols = boguscols; \ + boguscols = 0; \ + } + + if (startrow > endrow) { // past the end already! + return startrow; + } + + row = startrow; + + buf_T *buf = wp->w_buffer; + bool end_fill = (lnum == buf->b_ml.ml_line_count + 1); + + if (!number_only) { + // To speed up the loop below, set extra_check when there is linebreak, + // trailing white space and/or syntax processing to be done. + extra_check = wp->w_p_lbr; + if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow + && !has_fold && !end_fill) { + // Prepare for syntax highlighting in this line. When there is an + // error, stop syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = false; + syntax_start(wp, lnum); + if (did_emsg) { + wp->w_s->b_syn_error = true; + } else { + did_emsg = save_did_emsg; + if (!wp->w_s->b_syn_slow) { + has_syntax = true; + extra_check = true; + } + } + } + + has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); + + providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err); + + if (*provider_err) { + provider_err_virt_text(lnum, *provider_err); + has_decor = true; + *provider_err = NULL; + } + + if (has_decor) { + extra_check = true; + } + + // Check for columns to display for 'colorcolumn'. + color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; + if (color_cols != NULL) { + draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols); + } + + if (wp->w_p_spell + && !has_fold + && !end_fill + && *wp->w_s->b_p_spl != NUL + && !GA_EMPTY(&wp->w_s->b_langp) + && *(char **)(wp->w_s->b_langp.ga_data) != NULL) { + // Prepare for spell checking. + has_spell = true; + extra_check = true; + + // Get the start of the next line, so that words that wrap to the next + // line are found too: "et<line-break>al.". + // Trick: skip a few chars for C/shell/Vim comments + nextline[SPWORDLEN] = NUL; + if (lnum < wp->w_buffer->b_ml.ml_line_count) { + line = ml_get_buf(wp->w_buffer, lnum + 1, false); + spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); + } + + // When a word wrapped from the previous line the start of the current + // line is valid. + if (lnum == checked_lnum) { + cur_checked_col = checked_col; + } + checked_lnum = 0; + + // When there was a sentence end in the previous line may require a + // word starting with capital in this line. In line 1 always check + // the first word. + if (lnum != capcol_lnum) { + cap_col = -1; + } + if (lnum == 1) { + cap_col = 0; + } + capcol_lnum = 0; + } + + // handle Visual active in this window + if (VIsual_active && wp->w_buffer == curwin->w_buffer) { + pos_T *top, *bot; + + if (ltoreq(curwin->w_cursor, VIsual)) { + // Visual is after curwin->w_cursor + top = &curwin->w_cursor; + bot = &VIsual; + } else { + // Visual is before curwin->w_cursor + top = &VIsual; + bot = &curwin->w_cursor; + } + lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); + if (VIsual_mode == Ctrl_V) { + // block mode + if (lnum_in_visual_area) { + fromcol = wp->w_old_cursor_fcol; + tocol = wp->w_old_cursor_lcol; + } + } else { + // non-block mode + if (lnum > top->lnum && lnum <= bot->lnum) { + fromcol = 0; + } else if (lnum == top->lnum) { + if (VIsual_mode == 'V') { // linewise + fromcol = 0; + } else { + getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL); + if (gchar_pos(top) == NUL) { + tocol = fromcol + 1; + } + } + } + if (VIsual_mode != 'V' && lnum == bot->lnum) { + if (*p_sel == 'e' && bot->col == 0 + && bot->coladd == 0) { + fromcol = -10; + tocol = MAXCOL; + } else if (bot->col == MAXCOL) { + tocol = MAXCOL; + } else { + pos = *bot; + if (*p_sel == 'e') { + getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL); + } else { + getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol); + tocol++; + } + } + } + } + + // Check if the char under the cursor should be inverted (highlighted). + if (!highlight_match && lnum == curwin->w_cursor.lnum && wp == curwin + && cursor_is_block_during_visual(*p_sel == 'e')) { + noinvcur = true; + } + + // if inverting in this line set area_highlighting + if (fromcol >= 0) { + area_highlighting = true; + attr = win_hl_attr(wp, HLF_V); + } + // handle 'incsearch' and ":s///c" highlighting + } else if (highlight_match + && wp == curwin + && !has_fold + && lnum >= curwin->w_cursor.lnum + && lnum <= curwin->w_cursor.lnum + search_match_lines) { + if (lnum == curwin->w_cursor.lnum) { + getvcol(curwin, &(curwin->w_cursor), + (colnr_T *)&fromcol, NULL, NULL); + } else { + fromcol = 0; + } + if (lnum == curwin->w_cursor.lnum + search_match_lines) { + pos.lnum = lnum; + pos.col = search_match_endcol; + getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); + } + // do at least one character; happens when past end of line + if (fromcol == tocol && search_match_endcol) { + tocol = fromcol + 1; + } + area_highlighting = true; + attr = win_hl_attr(wp, HLF_I); + } + } + + int bg_attr = win_bg_attr(wp); + + filler_lines = diff_check(wp, lnum); + if (filler_lines < 0) { + if (filler_lines == -1) { + if (diff_find_change(wp, lnum, &change_start, &change_end)) { + diff_hlf = HLF_ADD; // added line + } else if (change_start == 0) { + diff_hlf = HLF_TXD; // changed text + } else { + diff_hlf = HLF_CHD; // changed line + } + } else { + diff_hlf = HLF_ADD; // added line + } + filler_lines = 0; + area_highlighting = true; + } + VirtLines virt_lines = KV_INITIAL_VALUE; + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + filler_lines += n_virt_lines; + if (lnum == wp->w_topline) { + filler_lines = wp->w_topfill; + n_virt_lines = MIN(n_virt_lines, filler_lines); + } + filler_todo = filler_lines; + + // Cursor line highlighting for 'cursorline' in the current window. + if (lnum == wp->w_cursor.lnum) { + // Do not show the cursor line in the text when Visual mode is active, + // because it's not clear what is selected then. + if (wp->w_p_cul && !(wp == curwin && VIsual_active) + && wp->w_p_culopt_flags != CULOPT_NBR) { + cul_screenline = (wp->w_p_wrap + && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); + if (!cul_screenline) { + apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio); + } else { + margin_columns_win(wp, &left_curline_col, &right_curline_col); + } + area_highlighting = true; + } + } + + SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column + int sign_num_attr = 0; // sign attribute for the number column + int sign_cul_attr = 0; // sign attribute for cursorline + CLEAR_FIELD(sattrs); + int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr); + + // Highlight the current line in the quickfix window. + if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { + line_attr = win_hl_attr(wp, HLF_QFL); + } + + if (line_attr_lowprio || line_attr) { + area_highlighting = true; + } + + if (cul_screenline) { + line_attr_save = line_attr; + line_attr_lowprio_save = line_attr_lowprio; + } + + line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false); + ptr = line; + + if (has_spell && !number_only) { + // For checking first word with a capital skip white space. + if (cap_col == 0) { + cap_col = (int)getwhitecols(line); + } + + // To be able to spell-check over line boundaries copy the end of the + // current line into nextline[]. Above the start of the next line was + // copied to nextline[SPWORDLEN]. + if (nextline[SPWORDLEN] == NUL) { + // No next line or it is empty. + nextlinecol = MAXCOL; + nextline_idx = 0; + } else { + v = (long)STRLEN(line); + if (v < SPWORDLEN) { + // Short line, use it completely and append the start of the + // next line. + nextlinecol = 0; + memmove(nextline, line, (size_t)v); + STRMOVE(nextline + v, nextline + SPWORDLEN); + nextline_idx = (int)v + 1; + } else { + // Long line, use only the last SPWORDLEN bytes. + nextlinecol = (int)v - SPWORDLEN; + memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 + nextline_idx = SPWORDLEN + 1; + } + } + } + + if (wp->w_p_list && !has_fold && !end_fill) { + if (wp->w_p_lcs_chars.space + || wp->w_p_lcs_chars.multispace != NULL + || wp->w_p_lcs_chars.leadmultispace != NULL + || wp->w_p_lcs_chars.trail + || wp->w_p_lcs_chars.lead + || wp->w_p_lcs_chars.nbsp) { + extra_check = true; + } + // find start of trailing whitespace + if (wp->w_p_lcs_chars.trail) { + trailcol = (colnr_T)STRLEN(ptr); + while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { + trailcol--; + } + trailcol += (colnr_T)(ptr - line); + } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } + } + + // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the + // first character to be displayed. + if (wp->w_p_wrap) { + v = wp->w_skipcol; + } else { + v = wp->w_leftcol; + } + if (v > 0 && !number_only) { + char_u *prev_ptr = ptr; + while (vcol < v && *ptr != NUL) { + c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); + vcol += c; + prev_ptr = ptr; + MB_PTR_ADV(ptr); + } + + // When: + // - 'cuc' is set, or + // - 'colorcolumn' is set, or + // - 'virtualedit' is set, or + // - the visual mode is active, + // the end of the line may be before the start of the displayed part. + if (vcol < v && (wp->w_p_cuc + || draw_color_col + || virtual_active() + || (VIsual_active && wp->w_buffer == curwin->w_buffer))) { + vcol = v; + } + + // Handle a character that's not completely on the screen: Put ptr at + // that character but skip the first few screen characters. + if (vcol > v) { + vcol -= c; + ptr = prev_ptr; + // If the character fits on the screen, don't need to skip it. + // Except for a TAB. + if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) { + n_skip = (int)(v - vcol); + } + } + + // Adjust for when the inverted text is before the screen, + // and when the start of the inverted text is before the screen. + if (tocol <= vcol) { + fromcol = 0; + } else if (fromcol >= 0 && fromcol < vcol) { |