drawline.c (122819B)
1 // drawline.c: Functions for drawing window lines on the screen. 2 // This is the middle level, drawscreen.c is the top and grid.c the lower level. 3 4 #include <assert.h> 5 #include <limits.h> 6 #include <stdbool.h> 7 #include <stddef.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #include "nvim/ascii_defs.h" 13 #include "nvim/buffer.h" 14 #include "nvim/buffer_defs.h" 15 #include "nvim/charset.h" 16 #include "nvim/cursor.h" 17 #include "nvim/cursor_shape.h" 18 #include "nvim/decoration.h" 19 #include "nvim/decoration_defs.h" 20 #include "nvim/decoration_provider.h" 21 #include "nvim/diff.h" 22 #include "nvim/drawline.h" 23 #include "nvim/drawscreen.h" 24 #include "nvim/eval/vars.h" 25 #include "nvim/fold.h" 26 #include "nvim/fold_defs.h" 27 #include "nvim/globals.h" 28 #include "nvim/grid.h" 29 #include "nvim/grid_defs.h" 30 #include "nvim/highlight.h" 31 #include "nvim/highlight_defs.h" 32 #include "nvim/highlight_group.h" 33 #include "nvim/indent.h" 34 #include "nvim/insexpand.h" 35 #include "nvim/mark_defs.h" 36 #include "nvim/marktree_defs.h" 37 #include "nvim/match.h" 38 #include "nvim/mbyte.h" 39 #include "nvim/mbyte_defs.h" 40 #include "nvim/memline.h" 41 #include "nvim/memory.h" 42 #include "nvim/move.h" 43 #include "nvim/option.h" 44 #include "nvim/option_vars.h" 45 #include "nvim/os/os_defs.h" 46 #include "nvim/plines.h" 47 #include "nvim/pos_defs.h" 48 #include "nvim/quickfix.h" 49 #include "nvim/sign_defs.h" 50 #include "nvim/spell.h" 51 #include "nvim/state.h" 52 #include "nvim/state_defs.h" 53 #include "nvim/statusline.h" 54 #include "nvim/statusline_defs.h" 55 #include "nvim/strings.h" 56 #include "nvim/syntax.h" 57 #include "nvim/terminal.h" 58 #include "nvim/types_defs.h" 59 #include "nvim/ui.h" 60 #include "nvim/ui_defs.h" 61 #include "nvim/vim_defs.h" 62 63 #define MB_FILLER_CHAR '<' // character used when a double-width character doesn't fit. 64 65 /// structure with variables passed between win_line() and other functions 66 typedef struct { 67 const linenr_T lnum; ///< line number to be drawn 68 const foldinfo_T foldinfo; ///< fold info for this line 69 70 const int startrow; ///< first row in the window to be drawn 71 int row; ///< row in the window, excl w_winrow 72 73 colnr_T vcol; ///< virtual column, before wrapping 74 int col; ///< visual column on screen, after wrapping 75 int boguscols; ///< nonexistent columns added to "col" to force wrapping 76 int old_boguscols; ///< bogus boguscols 77 int vcol_off_co; ///< offset for concealed characters 78 79 int off; ///< offset relative start of line 80 81 int cul_attr; ///< set when 'cursorline' active 82 int line_attr; ///< attribute for the whole line 83 int line_attr_lowprio; ///< low-priority attribute for the line 84 int sign_num_attr; ///< line number attribute (sign numhl) 85 int prev_num_attr; ///< previous line's number attribute (sign numhl) 86 int sign_cul_attr; ///< cursorline sign attribute (sign culhl) 87 88 int fromcol; ///< start of inverting 89 int tocol; ///< end of inverting 90 91 colnr_T vcol_sbr; ///< virtual column after showbreak 92 bool need_showbreak; ///< overlong line, skipping first x chars 93 94 int char_attr; ///< attributes for next character 95 96 int n_extra; ///< number of extra bytes 97 int n_attr; ///< chars with special attr 98 char *p_extra; ///< string of extra chars, plus NUL, only used 99 ///< when sc_extra and sc_final are NUL 100 int extra_attr; ///< attributes for p_extra 101 schar_T sc_extra; ///< extra chars, all the same 102 schar_T sc_final; ///< final char, mandatory if set 103 104 bool extra_for_extmark; ///< n_extra set for inline virtual text 105 106 char extra[11]; ///< must be as large as transchar_charbuf[] in charset.c 107 108 hlf_T diff_hlf; ///< type of diff highlighting 109 110 int n_virt_lines; ///< nr of virtual lines 111 int n_virt_below; ///< nr of virtual lines belonging to previous line 112 int filler_lines; ///< nr of filler lines to be drawn 113 int filler_todo; ///< nr of filler lines still to do + 1 114 SignTextAttrs sattrs[SIGN_SHOW_MAX]; ///< sign attributes for the sign column 115 /// do consider wrapping in linebreak mode only after encountering 116 /// a non whitespace char 117 bool need_lbr; 118 119 VirtText virt_inline; 120 size_t virt_inline_i; 121 HlMode virt_inline_hl_mode; 122 123 bool reset_extra_attr; 124 125 int skip_cells; ///< nr of cells to skip for w_leftcol 126 ///< or w_skipcol or concealing 127 int skipped_cells; ///< nr of skipped cells for virtual text 128 ///< to be added to wlv.vcol later 129 130 int *color_cols; ///< if not NULL, highlight colorcolumn using according columns array 131 } winlinevars_T; 132 133 #include "drawline.c.generated.h" 134 135 static char *extra_buf = NULL; 136 static size_t extra_buf_size = 0; 137 138 static char *get_extra_buf(size_t size) 139 { 140 size = MAX(size, 64); 141 if (extra_buf_size < size) { 142 xfree(extra_buf); 143 extra_buf = xmalloc(size); 144 extra_buf_size = size; 145 } 146 return extra_buf; 147 } 148 149 #ifdef EXITFREE 150 void drawline_free_all_mem(void) 151 { 152 xfree(extra_buf); 153 } 154 #endif 155 156 /// Get the 'listchars' "extends" characters to use for "wp", or NUL if it 157 /// shouldn't be used. 158 static schar_T get_lcs_ext(win_T *wp) 159 { 160 if (wp->w_p_wrap) { 161 // Line never continues beyond the right of the screen with 'wrap'. 162 return NUL; 163 } 164 if (wp->w_p_wrap_flags & kOptFlagInsecure) { 165 // If 'nowrap' was set from a modeline, forcibly use '>'. 166 return schar_from_ascii('>'); 167 } 168 return wp->w_p_list ? wp->w_p_lcs_chars.ext : NUL; 169 } 170 171 /// Advance wlv->color_cols if not NULL 172 static void advance_color_col(winlinevars_T *wlv, int vcol) 173 { 174 if (wlv->color_cols) { 175 while (*wlv->color_cols >= 0 && vcol > *wlv->color_cols) { 176 wlv->color_cols++; 177 } 178 if (*wlv->color_cols < 0) { 179 wlv->color_cols = NULL; 180 } 181 } 182 } 183 184 /// Used when 'cursorlineopt' contains "screenline": compute the margins between 185 /// which the highlighting is used. 186 static void margin_columns_win(win_T *wp, int *left_col, int *right_col) 187 { 188 // cache previous calculations depending on w_virtcol 189 static int saved_w_virtcol; 190 static win_T *prev_wp; 191 static int prev_width1; 192 static int prev_width2; 193 static int prev_left_col; 194 static int prev_right_col; 195 196 int cur_col_off = win_col_off(wp); 197 int width1 = wp->w_view_width - cur_col_off; 198 int width2 = width1 + win_col_off2(wp); 199 200 if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp 201 && prev_width1 == width1 && prev_width2 == width2) { 202 *right_col = prev_right_col; 203 *left_col = prev_left_col; 204 return; 205 } 206 207 *left_col = 0; 208 *right_col = width1; 209 210 if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { 211 *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; 212 } 213 if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) { 214 *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; 215 } 216 217 // cache values 218 prev_left_col = *left_col; 219 prev_right_col = *right_col; 220 prev_wp = wp; 221 prev_width1 = width1; 222 prev_width2 = width2; 223 saved_w_virtcol = wp->w_virtcol; 224 } 225 226 /// Put a single char from an UTF-8 buffer into a line buffer. 227 /// 228 /// If `*pp` is a double-width char and only one cell is left, emit a space, 229 /// and don't advance *pp 230 /// 231 /// Handles composing chars 232 static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells, int vcol) 233 { 234 // Caller should handle overwriting the right half of a double-width char. 235 assert(dest[0] != 0); 236 237 const char *p = *pp; 238 int cells = utf_ptr2cells(p); 239 int c_len = utfc_ptr2len(p); 240 assert(maxcells > 0); 241 if (cells > maxcells) { 242 dest[0] = schar_from_ascii(' '); 243 return 1; 244 } 245 246 if (*p == TAB) { 247 cells = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); 248 cells = MIN(cells, maxcells); 249 } 250 251 // When overwriting the left half of a double-width char, clear the right half. 252 if (cells < maxcells && dest[cells] == 0) { 253 dest[cells] = schar_from_ascii(' '); 254 } 255 if (*p == TAB) { 256 for (int c = 0; c < cells; c++) { 257 dest[c] = schar_from_ascii(' '); 258 } 259 } else { 260 int u8c; 261 dest[0] = utfc_ptr2schar(p, &u8c); 262 if (cells > 1) { 263 dest[1] = 0; 264 } 265 } 266 267 *pp += c_len; 268 return cells; 269 } 270 271 static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row) 272 { 273 DecorState *const state = &decor_state; 274 int const max_col = wp->w_view_width; 275 int right_pos = max_col; 276 bool const do_eol = state->eol_col > -1; 277 278 int const end = state->current_end; 279 int *const indices = state->ranges_i.items; 280 DecorRangeSlot *const slots = state->slots.items; 281 282 /// Total width of all virtual text with "eol_right_align" alignment 283 int totalWidthOfEolRightAlignedVirtText = 0; 284 285 for (int i = 0; i < end; i++) { 286 DecorRange *item = &slots[indices[i]].range; 287 if (!(item->start_row == state->row && decor_virt_pos(item))) { 288 continue; 289 } 290 291 DecorVirtText *vt = NULL; 292 if (item->kind == kDecorKindVirtText) { 293 assert(item->data.vt); 294 vt = item->data.vt; 295 } 296 if (decor_virt_pos(item) && item->draw_col == -1) { 297 bool updated = true; 298 VirtTextPos pos = decor_virt_pos_kind(item); 299 300 if (do_eol && pos == kVPosEndOfLineRightAlign) { 301 int eolOffset = 0; 302 if (totalWidthOfEolRightAlignedVirtText == 0) { 303 // Look ahead to the remaining decor items 304 for (int j = i; j < end; j++) { 305 /// A future decor to be handled in this function's call 306 DecorRange *lookaheadItem = &slots[indices[j]].range; 307 308 if (lookaheadItem->start_row != state->row 309 || !decor_virt_pos(lookaheadItem) 310 || lookaheadItem->draw_col != -1) { 311 continue; 312 } 313 314 /// The Virtual Text of the decor item we're looking ahead to 315 DecorVirtText *lookaheadVt = NULL; 316 if (lookaheadItem->kind == kDecorKindVirtText) { 317 assert(lookaheadItem->data.vt); 318 lookaheadVt = lookaheadItem->data.vt; 319 } 320 321 if (decor_virt_pos_kind(lookaheadItem) == kVPosEndOfLineRightAlign) { 322 // An extra space is added for single character spacing in EOL alignment 323 totalWidthOfEolRightAlignedVirtText += (lookaheadVt->width + 1); 324 } 325 } 326 327 // Remove one space from the total width since there's no single space after the last entry 328 totalWidthOfEolRightAlignedVirtText--; 329 330 if (totalWidthOfEolRightAlignedVirtText <= (right_pos - state->eol_col)) { 331 eolOffset = right_pos - totalWidthOfEolRightAlignedVirtText - state->eol_col; 332 } 333 } 334 335 item->draw_col = state->eol_col + eolOffset; 336 } else if (pos == kVPosRightAlign) { 337 right_pos -= vt->width; 338 item->draw_col = right_pos; 339 } else if (pos == kVPosEndOfLine && do_eol) { 340 item->draw_col = state->eol_col; 341 } else if (pos == kVPosWinCol) { 342 item->draw_col = MAX(col_off + vt->col, 0); 343 } else { 344 updated = false; 345 } 346 if (updated && (item->draw_col < 0 || item->draw_col >= wp->w_view_width)) { 347 // Out of window, don't draw at all. 348 item->draw_col = INT_MIN; 349 } 350 } 351 if (item->draw_col < 0) { 352 continue; 353 } 354 if (item->kind == kDecorKindUIWatched) { 355 // send mark position to UI 356 WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, item->draw_col }; 357 kv_push(win_extmark_arr, m); 358 } 359 if (vt) { 360 int vcol = item->draw_col - col_off; 361 int col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text, 362 vt->hl_mode, max_col, vcol, 0); 363 if (do_eol && ((vt->pos == kVPosEndOfLine) || (vt->pos == kVPosEndOfLineRightAlign))) { 364 state->eol_col = col + 1; 365 } 366 *end_col = MAX(*end_col, col); 367 } 368 if (!vt || !(vt->flags & kVTRepeatLinebreak)) { 369 item->draw_col = INT_MIN; // deactivate 370 } 371 } 372 } 373 374 static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col, 375 int vcol, int skip_cells) 376 { 377 const char *virt_str = ""; 378 int virt_attr = 0; 379 size_t virt_pos = 0; 380 381 while (col < max_col) { 382 if (skip_cells >= 0 && *virt_str == NUL) { 383 if (virt_pos >= kv_size(vt)) { 384 break; 385 } 386 virt_attr = 0; 387 virt_str = next_virt_text_chunk(vt, &virt_pos, &virt_attr); 388 if (virt_str == NULL) { 389 break; 390 } 391 } 392 // Skip cells in the text. 393 while (skip_cells > 0 && *virt_str != NUL) { 394 int c_len = utfc_ptr2len(virt_str); 395 int cells = *virt_str == TAB 396 ? tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array) 397 : utf_ptr2cells(virt_str); 398 skip_cells -= cells; 399 vcol += cells; 400 virt_str += c_len; 401 } 402 // If a double-width char or TAB doesn't fit, pad with spaces. 403 const char *draw_str = skip_cells < 0 ? " " : virt_str; 404 if (*draw_str == NUL) { 405 continue; 406 } 407 assert(skip_cells <= 0); 408 int attr; 409 bool through = false; 410 if (hl_mode == kHlModeCombine) { 411 attr = hl_combine_attr(linebuf_attr[col], virt_attr); 412 } else if (hl_mode == kHlModeBlend) { 413 through = (*draw_str == ' '); 414 attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through); 415 } else { 416 attr = virt_attr; 417 } 418 schar_T dummy[2] = { schar_from_ascii(' '), schar_from_ascii(' ') }; 419 int maxcells = max_col - col; 420 // When overwriting the right half of a double-width char, clear the left half. 421 if (!through && linebuf_char[col] == 0) { 422 assert(col > 0); 423 linebuf_char[col - 1] = schar_from_ascii(' '); 424 // Clear the right half as well for the assertion in line_putchar(). 425 linebuf_char[col] = schar_from_ascii(' '); 426 } 427 int cells = line_putchar(buf, &draw_str, through ? dummy : &linebuf_char[col], 428 maxcells, vcol); 429 for (int c = 0; c < cells; c++) { 430 linebuf_attr[col] = attr; 431 col++; 432 } 433 if (skip_cells < 0) { 434 skip_cells++; 435 } else { 436 vcol += cells; 437 virt_str = draw_str; 438 } 439 } 440 return col; 441 } 442 443 // TODO(bfredl): integrate with grid.c linebuf code? madness? 444 static void draw_col_buf(win_T *wp, winlinevars_T *wlv, const char *text, size_t len, int attr, 445 const colnr_T *fold_vcol, bool inc_vcol) 446 { 447 const char *ptr = text; 448 while (ptr < text + len && wlv->off < wp->w_view_width) { 449 int cells = line_putchar(wp->w_buffer, &ptr, &linebuf_char[wlv->off], 450 wp->w_view_width - wlv->off, wlv->off); 451 int myattr = attr; 452 if (inc_vcol) { 453 advance_color_col(wlv, wlv->vcol); 454 if (wlv->color_cols && wlv->vcol == *wlv->color_cols) { 455 myattr = hl_combine_attr(win_hl_attr(wp, HLF_MC), myattr); 456 } 457 } 458 for (int c = 0; c < cells; c++) { 459 linebuf_attr[wlv->off] = myattr; 460 linebuf_vcol[wlv->off] = inc_vcol ? wlv->vcol++ : fold_vcol ? *(fold_vcol++) : -1; 461 wlv->off++; 462 } 463 } 464 } 465 466 static void draw_col_fill(winlinevars_T *wlv, schar_T fillchar, int width, int attr) 467 { 468 for (int i = 0; i < width; i++) { 469 linebuf_char[wlv->off] = fillchar; 470 linebuf_attr[wlv->off] = attr; 471 wlv->off++; 472 } 473 } 474 475 /// Return true if CursorLineSign highlight is to be used. 476 bool use_cursor_line_highlight(win_T *wp, linenr_T lnum) 477 { 478 return wp->w_p_cul 479 && lnum == wp->w_cursorline 480 && (wp->w_p_culopt_flags & kOptCuloptFlagNumber); 481 } 482 483 /// Setup for drawing the 'foldcolumn', if there is one. 484 static void draw_foldcolumn(win_T *wp, winlinevars_T *wlv) 485 { 486 int fdc = compute_foldcolumn(wp, 0); 487 if (fdc > 0) { 488 int attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLF : HLF_FC); 489 fill_foldcolumn(wp, wlv->foldinfo, wlv->lnum, attr, fdc, &wlv->off, NULL, NULL); 490 } 491 } 492 493 /// Draw the foldcolumn or fill "out_buffer". Assume monocell characters. 494 /// 495 /// @param fdc Current width of the foldcolumn 496 /// @param[out] wlv_off Pointer to linebuf offset, incremented for default column 497 /// @param[out] out_buffer Char array to fill, only used for 'statuscolumn' 498 /// @param[out] out_vcol vcol array to fill, only used for 'statuscolumn' 499 void fill_foldcolumn(win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int attr, int fdc, int *wlv_off, 500 colnr_T *out_vcol, schar_T *out_buffer) 501 { 502 bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; 503 int level = foldinfo.fi_level; 504 505 // If the column is too narrow, we start at the lowest level that 506 // fits and use numbers to indicate the depth. 507 int first_level = MAX(level - fdc - closed + 1, 1); 508 int closedcol = MIN(fdc, level); 509 510 for (int i = 0; i < fdc; i++) { 511 schar_T symbol = 0; 512 if (i >= level) { 513 symbol = schar_from_ascii(' '); 514 } else if (i == closedcol - 1 && closed) { 515 symbol = wp->w_p_fcs_chars.foldclosed; 516 } else if (foldinfo.fi_lnum == lnum && first_level + i >= foldinfo.fi_low_level) { 517 symbol = wp->w_p_fcs_chars.foldopen; 518 } else if (first_level == 1) { 519 symbol = wp->w_p_fcs_chars.foldsep; 520 } else if (wp->w_p_fcs_chars.foldinner != NUL) { 521 symbol = wp->w_p_fcs_chars.foldinner; 522 } else if (first_level + i <= 9) { 523 symbol = schar_from_ascii('0' + first_level + i); 524 } else { 525 symbol = schar_from_ascii('>'); 526 } 527 528 int vcol = i >= level ? -1 : (i == closedcol - 1 && closed) ? -2 : -3; 529 if (out_buffer) { 530 out_vcol[i] = vcol; 531 out_buffer[i] = symbol; 532 } else { 533 linebuf_vcol[*wlv_off] = vcol; 534 linebuf_attr[*wlv_off] = attr; 535 linebuf_char[(*wlv_off)++] = symbol; 536 } 537 } 538 } 539 540 /// Get information needed to display the sign in line "wlv->lnum" in window "wp". 541 /// If "nrcol" is true, the sign is going to be displayed in the number column. 542 /// Otherwise the sign is going to be displayed in the sign column. If there is no 543 /// sign, draw blank cells instead. 544 static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx) 545 { 546 SignTextAttrs sattr = wlv->sattrs[sign_idx]; 547 int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); 548 549 if (sattr.text[0] && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { 550 int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH; 551 int attr = wlv->sign_cul_attr ? wlv->sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0; 552 attr = hl_combine_attr(scl_attr, attr); 553 draw_col_fill(wlv, schar_from_ascii(' '), fill, attr); 554 int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol; 555 assert(sign_pos >= 0); 556 linebuf_char[sign_pos] = sattr.text[0]; 557 linebuf_char[sign_pos + 1] = sattr.text[1]; 558 } else { 559 assert(!nrcol); // handled in draw_lnum_col() 560 draw_col_fill(wlv, schar_from_ascii(' '), SIGN_WIDTH, scl_attr); 561 } 562 } 563 564 static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) 565 { 566 linenr_T num; 567 char *fmt = "%*" PRIdLINENR " "; 568 569 if (wp->w_p_nu && !wp->w_p_rnu) { 570 // 'number' + 'norelativenumber' 571 num = lnum; 572 } else { 573 // 'relativenumber', don't use negative numbers 574 num = abs(get_cursor_rel_lnum(wp, lnum)); 575 if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { 576 // 'number' + 'relativenumber' 577 num = lnum; 578 fmt = "%-*" PRIdLINENR " "; 579 } 580 } 581 582 snprintf(buf, buf_len, fmt, number_width(wp), num); 583 } 584 585 /// Return true if CursorLineNr highlight is to be used for the number column. 586 /// - 'cursorline' must be set 587 /// - "wlv->lnum" must be the cursor line 588 /// - 'cursorlineopt' has "number" 589 /// - don't highlight filler lines (when in diff mode) 590 /// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number 591 /// itself on the first screenline of the wrapped line, otherwise highlight the number column of 592 /// all screenlines of the wrapped line. 593 static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv) 594 { 595 return wp->w_p_cul 596 && wlv->lnum == wp->w_cursorline 597 && (wp->w_p_culopt_flags & kOptCuloptFlagNumber) 598 && (wlv->row == wlv->startrow + wlv->filler_lines 599 || (wlv->row > wlv->startrow + wlv->filler_lines 600 && (wp->w_p_culopt_flags & kOptCuloptFlagLine))); 601 } 602 603 /// Return line number attribute, combining the appropriate LineNr* highlight 604 /// with the highest priority sign numhl highlight, if any. 605 static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) 606 { 607 int numhl_attr = wlv->sign_num_attr; 608 609 // Get previous sign numhl for virt_lines belonging to the previous line. 610 if ((wlv->n_virt_lines - wlv->filler_todo) < wlv->n_virt_below) { 611 if (wlv->prev_num_attr == -1) { 612 decor_redraw_signs(wp, wp->w_buffer, wlv->lnum - 2, NULL, NULL, NULL, &wlv->prev_num_attr); 613 if (wlv->prev_num_attr > 0) { 614 wlv->prev_num_attr = syn_id2attr(wlv->prev_num_attr); 615 } 616 } 617 numhl_attr = wlv->prev_num_attr; 618 } 619 620 if (use_cursor_line_nr(wp, wlv)) { 621 // TODO(vim): Can we use CursorLine instead of CursorLineNr 622 // when CursorLineNr isn't set? 623 return hl_combine_attr(win_hl_attr(wp, HLF_CLN), numhl_attr); 624 } 625 626 if (wp->w_p_rnu) { 627 if (wlv->lnum < wp->w_cursor.lnum) { 628 // Use LineNrAbove 629 return hl_combine_attr(win_hl_attr(wp, HLF_LNA), numhl_attr); 630 } 631 if (wlv->lnum > wp->w_cursor.lnum) { 632 // Use LineNrBelow 633 return hl_combine_attr(win_hl_attr(wp, HLF_LNB), numhl_attr); 634 } 635 } 636 637 return hl_combine_attr(win_hl_attr(wp, HLF_N), numhl_attr); 638 } 639 640 /// Display the absolute or relative line number. After the first row fill with 641 /// blanks when the 'n' flag isn't in 'cpo'. 642 static void draw_lnum_col(win_T *wp, winlinevars_T *wlv) 643 { 644 bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; 645 646 if ((wp->w_p_nu || wp->w_p_rnu) 647 && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n) 648 // there is no line number in a wrapped line when "n" is in 649 // 'cpoptions', but 'breakindent' assumes it anyway. 650 && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) { 651 // If 'signcolumn' is set to 'number' and a sign is present in "lnum", 652 // then display the sign instead of the line number. 653 if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text[0] 654 && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) { 655 draw_sign(true, wp, wlv, 0); 656 } else { 657 // Draw the line number (empty space after wrapping). 658 int width = number_width(wp) + 1; 659 int attr = get_line_number_attr(wp, wlv); 660 if (wlv->row == wlv->startrow + wlv->filler_lines 661 && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) { 662 char buf[32]; 663 get_line_number_str(wp, wlv->lnum, buf, sizeof(buf)); 664 if (wp->w_skipcol > 0 && wlv->startrow == 0) { 665 for (char *c = buf; *c == ' '; c++) { 666 *c = '-'; 667 } 668 } 669 if (wp->w_p_rl) { // reverse line numbers 670 char *num = skipwhite(buf); 671 rl_mirror_ascii(num, skiptowhite(num)); 672 } 673 draw_col_buf(wp, wlv, buf, (size_t)width, attr, NULL, false); 674 } else { 675 draw_col_fill(wlv, schar_from_ascii(' '), width, attr); 676 } 677 } 678 } 679 } 680 681 /// Build and draw the 'statuscolumn' string for line "lnum" in window "wp". 682 static void draw_statuscol(win_T *wp, winlinevars_T *wlv, int virtnum, int col_rows, 683 statuscol_T *stcp) 684 { 685 // Adjust lnum for filler lines belonging to the line above and set lnum v:vars for first 686 // row, first non-filler line, and first filler line belonging to the current line. 687 linenr_T lnum = wlv->lnum - ((wlv->n_virt_lines - wlv->filler_todo) < wlv->n_virt_below); 688 linenr_T relnum = (virtnum == -wlv->filler_lines || virtnum == 0 689 || virtnum == (wlv->n_virt_below - wlv->filler_lines)) 690 ? abs(get_cursor_rel_lnum(wp, lnum)) : -1; 691 692 char buf[MAXPATHL]; 693 // When a buffer's line count has changed, make a best estimate for the full 694 // width of the status column by building with the largest possible line number. 695 // Add potentially truncated width and rebuild before drawing anything. 696 if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) { 697 wp->w_statuscol_line_count = wp->w_nrwidth_line_count; 698 set_vim_var_nr(VV_VIRTNUM, 0); 699 int width = build_statuscol_str(wp, wp->w_nrwidth_line_count, 700 wp->w_nrwidth_line_count, buf, stcp); 701 if (width > stcp->width) { 702 int addwidth = MIN(width - stcp->width, MAX_STCWIDTH - stcp->width); 703 wp->w_nrwidth += addwidth; 704 wp->w_nrwidth_width = wp->w_nrwidth; 705 if (col_rows > 0) { 706 // If only column is being redrawn, we now need to redraw the text as well 707 wp->w_redr_statuscol = true; 708 return; 709 } 710 stcp->width += addwidth; 711 wp->w_valid &= ~VALID_WCOL; 712 } 713 } 714 set_vim_var_nr(VV_VIRTNUM, virtnum); 715 716 int width = build_statuscol_str(wp, lnum, relnum, buf, stcp); 717 // Force a redraw in case of error or when truncated 718 if (*wp->w_p_stc == NUL || (width > stcp->width && stcp->width < MAX_STCWIDTH)) { 719 if (*wp->w_p_stc == NUL) { // 'statuscolumn' reset due to error 720 wp->w_nrwidth_line_count = 0; 721 wp->w_nrwidth = (wp->w_p_nu || wp->w_p_rnu) * number_width(wp); 722 } else { // Avoid truncating 'statuscolumn' 723 wp->w_nrwidth += MIN(width - stcp->width, MAX_STCWIDTH - stcp->width); 724 wp->w_nrwidth_width = wp->w_nrwidth; 725 } 726 wp->w_redr_statuscol = true; 727 return; 728 } 729 730 char *p = buf; 731 char transbuf[MAXPATHL]; 732 colnr_T *fold_vcol = NULL; 733 size_t len = strlen(buf); 734 int scl_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC); 735 int num_attr = get_line_number_attr(wp, wlv); 736 int cur_attr = num_attr; 737 738 // Draw each segment with the specified highlighting. 739 for (stl_hlrec_t *sp = stcp->hlrec; sp->start != NULL; sp++) { 740 ptrdiff_t textlen = sp->start - p; 741 // Make all characters printable. 742 size_t translen = transstr_buf(p, textlen, transbuf, MAXPATHL, true); 743 draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false); 744 int attr = sp->item == STL_SIGNCOL ? scl_attr : sp->item == STL_FOLDCOL ? 0 : num_attr; 745 cur_attr = hl_combine_attr(attr, sp->userhl < 0 ? syn_id2attr(-sp->userhl) : 0); 746 fold_vcol = sp->item == STL_FOLDCOL ? stcp->fold_vcol : NULL; 747 p = sp->start; 748 } 749 size_t translen = transstr_buf(p, buf + len - p, transbuf, MAXPATHL, true); 750 draw_col_buf(wp, wlv, transbuf, translen, cur_attr, fold_vcol, false); 751 draw_col_fill(wlv, schar_from_ascii(' '), stcp->width - width, cur_attr); 752 } 753 754 static void handle_breakindent(win_T *wp, winlinevars_T *wlv) 755 { 756 // draw 'breakindent': indent wrapped text accordingly 757 // if wlv->need_showbreak is set, breakindent also applies 758 if (wp->w_p_bri && (wlv->row > wlv->startrow + wlv->filler_lines 759 || wlv->need_showbreak)) { 760 int attr = 0; 761 if (wlv->diff_hlf != (hlf_T)0) { 762 attr = win_hl_attr(wp, (int)wlv->diff_hlf); 763 } 764 int num = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum)); 765 if (wlv->row == wlv->startrow) { 766 num -= win_col_off2(wp); 767 if (wlv->n_extra < 0) { 768 num = 0; 769 } 770 } 771 772 colnr_T vcol_before = wlv->vcol; 773 774 for (int i = 0; i < num; i++) { 775 linebuf_char[wlv->off] = schar_from_ascii(' '); 776 777 advance_color_col(wlv, wlv->vcol); 778 int myattr = attr; 779 if (wlv->color_cols && wlv->vcol == *wlv->color_cols) { 780 myattr = hl_combine_attr(win_hl_attr(wp, HLF_MC), myattr); 781 } 782 linebuf_attr[wlv->off] = myattr; 783 linebuf_vcol[wlv->off] = wlv->vcol++; // These are vcols, sorry I don't make the rules 784 wlv->off++; 785 } 786 787 // Correct start of highlighted area for 'breakindent', 788 if (wlv->fromcol >= vcol_before && wlv->fromcol < wlv->vcol) { 789 wlv->fromcol = wlv->vcol; 790 } 791 792 // Correct end of highlighted area for 'breakindent', 793 // required wen 'linebreak' is also set. 794 if (wlv->tocol == vcol_before) { 795 wlv->tocol = wlv->vcol; 796 } 797 } 798 799 if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) { 800 wlv->need_showbreak = false; 801 } 802 } 803 804 static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv) 805 { 806 int remaining = wp->w_view_width - wlv->off; 807 if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) { 808 // TODO(bfredl): check this doesn't inhibit TUI-style 809 // clear-to-end-of-line. 810 draw_col_fill(wlv, schar_from_ascii(' '), remaining, 0); 811 } else if (wlv->filler_todo > 0) { 812 // Draw "deleted" diff line(s) 813 schar_T c = wp->w_p_fcs_chars.diff; 814 draw_col_fill(wlv, c, remaining, win_hl_attr(wp, HLF_DED)); 815 } 816 817 char *const sbr = get_showbreak_value(wp); 818 if (*sbr != NUL && wlv->need_showbreak) { 819 // Draw 'showbreak' at the start of each broken line. 820 // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'. 821 int attr = hl_combine_attr(wlv->cul_attr, win_hl_attr(wp, HLF_AT)); 822 colnr_T vcol_before = wlv->vcol; 823 draw_col_buf(wp, wlv, sbr, strlen(sbr), attr, NULL, true); 824 wlv->vcol_sbr = wlv->vcol; 825 826 // Correct start of highlighted area for 'showbreak'. 827 if (wlv->fromcol >= vcol_before && wlv->fromcol < wlv->vcol) { 828 wlv->fromcol = wlv->vcol; 829 } 830 831 // Correct end of highlighted area for 'showbreak', 832 // required when 'linebreak' is also set. 833 if (wlv->tocol == vcol_before) { 834 wlv->tocol = wlv->vcol; 835 } 836 } 837 838 if (wp->w_skipcol == 0 || wlv->startrow > 0 || !wp->w_p_wrap || !wp->w_briopt_sbr) { 839 wlv->need_showbreak = false; 840 } 841 } 842 843 static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv) 844 { 845 wlv->cul_attr = win_hl_attr(wp, HLF_CUL); 846 HlAttrs ae = syn_attr2entry(wlv->cul_attr); 847 // We make a compromise here (#7383): 848 // * low-priority CursorLine if fg is not set 849 // * high-priority ("same as Vim" priority) CursorLine if fg is set 850 if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { 851 wlv->line_attr_lowprio = wlv->cul_attr; 852 } else { 853 if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer) 854 && qf_current_entry(wp) == wlv->lnum) { 855 wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr); 856 } else { 857 wlv->line_attr = wlv->cul_attr; 858 } 859 } 860 } 861 862 static void set_line_attr_for_diff(win_T *wp, winlinevars_T *wlv) 863 { 864 wlv->line_attr = win_hl_attr(wp, (int)wlv->diff_hlf); 865 // Overlay CursorLine onto diff-mode highlight. 866 if (wlv->cul_attr) { 867 wlv->line_attr = 0 != wlv->line_attr_lowprio // Low-priority CursorLine 868 ? hl_combine_attr(hl_combine_attr(wlv->cul_attr, wlv->line_attr), 869 hl_get_underline()) 870 : hl_combine_attr(wlv->line_attr, wlv->cul_attr); 871 } 872 } 873 874 /// Checks if there is more inline virtual text that need to be drawn. 875 static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v) 876 { 877 if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) { 878 return true; 879 } 880 881 int const count = (int)kv_size(decor_state.ranges_i); 882 int const cur_end = decor_state.current_end; 883 int const fut_beg = decor_state.future_begin; 884 int *const indices = decor_state.ranges_i.items; 885 DecorRangeSlot *const slots = decor_state.slots.items; 886 887 int const beg_pos[] = { 0, fut_beg }; 888 int const end_pos[] = { cur_end, count }; 889 890 for (int pos_i = 0; pos_i < 2; pos_i++) { 891 for (int i = beg_pos[pos_i]; i < end_pos[pos_i]; i++) { 892 DecorRange *item = &slots[indices[i]].range; 893 if (item->start_row != decor_state.row 894 || item->kind != kDecorKindVirtText 895 || item->data.vt->pos != kVPosInline 896 || item->data.vt->width == 0) { 897 continue; 898 } 899 if (item->draw_col >= -1 && item->start_col >= v) { 900 return true; 901 } 902 } 903 } 904 return false; 905 } 906 907 static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v, bool selected) 908 { 909 while (wlv->n_extra == 0) { 910 if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) { 911 // need to find inline virtual text 912 wlv->virt_inline = VIRTTEXT_EMPTY; 913 wlv->virt_inline_i = 0; 914 DecorState *state = &decor_state; 915 int const end = state->current_end; 916 int *const indices = state->ranges_i.items; 917 DecorRangeSlot *const slots = state->slots.items; 918 919 for (int i = 0; i < end; i++) { 920 DecorRange *item = &slots[indices[i]].range; 921 if (item->draw_col == -3) { 922 // No more inline virtual text before this non-inline virtual text item, 923 // so its position can be decided now. 924 decor_init_draw_col(wlv->off, selected, item); 925 } 926 if (item->start_row != state->row 927 || item->kind != kDecorKindVirtText 928 || item->data.vt->pos != kVPosInline 929 || item->data.vt->width == 0) { 930 continue; 931 } 932 if (item->draw_col >= -1 && item->start_col == v) { 933 wlv->virt_inline = item->data.vt->data.virt_text; 934 wlv->virt_inline_hl_mode = item->data.vt->hl_mode; 935 item->draw_col = INT_MIN; 936 break; 937 } 938 } 939 if (!kv_size(wlv->virt_inline)) { 940 // no more inline virtual text here 941 break; 942 } 943 } else { 944 // already inside existing inline virtual text with multiple chunks 945 int attr = 0; 946 char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr); 947 if (text == NULL) { 948 continue; 949 } 950 wlv->p_extra = text; 951 wlv->n_extra = (int)strlen(text); 952 if (wlv->n_extra == 0) { 953 continue; 954 } 955 wlv->sc_extra = NUL; 956 wlv->sc_final = NUL; 957 wlv->extra_attr = attr; 958 wlv->n_attr = mb_charlen(text); 959 // If the text didn't reach until the first window 960 // column we need to skip cells. 961 if (wlv->skip_cells > 0) { 962 int virt_text_width = (int)mb_string2cells(wlv->p_extra); 963 if (virt_text_width > wlv->skip_cells) { 964 int skip_cells_remaining = wlv->skip_cells; 965 // Skip cells in the text. 966 while (skip_cells_remaining > 0) { 967 int cells = utf_ptr2cells(wlv->p_extra); 968 if (cells > skip_cells_remaining) { 969 break; 970 } 971 int c_len = utfc_ptr2len(wlv->p_extra); 972 skip_cells_remaining -= cells; 973 wlv->p_extra += c_len; 974 wlv->n_extra -= c_len; 975 wlv->n_attr--; 976 } 977 // Skipped cells needed to be accounted for in vcol. 978 wlv->skipped_cells += wlv->skip_cells - skip_cells_remaining; 979 wlv->skip_cells = skip_cells_remaining; 980 } else { 981 // The whole text is left of the window, drop 982 // it and advance to the next one. 983 wlv->skip_cells -= virt_text_width; 984 // Skipped cells needed to be accounted for in vcol. 985 wlv->skipped_cells += virt_text_width; 986 wlv->n_attr = 0; 987 wlv->n_extra = 0; 988 // Go to the start so the next virtual text chunk can be selected. 989 continue; 990 } 991 } 992 assert(wlv->n_extra > 0); 993 wlv->extra_for_extmark = true; 994 } 995 } 996 } 997 998 /// Start a screen line at column zero. 999 static void win_line_start(win_T *wp, winlinevars_T *wlv) 1000 { 1001 wlv->col = 0; 1002 wlv->off = 0; 1003 wlv->need_lbr = false; 1004 for (int i = 0; i < wp->w_view_width; i++) { 1005 linebuf_char[i] = schar_from_ascii(' '); 1006 linebuf_attr[i] = 0; 1007 linebuf_vcol[i] = -1; 1008 } 1009 } 1010 1011 static void fix_for_boguscols(winlinevars_T *wlv) 1012 { 1013 wlv->n_extra += wlv->vcol_off_co; 1014 wlv->vcol -= wlv->vcol_off_co; 1015 wlv->vcol_off_co = 0; 1016 wlv->col -= wlv->boguscols; 1017 wlv->old_boguscols = wlv->boguscols; 1018 wlv->boguscols = 0; 1019 } 1020 1021 static int get_rightmost_vcol(win_T *wp, const int *color_cols) 1022 { 1023 int ret = 0; 1024 1025 if (wp->w_p_cuc) { 1026 ret = wp->w_virtcol; 1027 } 1028 1029 if (color_cols) { 1030 // determine rightmost colorcolumn to possibly draw 1031 for (int i = 0; color_cols[i] >= 0; i++) { 1032 ret = MAX(ret, color_cols[i]); 1033 } 1034 } 1035 1036 return ret; 1037 } 1038 1039 /// Display line "lnum" of window "wp" on the screen. 1040 /// wp->w_virtcol needs to be valid. 1041 /// 1042 /// @param lnum line to display 1043 /// @param startrow first row relative to window grid 1044 /// @param endrow last grid row to be redrawn 1045 /// @param col_rows set to the height of the line when only updating the columns, 1046 /// otherwise set to 0 1047 /// @param concealed only draw virtual lines belonging to the line above 1048 /// @param spv 'spell' related variables kept between calls for "wp" 1049 /// @param foldinfo fold info for this line 1050 /// @param[in, out] providers decoration providers active this line 1051 /// items will be disables if they cause errors 1052 /// or explicitly return `false`. 1053 /// 1054 /// @return the number of last row the line occupies. 1055 int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, bool concealed, 1056 spellvars_T *spv, foldinfo_T foldinfo) 1057 { 1058 colnr_T vcol_prev = -1; // "wlv.vcol" of previous character 1059 GridView *grid = &wp->w_grid; // grid specific to the window 1060 const int view_width = wp->w_view_width; 1061 const int view_height = wp->w_view_height; 1062 1063 const bool in_curline = wp == curwin && lnum == curwin->w_cursor.lnum; 1064 const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; 1065 const bool has_foldtext = has_fold && *wp->w_p_fdt != NUL; 1066 1067 const bool is_wrapped = wp->w_p_wrap 1068 && !has_fold; // Never wrap folded lines 1069 1070 int saved_attr2 = 0; // char_attr saved for n_attr 1071 int n_attr3 = 0; // chars with overruling special attr 1072 int saved_attr3 = 0; // char_attr saved for n_attr3 1073 1074 int fromcol_prev = -2; // start of inverting after cursor 1075 bool noinvcur = false; // don't invert the cursor 1076 bool lnum_in_visual_area = false; 1077 1078 int char_attr_pri = 0; // attributes with high priority 1079 int char_attr_base = 0; // attributes with low priority 1080 bool area_highlighting = false; // Visual or incsearch highlighting in this line 1081 int vi_attr = 0; // attributes for Visual and incsearch highlighting 1082 int area_attr = 0; // attributes desired by highlighting 1083 int search_attr = 0; // attributes desired by 'hlsearch' or ComplMatchIns 1084 int vcol_save_attr = 0; // saved attr for 'cursorcolumn' 1085 int decor_attr = 0; // attributes desired by syntax and extmarks 1086 bool has_syntax = false; // this buffer has syntax highl. 1087 int folded_attr = 0; // attributes for folded line 1088 int eol_hl_off = 0; // 1 if highlighted char after EOL 1089 #define SPWORDLEN 150 1090 char nextline[SPWORDLEN * 2]; // text with start of the next line 1091 int nextlinecol = 0; // column where nextline[] starts 1092 int nextline_idx = 0; // index in nextline[] where next line 1093 // starts 1094 int spell_attr = 0; // attributes desired by spelling 1095 int word_end = 0; // last byte with same spell_attr 1096 int cur_checked_col = 0; // checked column for current line 1097 bool extra_check = false; // has extra highlighting 1098 int multi_attr = 0; // attributes desired by multibyte 1099 int mb_l = 1; // multi-byte byte length 1100 int mb_c = 0; // decoded multi-byte character 1101 schar_T mb_schar = 0; // complete screen char 1102 int change_start = MAXCOL; // first col of changed area 1103 int change_end = -1; // last col of changed area 1104 bool in_multispace = false; // in multiple consecutive spaces 1105 int multispace_pos = 0; // position in lcs-multispace string 1106 1107 int n_extra_next = 0; // n_extra to use after current extra chars 1108 int extra_attr_next = -1; // extra_attr to use after current extra chars 1109 1110 bool search_attr_from_match = false; // if search_attr is from :match 1111 bool has_decor = false; // this buffer has decoration 1112 1113 int saved_search_attr = 0; // search_attr to be used when n_extra goes to zero 1114 int saved_area_attr = 0; // idem for area_attr 1115 int saved_decor_attr = 0; // idem for decor_attr 1116 bool saved_search_attr_from_match = false; 1117 1118 int win_col_offset = 0; // offset for window columns 1119 bool area_active = false; // whether in Visual selection, for virtual text 1120 bool decor_need_recheck = false; // call decor_recheck_draw_col() at next char 1121 1122 char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext 1123 VirtText fold_vt = VIRTTEXT_EMPTY; 1124 char *foldtext_free = NULL; 1125 1126 // 'cursorlineopt' has "screenline" and cursor is in this line 1127 bool cul_screenline = false; 1128 // margin columns for the screen line, needed for when 'cursorlineopt' 1129 // contains "screenline" 1130 int left_curline_col = 0; 1131 int right_curline_col = 0; 1132 1133 int match_conc = 0; ///< cchar for match functions 1134 bool on_last_col = false; 1135 int syntax_flags = 0; 1136 int syntax_seqnr = 0; 1137 int prev_syntax_id = 0; 1138 int conceal_attr = win_hl_attr(wp, HLF_CONCEAL); 1139 bool is_concealing = false; 1140 bool did_wcol = false; 1141 #define vcol_hlc(wlv) ((wlv).vcol - (wlv).vcol_off_co) 1142 1143 assert(startrow < endrow); 1144 1145 // variables passed between functions 1146 winlinevars_T wlv = { 1147 .lnum = lnum, 1148 .foldinfo = foldinfo, 1149 .startrow = startrow, 1150 .row = startrow, 1151 .fromcol = -10, 1152 .tocol = MAXCOL, 1153 .vcol_sbr = -1, 1154 .old_boguscols = 0, 1155 .prev_num_attr = -1, 1156 }; 1157 1158 buf_T *buf = wp->w_buffer; 1159 // Not drawing text when line is concealed or drawing filler lines beyond last line. 1160 const bool draw_text = !concealed && (lnum != buf->b_ml.ml_line_count + 1); 1161 1162 int decor_provider_end_col; 1163 bool check_decor_providers = false; 1164 1165 if (col_rows == 0 && draw_text) { 1166 // To speed up the loop below, set extra_check when there is linebreak, 1167 // trailing white space and/or syntax processing to be done. 1168 extra_check = wp->w_p_lbr; 1169 if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow && !has_foldtext) { 1170 // Prepare for syntax highlighting in this line. When there is an 1171 // error, stop syntax highlighting. 1172 int save_did_emsg = did_emsg; 1173 did_emsg = false; 1174 syntax_start(wp, lnum); 1175 if (did_emsg) { 1176 wp->w_s->b_syn_error = true; 1177 } else { 1178 did_emsg = save_did_emsg; 1179 if (!wp->w_s->b_syn_slow) { 1180 has_syntax = true; 1181 extra_check = true; 1182 } 1183 } 1184 } 1185 1186 check_decor_providers = true; 1187 1188 // Check for columns to display for 'colorcolumn'. 1189 wlv.color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; 1190 advance_color_col(&wlv, vcol_hlc(wlv)); 1191 1192 // handle Visual active in this window 1193 if (VIsual_active && wp->w_buffer == curwin->w_buffer) { 1194 pos_T *top, *bot; 1195 1196 if (ltoreq(curwin->w_cursor, VIsual)) { 1197 // Visual is after curwin->w_cursor 1198 top = &curwin->w_cursor; 1199 bot = &VIsual; 1200 } else { 1201 // Visual is before curwin->w_cursor 1202 top = &VIsual; 1203 bot = &curwin->w_cursor; 1204 } 1205 lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); 1206 if (VIsual_mode == Ctrl_V) { 1207 // block mode 1208 if (lnum_in_visual_area) { 1209 wlv.fromcol = wp->w_old_cursor_fcol; 1210 wlv.tocol = wp->w_old_cursor_lcol; 1211 } 1212 } else { 1213 // non-block mode 1214 if (lnum > top->lnum && lnum <= bot->lnum) { 1215 wlv.fromcol = 0; 1216 } else if (lnum == top->lnum) { 1217 if (VIsual_mode == 'V') { // linewise 1218 wlv.fromcol = 0; 1219 } else { 1220 getvvcol(wp, top, (colnr_T *)&wlv.fromcol, NULL, NULL); 1221 if (gchar_pos(top) == NUL) { 1222 wlv.tocol = wlv.fromcol + 1; 1223 } 1224 } 1225 } 1226 if (VIsual_mode != 'V' && lnum == bot->lnum) { 1227 if (*p_sel == 'e' && bot->col == 0 1228 && bot->coladd == 0) { 1229 wlv.fromcol = -10; 1230 wlv.tocol = MAXCOL; 1231 } else if (bot->col == MAXCOL) { 1232 wlv.tocol = MAXCOL; 1233 } else { 1234 pos_T pos = *bot; 1235 if (*p_sel == 'e') { 1236 getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, NULL, NULL); 1237 } else { 1238 getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&wlv.tocol); 1239 wlv.tocol++; 1240 } 1241 } 1242 } 1243 } 1244 1245 // Check if the char under the cursor should be inverted (highlighted). 1246 if (!highlight_match && in_curline 1247 && cursor_is_block_during_visual(*p_sel == 'e')) { 1248 noinvcur = true; 1249 } 1250 1251 // if inverting in this line set area_highlighting 1252 if (wlv.fromcol >= 0) { 1253 area_highlighting = true; 1254 vi_attr = win_hl_attr(wp, HLF_V); 1255 } 1256 // handle 'incsearch' and ":s///c" highlighting 1257 } else if (highlight_match 1258 && wp == curwin 1259 && !has_foldtext 1260 && lnum >= curwin->w_cursor.lnum 1261 && lnum <= curwin->w_cursor.lnum + search_match_lines) { 1262 if (lnum == curwin->w_cursor.lnum) { 1263 getvcol(curwin, &(curwin->w_cursor), 1264 (colnr_T *)&wlv.fromcol, NULL, NULL); 1265 } else { 1266 wlv.fromcol = 0; 1267 } 1268 if (lnum == curwin->w_cursor.lnum + search_match_lines) { 1269 pos_T pos = { 1270 .lnum = lnum, 1271 .col = search_match_endcol, 1272 }; 1273 getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL); 1274 } 1275 // do at least one character; happens when past end of line 1276 if (wlv.fromcol == wlv.tocol && search_match_endcol) { 1277 wlv.tocol = wlv.fromcol + 1; 1278 } 1279 area_highlighting = true; 1280 vi_attr = win_hl_attr(wp, HLF_I); 1281 } 1282 } 1283 1284 int bg_attr = win_bg_attr(wp); 1285 1286 int linestatus = 0; 1287 wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus); 1288 diffline_T line_changes = { 0 }; 1289 int change_index = -1; 1290 if (linestatus < 0) { 1291 if (linestatus == -1) { 1292 if (diff_find_change(wp, lnum, &line_changes)) { 1293 wlv.diff_hlf = HLF_ADD; // added line 1294 } else if (line_changes.num_changes > 0) { 1295 bool added = diff_change_parse(&line_changes, &line_changes.changes[0], 1296 &change_start, &change_end); 1297 if (change_start == 0) { 1298 if (added) { 1299 wlv.diff_hlf = HLF_TXA; // added text on changed line 1300 } else { 1301 wlv.diff_hlf = HLF_TXD; // changed text on changed line 1302 } 1303 } else { 1304 wlv.diff_hlf = HLF_CHD; // unchanged text on changed line 1305 } 1306 change_index = 0; 1307 } else { 1308 wlv.diff_hlf = HLF_CHD; // changed line 1309 change_index = 0; 1310 } 1311 } else { 1312 wlv.diff_hlf = HLF_ADD; // added line 1313 } 1314 area_highlighting = true; 1315 } 1316 VirtLines virt_lines = KV_INITIAL_VALUE; 1317 wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &wlv.n_virt_below, &virt_lines, true); 1318 wlv.filler_lines += wlv.n_virt_lines; 1319 if (lnum == wp->w_topline) { 1320 wlv.filler_lines = wp->w_topfill; 1321 wlv.n_virt_lines = MIN(wlv.n_virt_lines, wlv.filler_lines); 1322 } 1323 wlv.filler_todo = wlv.filler_lines; 1324 1325 // Cursor line highlighting for 'cursorline' in the current window. 1326 if (wp->w_p_cul && wp->w_p_culopt_flags != kOptCuloptFlagNumber && lnum == wp->w_cursorline 1327 // Do not show the cursor line in the text when Visual mode is active, 1328 // because it's not clear what is selected then. 1329 && !(wp == curwin && VIsual_active)) { 1330 cul_screenline = (is_wrapped && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline)); 1331 if (!cul_screenline) { 1332 apply_cursorline_highlight(wp, &wlv); 1333 } else { 1334 margin_columns_win(wp, &left_curline_col, &right_curline_col); 1335 } 1336 area_highlighting = true; 1337 } 1338 1339 int sign_line_attr = 0; 1340 // TODO(bfredl, vigoux): line_attr should not take priority over decoration! 1341 decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, 1342 &sign_line_attr, &wlv.sign_cul_attr, &wlv.sign_num_attr); 1343 1344 statuscol_T statuscol = { 0 }; 1345 if (*wp->w_p_stc != NUL) { 1346 // Draw the 'statuscolumn' if option is set. 1347 statuscol.draw = true; 1348 statuscol.sattrs = wlv.sattrs; 1349 statuscol.foldinfo = foldinfo; 1350 statuscol.width = win_col_off(wp) - (wp == cmdwin_win); 1351 statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? wlv.sign_cul_attr : 0; 1352 } else if (wlv.sign_cul_attr > 0) { 1353 wlv.sign_cul_attr = use_cursor_line_highlight(wp, lnum) ? syn_id2attr(wlv.sign_cul_attr) : 0; 1354 } 1355 if (wlv.sign_num_attr > 0) { 1356 wlv.sign_num_attr = syn_id2attr(wlv.sign_num_attr); 1357 } 1358 if (sign_line_attr > 0) { 1359 wlv.line_attr = syn_id2attr(sign_line_attr); 1360 } 1361 1362 // Highlight the current line in the quickfix window. 1363 if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) { 1364 wlv.line_attr = win_hl_attr(wp, HLF_QFL); 1365 } 1366 1367 if (wlv.line_attr_lowprio || wlv.line_attr) { 1368 area_highlighting = true; 1369 } 1370 1371 int line_attr_save = wlv.line_attr; 1372 int line_attr_lowprio_save = wlv.line_attr_lowprio; 1373 1374 if (spv->spv_has_spell && col_rows == 0 && draw_text) { 1375 // Prepare for spell checking. 1376 extra_check = true; 1377 1378 // When a word wrapped from the previous line the start of the 1379 // current line is valid. 1380 if (lnum == spv->spv_checked_lnum) { 1381 cur_checked_col = spv->spv_checked_col; 1382 } 1383 // Previous line was not spell checked, check for capital. This happens 1384 // for the first line in an updated region or after a closed fold. 1385 if (spv->spv_capcol_lnum == 0 && check_need_cap(wp, lnum, 0)) { 1386 spv->spv_cap_col = 0; 1387 } else if (lnum != spv->spv_capcol_lnum) { 1388 spv->spv_cap_col = -1; 1389 } 1390 spv->spv_checked_lnum = 0; 1391 1392 // Get the start of the next line, so that words that wrap to the 1393 // next line are found too: "et<line-break>al.". 1394 // Trick: skip a few chars for C/shell/Vim comments 1395 nextline[SPWORDLEN] = NUL; 1396 if (lnum < wp->w_buffer->b_ml.ml_line_count) { 1397 char *line = ml_get_buf(wp->w_buffer, lnum + 1); 1398 spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); 1399 } 1400 char *line = ml_get_buf(wp->w_buffer, lnum); 1401 1402 // If current line is empty, check first word in next line for capital. 1403 char *ptr = skipwhite(line); 1404 if (*ptr == NUL) { 1405 spv->spv_cap_col = 0; 1406 spv->spv_capcol_lnum = lnum + 1; 1407 } else if (spv->spv_cap_col == 0) { 1408 // For checking first word with a capital skip white space. 1409 spv->spv_cap_col = (int)(ptr - line); 1410 } 1411 1412 // Copy the end of the current line into nextline[]. 1413 if (nextline[SPWORDLEN] == NUL) { 1414 // No next line or it is empty. 1415 nextlinecol = MAXCOL; 1416 nextline_idx = 0; 1417 } else { 1418 const colnr_T line_len = ml_get_buf_len(wp->w_buffer, lnum); 1419 if (line_len < SPWORDLEN) { 1420 // Short line, use it completely and append the start of the 1421 // next line. 1422 nextlinecol = 0; 1423 memmove(nextline, line, (size_t)line_len); 1424 STRMOVE(nextline + line_len, nextline + SPWORDLEN); 1425 nextline_idx = line_len + 1; 1426 } else { 1427 // Long line, use only the last SPWORDLEN bytes. 1428 nextlinecol = line_len - SPWORDLEN; 1429 memmove(nextline, line + nextlinecol, SPWORDLEN); 1430 nextline_idx = SPWORDLEN + 1; 1431 } 1432 } 1433 } 1434 1435 // current line 1436 char *line = draw_text ? ml_get_buf(wp->w_buffer, lnum) : ""; 1437 // current position in "line" 1438 char *ptr = line; 1439 1440 colnr_T trailcol = MAXCOL; // start of trailing spaces 1441 colnr_T leadcol = 0; // start of leading spaces 1442 1443 bool lcs_eol_todo = true; // need to keep track of this even if lcs_eol is NUL 1444 const schar_T lcs_eol = wp->w_p_lcs_chars.eol; // 'eol' value 1445 schar_T lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used, then NUL 1446 1447 if (wp->w_p_list && !has_foldtext && draw_text) { 1448 if (wp->w_p_lcs_chars.space 1449 || wp->w_p_lcs_chars.multispace != NULL 1450 || wp->w_p_lcs_chars.leadmultispace != NULL 1451 || wp->w_p_lcs_chars.trail 1452 || wp->w_p_lcs_chars.lead 1453 || wp->w_p_lcs_chars.nbsp) { 1454 extra_check = true; 1455 } 1456 // find start of trailing whitespace 1457 if (wp->w_p_lcs_chars.trail) { 1458 trailcol = ml_get_buf_len(wp->w_buffer, lnum); 1459 while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) { 1460 trailcol--; 1461 } 1462 trailcol += (colnr_T)(ptr - line); 1463 } 1464 // find end of leading whitespace 1465 if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL 1466 || wp->w_p_lcs_chars.leadtab1 != NUL) { 1467 leadcol = 0; 1468 while (ascii_iswhite(ptr[leadcol])) { 1469 leadcol++; 1470 } 1471 if (ptr[leadcol] == NUL) { 1472 // in a line full of spaces all of them are treated as trailing 1473 leadcol = 0; 1474 } else { 1475 // keep track of the first column not filled with spaces 1476 leadcol += (colnr_T)(ptr - line + 1); 1477 } 1478 } 1479 } 1480 1481 // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the 1482 // first character to be displayed. 1483 const int start_vcol = wp->w_p_wrap 1484 ? (startrow == 0 ? wp->w_skipcol : 0) 1485 : wp->w_leftcol; 1486 1487 if (has_foldtext) { 1488 wlv.vcol = start_vcol; 1489 } else if (start_vcol > 0 && col_rows == 0) { 1490 char *prev_ptr = ptr; 1491 CharSize cs = { 0 }; 1492 1493 CharsizeArg csarg; 1494 CSType cstype = init_charsize_arg(&csarg, wp, lnum, line); 1495 csarg.max_head_vcol = start_vcol; 1496 int vcol = wlv.vcol; 1497 StrCharInfo ci = utf_ptr2StrCharInfo(ptr); 1498 while (vcol < start_vcol) { 1499 cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg); 1500 vcol += cs.width; 1501 prev_ptr = ci.ptr; 1502 if (*prev_ptr == NUL) { 1503 break; 1504 } 1505 ci = utfc_next(ci); 1506 if (wp->w_p_list) { 1507 in_multispace = *prev_ptr == ' ' && (*ci.ptr == ' ' 1508 || (prev_ptr > line && prev_ptr[-1] == ' ')); 1509 if (!in_multispace) { 1510 multispace_pos = 0; 1511 } else if (ci.ptr >= line + leadcol 1512 && wp->w_p_lcs_chars.multispace != NULL) { 1513 multispace_pos++; 1514 if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { 1515 multispace_pos = 0; 1516 } 1517 } else if (ci.ptr < line + leadcol 1518 && wp->w_p_lcs_chars.leadmultispace != NULL) { 1519 multispace_pos++; 1520 if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { 1521 multispace_pos = 0; 1522 } 1523 } 1524 } 1525 } 1526 wlv.vcol = vcol; 1527 ptr = ci.ptr; 1528 int charsize = cs.width; 1529 int head = cs.head; 1530 1531 // When: 1532 // - 'cuc' is set, or 1533 // - 'colorcolumn' is set, or 1534 // - 'virtualedit' is set, or 1535 // - the visual mode is active, or 1536 // - drawing a fold 1537 // the end of the line may be before the start of the displayed part. 1538 if (wlv.vcol < start_vcol && (wp->w_p_cuc 1539 || wlv.color_cols 1540 || virtual_active(wp) 1541 || (VIsual_active && wp->w_buffer == curwin->w_buffer) 1542 || has_fold)) { 1543 wlv.vcol = start_vcol; 1544 } 1545 1546 // Handle a character that's not completely on the screen: Put ptr at 1547 // that character but skip the first few screen characters. 1548 if (wlv.vcol > start_vcol) { 1549 wlv.vcol -= charsize; 1550 ptr = prev_ptr; 1551 } 1552 1553 if (start_vcol > wlv.vcol) { 1554 wlv.skip_cells = start_vcol - wlv.vcol - head; 1555 } 1556 1557 // Adjust for when the inverted text is before the screen, 1558 // and when the start of the inverted text is before the screen. 1559 if (wlv.tocol <= wlv.vcol) { 1560 wlv.fromcol = 0; 1561 } else if (wlv.fromcol >= 0 && wlv.fromcol < wlv.vcol) { 1562 wlv.fromcol = wlv.vcol; 1563 } 1564 1565 // When w_skipcol is non-zero, first line needs 'showbreak' 1566 if (wp->w_p_wrap) { 1567 wlv.need_showbreak = true; 1568 } 1569 // When spell checking a word we need to figure out the start of the 1570 // word and if it's badly spelled or not. 1571 if (spv->spv_has_spell) { 1572 colnr_T linecol = (colnr_T)(ptr - line); 1573 hlf_T spell_hlf = HLF_COUNT; 1574 1575 pos_T pos = wp->w_cursor; 1576 wp->w_cursor.lnum = lnum; 1577 wp->w_cursor.col = linecol; 1578 size_t len = spell_move_to(wp, FORWARD, SMT_ALL, true, &spell_hlf); 1579 1580 // spell_move_to() may call ml_get() and make "line" invalid 1581 line = ml_get_buf(wp->w_buffer, lnum); 1582 ptr = line + linecol; 1583 1584 if (len == 0 || wp->w_cursor.col > linecol) { 1585 // no bad word found at line start, don't check until end of a 1586 // word 1587 spell_hlf = HLF_COUNT; 1588 word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); 1589 } else { 1590 // bad word found, use attributes until end of word 1591 assert(len <= INT_MAX); 1592 word_end = wp->w_cursor.col + (int)len + 1; 1593 1594 // Turn index into actual attributes. 1595 if (spell_hlf != HLF_COUNT) { 1596 spell_attr = highlight_attr[spell_hlf]; 1597 } 1598 } 1599 wp->w_cursor = pos; 1600 1601 // Need to restart syntax highlighting for this line. 1602 if (has_syntax) { 1603 syntax_start(wp, lnum); 1604 } 1605 } 1606 } 1607 1608 if (check_decor_providers) { 1609 int const col = (int)(ptr - line); 1610 decor_provider_end_col = decor_providers_setup(endrow - startrow, 1611 start_vcol == 0, 1612 lnum, 1613 col, 1614 wp); 1615 line = ml_get_buf(wp->w_buffer, lnum); 1616 ptr = line + col; 1617 } 1618 1619 decor_redraw_line(wp, lnum - 1, &decor_state); 1620 if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) { 1621 has_decor = true; 1622 extra_check = true; 1623 } 1624 1625 // Correct highlighting for cursor that can't be disabled. 1626 // Avoids having to check this for each character. 1627 if (wlv.fromcol >= 0) { 1628 if (noinvcur) { 1629 if ((colnr_T)wlv.fromcol == wp->w_virtcol) { 1630 // highlighting starts at cursor, let it start just after the 1631 // cursor 1632 fromcol_prev = wlv.fromcol; 1633 wlv.fromcol = -1; 1634 } else if ((colnr_T)wlv.fromcol < wp->w_virtcol) { 1635 // restart highlighting after the cursor 1636 fromcol_prev = wp->w_virtcol; 1637 } 1638 } 1639 if (wlv.fromcol >= wlv.tocol) { 1640 wlv.fromcol = -1; 1641 } 1642 } 1643 1644 if (col_rows == 0 && draw_text && !has_foldtext) { 1645 const int v = (int)(ptr - line); 1646 area_highlighting |= prepare_search_hl_line(wp, lnum, v, 1647 &line, &screen_search_hl, &search_attr, 1648 &search_attr_from_match); 1649 ptr = line + v; // "line" may have been updated 1650 } 1651 1652 if ((State & MODE_INSERT) && ins_compl_win_active(wp) 1653 && (in_curline || ins_compl_lnum_in_range(lnum))) { 1654 area_highlighting = true; 1655 } 1656 1657 win_line_start(wp, &wlv); 1658 bool draw_cols = true; 1659 int leftcols_width = 0; 1660 1661 // won't highlight after TERM_ATTRS_MAX columns 1662 int term_attrs[TERM_ATTRS_MAX] = { 0 }; 1663 if (wp->w_buffer->terminal) { 1664 terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs); 1665 extra_check = true; 1666 } 1667 1668 const bool may_have_inline_virt 1669 = !has_foldtext && buf_meta_total(wp->w_buffer, kMTMetaInline) > 0; 1670 int virt_line_index = -1; 1671 int virt_line_flags = 0; 1672 1673 // Repeat for each cell in the displayed line. 1674 while (true) { 1675 int has_match_conc = 0; ///< match wants to conceal 1676 int decor_conceal = 0; 1677 1678 bool did_decrement_ptr = false; 1679 1680 // Get next chunk of extmark highlights if previous approximation was smaller than needed. 1681 if (check_decor_providers && (int)(ptr - line) >= decor_provider_end_col) { 1682 int const col = (int)(ptr - line); 1683 decor_provider_end_col = invoke_range_next(wp, lnum, col, 100); 1684 line = ml_get_buf(wp->w_buffer, lnum); 1685 ptr = line + col; 1686 if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) { 1687 has_decor = true; 1688 extra_check = true; 1689 } 1690 } 1691 1692 // Skip this quickly when working on the text. 1693 if (draw_cols) { 1694 if (cul_screenline) { 1695 wlv.cul_attr = 0; 1696 wlv.line_attr = line_attr_save; 1697 wlv.line_attr_lowprio = line_attr_lowprio_save; 1698 } 1699 1700 assert(wlv.off == 0); 1701 1702 if (wp == cmdwin_win) { 1703 // Draw the cmdline character. 1704 draw_col_fill(&wlv, schar_from_ascii(cmdwin_type), 1, win_hl_attr(wp, HLF_AT)); 1705 } 1706 1707 if (wlv.filler_todo > 0) { 1708 int index = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines); 1709 if (index > 0) { 1710 virt_line_index = (int)kv_size(virt_lines) - index; 1711 assert(virt_line_index >= 0); 1712 virt_line_flags = kv_A(virt_lines, virt_line_index).flags; 1713 } 1714 } 1715 1716 if (virt_line_index >= 0 && (virt_line_flags & kVLLeftcol)) { 1717 // skip columns 1718 } else if (statuscol.draw) { 1719 // Draw 'statuscolumn' if it is set. 1720 const int v = (int)(ptr - line); 1721 draw_statuscol(wp, &wlv, wlv.row - startrow - wlv.filler_lines, col_rows, &statuscol); 1722 if (wp->w_redr_statuscol) { 1723 break; 1724 } 1725 if (draw_text) { 1726 // Get the line again as evaluating 'statuscolumn' may free it. 1727 line = ml_get_buf(wp->w_buffer, lnum); 1728 ptr = line + v; 1729 } 1730 } else { 1731 // draw builtin info columns: fold, sign, number 1732 draw_foldcolumn(wp, &wlv); 1733 1734 // wp->w_scwidth is zero if signcol=number is used 1735 for (int sign_idx = 0; sign_idx < wp->w_scwidth; sign_idx++) { 1736 draw_sign(false, wp, &wlv, sign_idx); 1737 } 1738 1739 draw_lnum_col(wp, &wlv); 1740 } 1741 1742 win_col_offset = wlv.off; 1743 1744 // When only updating the columns and that's done, stop here. 1745 if (col_rows > 0) { 1746 wlv_put_linebuf(wp, &wlv, MIN(wlv.off, view_width), false, bg_attr, 0); 1747 // Need to update more screen lines if: 1748 // - 'statuscolumn' needs to be drawn, or 1749 // - LineNrAbove or LineNrBelow is used, or 1750 // - still drawing filler lines. 1751 if ((wlv.row + 1 - wlv.startrow < col_rows 1752 && (statuscol.draw 1753 || win_hl_attr(wp, HLF_LNA) != win_hl_attr(wp, HLF_N) 1754 || win_hl_attr(wp, HLF_LNB) != win_hl_attr(wp, HLF_N))) 1755 || wlv.filler_todo > 0) { 1756 wlv.row++; 1757 if (wlv.row == endrow) { 1758 break; 1759 } 1760 wlv.filler_todo--; 1761 virt_line_index = -1; 1762 if (wlv.filler_todo == 0 && (wp->w_botfill || !draw_text)) { 1763 break; 1764 } 1765 // win_line_start(wp, &wlv); 1766 wlv.col = 0; 1767 wlv.off = 0; 1768 continue; 1769 } else { 1770 break; 1771 } 1772 } 1773 1774 // Check if 'breakindent' applies and show it. 1775 if (!wp->w_briopt_sbr) { 1776 handle_breakindent(wp, &wlv); 1777 } 1778 handle_showbreak_and_filler(wp, &wlv); 1779 if (wp->w_briopt_sbr) { 1780 handle_breakindent(wp, &wlv); 1781 } 1782 1783 wlv.col = wlv.off; 1784 draw_cols = false; 1785 if (wlv.filler_todo <= 0) { 1786 leftcols_width = wlv.off; 1787 } 1788 if (has_decor && wlv.row == startrow + wlv.filler_lines) { 1789 // hide virt_text on text hidden by 'nowrap' or 'smoothscroll' 1790 decor_redraw_col(wp, (colnr_T)(ptr - line) - 1, wlv.off, true, &decor_state); 1791 } 1792 if (wlv.col >= view_width) { 1793 wlv.col = wlv.off = view_width; 1794 goto end_check; 1795 } 1796 } 1797 1798 if (cul_screenline && wlv.filler_todo <= 0 1799 && wlv.vcol >= left_curline_col && wlv.vcol < right_curline_col) { 1800 apply_cursorline_highlight(wp, &wlv); 1801 } 1802 1803 // When still displaying '$' of change command, stop at cursor. 1804 if (dollar_vcol >= 0 && in_curline && wlv.vcol >= wp->w_virtcol) { 1805 draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); 1806 // don't clear anything after wlv.col 1807 wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0); 1808 // Pretend we have finished updating the window. Except when 1809 // 'cursorcolumn' is set. 1810 if (wp->w_p_cuc) { 1811 wlv.row = wp->w_cline_row + wp->w_cline_height; 1812 } else { 1813 wlv.row = view_height; 1814 } 1815 break; 1816 } 1817 1818 const bool draw_folded = has_fold && wlv.row == startrow + wlv.filler_lines; 1819 if (draw_folded && wlv.n_extra == 0) { 1820 wlv.char_attr = folded_attr = win_hl_attr(wp, HLF_FL); 1821 decor_attr = 0; 1822 } 1823 1824 int extmark_attr = 0; 1825 if (wlv.filler_todo <= 0 1826 && (area_highlighting || spv->spv_has_spell || extra_check)) { 1827 if (wlv.n_extra == 0 || !wlv.extra_for_extmark) { 1828 wlv.reset_extra_attr = false; 1829 } 1830 1831 if (has_decor && wlv.n_extra == 0) { 1832 // Duplicate the Visual area check after this block, 1833 // but don't check inside p_extra here. 1834 if (wlv.vcol == wlv.fromcol 1835 || (wlv.vcol + 1 == wlv.fromcol 1836 && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)) 1837 || (vcol_prev == fromcol_prev 1838 && vcol_prev < wlv.vcol 1839 && wlv.vcol < wlv.tocol)) { 1840 area_active = true; 1841 } else if (area_active 1842 && (wlv.vcol == wlv.tocol 1843 || (noinvcur && wlv.vcol == wp->w_virtcol))) { 1844 area_active = false; 1845 } 1846 1847 bool selected = (area_active || (area_highlighting && noinvcur 1848 && wlv.vcol == wp->w_virtcol)); 1849 // When there may be inline virtual text, position of non-inline virtual text 1850 // can only be decided after drawing inline virtual text with lower priority. 1851 if (decor_need_recheck) { 1852 if (!may_have_inline_virt) { 1853 decor_recheck_draw_col(wlv.off, selected, &decor_state); 1854 } 1855 decor_need_recheck = false; 1856 } 1857 extmark_attr = decor_redraw_col(wp, (colnr_T)(ptr - line), 1858 may_have_inline_virt ? -3 : wlv.off, 1859 selected, &decor_state); 1860 if (may_have_inline_virt) { 1861 handle_inline_virtual_text(wp, &wlv, ptr - line, selected); 1862 if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) { 1863 // restore search_attr and area_attr when n_extra is down to zero 1864 // TODO(bfredl): this is ugly as fuck. look if we can do this some other way. 1865 saved_search_attr = search_attr; 1866 saved_area_attr = area_attr; 1867 saved_decor_attr = decor_attr; 1868 saved_search_attr_from_match = search_attr_from_match; 1869 search_attr = 0; 1870 area_attr = 0; 1871 decor_attr = 0; 1872 search_attr_from_match = false; 1873 } 1874 } 1875 } 1876 1877 int *area_attr_p = wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace 1878 ? &saved_area_attr : &area_attr; 1879 1880 // handle Visual or match highlighting in this line 1881 if (wlv.vcol == wlv.fromcol 1882 || (wlv.vcol + 1 == wlv.fromcol 1883 && ((wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1) 1884 || (wlv.n_extra > 0 && wlv.p_extra != NULL 1885 && utf_ptr2cells(wlv.p_extra) > 1))) 1886 || (vcol_prev == fromcol_prev 1887 && vcol_prev < wlv.vcol // not at margin 1888 && wlv.vcol < wlv.tocol)) { 1889 *area_attr_p = vi_attr; // start highlighting 1890 area_active = true; 1891 } else if (*area_attr_p != 0 1892 && (wlv.vcol == wlv.tocol 1893 || (noinvcur && wlv.vcol == wp->w_virtcol))) { 1894 *area_attr_p = 0; // stop highlighting 1895 area_active = false; 1896 } 1897 1898 if (!has_foldtext && wlv.n_extra == 0) { 1899 // Check for start/end of 'hlsearch' and other matches. 1900 // After end, check for start/end of next match. 1901 // When another match, have to check for start again. 1902 const int v = (int)(ptr - line); 1903 search_attr = update_search_hl(wp, lnum, v, &line, &screen_search_hl, 1904 &has_match_conc, &match_conc, lcs_eol_todo, 1905 &on_last_col, &search_attr_from_match); 1906 ptr = line + v; // "line" may have been changed 1907 1908 // Do not allow a conceal over EOL otherwise EOL will be missed 1909 // and bad things happen. 1910 if (*ptr == NUL) { 1911 has_match_conc = 0; 1912 } 1913 1914 // Check if ComplMatchIns highlight is needed. 1915 if ((State & MODE_INSERT) && ins_compl_win_active(wp) 1916 && (in_curline || ins_compl_lnum_in_range(lnum))) { 1917 int ins_match_attr = ins_compl_col_range_attr(lnum, (int)(ptr - line)); 1918 if (ins_match_attr > 0) { 1919 search_attr = hl_combine_attr(search_attr, ins_match_attr); 1920 } 1921 } 1922 } 1923 1924 if (wlv.diff_hlf != (hlf_T)0) { 1925 if (line_changes.num_changes > 0 1926 && change_index >= 0 1927 && change_index < line_changes.num_changes - 1) { 1928 if (ptr - line 1929 >= line_changes.changes[change_index + 1].dc_start[line_changes.bufidx]) { 1930 change_index += 1; 1931 } 1932 } 1933 bool added = false; 1934 if (line_changes.num_changes > 0 && change_index >= 0 1935 && change_index < line_changes.num_changes) { 1936 added = diff_change_parse(&line_changes, &line_changes.changes[change_index], 1937 &change_start, &change_end); 1938 } 1939 // When there is extra text (eg: virtual text) it gets the 1940 // diff highlighting for the line, but not for changed text. 1941 if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start 1942 && wlv.n_extra == 0) { 1943 wlv.diff_hlf = added ? HLF_TXA : HLF_TXD; // added/changed text 1944 } 1945 if ((wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) 1946 && ((ptr - line >= change_end && wlv.n_extra == 0) 1947 || (wlv.n_extra > 0 && wlv.extra_for_extmark))) { 1948 wlv.diff_hlf = HLF_CHD; // changed line 1949 } 1950 set_line_attr_for_diff(wp, &wlv); 1951 } 1952 1953 // Decide which of the highlight attributes to use. 1954 if (area_attr != 0) { 1955 char_attr_pri = hl_combine_attr(wlv.line_attr, area_attr); 1956 if (!highlight_match) { 1957 // let search highlight show in Visual area if possible 1958 char_attr_pri = hl_combine_attr(search_attr, char_attr_pri); 1959 } 1960 } else if (search_attr != 0) { 1961 char_attr_pri = hl_combine_attr(wlv.line_attr, search_attr); 1962 } else if (wlv.line_attr != 0 1963 && ((wlv.fromcol == -10 && wlv.tocol == MAXCOL) 1964 || wlv.vcol < wlv.fromcol 1965 || vcol_prev < fromcol_prev 1966 || wlv.vcol >= wlv.tocol)) { 1967 // Use wlv.line_attr when not in the Visual or 'incsearch' area 1968 // (area_attr may be 0 when "noinvcur" is set). 1969 char_attr_pri = wlv.line_attr; 1970 } else { 1971 char_attr_pri = 0; 1972 } 1973 char_attr_base = hl_combine_attr(folded_attr, decor_attr); 1974 wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); 1975 } 1976 1977 if (draw_folded && has_foldtext && wlv.n_extra == 0 && wlv.col == win_col_offset) { 1978 const int v = (int)(ptr - line); 1979 linenr_T lnume = lnum + foldinfo.fi_lines - 1; 1980 memset(buf_fold, ' ', FOLD_TEXT_LEN); 1981 wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt); 1982 wlv.n_extra = (int)strlen(wlv.p_extra); 1983 1984 if (wlv.p_extra != buf_fold) { 1985 assert(foldtext_free == NULL); 1986 foldtext_free = wlv.p_extra; 1987 } 1988 wlv.sc_extra = NUL; 1989 wlv.sc_final = NUL; 1990 wlv.p_extra[wlv.n_extra] = NUL; 1991 1992 // Get the line again as evaluating 'foldtext' may free it. 1993 line = ml_get_buf(wp->w_buffer, lnum); 1994 ptr = line + v; 1995 } 1996 1997 // Draw 'fold' fillchar after 'foldtext', or after 'eol' listchar for transparent 'foldtext'. 1998 if (draw_folded && wlv.n_extra == 0 && wlv.col < view_width 1999 && (has_foldtext || (*ptr == NUL && (!wp->w_p_list || !lcs_eol_todo || lcs_eol == NUL)))) { 2000 // Fill rest of line with 'fold'. 2001 wlv.sc_extra = wp->w_p_fcs_chars.fold; 2002 wlv.sc_final = NUL; 2003 wlv.n_extra = view_width - wlv.col; 2004 // Don't continue search highlighting past the first filler char. 2005 search_attr = 0; 2006 } 2007 2008 if (draw_folded && wlv.n_extra != 0 && wlv.col >= view_width) { 2009 // Truncate the folding. 2010 wlv.n_extra = 0; 2011 } 2012 2013 // Get the next character to put on the screen. 2014 // 2015 // The "p_extra" points to the extra stuff that is inserted to 2016 // represent special characters (non-printable stuff) and other 2017 // things. When all characters are the same, sc_extra is used. 2018 // If sc_final is set, it will compulsorily be used at the end. 2019 // "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past 2020 // "p_extra[n_extra]". 2021 // For the '$' of the 'list' option, n_extra == 1, p_extra == "". 2022 if (wlv.n_extra > 0) { 2023 if (wlv.sc_extra != NUL || (wlv.n_extra == 1 && wlv.sc_final != NUL)) { 2024 mb_schar = (wlv.n_extra == 1 && wlv.sc_final != NUL) ? wlv.sc_final : wlv.sc_extra; 2025 mb_c = schar_get_first_codepoint(mb_schar); 2026 wlv.n_extra--; 2027 } else { 2028 assert(wlv.p_extra != NULL); 2029 mb_l = utfc_ptr2len(wlv.p_extra); 2030 mb_schar = utfc_ptr2schar(wlv.p_extra, &mb_c); 2031 // mb_l=0 at the end-of-line NUL 2032 if (mb_l > wlv.n_extra || mb_l == 0) { 2033 mb_l = 1; 2034 } 2035 2036 // If a double-width char doesn't fit display a '>' in the last column. 2037 // Don't advance the pointer but put the character at the start of the next line. 2038 if (wlv.col >= view_width - 1 && schar_cells(mb_schar) == 2) { 2039 mb_c = '>'; 2040 mb_l = 1; 2041 mb_schar = schar_from_ascii(mb_c); 2042 multi_attr = win_hl_attr(wp, HLF_AT); 2043 2044 if (wlv.cul_attr) { 2045 multi_attr = 0 != wlv.line_attr_lowprio 2046 ? hl_combine_attr(wlv.cul_attr, multi_attr) 2047 : hl_combine_attr(multi_attr, wlv.cul_attr); 2048 } 2049 } else { 2050 wlv.n_extra -= mb_l; 2051 wlv.p_extra += mb_l; 2052 } 2053 2054 // If a double-width char doesn't fit at the left side display a '<'. 2055 if (wlv.filler_todo <= 0 && wlv.skip_cells > 0 && mb_l > 1) { 2056 if (wlv.n_extra > 0) { 2057 n_extra_next = wlv.n_extra; 2058 extra_attr_next = wlv.extra_attr; 2059 } 2060 wlv.n_extra = 1; 2061 wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR); 2062 wlv.sc_final = NUL; 2063 mb_schar = schar_from_ascii(' '); 2064 mb_c = ' '; 2065 mb_l = 1; 2066 (void)mb_l; 2067 wlv.n_attr++; 2068 wlv.extra_attr = win_hl_attr(wp, HLF_AT); 2069 } 2070 } 2071 2072 if (wlv.n_extra <= 0) { 2073 // Only restore search_attr and area_attr when there is no "n_extra" to show. 2074 if (n_extra_next <= 0) { 2075 if (search_attr == 0) { 2076 search_attr = saved_search_attr; 2077 saved_search_attr = 0; 2078 } 2079 if (area_attr == 0 && *ptr != NUL) { 2080 area_attr = saved_area_attr; 2081 saved_area_attr = 0; 2082 } 2083 if (decor_attr == 0) { 2084 decor_attr = saved_decor_attr; 2085 saved_decor_attr = 0; 2086 } 2087 if (wlv.extra_for_extmark) { 2088 // wlv.extra_attr should be used at this position but not any further. 2089 wlv.reset_extra_attr = true; 2090 extra_attr_next = -1; 2091 } 2092 wlv.extra_for_extmark = false; 2093 } else { 2094 assert(wlv.sc_extra != NUL || wlv.sc_final != NUL); 2095 assert(wlv.p_extra != NULL); 2096 wlv.sc_extra = NUL; 2097 wlv.sc_final = NUL; 2098 wlv.n_extra = n_extra_next; 2099 n_extra_next = 0; 2100 // wlv.extra_attr should be used at this position, but extra_attr_next 2101 // should be used after that. 2102 wlv.reset_extra_attr = true; 2103 assert(extra_attr_next >= 0); 2104 } 2105 } 2106 } else if (wlv.filler_todo > 0) { 2107 // Wait with reading text until filler lines are done. Still need to 2108 // initialize these. 2109 mb_c = ' '; 2110 mb_schar = schar_from_ascii(' '); 2111 } else if (has_foldtext || (has_fold && wlv.col >= view_width)) { 2112 // skip writing the buffer line itself 2113 mb_schar = NUL; 2114 } else { 2115 const char *prev_ptr = ptr; 2116 2117 // first byte of next char 2118 int c0 = (uint8_t)(*ptr); 2119 if (c0 == NUL) { 2120 // no more cells to skip 2121 wlv.skip_cells = 0; 2122 } 2123 2124 // Get a character from the line itself. 2125 mb_l = utfc_ptr2len(ptr); 2126 mb_schar = utfc_ptr2schar(ptr, &mb_c); 2127 2128 // Overlong encoded ASCII or ASCII with composing char 2129 // is displayed normally, except a NUL. 2130 if (mb_l > 1 && mb_c < 0x80) { 2131 c0 = mb_c; 2132 } 2133 2134 if ((mb_l == 1 && c0 >= 0x80) 2135 || (mb_l >= 1 && mb_c == 0) 2136 || (mb_l > 1 && (!vim_isprintc(mb_c)))) { 2137 // Illegal UTF-8 byte: display as <xx>. 2138 // Non-printable character : display as ? or fullwidth ?. 2139 transchar_hex(wlv.extra, mb_c); 2140 if (wp->w_p_rl) { // reverse 2141 rl_mirror_ascii(wlv.extra, NULL); 2142 } 2143 2144 wlv.p_extra = wlv.extra; 2145 mb_c = mb_ptr2char_adv((const char **)&wlv.p_extra); 2146 mb_schar = schar_from_char(mb_c); 2147 wlv.n_extra = (int)strlen(wlv.p_extra); 2148 wlv.sc_extra = NUL; 2149 wlv.sc_final = NUL; 2150 if (area_attr == 0 && search_attr == 0) { 2151 wlv.n_attr = wlv.n_extra + 1; 2152 wlv.extra_attr = win_hl_attr(wp, HLF_8); 2153 saved_attr2 = wlv.char_attr; // save current attr 2154 } 2155 } else if (mb_l == 0) { // at the NUL at end-of-line 2156 mb_l = 1; 2157 } 2158 // If a double-width char doesn't fit display a '>' in the 2159 // last column; the character is displayed at the start of the 2160 // next line. 2161 if (wlv.col >= view_width - 1 && schar_cells(mb_schar) == 2) { 2162 mb_schar = schar_from_ascii('>'); 2163 mb_c = '>'; 2164 mb_l = 1; 2165 multi_attr = win_hl_attr(wp, HLF_AT); 2166 // Put pointer back so that the character will be 2167 // displayed at the start of the next line. 2168 ptr--; 2169 did_decrement_ptr = true; 2170 } else if (*ptr != NUL) { 2171 ptr += mb_l - 1; 2172 } 2173 2174 // If a double-width char doesn't fit at the left side display a '<' in 2175 // the first column. Don't do this for unprintable characters. 2176 if (wlv.skip_cells > 0 && mb_l > 1 && wlv.n_extra == 0) { 2177 wlv.n_extra = 1; 2178 wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR); 2179 wlv.sc_final = NUL; 2180 mb_schar = schar_from_ascii(' '); 2181 mb_c = ' '; 2182 mb_l = 1; 2183 if (area_attr == 0 && search_attr == 0) { 2184 wlv.n_attr = wlv.n_extra + 1; 2185 wlv.extra_attr = win_hl_attr(wp, HLF_AT); 2186 saved_attr2 = wlv.char_attr; // save current attr 2187 } 2188 } 2189 ptr++; 2190 2191 decor_attr = 0; 2192 if (extra_check) { 2193 const bool no_plain_buffer = (wp->w_s->b_p_spo_flags & kOptSpoFlagNoplainbuffer) != 0; 2194 bool can_spell = !no_plain_buffer; 2195 2196 // Get extmark and syntax attributes, unless still at the start of the line 2197 // (double-wide char that doesn't fit). 2198 const int v = (int)(ptr - line); 2199 const ptrdiff_t prev_v = prev_ptr - line; 2200 if (has_syntax && v > 0) { 2201 // Get the syntax attribute for the character. If there 2202 // is an error, disable syntax highlighting. 2203 int save_did_emsg = did_emsg; 2204 did_emsg = false; 2205 2206 decor_attr = get_syntax_attr(v - 1, spv->spv_has_spell ? &can_spell : NULL, false); 2207 2208 if (did_emsg) { 2209 wp->w_s->b_syn_error = true; 2210 has_syntax = false; 2211 } else { 2212 did_emsg = save_did_emsg; 2213 } 2214 2215 if (wp->w_s->b_syn_slow) { 2216 has_syntax = false; 2217 } 2218 2219 // Need to get the line again, a multi-line regexp may 2220 // have made it invalid. 2221 line = ml_get_buf(wp->w_buffer, lnum); 2222 ptr = line + v; 2223 prev_ptr = line + prev_v; 2224 2225 // no concealing past the end of the line, it interferes 2226 // with line highlighting. 2227 syntax_flags = (mb_schar == 0) ? 0 : get_syntax_info(&syntax_seqnr); 2228 } 2229 2230 if (has_decor && v > 0) { 2231 // extmarks take preceedence over syntax.c 2232 decor_attr = hl_combine_attr(decor_attr, extmark_attr); 2233 decor_conceal = decor_state.conceal; 2234 can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell); 2235 } 2236 2237 char_attr_base = hl_combine_attr(folded_attr, decor_attr); 2238 wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); 2239 2240 // Check spelling (unless at the end of the line). 2241 // Only do this when there is no syntax highlighting, the 2242 // @Spell cluster is not used or the current syntax item 2243 // contains the @Spell cluster. 2244 int v1 = (int)(ptr - line); 2245 if (spv->spv_has_spell && v1 >= word_end && v1 > cur_checked_col) { 2246 spell_attr = 0; 2247 // do not calculate cap_col at the end of the line or when 2248 // only white space is following 2249 if (mb_schar != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) { 2250 char *p; 2251 hlf_T spell_hlf = HLF_COUNT; 2252 v1 -= mb_l - 1; 2253 2254 // Use nextline[] if possible, it has the start of the 2255 // next line concatenated. 2256 if ((prev_ptr - line) - nextlinecol >= 0) { 2257 p = nextline + ((prev_ptr - line) - nextlinecol); 2258 } else { 2259 p = (char *)prev_ptr; 2260 } 2261 spv->spv_cap_col -= (int)(prev_ptr - line); 2262 size_t tmplen = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, spv->spv_unchanged); 2263 assert(tmplen <= INT_MAX); 2264 int len = (int)tmplen; 2265 word_end = v1 + len; 2266 2267 // In Insert mode only highlight a word that 2268 // doesn't touch the cursor. 2269 if (spell_hlf != HLF_COUNT 2270 && (State & MODE_INSERT) 2271 && wp->w_cursor.lnum == lnum 2272 && wp->w_cursor.col >= (colnr_T)(prev_ptr - line) 2273 && wp->w_cursor.col < (colnr_T)word_end) { 2274 spell_hlf = HLF_COUNT; 2275 spell_redraw_lnum = lnum; 2276 } 2277 2278 if (spell_hlf == HLF_COUNT && p != prev_ptr 2279 && (p - nextline) + len > nextline_idx) { 2280 // Remember that the good word continues at the 2281 // start of the next line. 2282 spv->spv_checked_lnum = lnum + 1; 2283 spv->spv_checked_col = (int)((p - nextline) + len - nextline_idx); 2284 } 2285 2286 // Turn index into actual attributes. 2287 if (spell_hlf != HLF_COUNT) { 2288 spell_attr = highlight_attr[spell_hlf]; 2289 } 2290 2291 if (spv->spv_cap_col > 0) { 2292 if (p != prev_ptr && (p - nextline) + spv->spv_cap_col >= nextline_idx) { 2293 // Remember that the word in the next line 2294 // must start with a capital. 2295 spv->spv_capcol_lnum = lnum + 1; 2296 spv->spv_cap_col = (int)((p - nextline) + spv->spv_cap_col - nextline_idx); 2297 } else { 2298 // Compute the actual column. 2299 spv->spv_cap_col += (int)(prev_ptr - line); 2300 } 2301 } 2302 } 2303 } 2304 if (spell_attr != 0) { 2305 char_attr_base = hl_combine_attr(char_attr_base, spell_attr); 2306 wlv.char_attr = hl_combine_attr(char_attr_base, char_attr_pri); 2307 } 2308 2309 if (wp->w_buffer->terminal) { 2310 wlv.char_attr = hl_combine_attr(wlv.vcol < TERM_ATTRS_MAX ? term_attrs[wlv.vcol] : 0, 2311 wlv.char_attr); 2312 } 2313 2314 // we don't want linebreak to apply for lines that start with 2315 // leading spaces, followed by long letters (since it would add 2316 // a break at the beginning of a line and this might be unexpected) 2317 // 2318 // So only allow to linebreak, once we have found chars not in 2319 // 'breakat' in the line. 2320 if (wp->w_p_lbr && !wlv.need_lbr && mb_schar != NUL 2321 && !vim_isbreak((uint8_t)(*ptr))) { 2322 wlv.need_lbr = true; 2323 } 2324 // Found last space before word: check for line break. 2325 if (wp->w_p_lbr && c0 == mb_c && mb_c < 128 && wlv.need_lbr 2326 && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) { 2327 int mb_off = utf_head_off(line, ptr - 1); 2328 char *p = ptr - (mb_off + 1); 2329 2330 CharsizeArg csarg; 2331 // lnum == 0, do not want virtual text to be counted here 2332 CSType cstype = init_charsize_arg(&csarg, wp, 0, line); 2333 wlv.n_extra = win_charsize(cstype, wlv.vcol, p, utf_ptr2CharInfo(p).value, 2334 &csarg).width - 1; 2335 2336 if (on_last_col && mb_c != TAB) { 2337 // Do not continue search/match highlighting over the 2338 // line break, but for TABs the highlighting should 2339 // include the complete width of the character 2340 search_attr = 0; 2341 } 2342 2343 if (mb_c == TAB && wlv.n_extra + wlv.col > view_width) { 2344 wlv.n_extra = tabstop_padding(wlv.vcol, wp->w_buffer->b_p_ts, 2345 wp->w_buffer->b_p_vts_array) - 1; 2346 } 2347 wlv.sc_extra = schar_from_ascii(mb_off > 0 ? MB_FILLER_CHAR : ' '); 2348 wlv.sc_final = NUL; 2349 if (mb_c < 128 && ascii_iswhite(mb_c)) { 2350 if (mb_c == TAB) { 2351 // See "Tab alignment" below. 2352 fix_for_boguscols(&wlv); 2353 } 2354 if (!wp->w_p_list) { 2355 mb_c = ' '; 2356 mb_schar = schar_from_ascii(mb_c); 2357 } 2358 } 2359 } 2360 2361 if (wp->w_p_list) { 2362 in_multispace = mb_c == ' ' && (*ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' ')); 2363 if (!in_multispace) { 2364 multispace_pos = 0; 2365 } 2366 } 2367 2368 // 'list': Change char 160 to 'nbsp' and space to 'space'. 2369 // But not when the character is followed by a composing 2370 // character (use mb_l to check that). 2371 if (wp->w_p_list 2372 && ((((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3)) 2373 && wp->w_p_lcs_chars.nbsp) 2374 || (mb_c == ' ' 2375 && mb_l == 1 2376 && (wp->w_p_lcs_chars.space 2377 || (in_multispace && wp->w_p_lcs_chars.multispace != NULL)) 2378 && ptr - line >= leadcol 2379 && ptr - line <= trailcol))) { 2380 if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) { 2381 mb_schar = wp->w_p_lcs_chars.multispace[multispace_pos++]; 2382 if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { 2383 multispace_pos = 0; 2384 } 2385 } else { 2386 mb_schar = (mb_c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; 2387 } 2388 wlv.n_attr = 1; 2389 wlv.extra_attr = win_hl_attr(wp, HLF_0); 2390 saved_attr2 = wlv.char_attr; // save current attr 2391 mb_c = schar_get_first_codepoint(mb_schar); 2392 } 2393 2394 if (mb_c == ' ' && mb_l == 1 && ((trailcol != MAXCOL && ptr > line + trailcol) 2395 || (leadcol != 0 && ptr < line + leadcol))) { 2396 if (leadcol != 0 && in_multispace && ptr < line + leadcol 2397 && wp->w_p_lcs_chars.leadmultispace != NULL) { 2398 mb_schar = wp->w_p_lcs_chars.leadmultispace[multispace_pos++]; 2399 if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { 2400 multispace_pos = 0; 2401 } 2402 } else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) { 2403 mb_schar = wp->w_p_lcs_chars.trail; 2404 } else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) { 2405 mb_schar = wp->w_p_lcs_chars.lead; 2406 } else if (leadcol != 0 && wp->w_p_lcs_chars.space) { 2407 mb_schar = wp->w_p_lcs_chars.space; 2408 } 2409 2410 wlv.n_attr = 1; 2411 wlv.extra_attr = win_hl_attr(wp, HLF_0); 2412 saved_attr2 = wlv.char_attr; // save current attr 2413 mb_c = schar_get_first_codepoint(mb_schar); 2414 } 2415 } 2416 2417 // Handling of non-printable characters. 2418 if (!vim_isprintc(mb_c)) { 2419 // when getting a character from the file, we may have to 2420 // turn it into something else on the way to putting it on the screen. 2421 if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { 2422 int tab_len = 0; 2423 colnr_T vcol_adjusted = wlv.vcol; // removed showbreak length 2424 schar_T lcs_tab1 = wp->w_p_lcs_chars.tab1; 2425 schar_T lcs_tab2 = wp->w_p_lcs_chars.tab2; 2426 schar_T lcs_tab3 = wp->w_p_lcs_chars.tab3; 2427 // check if leadtab is set in 'listchars' 2428 if (wp->w_p_list && wp->w_p_lcs_chars.leadtab1 != NUL 2429 && ptr < line + leadcol) { 2430 lcs_tab1 = wp->w_p_lcs_chars.leadtab1; 2431 lcs_tab2 = wp->w_p_lcs_chars.leadtab2; 2432 lcs_tab3 = wp->w_p_lcs_chars.leadtab3; 2433 } 2434 char *const sbr = get_showbreak_value(wp); 2435 2436 // Only adjust the tab_len, when at the first column after the 2437 // showbreak value was drawn. 2438 if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap) { 2439 vcol_adjusted = wlv.vcol - mb_charlen(sbr); 2440 } 2441 // tab amount depends on current column 2442 tab_len = tabstop_padding(vcol_adjusted, 2443 wp->w_buffer->b_p_ts, 2444 wp->w_buffer->b_p_vts_array) - 1; 2445 2446 if (!wp->w_p_lbr || !wp->w_p_list) { 2447 wlv.n_extra = tab_len; 2448 } else { 2449 int saved_nextra = wlv.n_extra; 2450 2451 if (wlv.vcol_off_co > 0) { 2452 // there are characters to conceal 2453 tab_len += wlv.vcol_off_co; 2454 } 2455 // boguscols before fix_for_boguscols() from above. 2456 if (lcs_tab1 && wlv.old_boguscols > 0 && wlv.n_extra > tab_len) { 2457 tab_len += wlv.n_extra - tab_len; 2458 } 2459 2460 if (tab_len > 0) { 2461 // If wlv.n_extra > 0, it gives the number of chars 2462 // to use for a tab, else we need to calculate the 2463 // width for a tab. 2464 size_t tab2_len = schar_len(lcs_tab2); 2465 size_t len = (size_t)tab_len * tab2_len; 2466 if (lcs_tab3) { 2467 len += schar_len(lcs_tab3) - tab2_len; 2468 } 2469 if (wlv.n_extra > 0) { 2470 len += (size_t)(wlv.n_extra - tab_len); 2471 } 2472 mb_schar = lcs_tab1; 2473 mb_c = schar_get_first_codepoint(mb_schar); 2474 char *p = get_extra_buf(len + 1); 2475 memset(p, ' ', len); 2476 p[len] = NUL; 2477 wlv.p_extra = p; 2478 for (int i = 0; i < tab_len; i++) { 2479 if (*p == NUL) { 2480 tab_len = i; 2481 break; 2482 } 2483 schar_T lcs = lcs_tab2; 2484 2485 // if tab3 is given, use it for the last char 2486 if (lcs_tab3 && i == tab_len - 1) { 2487 lcs = lcs_tab3; 2488 } 2489 size_t slen = schar_get_adv(&p, lcs); 2490 wlv.n_extra += (int)slen - (saved_nextra > 0 ? 1 : 0); 2491 } 2492 2493 // n_extra will be increased by fix_for_boguscols() 2494 // below, so need to adjust for that here 2495 if (wlv.vcol_off_co > 0) { 2496 wlv.n_extra -= wlv.vcol_off_co; 2497 } 2498 } 2499 } 2500 2501 { 2502 int vc_saved = wlv.vcol_off_co; 2503 2504 // Tab alignment should be identical regardless of 2505 // 'conceallevel' value. So tab compensates of all 2506 // previous concealed characters, and thus resets 2507 // vcol_off_co and boguscols accumulated so far in the 2508 // line. Note that the tab can be longer than 2509 // 'tabstop' when there are concealed characters. 2510 fix_for_boguscols(&wlv); 2511 2512 // Make sure, the highlighting for the tab char will be 2513 // correctly set further below (effectively reverts the 2514 // fix_for_boguscols() call). 2515 if (wlv.n_extra == tab_len + vc_saved && wp->w_p_list 2516 && wp->w_p_lcs_chars.tab1) { 2517 tab_len += vc_saved; 2518 } 2519 } 2520 2521 if (wp->w_p_list) { 2522 mb_schar = (wlv.n_extra == 0 && lcs_tab3) ? lcs_tab3 : lcs_tab1; 2523 if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) { 2524 wlv.sc_extra = NUL; // using p_extra from above 2525 } else { 2526 wlv.sc_extra = lcs_tab2; 2527 } 2528 wlv.sc_final = lcs_tab3; 2529 wlv.n_attr = tab_len + 1; 2530 wlv.extra_attr = win_hl_attr(wp, HLF_0); 2531 saved_attr2 = wlv.char_attr; // save current attr 2532 } else { 2533 wlv.sc_final = NUL; 2534 wlv.sc_extra = schar_from_ascii(' '); 2535 mb_schar = schar_from_ascii(' '); 2536 } 2537 mb_c = schar_get_first_codepoint(mb_schar); 2538 } else if (mb_schar == NUL 2539 && (wp->w_p_list 2540 || ((wlv.fromcol >= 0 || fromcol_prev >= 0) 2541 && wlv.tocol > wlv.vcol 2542 && VIsual_mode != Ctrl_V 2543 && wlv.col < view_width 2544 && !(noinvcur 2545 && lnum == wp->w_cursor.lnum 2546 && wlv.vcol == wp->w_virtcol))) 2547 && lcs_eol_todo && lcs_eol != NUL) { 2548 // Display a '$' after the line or highlight an extra 2549 // character if the line break is included. 2550 // For a diff line the highlighting continues after the "$". 2551 if (wlv.diff_hlf == (hlf_T)0 2552 && wlv.line_attr == 0 2553 && wlv.line_attr_lowprio == 0) { 2554 // In virtualedit, visual selections may extend beyond end of line 2555 if (!(area_highlighting && virtual_active(wp) 2556 && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) { 2557 wlv.p_extra = ""; 2558 } 2559 wlv.n_extra = 0; 2560 } 2561 if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) { 2562 mb_schar = wp->w_p_lcs_chars.eol; 2563 } else { 2564 mb_schar = schar_from_ascii(' '); 2565 } 2566 lcs_eol_todo = false; 2567 ptr--; // put it back at the NUL 2568 wlv.extra_attr = win_hl_attr(wp, HLF_AT); 2569 wlv.n_attr = 1; 2570 mb_c = schar_get_first_codepoint(mb_schar); 2571 } else if (mb_schar != NUL) { 2572 wlv.p_extra = transchar_buf(wp->w_buffer, mb_c); 2573 if (wlv.n_extra == 0) { 2574 wlv.n_extra = byte2cells(mb_c) - 1; 2575 } 2576 if ((dy_flags & kOptDyFlagUhex) && wp->w_p_rl) { 2577 rl_mirror_ascii(wlv.p_extra, NULL); // reverse "<12>" 2578 } 2579 wlv.sc_extra = NUL; 2580 wlv.sc_final = NUL; 2581 if (wp->w_p_lbr) { 2582 mb_c = (uint8_t)(*wlv.p_extra); 2583 char *p = get_extra_buf((size_t)wlv.n_extra + 1); 2584 memset(p, ' ', (size_t)wlv.n_extra); 2585 memcpy(p, wlv.p_extra + 1, strlen(wlv.p_extra) - 1); 2586 p[wlv.n_extra] = NUL; 2587 wlv.p_extra = p; 2588 } else { 2589 wlv.n_extra = byte2cells(mb_c) - 1; 2590 mb_c = (uint8_t)(*wlv.p_extra++); 2591 } 2592 wlv.n_attr = wlv.n_extra + 1; 2593 wlv.extra_attr = win_hl_attr(wp, HLF_8); 2594 saved_attr2 = wlv.char_attr; // save current attr 2595 mb_schar = schar_from_ascii(mb_c); 2596 } else if (VIsual_active 2597 && (VIsual_mode == Ctrl_V || VIsual_mode == 'v') 2598 && virtual_active(wp) 2599 && wlv.tocol != MAXCOL 2600 && wlv.vcol < wlv.tocol 2601 && wlv.col < view_width) { 2602 mb_c = ' '; 2603 mb_schar = schar_from_char(mb_c); 2604 ptr--; // put it back at the NUL 2605 } 2606 } 2607 2608 if (wp->w_p_cole > 0 2609 && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) 2610 && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) 2611 && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { 2612 bool syntax_conceal = (syntax_flags & HL_CONCEAL) != 0; 2613 wlv.char_attr = conceal_attr; 2614 if (((prev_syntax_id != syntax_seqnr && syntax_conceal) 2615 || has_match_conc > 1 || decor_conceal > 1) 2616 && ((syntax_conceal && syn_get_sub_char() != NUL) 2617 || (has_match_conc && match_conc) 2618 || (decor_conceal && decor_state.conceal_char) 2619 || wp->w_p_cole == 1) 2620 && wp->w_p_cole != 3) { 2621 if (schar_cells(mb_schar) > 1) { 2622 // When the first char to be concealed is double-width, 2623 // need to advance one more virtual column. 2624 wlv.n_extra++; 2625 } 2626 2627 // First time at this concealed item: display one 2628 // character. 2629 if (has_match_conc && match_conc) { 2630 mb_schar = schar_from_char(match_conc); 2631 } else if (decor_conceal && decor_state.conceal_char) { 2632 mb_schar = decor_state.conceal_char; 2633 if (decor_state.conceal_attr) { 2634 wlv.char_attr = decor_state.conceal_attr; 2635 } 2636 } else if (syntax_conceal && syn_get_sub_char() != NUL) { 2637 mb_schar = schar_from_char(syn_get_sub_char()); 2638 } else if (wp->w_p_lcs_chars.conceal != NUL) { 2639 mb_schar = wp->w_p_lcs_chars.conceal; 2640 } else { 2641 mb_schar = schar_from_ascii(' '); 2642 } 2643 2644 mb_c = schar_get_first_codepoint(mb_schar); 2645 2646 prev_syntax_id = syntax_seqnr; 2647 2648 if (wlv.n_extra > 0) { 2649 wlv.vcol_off_co += wlv.n_extra; 2650 } 2651 wlv.vcol += wlv.n_extra; 2652 if (is_wrapped && wlv.n_extra > 0) { 2653 wlv.boguscols += wlv.n_extra; 2654 wlv.col += wlv.n_extra; 2655 } 2656 wlv.n_extra = 0; 2657 wlv.n_attr = 0; 2658 } else if (wlv.skip_cells == 0) { 2659 is_concealing = true; 2660 wlv.skip_cells = 1; 2661 } 2662 } else { 2663 prev_syntax_id = 0; 2664 is_concealing = false; 2665 } 2666 2667 if (wlv.skip_cells > 0 && did_decrement_ptr) { 2668 // not showing the '>', put pointer back to avoid getting stuck 2669 ptr++; 2670 } 2671 } // end of printing from buffer content 2672 2673 // In the cursor line and we may be concealing characters: correct 2674 // the cursor column when we reach its position. 2675 // With 'virtualedit' we may never reach cursor position, but we still 2676 // need to correct the cursor column, so do that at end of line. 2677 if (!did_wcol && wlv.filler_todo <= 0 2678 && in_curline && conceal_cursor_line(wp) 2679 && (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) { 2680 wp->w_wcol = wlv.col - wlv.boguscols; 2681 if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) { 2682 // Cursor beyond end of the line with 'virtualedit'. 2683 wp->w_wcol += wp->w_virtcol - wlv.vcol - wlv.skip_cells; 2684 } 2685 wp->w_wrow = wlv.row; 2686 did_wcol = true; 2687 wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; 2688 } 2689 2690 // Use "wlv.extra_attr", but don't override visual selection highlighting. 2691 if (wlv.n_attr > 0 && !search_attr_from_match) { 2692 wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr); 2693 if (wlv.reset_extra_attr) { 2694 wlv.reset_extra_attr = false; 2695 if (extra_attr_next >= 0) { 2696 wlv.extra_attr = extra_attr_next; 2697 extra_attr_next = -1; 2698 } else { 2699 wlv.extra_attr = 0; 2700 // search_attr_from_match can be restored now that the extra_attr has been applied 2701 search_attr_from_match = saved_search_attr_from_match; 2702 } 2703 } 2704 } 2705 2706 // Handle the case where we are in column 0 but not on the first 2707 // character of the line and the user wants us to show us a 2708 // special character (via 'listchars' option "precedes:<char>"). 2709 if (lcs_prec_todo != NUL 2710 && wp->w_p_list 2711 && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0) 2712 && wlv.filler_todo <= 0 2713 && wlv.skip_cells <= 0 2714 && mb_schar != NUL) { 2715 lcs_prec_todo = NUL; 2716 if (schar_cells(mb_schar) > 1) { 2717 // Double-width character being overwritten by the "precedes" 2718 // character, need to fill up half the character. 2719 wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR); 2720 wlv.sc_final = NUL; 2721 if (wlv.n_extra > 0) { 2722 assert(wlv.p_extra != NULL); 2723 n_extra_next = wlv.n_extra; 2724 extra_attr_next = wlv.extra_attr; 2725 wlv.n_attr = MAX(wlv.n_attr + 1, 2); 2726 } else { 2727 wlv.n_attr = 2; 2728 } 2729 wlv.n_extra = 1; 2730 wlv.extra_attr = win_hl_attr(wp, HLF_AT); 2731 } 2732 mb_schar = wp->w_p_lcs_chars.prec; 2733 mb_c = schar_get_first_codepoint(mb_schar); 2734 saved_attr3 = wlv.char_attr; // save current attr 2735 wlv.char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr 2736 n_attr3 = 1; 2737 } 2738 2739 // At end of the text line or just after the last character. 2740 if (mb_schar == NUL && eol_hl_off == 0) { 2741 // flag to indicate whether prevcol equals startcol of search_hl or 2742 // one of the matches 2743 const bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, 2744 (colnr_T)(ptr - line) - 1); 2745 2746 // Invert at least one char, used for Visual and empty line or 2747 // highlight match at end of line. If it's beyond the last 2748 // char on the screen, just overwrite that one (tricky!) Not 2749 // needed when a '$' was displayed for 'list'. 2750 if (lcs_eol_todo 2751 && ((area_attr != 0 && wlv.vcol == wlv.fromcol 2752 && (VIsual_mode != Ctrl_V 2753 || lnum == VIsual.lnum 2754 || lnum == curwin->w_cursor.lnum)) 2755 // highlight 'hlsearch' match at end of line 2756 || prevcol_hl_flag)) { 2757 int n = 0; 2758 2759 if (wlv.col >= view_width) { 2760 n = -1; 2761 } 2762 if (n != 0) { 2763 // At the window boundary, highlight the last character 2764 // instead (better than nothing). 2765 wlv.off += n; 2766 wlv.col += n; 2767 } else { 2768 // Add a blank character to highlight. 2769 linebuf_char[wlv.off] = schar_from_ascii(' '); 2770 } 2771 if (area_attr == 0 && !has_fold) { 2772 // Use attributes from match with highest priority among 2773 // 'search_hl' and the match list. 2774 get_search_match_hl(wp, 2775 &screen_search_hl, 2776 (colnr_T)(ptr - line), 2777 &wlv.char_attr); 2778 } 2779 2780 const int eol_attr = wlv.cul_attr 2781 ? hl_combine_attr(wlv.cul_attr, wlv.char_attr) 2782 : wlv.char_attr; 2783 2784 linebuf_attr[wlv.off] = eol_attr; 2785 linebuf_vcol[wlv.off] = wlv.vcol; 2786 wlv.col++; 2787 wlv.off++; 2788 wlv.vcol++; 2789 eol_hl_off = 1; 2790 } 2791 } 2792 2793 // At end of the text line. 2794 if (mb_schar == NUL) { 2795 // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. 2796 2797 // check if line ends before left margin 2798 wlv.vcol = MAX(wlv.vcol, start_vcol + wlv.col - win_col_off(wp)); 2799 // Get rid of the boguscols now, we want to draw until the right 2800 // edge for 'cursorcolumn'. 2801 wlv.col -= wlv.boguscols; 2802 wlv.boguscols = 0; 2803 2804 advance_color_col(&wlv, vcol_hlc(wlv)); 2805 2806 // Make sure alignment is the same regardless 2807 // if listchars=eol:X is used or not. 2808 const int eol_skip = (lcs_eol_todo && eol_hl_off == 0 ? 1 : 0); 2809 2810 if (has_decor) { 2811 decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip); 2812 } 2813 2814 for (int i = wlv.col; i < view_width; i++) { 2815 linebuf_vcol[wlv.off + (i - wlv.col)] = wlv.vcol + (i - wlv.col); 2816 } 2817 2818 if (((wp->w_p_cuc 2819 && wp->w_virtcol >= vcol_hlc(wlv) - eol_hl_off 2820 && wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_vcol 2821 && lnum != wp->w_cursor.lnum) 2822 || wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr 2823 || wlv.diff_hlf != 0 || wp->w_buffer->terminal)) { 2824 int rightmost_vcol = get_rightmost_vcol(wp, wlv.color_cols); 2825 const int cuc_attr = win_hl_attr(wp, HLF_CUC); 2826 const int mc_attr = win_hl_attr(wp, HLF_MC); 2827 2828 if (wlv.diff_hlf == HLF_TXD || wlv.diff_hlf == HLF_TXA) { 2829 wlv.diff_hlf = HLF_CHD; 2830 set_line_attr_for_diff(wp, &wlv); 2831 } 2832 2833 const int diff_attr = wlv.diff_hlf != 0 2834 ? win_hl_attr(wp, (int)wlv.diff_hlf) 2835 : 0; 2836 2837 const int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr); 2838 if (base_attr || wlv.line_attr || wp->w_buffer->terminal) { 2839 rightmost_vcol = INT_MAX; 2840 } 2841 2842 while (wlv.col < view_width) { 2843 linebuf_char[wlv.off] = schar_from_ascii(' '); 2844 2845 advance_color_col(&wlv, vcol_hlc(wlv)); 2846 2847 int col_attr = base_attr; 2848 2849 if (wp->w_p_cuc && vcol_hlc(wlv) == wp->w_virtcol 2850 && lnum != wp->w_cursor.lnum) { 2851 col_attr = hl_combine_attr(col_attr, cuc_attr); 2852 } else if (wlv.color_cols && vcol_hlc(wlv) == *wlv.color_cols) { 2853 col_attr = hl_combine_attr(col_attr, mc_attr); 2854 } 2855 2856 if (wp->w_buffer->terminal && wlv.vcol < TERM_ATTRS_MAX) { 2857 col_attr = hl_combine_attr(col_attr, term_attrs[wlv.vcol]); 2858 } 2859 2860 col_attr = hl_combine_attr(col_attr, wlv.line_attr); 2861 2862 linebuf_attr[wlv.off] = col_attr; 2863 // linebuf_vcol[] already filled by the for loop above 2864 wlv.off++; 2865 wlv.col++; 2866 wlv.vcol++; 2867 2868 if (vcol_hlc(wlv) > rightmost_vcol) { 2869 break; 2870 } 2871 } 2872 } 2873 2874 if (kv_size(fold_vt) > 0) { 2875 draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, view_width, 0, 0); 2876 } 2877 draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); 2878 // Set increasing virtual columns in grid->vcols[] to set correct curswant 2879 // (or "coladd" for 'virtualedit') when clicking after end of line. 2880 wlv_put_linebuf(wp, &wlv, wlv.col, true, bg_attr, SLF_INC_VCOL); 2881 wlv.row++; 2882 2883 // Update w_cline_height and w_cline_folded if the cursor line was 2884 // updated (saves a call to plines_win() later). 2885 if (in_curline) { 2886 curwin->w_cline_row = startrow; 2887 curwin->w_cline_height = wlv.row - startrow; 2888 curwin->w_cline_folded = has_fold; 2889 curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); 2890 } 2891 2892 break; 2893 } 2894 2895 // Show "extends" character from 'listchars' if beyond the line end. 2896 const schar_T lcs_ext = get_lcs_ext(wp); 2897 if (lcs_ext != NUL 2898 && wlv.filler_todo <= 0 2899 && wlv.col == view_width - 1 2900 && !has_foldtext) { 2901 if (has_decor && *ptr == NUL && lcs_eol == 0 && lcs_eol_todo) { 2902 // Tricky: there might be a virtual text just _after_ the last char 2903 decor_redraw_col(wp, (colnr_T)(ptr - line), -1, false, &decor_state); 2904 } 2905 if (*ptr != NUL 2906 || (lcs_eol > 0 && lcs_eol_todo) 2907 || (wlv.n_extra > 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) 2908 || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line))) { 2909 mb_schar = lcs_ext; 2910 wlv.char_attr = win_hl_attr(wp, HLF_AT); 2911 mb_c = schar_get_first_codepoint(mb_schar); 2912 } 2913 } 2914 2915 advance_color_col(&wlv, vcol_hlc(wlv)); 2916 2917 // Highlight the cursor column if 'cursorcolumn' is set. But don't 2918 // highlight the cursor position itself. 2919 // Also highlight the 'colorcolumn' if it is different than 2920 // 'cursorcolumn' 2921 vcol_save_attr = -1; 2922 if (!lnum_in_visual_area 2923 && search_attr == 0 2924 && area_attr == 0 2925 && wlv.filler_todo <= 0) { 2926 if (wp->w_p_cuc && vcol_hlc(wlv) == wp->w_virtcol 2927 && lnum != wp->w_cursor.lnum) { 2928 vcol_save_attr = wlv.char_attr; 2929 wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), wlv.char_attr); 2930 } else if (wlv.color_cols && vcol_hlc(wlv) == *wlv.color_cols) { 2931 vcol_save_attr = wlv.char_attr; 2932 wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), wlv.char_attr); 2933 } 2934 } 2935 2936 if (wlv.filler_todo <= 0) { 2937 // Apply lowest-priority line attr now, so everything can override it. 2938 int low = wlv.line_attr_lowprio; 2939 int high = wlv.char_attr; 2940 2941 if (wlv.line_attr_lowprio != 0) { 2942 HlAttrs line_ae = syn_attr2entry(wlv.line_attr_lowprio); 2943 HlAttrs char_ae = syn_attr2entry(wlv.char_attr); 2944 int win_normal_bg = normal_bg; 2945 int win_normal_cterm_bg = cterm_normal_bg_color; 2946 2947 // Get window-local Normal background (respects 'winhighlight' option). 2948 if (bg_attr != 0) { 2949 HlAttrs norm_ae = syn_attr2entry(bg_attr); 2950 win_normal_bg = norm_ae.rgb_bg_color; 2951 win_normal_cterm_bg = norm_ae.cterm_bg_color; 2952 } 2953 bool char_is_normal_bg = ui_rgb_attached() 2954 ? (char_ae.rgb_bg_color == win_normal_bg) 2955 : (char_ae.cterm_bg_color == win_normal_cterm_bg); 2956 2957 // If line has background (CursorLine) and char's background equals Normal's background, 2958 // reverse the combination order to let CursorLine override normal_bg. 2959 if ((line_ae.rgb_bg_color >= 0 || line_ae.cterm_bg_color > 0) && char_is_normal_bg) { 2960 low = wlv.char_attr; 2961 high = wlv.line_attr_lowprio; 2962 } 2963 } 2964 wlv.char_attr = hl_combine_attr(low, high); 2965 } 2966 2967 if (wlv.filler_todo <= 0) { 2968 vcol_prev = wlv.vcol; 2969 } 2970 2971 // Store character to be displayed. 2972 // Skip characters that are left of the screen for 'nowrap'. 2973 if (wlv.filler_todo > 0) { 2974 // TODO(bfredl): the main render loop should get called also with the virtual 2975 // lines chunks, so we get line wrapping and other Nice Things. 2976 } else if (wlv.skip_cells <= 0) { 2977 // Store the character. 2978 linebuf_char[wlv.off] = mb_schar; 2979 if (multi_attr) { 2980 linebuf_attr[wlv.off] = multi_attr; 2981 multi_attr = 0; 2982 } else { 2983 linebuf_attr[wlv.off] = wlv.char_attr; 2984 } 2985 2986 linebuf_vcol[wlv.off] = wlv.vcol; 2987 2988 if (schar_cells(mb_schar) > 1) { 2989 // Need to fill two screen columns. 2990 wlv.off++; 2991 wlv.col++; 2992 // UTF-8: Put a 0 in the second screen char. 2993 linebuf_char[wlv.off] = 0; 2994 linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1]; 2995 2996 linebuf_vcol[wlv.off] = ++wlv.vcol; 2997 2998 // When "wlv.tocol" is halfway through a character, set it to the end 2999 // of the character, otherwise highlighting won't stop. 3000 if (wlv.tocol == wlv.vcol) { 3001 wlv.tocol++; 3002 } 3003 } 3004 wlv.off++; 3005 wlv.col++; 3006 } else if (wp->w_p_cole > 0 && is_concealing) { 3007 bool concealed_wide = schar_cells(mb_schar) > 1; 3008 3009 wlv.skip_cells--; 3010 wlv.vcol_off_co++; 3011 if (concealed_wide) { 3012 // When a double-width char is concealed, 3013 // need to advance one more virtual column. 3014 wlv.vcol++; 3015 wlv.vcol_off_co++; 3016 } 3017 3018 if (wlv.n_extra > 0) { 3019 wlv.vcol_off_co += wlv.n_extra; 3020 } 3021 3022 if (is_wrapped) { 3023 // Special voodoo required if 'wrap' is on. 3024 // 3025 // Advance the column indicator to force the line 3026 // drawing to wrap early. This will make the line 3027 // take up the same screen space when parts are concealed, 3028 // so that cursor line computations aren't messed up. 3029 // 3030 // To avoid the fictitious advance of 'wlv.col' causing 3031 // trailing junk to be written out of the screen line 3032 // we are building, 'boguscols' keeps track of the number 3033 // of bad columns we have advanced. 3034 if (wlv.n_extra > 0) { 3035 wlv.vcol += wlv.n_extra; 3036 wlv.col += wlv.n_extra; 3037 wlv.boguscols += wlv.n_extra; 3038 wlv.n_extra = 0; 3039 wlv.n_attr = 0; 3040 } 3041 3042 if (concealed_wide) { 3043 // Need to fill two screen columns. 3044 wlv.boguscols++; 3045 wlv.col++; 3046 } 3047 3048 wlv.boguscols++; 3049 wlv.col++; 3050 } else { 3051 if (wlv.n_extra > 0) { 3052 wlv.vcol += wlv.n_extra; 3053 wlv.n_extra = 0; 3054 wlv.n_attr = 0; 3055 } 3056 } 3057 } else { 3058 wlv.skip_cells--; 3059 } 3060 3061 // The skipped cells need to be accounted for in vcol. 3062 if (wlv.skipped_cells > 0) { 3063 wlv.vcol += wlv.skipped_cells; 3064 wlv.skipped_cells = 0; 3065 } 3066 3067 // Only advance the "wlv.vcol" when after the 'number' or 3068 // 'relativenumber' column. 3069 if (wlv.filler_todo <= 0) { 3070 wlv.vcol++; 3071 } 3072 3073 if (vcol_save_attr >= 0) { 3074 wlv.char_attr = vcol_save_attr; 3075 } 3076 3077 // restore attributes after "precedes" in 'listchars' 3078 if (n_attr3 > 0 && --n_attr3 == 0) { 3079 wlv.char_attr = saved_attr3; 3080 } 3081 3082 // restore attributes after last 'listchars' or 'number' char 3083 if (wlv.n_attr > 0 && --wlv.n_attr == 0) { 3084 wlv.char_attr = saved_attr2; 3085 } 3086 3087 if (has_decor && wlv.filler_todo <= 0 && wlv.col >= view_width) { 3088 // At the end of screen line: might need to peek for decorations just after 3089 // this position. 3090 if (is_wrapped && wlv.n_extra == 0) { 3091 decor_redraw_col(wp, (colnr_T)(ptr - line), -3, false, &decor_state); 3092 // Check position/hiding of virtual text again on next screen line. 3093 decor_need_recheck = true; 3094 } else if (!is_wrapped) { 3095 // Without wrapping, we might need to display right_align and win_col 3096 // virt_text for the entire text line. 3097 decor_recheck_draw_col(-1, true, &decor_state); 3098 decor_redraw_col(wp, MAXCOL, -1, true, &decor_state); 3099 } 3100 } 3101 3102 end_check: 3103 // At end of screen line and there is more to come: Display the line 3104 // so far. If there is no more to display it is caught above. 3105 if (wlv.col >= view_width && (!has_foldtext || virt_line_index >= 0) 3106 && (wlv.col <= leftcols_width 3107 || *ptr != NUL 3108 || wlv.filler_todo > 0 3109 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && lcs_eol_todo) 3110 || (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL)) 3111 || (may_have_inline_virt && has_more_inline_virt(&wlv, ptr - line)))) { 3112 int grid_width = wp->w_grid.target->cols; 3113 const bool wrap = is_wrapped // Wrapping enabled (not a folded line). 3114 && wlv.filler_todo <= 0 // Not drawing diff filler lines. 3115 && lcs_eol_todo // Haven't printed the lcs_eol character. 3116 && wlv.row != endrow - 1 // Not the last line being displayed. 3117 && view_width == grid_width // Window spans the width of its grid. 3118 && !wp->w_p_rl; // Not right-to-left. 3119 3120 int draw_col = wlv.col - wlv.boguscols; 3121 3122 for (int i = draw_col; i < view_width; i++) { 3123 linebuf_vcol[wlv.off + (i - draw_col)] = wlv.vcol - 1; 3124 } 3125 3126 // Apply 'cursorline' highlight. 3127 if (wlv.boguscols != 0 && (wlv.line_attr_lowprio != 0 || wlv.line_attr != 0)) { 3128 int attr = hl_combine_attr(wlv.line_attr_lowprio, wlv.line_attr); 3129 while (draw_col < view_width) { 3130 linebuf_char[wlv.off] = schar_from_char(' '); 3131 linebuf_attr[wlv.off] = attr; 3132 // linebuf_vcol[] already filled by the for loop above 3133 wlv.off++; 3134 draw_col++; 3135 } 3136 } 3137 3138 if (virt_line_index >= 0) { 3139 draw_virt_text_item(buf, 3140 virt_line_flags & kVLLeftcol ? 0 : win_col_offset, 3141 kv_A(virt_lines, virt_line_index).line, 3142 kHlModeReplace, 3143 view_width, 3144 0, 3145 virt_line_flags & kVLScroll ? wp->w_leftcol : 0); 3146 } else if (wlv.filler_todo <= 0) { 3147 draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row); 3148 } 3149 3150 wlv_put_linebuf(wp, &wlv, draw_col, true, bg_attr, wrap ? SLF_WRAP : 0); 3151 if (wrap) { 3152 int current_row = wlv.row; 3153 int dummy_col = 0; // unused 3154 ScreenGrid *current_grid = grid_adjust(grid, ¤t_row, &dummy_col); 3155 3156 // Force a redraw of the first column of the next line. 3157 current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; 3158 } 3159 3160 wlv.boguscols = 0; 3161 wlv.vcol_off_co = 0; 3162 wlv.row++; 3163 3164 // When not wrapping and finished diff lines, break here. 3165 if (!is_wrapped && wlv.filler_todo <= 0) { 3166 break; 3167 } 3168 3169 // When the window is too narrow draw all "@" lines. 3170 if (wlv.col <= leftcols_width) { 3171 win_draw_end(wp, schar_from_ascii('@'), true, wlv.row, wp->w_view_height, HLF_AT); 3172 set_empty_rows(wp, wlv.row); 3173 wlv.row = endrow; 3174 } 3175 3176 // When line got too long for screen break here. 3177 if (wlv.row == endrow) { 3178 wlv.row++; 3179 break; 3180 } 3181 3182 win_line_start(wp, &wlv); 3183 draw_cols = true; 3184 3185 lcs_prec_todo = wp->w_p_lcs_chars.prec; 3186 if (wlv.filler_todo <= 0) { 3187 wlv.need_showbreak = true; 3188 } 3189 if (statuscol.draw && vim_strchr(p_cpo, CPO_NUMCOL) 3190 && wlv.row > startrow + wlv.filler_lines) { 3191 statuscol.draw = false; // don't draw status column if "n" is in 'cpo' 3192 } 3193 wlv.filler_todo--; 3194 virt_line_index = -1; 3195 virt_line_flags = 0; 3196 // When the filler lines are actually below the last line of the 3197 // file, or we are not drawing text for this line, break here. 3198 if (wlv.filler_todo == 0 && (wp->w_botfill || !draw_text)) { 3199 break; 3200 } 3201 } 3202 } // for every character in the line 3203 3204 clear_virttext(&fold_vt); 3205 kv_destroy(virt_lines); 3206 xfree(foldtext_free); 3207 return wlv.row; 3208 } 3209 3210 /// Call grid_put_linebuf() using values from "wlv". 3211 /// Also takes care of putting "<<<" on the first line for 'smoothscroll' 3212 /// when 'showbreak' is not set. 3213 /// 3214 /// @param clear_end clear until the end of the screen line. 3215 /// @param flags for grid_put_linebuf(), but shouldn't contain SLF_RIGHTLEFT. 3216 static void wlv_put_linebuf(win_T *wp, const winlinevars_T *wlv, int endcol, bool clear_end, 3217 int bg_attr, int flags) 3218 { 3219 GridView *grid = &wp->w_grid; 3220 3221 int startcol = 0; 3222 int clear_width = clear_end ? wp->w_view_width : endcol; 3223 3224 assert(!(flags & SLF_RIGHTLEFT)); 3225 if (wp->w_p_rl) { 3226 linebuf_mirror(&startcol, &endcol, &clear_width, wp->w_view_width); 3227 flags |= SLF_RIGHTLEFT; 3228 } 3229 3230 // Take care of putting "<<<" on the first line for 'smoothscroll'. 3231 if (wlv->row == 0 && wp->w_skipcol > 0 3232 // do not overwrite the 'showbreak' text with "<<<" 3233 && *get_showbreak_value(wp) == NUL 3234 // do not overwrite the 'listchars' "precedes" text with "<<<" 3235 && !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) { 3236 int off = 0; 3237 if (wp->w_p_nu && wp->w_p_rnu) { 3238 // do not overwrite the line number, change "123 text" to "123<<<xt". 3239 while (off < wp->w_view_width && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) { 3240 off++; 3241 } 3242 } 3243 3244 for (int i = 0; i < 3 && off < wp->w_view_width; i++) { 3245 if (off + 1 < wp->w_view_width && linebuf_char[off + 1] == NUL) { 3246 // When the first half of a double-width character is 3247 // overwritten, change the second half to a space. 3248 linebuf_char[off + 1] = schar_from_ascii(' '); 3249 } 3250 linebuf_char[off] = schar_from_ascii('<'); 3251 linebuf_attr[off] = HL_ATTR(HLF_AT); 3252 off++; 3253 } 3254 } 3255 3256 int row = wlv->row; 3257 int coloff = 0; 3258 ScreenGrid *g = grid_adjust(grid, &row, &coloff); 3259 grid_put_linebuf(g, row, coloff, startcol, endcol, clear_width, bg_attr, 0, wlv->vcol - 1, flags); 3260 } 3261 3262 static int decor_providers_setup(int rows_to_draw, bool draw_from_line_start, linenr_T lnum, 3263 colnr_T col, win_T *wp) 3264 { 3265 // Approximate the number of bytes that will be drawn. 3266 // Assume we're dealing with 1-cell ascii and ignore 3267 // the effects of 'linebreak', 'breakindent', etc. 3268 int rem_vcols; 3269 if (wp->w_p_wrap) { 3270 int width = wp->w_view_width - win_col_off(wp); 3271 int width2 = width + win_col_off2(wp); 3272 3273 int first_row_width = draw_from_line_start ? width : width2; 3274 rem_vcols = first_row_width + (rows_to_draw - 1) * width2; 3275 } else { 3276 rem_vcols = wp->w_view_height - win_col_off(wp); 3277 } 3278 3279 // Call it here since we need to invalidate the line pointer anyway. 3280 decor_providers_invoke_line(wp, lnum - 1); 3281 validate_virtcol(wp); 3282 3283 return invoke_range_next(wp, lnum, col, rem_vcols + 1); 3284 } 3285 3286 /// @return New begin column, or INT_MAX. 3287 static int invoke_range_next(win_T *wp, int lnum, colnr_T begin_col, colnr_T col_off) 3288 { 3289 char const *const line = ml_get_buf(wp->w_buffer, lnum); 3290 int const line_len = ml_get_buf_len(wp->w_buffer, lnum); 3291 col_off = MAX(col_off, 1); 3292 3293 colnr_T new_col; 3294 if (col_off <= line_len - begin_col) { 3295 int end_col = begin_col + col_off; 3296 end_col += mb_off_next(line, line + end_col); 3297 decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum - 1, end_col); 3298 validate_virtcol(wp); 3299 new_col = end_col; 3300 } else { 3301 decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum, 0); 3302 validate_virtcol(wp); 3303 new_col = INT_MAX; 3304 } 3305 3306 return new_col; 3307 }