aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/drawline.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/drawline.c')
-rw-r--r--src/nvim/drawline.c2706
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) {
+ fromcol = (int)vcol;
+ }
+
+ // When w_skipcol is non-zero, first line needs 'showbreak'
+ if (wp->w_p_wrap) {
+ need_showbreak = true;
+ }
+ // When spell checking a word we need to figure out the start of the
+ // word and if it's badly spelled or not.
+ if (has_spell) {
+ size_t len;
+ colnr_T linecol = (colnr_T)(ptr - line);
+ hlf_T spell_hlf = HLF_COUNT;
+
+ pos = wp->w_cursor;
+ wp->w_cursor.lnum = lnum;
+ wp->w_cursor.col = linecol;
+ len = spell_move_to(wp, FORWARD, true, true, &spell_hlf);
+
+ // spell_move_to() may call ml_get() and make "line" invalid
+ line = ml_get_buf(wp->w_buffer, lnum, false);
+ ptr = line + linecol;
+
+ if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
+ // no bad word found at line start, don't check until end of a
+ // word
+ spell_hlf = HLF_COUNT;
+ word_end = (int)(spell_to_word_end(ptr, wp) - line + 1);
+ } else {
+ // bad word found, use attributes until end of word
+ assert(len <= INT_MAX);
+ word_end = wp->w_cursor.col + (int)len + 1;
+
+ // Turn index into actual attributes.
+ if (spell_hlf