plines.c (31961B)
1 // plines.c: calculate the vertical and horizontal size of text in a window 2 3 #include <limits.h> 4 #include <stdbool.h> 5 #include <stdint.h> 6 #include <string.h> 7 8 #include "nvim/api/extmark.h" 9 #include "nvim/ascii_defs.h" 10 #include "nvim/buffer.h" 11 #include "nvim/buffer_defs.h" 12 #include "nvim/charset.h" 13 #include "nvim/decoration.h" 14 #include "nvim/decoration_defs.h" 15 #include "nvim/diff.h" 16 #include "nvim/fold.h" 17 #include "nvim/globals.h" 18 #include "nvim/indent.h" 19 #include "nvim/macros_defs.h" 20 #include "nvim/mark_defs.h" 21 #include "nvim/marktree.h" 22 #include "nvim/mbyte.h" 23 #include "nvim/mbyte_defs.h" 24 #include "nvim/memline.h" 25 #include "nvim/move.h" 26 #include "nvim/option.h" 27 #include "nvim/option_vars.h" 28 #include "nvim/plines.h" 29 #include "nvim/pos_defs.h" 30 #include "nvim/state.h" 31 #include "nvim/state_defs.h" 32 #include "nvim/types_defs.h" 33 34 #include "plines.c.generated.h" 35 36 /// Functions calculating horizontal size of text, when displayed in a window. 37 38 /// Return the number of cells the first char in "p" will take on the screen, 39 /// taking into account the size of a tab. 40 /// Also see getvcol() 41 /// 42 /// @param p 43 /// @param col 44 /// 45 /// @return Number of cells. 46 /// 47 /// @see charsize_nowrap() 48 int win_chartabsize(win_T *wp, char *p, colnr_T col) 49 { 50 buf_T *buf = wp->w_buffer; 51 if (*p == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { 52 return tabstop_padding(col, buf->b_p_ts, buf->b_p_vts_array); 53 } 54 return ptr2cells(p); 55 } 56 57 /// Like linetabsize_str(), but "s" starts at virtual column "startvcol". 58 /// 59 /// @param startvcol 60 /// @param s 61 /// 62 /// @return Number of cells the string will take on the screen. 63 int linetabsize_col(int startvcol, char *s) 64 { 65 CharsizeArg csarg; 66 CSType const cstype = init_charsize_arg(&csarg, curwin, 0, s); 67 if (cstype == kCharsizeFast) { 68 return linesize_fast(&csarg, startvcol, MAXCOL); 69 } else { 70 return linesize_regular(&csarg, startvcol, MAXCOL); 71 } 72 } 73 74 /// Return the number of cells line "lnum" of window "wp" will take on the 75 /// screen, taking into account the size of a tab and inline virtual text. 76 /// Doesn't count the size of 'listchars' "eol". 77 int linetabsize(win_T *wp, linenr_T lnum) 78 { 79 return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), MAXCOL); 80 } 81 82 /// Like linetabsize(), but counts the size of 'listchars' "eol". 83 int linetabsize_eol(win_T *wp, linenr_T lnum) 84 { 85 return linetabsize(wp, lnum) 86 + ((wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) ? 1 : 0); 87 } 88 89 static const uint32_t inline_filter[kMTMetaCount] = {[kMTMetaInline] = kMTFilterSelect }; 90 91 /// Prepare the structure passed to charsize functions. 92 /// 93 /// "line" is the start of the line. 94 /// When "lnum" is zero do not use inline virtual text. 95 CSType init_charsize_arg(CharsizeArg *csarg, win_T *wp, linenr_T lnum, char *line) 96 { 97 csarg->win = wp; 98 csarg->line = line; 99 csarg->max_head_vcol = 0; 100 csarg->cur_text_width_left = 0; 101 csarg->cur_text_width_right = 0; 102 csarg->virt_row = -1; 103 csarg->indent_width = INT_MIN; 104 csarg->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1; 105 106 if (lnum > 0) { 107 if (marktree_itr_get_filter(wp->w_buffer->b_marktree, lnum - 1, 0, lnum, 0, 108 inline_filter, csarg->iter)) { 109 csarg->virt_row = lnum - 1; 110 } 111 } 112 113 if (csarg->virt_row >= 0 114 || (wp->w_p_wrap && (wp->w_p_lbr || wp->w_p_bri || *get_showbreak_value(wp) != NUL))) { 115 return kCharsizeRegular; 116 } else { 117 return kCharsizeFast; 118 } 119 } 120 121 /// Get the number of cells taken up on the screen for the given arguments. 122 /// "csarg->cur_text_width_left" and "csarg->cur_text_width_right" are set 123 /// to the extra size for inline virtual text. 124 /// 125 /// When "csarg->max_head_vcol" is positive, only count in "head" the size 126 /// of 'showbreak'/'breakindent' before "csarg->max_head_vcol". 127 /// When "csarg->max_head_vcol" is negative, only count in "head" the size 128 /// of 'showbreak'/'breakindent' before where cursor should be placed. 129 CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vcol, 130 int32_t const cur_char) 131 { 132 csarg->cur_text_width_left = 0; 133 csarg->cur_text_width_right = 0; 134 135 win_T *wp = csarg->win; 136 buf_T *buf = wp->w_buffer; 137 char *line = csarg->line; 138 bool const use_tabstop = cur_char == TAB && csarg->use_tabstop; 139 int mb_added = 0; 140 141 bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL; 142 143 // First get normal size, without 'linebreak' or inline virtual text 144 int size; 145 int is_doublewidth = false; 146 if (use_tabstop) { 147 size = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); 148 } else if (*cur == NUL) { 149 // 1 cell for EOL list char (if present), as opposed to the two cell ^@ 150 // for a NUL character in the text. 151 size = has_lcs_eol ? 1 : 0; 152 } else if (cur_char < 0) { 153 size = kInvalidByteCells; 154 } else { 155 size = ptr2cells(cur); 156 is_doublewidth = size == 2 && cur_char >= 0x80; 157 } 158 159 if (csarg->virt_row >= 0) { 160 int tab_size = size; 161 int col = (int)(cur - line); 162 while (true) { 163 MTKey mark = marktree_itr_current(csarg->iter); 164 if (mark.pos.row != csarg->virt_row || mark.pos.col > col) { 165 break; 166 } else if (mark.pos.col == col) { 167 if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) { 168 DecorInline decor = mt_decor(mark); 169 DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL; 170 while (vt) { 171 if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { 172 if (mt_right(mark)) { 173 csarg->cur_text_width_right += vt->width; 174 } else { 175 csarg->cur_text_width_left += vt->width; 176 } 177 size += vt->width; 178 if (use_tabstop) { 179 // tab size changes because of the inserted text 180 size -= tab_size; 181 tab_size = tabstop_padding(vcol + size, buf->b_p_ts, buf->b_p_vts_array); 182 size += tab_size; 183 } 184 } 185 vt = vt->next; 186 } 187 } 188 } 189 marktree_itr_next_filter(wp->w_buffer->b_marktree, csarg->iter, csarg->virt_row + 1, 0, 190 inline_filter); 191 } 192 } 193 194 if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) { 195 // Count the ">" in the last column. 196 size++; 197 mb_added = 1; 198 } 199 200 char *const sbr = get_showbreak_value(wp); 201 202 // May have to add something for 'breakindent' and/or 'showbreak' 203 // string at the start of a screen line. 204 int head = mb_added; 205 // When "size" is 0, no new screen line is started. 206 if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) { 207 int col_off_prev = win_col_off(wp); 208 int width2 = wp->w_view_width - col_off_prev + win_col_off2(wp); 209 colnr_T wcol = vcol + col_off_prev; 210 colnr_T max_head_vcol = csarg->max_head_vcol; 211 int added = 0; 212 213 // cells taken by 'showbreak'/'breakindent' before current char 214 int head_prev = 0; 215 if (wcol >= wp->w_view_width) { 216 wcol -= wp->w_view_width; 217 col_off_prev = wp->w_view_width - width2; 218 if (wcol >= width2 && width2 > 0) { 219 wcol %= width2; 220 } 221 head_prev = csarg->indent_width; 222 if (head_prev == INT_MIN) { 223 head_prev = 0; 224 if (*sbr != NUL) { 225 head_prev += vim_strsize(sbr); 226 } 227 if (wp->w_p_bri) { 228 head_prev += get_breakindent_win(wp, line); 229 } 230 csarg->indent_width = head_prev; 231 } 232 if (wcol < head_prev) { 233 head_prev -= wcol; 234 wcol += head_prev; 235 added += head_prev; 236 if (max_head_vcol <= 0 || vcol < max_head_vcol) { 237 head += head_prev; 238 } 239 } else { 240 head_prev = 0; 241 } 242 wcol += col_off_prev; 243 } 244 245 if (wcol + size > wp->w_view_width) { 246 // cells taken by 'showbreak'/'breakindent' halfway current char 247 int head_mid = csarg->indent_width; 248 if (head_mid == INT_MIN) { 249 head_mid = 0; 250 if (*sbr != NUL) { 251 head_mid += vim_strsize(sbr); 252 } 253 if (wp->w_p_bri) { 254 head_mid += get_breakindent_win(wp, line); 255 } 256 csarg->indent_width = head_mid; 257 } 258 if (head_mid > 0) { 259 // Calculate effective window width. 260 int prev_rem = wp->w_view_width - wcol; 261 int width = width2 - head_mid; 262 263 if (width <= 0) { 264 width = 1; 265 } 266 // Divide "size - prev_rem" by "width", rounding up. 267 int cnt = (size - prev_rem + width - 1) / width; 268 added += cnt * head_mid; 269 270 if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) { 271 head += cnt * head_mid; 272 } else if (max_head_vcol > vcol + head_prev + prev_rem) { 273 head += (max_head_vcol - (vcol + head_prev + prev_rem) 274 + width2 - 1) / width2 * head_mid; 275 } else if (max_head_vcol < 0) { 276 int off = mb_added + virt_text_cursor_off(csarg, *cur == NUL); 277 if (off >= prev_rem) { 278 if (size > off) { 279 head += (1 + (off - prev_rem) / width) * head_mid; 280 } else { 281 head += (off - prev_rem + width - 1) / width * head_mid; 282 } 283 } 284 } 285 } 286 } 287 288 size += added; 289 } 290 291 bool need_lbr = false; 292 // If 'linebreak' set check at a blank before a non-blank if the line 293 // needs a break here. 294 if (wp->w_p_lbr && wp->w_p_wrap && wp->w_view_width != 0 295 && vim_isbreak((uint8_t)cur[0]) && !vim_isbreak((uint8_t)cur[1])) { 296 char *t = csarg->line; 297 while (vim_isbreak((uint8_t)t[0])) { 298 t++; 299 } 300 // 'linebreak' is only needed when not in leading whitespace. 301 need_lbr = cur >= t; 302 } 303 if (need_lbr) { 304 char *s = cur; 305 // Count all characters from first non-blank after a blank up to next 306 // non-blank after a blank. 307 int numberextra = win_col_off(wp); 308 colnr_T col_adj = size - 1; 309 colnr_T colmax = (colnr_T)(wp->w_view_width - numberextra - col_adj); 310 if (vcol >= colmax) { 311 colmax += col_adj; 312 int n = colmax + win_col_off2(wp); 313 if (n > 0) { 314 colmax += (((vcol - colmax) / n) + 1) * n - col_adj; 315 } 316 } 317 318 colnr_T vcol2 = vcol; 319 while (true) { 320 char *ps = s; 321 MB_PTR_ADV(s); 322 int c = (uint8_t)(*s); 323 if (!(c != NUL 324 && (vim_isbreak(c) || vcol2 == vcol || !vim_isbreak((uint8_t)(*ps))))) { 325 break; 326 } 327 328 vcol2 += win_chartabsize(wp, s, vcol2); 329 if (vcol2 >= colmax) { // doesn't fit 330 size = colmax - vcol + col_adj; 331 break; 332 } 333 } 334 } 335 336 return (CharSize){ .width = size, .head = head }; 337 } 338 339 /// Like charsize_regular(), except it doesn't handle inline virtual text, 340 /// 'linebreak', 'breakindent' or 'showbreak'. 341 /// Handles normal characters, tabs and wrapping. 342 /// This function is always inlined. 343 /// 344 /// @see charsize_regular 345 /// @see charsize_fast 346 static inline CharSize charsize_fast_impl(win_T *const wp, const char *cur, bool use_tabstop, 347 colnr_T const vcol, int32_t const cur_char) 348 FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE 349 { 350 // A tab gets expanded, depending on the current column 351 if (cur_char == TAB && use_tabstop) { 352 return (CharSize){ 353 .width = tabstop_padding(vcol, wp->w_buffer->b_p_ts, 354 wp->w_buffer->b_p_vts_array) 355 }; 356 } else { 357 int width; 358 if (cur_char < 0) { 359 width = kInvalidByteCells; 360 } else { 361 // TODO(bfredl): perf: often cur_char is enough at this point to determine width. 362 // we likely want a specialized version of utf_ptr2StrCharInfo also determining 363 // the ptr2cells width at the same time without any extra decoding. (also applies 364 // to charsize_regular and charsize_nowrap) 365 width = ptr2cells(cur); 366 } 367 368 // If a double-width char doesn't fit at the end of a line, it wraps to the next line, 369 // and the last column displays a '>'. 370 if (width == 2 && cur_char >= 0x80 && wp->w_p_wrap && in_win_border(wp, vcol)) { 371 return (CharSize){ .width = 3, .head = 1 }; 372 } else { 373 return (CharSize){ .width = width }; 374 } 375 } 376 } 377 378 /// Like charsize_regular(), except it doesn't handle inline virtual text, 379 /// 'linebreak', 'breakindent' or 'showbreak'. 380 /// Handles normal characters, tabs and wrapping. 381 /// Can be used if CSType is kCharsizeFast. 382 /// 383 /// @see charsize_regular 384 CharSize charsize_fast(CharsizeArg *csarg, const char *cur, colnr_T vcol, int32_t cur_char) 385 FUNC_ATTR_PURE 386 { 387 return charsize_fast_impl(csarg->win, cur, csarg->use_tabstop, vcol, cur_char); 388 } 389 390 /// Get the number of cells taken up on the screen at given virtual column. 391 /// 392 /// @see win_chartabsize() 393 int charsize_nowrap(buf_T *buf, const char *cur, bool use_tabstop, colnr_T vcol, int32_t cur_char) 394 { 395 if (cur_char == TAB && use_tabstop) { 396 return tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); 397 } else if (cur_char < 0) { 398 return kInvalidByteCells; 399 } else { 400 return ptr2cells(cur); 401 } 402 } 403 404 /// Check that virtual column "vcol" is in the rightmost column of window "wp". 405 /// 406 /// @param wp window 407 /// @param vcol column number 408 static bool in_win_border(win_T *wp, colnr_T vcol) 409 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) 410 { 411 if (wp->w_view_width == 0) { 412 // there is no border 413 return false; 414 } 415 int width1 = wp->w_view_width - win_col_off(wp); // width of first line (after line number) 416 417 if ((int)vcol < width1 - 1) { 418 return false; 419 } 420 421 if ((int)vcol == width1 - 1) { 422 return true; 423 } 424 int width2 = width1 + win_col_off2(wp); // width of further lines 425 426 if (width2 <= 0) { 427 return false; 428 } 429 return (vcol - width1) % width2 == width2 - 1; 430 } 431 432 /// Calculate virtual column until the given "len". 433 /// 434 /// @param csarg Argument to charsize functions. 435 /// @param vcol_arg Starting virtual column. 436 /// @param len First byte of the end character, or MAXCOL. 437 /// 438 /// @return virtual column before the character at "len", 439 /// or full size of the line if "len" is MAXCOL. 440 int linesize_regular(CharsizeArg *const csarg, int vcol_arg, colnr_T const len) 441 { 442 char *const line = csarg->line; 443 int64_t vcol = vcol_arg; 444 445 StrCharInfo ci = utf_ptr2StrCharInfo(line); 446 while (ci.ptr - line < len && *ci.ptr != NUL) { 447 vcol += charsize_regular(csarg, ci.ptr, vcol_arg, ci.chr.value).width; 448 ci = utfc_next(ci); 449 if (vcol > MAXCOL) { 450 vcol_arg = MAXCOL; 451 break; 452 } else { 453 vcol_arg = (int)vcol; 454 } 455 } 456 457 // Check for inline virtual text after the end of the line. 458 if (len == MAXCOL && csarg->virt_row >= 0 && *ci.ptr == NUL) { 459 int head = charsize_regular(csarg, ci.ptr, vcol_arg, ci.chr.value).head; 460 vcol += csarg->cur_text_width_left + csarg->cur_text_width_right + head; 461 vcol_arg = vcol > MAXCOL ? MAXCOL : (int)vcol; 462 } 463 464 return vcol_arg; 465 } 466 467 /// Like linesize_regular(), but can be used when CSType is kCharsizeFast. 468 /// 469 /// @see linesize_regular 470 int linesize_fast(CharsizeArg const *const csarg, int vcol_arg, colnr_T const len) 471 { 472 win_T *const wp = csarg->win; 473 bool const use_tabstop = csarg->use_tabstop; 474 475 char *const line = csarg->line; 476 int64_t vcol = vcol_arg; 477 478 StrCharInfo ci = utf_ptr2StrCharInfo(line); 479 while (ci.ptr - line < len && *ci.ptr != NUL) { 480 vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol_arg, ci.chr.value).width; 481 ci = utfc_next(ci); 482 if (vcol > MAXCOL) { 483 vcol_arg = MAXCOL; 484 break; 485 } else { 486 vcol_arg = (int)vcol; 487 } 488 } 489 490 return vcol_arg; 491 } 492 493 /// Get how many virtual columns inline virtual text should offset the cursor. 494 /// 495 /// @param csarg should contain information stored by charsize_regular() 496 /// about widths of left and right gravity virtual text 497 /// @param on_NUL whether this is the end of the line 498 static int virt_text_cursor_off(const CharsizeArg *csarg, bool on_NUL) 499 { 500 int off = 0; 501 if (!on_NUL || !(State & MODE_NORMAL)) { 502 off += csarg->cur_text_width_left; 503 } 504 if (!on_NUL && (State & MODE_NORMAL)) { 505 off += csarg->cur_text_width_right; 506 } 507 return off; 508 } 509 510 /// Get virtual column number of pos. 511 /// start: on the first position of this character (TAB, ctrl) 512 /// cursor: where the cursor is on this character (first char, except for TAB) 513 /// end: on the last position of this character (TAB, ctrl) 514 /// 515 /// This is used very often, keep it fast! 516 /// 517 /// @param wp 518 /// @param pos 519 /// @param start 520 /// @param cursor 521 /// @param end 522 void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) 523 { 524 char *const line = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line 525 colnr_T const end_col = pos->col; 526 527 CharsizeArg csarg; 528 bool on_NUL = false; 529 CSType const cstype = init_charsize_arg(&csarg, wp, pos->lnum, line); 530 csarg.max_head_vcol = -1; 531 532 colnr_T vcol = 0; 533 CharSize char_size; 534 StrCharInfo ci = utf_ptr2StrCharInfo(line); 535 if (cstype == kCharsizeFast) { 536 bool const use_tabstop = csarg.use_tabstop; 537 while (true) { 538 if (*ci.ptr == NUL) { 539 // if cursor is at NUL, it is treated like 1 cell char 540 char_size = (CharSize){ .width = 1 }; 541 break; 542 } 543 char_size = charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value); 544 StrCharInfo const next = utfc_next(ci); 545 if (next.ptr - line > end_col) { 546 break; 547 } 548 ci = next; 549 vcol += char_size.width; 550 } 551 } else { 552 while (true) { 553 char_size = charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value); 554 // make sure we don't go past the end of the line 555 if (*ci.ptr == NUL) { 556 // NUL at end of line only takes one column unless there is virtual text 557 char_size.width = 1 + csarg.cur_text_width_left + csarg.cur_text_width_right; 558 on_NUL = true; 559 break; 560 } 561 StrCharInfo const next = utfc_next(ci); 562 if (next.ptr - line > end_col) { 563 break; 564 } 565 ci = next; 566 vcol += char_size.width; 567 } 568 } 569 570 if (*ci.ptr == NUL && end_col < MAXCOL && end_col > ci.ptr - line) { 571 pos->col = (colnr_T)(ci.ptr - line); 572 } 573 574 int head = char_size.head; 575 int incr = char_size.width; 576 577 if (start != NULL) { 578 *start = vcol + head; 579 } 580 581 if (end != NULL) { 582 *end = vcol + incr - 1; 583 } 584 585 if (cursor != NULL) { 586 if (ci.chr.value == TAB 587 && (State & MODE_NORMAL) 588 && !wp->w_p_list 589 && !virtual_active(wp) 590 && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) { 591 // cursor at end 592 *cursor = vcol + incr - 1; 593 } else { 594 vcol += virt_text_cursor_off(&csarg, on_NUL); 595 // cursor at start 596 *cursor = vcol + head; 597 } 598 } 599 } 600 601 /// Get virtual cursor column in the current window, pretending 'list' is off. 602 /// 603 /// @param posp 604 /// 605 /// @return The virtual cursor column. 606 colnr_T getvcol_nolist(pos_T *posp) 607 { 608 int list_save = curwin->w_p_list; 609 colnr_T vcol; 610 611 curwin->w_p_list = false; 612 if (posp->coladd) { 613 getvvcol(curwin, posp, NULL, &vcol, NULL); 614 } else { 615 getvcol(curwin, posp, NULL, &vcol, NULL); 616 } 617 curwin->w_p_list = list_save; 618 return vcol; 619 } 620 621 /// Get virtual column in virtual mode. 622 /// 623 /// @param wp 624 /// @param pos 625 /// @param start 626 /// @param cursor 627 /// @param end 628 void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) 629 { 630 colnr_T col; 631 632 if (virtual_active(wp)) { 633 // For virtual mode, only want one value 634 getvcol(wp, pos, &col, NULL, NULL); 635 636 colnr_T coladd = pos->coladd; 637 colnr_T endadd = 0; 638 639 // Cannot put the cursor on part of a wide character. 640 char *ptr = ml_get_buf(wp->w_buffer, pos->lnum); 641 642 if (pos->col < ml_get_buf_len(wp->w_buffer, pos->lnum)) { 643 int c = utf_ptr2char(ptr + pos->col); 644 if ((c != TAB) && vim_isprintc(c)) { 645 endadd = (colnr_T)(ptr2cells(ptr + pos->col) - 1); 646 if (coladd > endadd) { 647 // past end of line 648 endadd = 0; 649 } else { 650 coladd = 0; 651 } 652 } 653 } 654 col += coladd; 655 656 if (start != NULL) { 657 *start = col; 658 } 659 660 if (cursor != NULL) { 661 *cursor = col; 662 } 663 664 if (end != NULL) { 665 *end = col + endadd; 666 } 667 } else { 668 getvcol(wp, pos, start, cursor, end); 669 } 670 } 671 672 /// Get the leftmost and rightmost virtual column of pos1 and pos2. 673 /// Used for Visual block mode. 674 /// 675 /// @param wp 676 /// @param pos1 677 /// @param pos2 678 /// @param left 679 /// @param right 680 void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right) 681 { 682 colnr_T from1; 683 colnr_T from2; 684 colnr_T to1; 685 colnr_T to2; 686 687 if (lt(*pos1, *pos2)) { 688 getvvcol(wp, pos1, &from1, NULL, &to1); 689 getvvcol(wp, pos2, &from2, NULL, &to2); 690 } else { 691 getvvcol(wp, pos2, &from1, NULL, &to1); 692 getvvcol(wp, pos1, &from2, NULL, &to2); 693 } 694 695 if (from2 < from1) { 696 *left = from2; 697 } else { 698 *left = from1; 699 } 700 701 if (to2 > to1) { 702 if ((*p_sel == 'e') && (from2 - 1 >= to1)) { 703 *right = from2 - 1; 704 } else { 705 *right = to2; 706 } 707 } else { 708 *right = to1; 709 } 710 } 711 712 /// Functions calculating vertical size of text when displayed inside a window. 713 /// Calls horizontal size functions defined above. 714 715 /// Check if there may be filler lines anywhere in window "wp". 716 bool win_may_fill(win_T *wp) 717 { 718 return ((wp->w_p_diff && diffopt_filler()) 719 || buf_meta_total(wp->w_buffer, kMTMetaLines)); 720 } 721 722 /// Return the number of filler lines above "lnum". 723 /// 724 /// @param wp 725 /// @param lnum 726 /// 727 /// @return Number of filler lines above lnum 728 int win_get_fill(win_T *wp, linenr_T lnum) 729 { 730 int virt_lines = decor_virt_lines(wp, lnum - 1, lnum, NULL, NULL, true); 731 732 // be quick when there are no filler lines 733 if (diffopt_filler()) { 734 int n = diff_check_fill(wp, lnum); 735 736 if (n > 0) { 737 return virt_lines + n; 738 } 739 } 740 return virt_lines; 741 } 742 743 /// Return the number of window lines occupied by buffer line "lnum". 744 /// Includes any filler lines. 745 /// 746 /// @param limit_winheight when true limit to window height 747 int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight) 748 { 749 // Check for filler lines above this buffer line. 750 return plines_win_nofill(wp, lnum, limit_winheight) + win_get_fill(wp, lnum); 751 } 752 753 /// Return the number of window lines occupied by buffer line "lnum". 754 /// Does not include filler lines. 755 /// 756 /// @param limit_winheight when true limit to window height 757 int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) 758 { 759 if (decor_conceal_line(wp, lnum - 1, false)) { 760 return 0; 761 } 762 763 if (!wp->w_p_wrap) { 764 return 1; 765 } 766 767 if (wp->w_view_width == 0) { 768 return 1; 769 } 770 771 // Folded lines are handled just like an empty line. 772 if (lineFolded(wp, lnum)) { 773 return 1; 774 } 775 776 const int lines = plines_win_nofold(wp, lnum); 777 if (limit_winheight && lines > wp->w_view_height) { 778 return wp->w_view_height; 779 } 780 return lines; 781 } 782 783 /// Get number of window lines physical line "lnum" will occupy in window "wp". 784 /// Does not care about folding, 'wrap' or filler lines. 785 int plines_win_nofold(win_T *wp, linenr_T lnum) 786 { 787 char *s = ml_get_buf(wp->w_buffer, lnum); 788 CharsizeArg csarg; 789 CSType const cstype = init_charsize_arg(&csarg, wp, lnum, s); 790 if (*s == NUL && csarg.virt_row < 0) { 791 return 1; // be quick for an empty line 792 } 793 794 int64_t col; 795 if (cstype == kCharsizeFast) { 796 col = linesize_fast(&csarg, 0, MAXCOL); 797 } else { 798 col = linesize_regular(&csarg, 0, MAXCOL); 799 } 800 801 // If list mode is on, then the '$' at the end of the line may take up one 802 // extra column. 803 if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) { 804 col += 1; 805 } 806 807 // Add column offset for 'number', 'relativenumber' and 'foldcolumn'. 808 int width = wp->w_view_width - win_col_off(wp); 809 if (width <= 0) { 810 return 32000; // bigger than the number of screen lines 811 } 812 if (col <= width) { 813 return 1; 814 } 815 col -= width; 816 width += win_col_off2(wp); 817 const int64_t lines = (col + (width - 1)) / width + 1; 818 return (lines > 0 && lines <= INT_MAX) ? (int)lines : INT_MAX; 819 } 820 821 /// Like plines_win(), but only reports the number of physical screen lines 822 /// used from the start of the line to the given column number. 823 int plines_win_col(win_T *wp, linenr_T lnum, long column) 824 { 825 // Check for filler lines above this buffer line. 826 int lines = win_get_fill(wp, lnum); 827 828 if (!wp->w_p_wrap) { 829 return lines + 1; 830 } 831 832 if (wp->w_view_width == 0) { 833 return lines + 1; 834 } 835 836 char *line = ml_get_buf(wp->w_buffer, lnum); 837 838 CharsizeArg csarg; 839 CSType const cstype = init_charsize_arg(&csarg, wp, lnum, line); 840 841 colnr_T vcol = 0; 842 StrCharInfo ci = utf_ptr2StrCharInfo(line); 843 if (cstype == kCharsizeFast) { 844 bool const use_tabstop = csarg.use_tabstop; 845 while (*ci.ptr != NUL && --column >= 0) { 846 vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value).width; 847 ci = utfc_next(ci); 848 } 849 } else { 850 while (*ci.ptr != NUL && --column >= 0) { 851 vcol += charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value).width; 852 ci = utfc_next(ci); 853 } 854 } 855 856 // If current char is a TAB, and the TAB is not displayed as ^I, and we're not 857 // in MODE_INSERT state, then col must be adjusted so that it represents the 858 // last screen position of the TAB. This only fixes an error when the TAB 859 // wraps from one screen line to the next (when 'columns' is not a multiple 860 // of 'ts') -- webb. 861 colnr_T col = vcol; 862 if (ci.chr.value == TAB && (State & MODE_NORMAL) && csarg.use_tabstop) { 863 col += win_charsize(cstype, col, ci.ptr, ci.chr.value, &csarg).width - 1; 864 } 865 866 // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. 867 int width = wp->w_view_width - win_col_off(wp); 868 if (width <= 0) { 869 return 9999; 870 } 871 872 lines += 1; 873 if (col > width) { 874 lines += (col - width) / (width + win_col_off2(wp)) + 1; 875 } 876 return lines; 877 } 878 879 /// Get the number of screen lines buffer line "lnum" will take in window "wp". 880 /// This takes care of both folds and topfill. 881 /// 882 /// XXX: Because of topfill, this only makes sense when lnum >= wp->w_topline. 883 /// 884 /// @param[in] wp window the line is in 885 /// @param[in] lnum line number 886 /// @param[out] nextp if not NULL, the last line of a fold 887 /// @param[out] foldedp if not NULL, whether lnum is on a fold 888 /// @param[in] cache whether to use the window's cache for folds 889 /// @param[in] limit_winheight when true limit to window height 890 /// 891 /// @return the total number of screen lines 892 int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp, 893 const bool cache, const bool limit_winheight) 894 { 895 bool folded = hasFoldingWin(wp, lnum, &lnum, nextp, cache, NULL); 896 if (foldedp != NULL) { 897 *foldedp = folded; 898 } 899 900 int filler_lines = lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum); 901 902 if (decor_conceal_line(wp, lnum - 1, false)) { 903 return filler_lines; 904 } 905 906 return (folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) + filler_lines; 907 } 908 909 /// Return number of window lines a physical line range will occupy in window "wp". 910 /// Takes into account folding, 'wrap', topfill and filler lines beyond the end of the buffer. 911 /// 912 /// XXX: Because of topfill, this only makes sense when first >= wp->w_topline. 913 /// 914 /// @param first first line number 915 /// @param last last line number 916 /// @param max number of lines to limit the height to 917 /// 918 /// @see win_text_height 919 int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max) 920 { 921 int count = 0; 922 923 while (first <= last && count < max) { 924 linenr_T next = first; 925 count += plines_win_full(wp, first, &next, NULL, false, false); 926 first = next + 1; 927 } 928 if (first == wp->w_buffer->b_ml.ml_line_count + 1) { 929 count += win_get_fill(wp, first); 930 } 931 return MIN(max, count); 932 } 933 934 /// Return total number of physical and filler lines in a physical line range. 935 /// Doesn't treat a fold as a single line or consider a wrapped line multiple lines, 936 /// unlike plines_m_win() or win_text_height(). 937 /// 938 /// Mainly used for calculating scrolling offsets. 939 int plines_m_win_fill(win_T *wp, linenr_T first, linenr_T last) 940 { 941 int count = last - first + 1 + decor_virt_lines(wp, first - 1, last, NULL, NULL, false); 942 943 if (diffopt_filler()) { 944 for (int lnum = first; lnum <= last; lnum++) { 945 // Note: this also considers folds (no filler lines inside folds). 946 int n = diff_check_fill(wp, lnum); 947 count += MAX(n, 0); 948 } 949 } 950 951 return MAX(count, 0); 952 } 953 954 /// Get the number of screen lines a range of text will take in window "wp". 955 /// 956 /// @param[in] start_lnum Starting line number, 1-based inclusive. 957 /// @param[in] start_vcol >= 0: Starting virtual column index on "start_lnum", 958 /// 0-based inclusive, rounded down to full screen lines. 959 /// < 0: Count a full "start_lnum", including filler lines above. 960 /// @param[in,out] end_lnum Ending line number, 1-based inclusive. Set to last line for 961 /// which the height is calculated (smaller if "max" is reached). 962 /// @param[in,out] end_vcol >= 0: Ending virtual column index on "end_lnum", 963 /// 0-based exclusive, rounded up to full screen lines. 964 /// < 0: Count a full "end_lnum", not including filler lines below. 965 /// Set to the number of columns in "end_lnum" to reach "max". 966 /// @param[in] max Don't calculate the height for lines beyond the line where "max" 967 /// height is reached. 968 /// @param[out] fill If not NULL, set to the number of filler lines in the range. 969 int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_t start_vcol, 970 linenr_T *const end_lnum, int64_t *const end_vcol, int64_t *const fill, 971 int64_t const max) 972 { 973 int width1 = wp->w_view_width - win_col_off(wp); 974 int width2 = width1 + win_col_off2(wp); 975 width1 = MAX(width1, 0); 976 width2 = MAX(width2, 0); 977 int64_t height_sum_fill = 0; 978 int64_t height_cur_nofill = 0; 979 int64_t height_sum_nofill = 0; 980 linenr_T lnum = start_lnum; 981 linenr_T cur_lnum = lnum; 982 bool cur_folded = false; 983 984 if (start_vcol >= 0) { 985 linenr_T lnum_next = lnum; 986 cur_folded = hasFolding(wp, lnum, &lnum, &lnum_next); 987 height_cur_nofill = plines_win_nofill(wp, lnum, false); 988 height_sum_nofill += height_cur_nofill; 989 const int64_t row_off = (start_vcol < width1 || width2 <= 0) 990 ? 0 991 : 1 + (start_vcol - width1) / width2; 992 height_sum_nofill -= MIN(row_off, height_cur_nofill); 993 lnum = lnum_next + 1; 994 } 995 996 while (lnum <= *end_lnum && height_sum_nofill + height_sum_fill < max) { 997 linenr_T lnum_next = lnum; 998 cur_folded = hasFolding(wp, lnum, &lnum, &lnum_next); 999 height_sum_fill += win_get_fill(wp, lnum); 1000 height_cur_nofill = plines_win_nofill(wp, lnum, false); 1001 height_sum_nofill += height_cur_nofill; 1002 cur_lnum = lnum; 1003 lnum = lnum_next + 1; 1004 } 1005 1006 int64_t vcol_end = *end_vcol; 1007 bool use_vcol = vcol_end >= 0 && lnum > *end_lnum; 1008 if (use_vcol) { 1009 height_sum_nofill -= height_cur_nofill; 1010 const int64_t row_off = vcol_end == 0 1011 ? 0 1012 : (vcol_end <= width1 || width2 <= 0) 1013 ? 1 1014 : 1 + (vcol_end - width1 + width2 - 1) / width2; 1015 height_sum_nofill += MIN(row_off, height_cur_nofill); 1016 } 1017 1018 if (cur_folded) { 1019 vcol_end = 0; 1020 } else { 1021 int linesize = linetabsize_eol(wp, cur_lnum); 1022 vcol_end = MIN(use_vcol ? vcol_end : INT64_MAX, linesize); 1023 } 1024 1025 int64_t overflow = height_sum_nofill + height_sum_fill - max; 1026 if (overflow > 0 && width2 > 0 && vcol_end > width2) { 1027 vcol_end -= (vcol_end - width1) % width2 + (overflow - 1) * width2; 1028 } 1029 1030 *end_lnum = cur_lnum; 1031 *end_vcol = vcol_end; 1032 if (fill != NULL) { 1033 *fill = height_sum_fill; 1034 } 1035 return height_sum_fill + height_sum_nofill; 1036 }