grid.c (38353B)
1 // Low-level functions to manipulate individual character cells on the 2 // screen grid. 3 // 4 // Most of the routines in this file perform screen (grid) manipulations. The 5 // given operation is performed physically on the screen. The corresponding 6 // change is also made to the internal screen image. In this way, the editor 7 // anticipates the effect of editing changes on the appearance of the screen. 8 // That way, when we call update_screen() a complete redraw isn't usually 9 // necessary. Another advantage is that we can keep adding code to anticipate 10 // screen changes, and in the meantime, everything still works. 11 // 12 // The grid_*() functions write to the screen and handle updating grid->lines[]. 13 14 #include <assert.h> 15 #include <limits.h> 16 #include <stdint.h> 17 #include <stdlib.h> 18 #include <string.h> 19 20 #include "nvim/api/private/defs.h" 21 #include "nvim/arabic.h" 22 #include "nvim/ascii_defs.h" 23 #include "nvim/buffer_defs.h" 24 #include "nvim/decoration.h" 25 #include "nvim/globals.h" 26 #include "nvim/grid.h" 27 #include "nvim/highlight.h" 28 #include "nvim/log.h" 29 #include "nvim/map_defs.h" 30 #include "nvim/mbyte.h" 31 #include "nvim/memory.h" 32 #include "nvim/message.h" 33 #include "nvim/option_vars.h" 34 #include "nvim/optionstr.h" 35 #include "nvim/types_defs.h" 36 #include "nvim/ui.h" 37 #include "nvim/ui_defs.h" 38 39 #include "grid.c.generated.h" 40 41 // temporary buffer for rendering a single screenline, so it can be 42 // compared with previous contents to calculate smallest delta. 43 // Per-cell attributes 44 static size_t linebuf_size = 0; 45 46 // Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string. 47 // Then it instead stores an index into glyph_cache.keys[] which is a flat char array. 48 // The hash part is used by schar_from_buf() to quickly lookup glyphs which already 49 // has been interned. schar_get() should used to convert a schar_T value 50 // back to a string buffer. 51 // 52 // The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL). 53 static Set(glyph) glyph_cache = SET_INIT; 54 55 /// Determine if dedicated window grid should be used or the default_grid 56 /// 57 /// If UI did not request multigrid support, draw all windows on the 58 /// default_grid. 59 /// 60 /// NB: this function can only been used with window grids in a context where 61 /// win_grid_alloc already has been called! 62 /// 63 /// If the default_grid is used, adjust window relative positions to global 64 /// screen positions. 65 ScreenGrid *grid_adjust(GridView *grid, int *row_off, int *col_off) 66 { 67 *row_off += grid->row_offset; 68 *col_off += grid->col_offset; 69 return grid->target; 70 } 71 72 schar_T schar_from_str(const char *str) 73 { 74 if (str == NULL) { 75 return 0; 76 } 77 return schar_from_buf(str, strlen(str)); 78 } 79 80 /// @param buf need not be NUL terminated, but may not contain embedded NULs. 81 /// 82 /// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte) 83 schar_T schar_from_buf(const char *buf, size_t len) 84 { 85 assert(len < MAX_SCHAR_SIZE); 86 if (len <= 4) { 87 schar_T sc = 0; 88 memcpy((char *)&sc, buf, len); 89 return sc; 90 } else { 91 String str = { .data = (char *)buf, .size = len }; 92 93 MHPutStatus status; 94 uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status); 95 assert(idx < 0xFFFFFF); 96 #ifdef ORDER_BIG_ENDIAN 97 return idx + ((uint32_t)0xFF << 24); 98 #else 99 return 0xFF + (idx << 8); 100 #endif 101 } 102 } 103 104 /// Check if cache is full, and if it is, clear it. 105 /// 106 /// This should normally only be called in update_screen() 107 /// 108 /// @return true if cache was clered, and all your screen buffers now are hosed 109 /// and you need to use UPD_CLEAR 110 bool schar_cache_clear_if_full(void) 111 { 112 // note: critical max is really (1<<24)-1. This gives us some marginal 113 // until next time update_screen() is called 114 if (glyph_cache.h.n_keys > (1<<21)) { 115 schar_cache_clear(); 116 return true; 117 } 118 return false; 119 } 120 121 void schar_cache_clear(void) 122 { 123 decor_check_invalid_glyphs(); 124 set_clear(glyph, &glyph_cache); 125 126 // for char options we have stored the original strings. Regenerate 127 // the parsed schar_T values with the new clean cache. 128 // This must not return an error as cell widths have not changed. 129 if (check_chars_options()) { 130 abort(); 131 } 132 } 133 134 bool schar_high(schar_T sc) 135 { 136 #ifdef ORDER_BIG_ENDIAN 137 return ((sc & 0xFF000000) == 0xFF000000); 138 #else 139 return ((sc & 0xFF) == 0xFF); 140 #endif 141 } 142 143 #ifdef ORDER_BIG_ENDIAN 144 # define schar_idx(sc) (sc & (0x00FFFFFF)) 145 #else 146 # define schar_idx(sc) (sc >> 8) 147 #endif 148 149 /// sets final NUL 150 size_t schar_get(char *buf_out, schar_T sc) 151 { 152 size_t len = schar_get_adv(&buf_out, sc); 153 *buf_out = NUL; 154 return len; 155 } 156 157 /// advance buf_out. do NOT set final NUL 158 size_t schar_get_adv(char **buf_out, schar_T sc) 159 { 160 size_t len; 161 if (schar_high(sc)) { 162 uint32_t idx = schar_idx(sc); 163 assert(idx < glyph_cache.h.n_keys); 164 len = strlen(&glyph_cache.keys[idx]); 165 memcpy(*buf_out, &glyph_cache.keys[idx], len); 166 } else { 167 len = strnlen((char *)&sc, 4); 168 memcpy(*buf_out, (char *)&sc, len); 169 } 170 *buf_out += len; 171 return len; 172 } 173 174 size_t schar_len(schar_T sc) 175 { 176 if (schar_high(sc)) { 177 uint32_t idx = schar_idx(sc); 178 assert(idx < glyph_cache.h.n_keys); 179 return strlen(&glyph_cache.keys[idx]); 180 } else { 181 return strnlen((char *)&sc, 4); 182 } 183 } 184 185 int schar_cells(schar_T sc) 186 { 187 // hot path 188 #ifdef ORDER_BIG_ENDIAN 189 if (!(sc & 0x80FFFFFF)) { 190 return 1; 191 } 192 #else 193 if (sc < 0x80) { 194 return 1; 195 } 196 #endif 197 198 char sc_buf[MAX_SCHAR_SIZE]; 199 schar_get(sc_buf, sc); 200 return utf_ptr2cells(sc_buf); 201 } 202 203 /// gets first raw UTF-8 byte of an schar 204 static char schar_get_first_byte(schar_T sc) 205 { 206 assert(!(schar_high(sc) && schar_idx(sc) >= glyph_cache.h.n_keys)); 207 return schar_high(sc) ? glyph_cache.keys[schar_idx(sc)] : *(char *)≻ 208 } 209 210 int schar_get_first_codepoint(schar_T sc) 211 { 212 char sc_buf[MAX_SCHAR_SIZE]; 213 schar_get(sc_buf, sc); 214 return utf_ptr2char(sc_buf); 215 } 216 217 /// @return ascii char or NUL if not ascii 218 char schar_get_ascii(schar_T sc) 219 { 220 #ifdef ORDER_BIG_ENDIAN 221 return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL; 222 #else 223 return (sc < 0x80) ? (char)sc : NUL; 224 #endif 225 } 226 227 static bool schar_in_arabic_block(schar_T sc) 228 { 229 char first_byte = schar_get_first_byte(sc); 230 return ((uint8_t)first_byte & 0xFE) == 0xD8; 231 } 232 233 /// Get the first two codepoints of an schar, or NUL when not available 234 static void schar_get_first_two_codepoints(schar_T sc, int *c0, int *c1) 235 { 236 char sc_buf[MAX_SCHAR_SIZE]; 237 schar_get(sc_buf, sc); 238 239 *c0 = utf_ptr2char(sc_buf); 240 int len = utf_ptr2len(sc_buf); 241 if (*c0 == NUL) { 242 *c1 = NUL; 243 } else { 244 *c1 = utf_ptr2char(sc_buf + len); 245 } 246 } 247 248 void line_do_arabic_shape(schar_T *buf, int cols) 249 { 250 int i = 0; 251 252 for (i = 0; i < cols; i++) { 253 // quickly skip over non-arabic text 254 if (schar_in_arabic_block(buf[i])) { 255 break; 256 } 257 } 258 259 if (i == cols) { 260 return; 261 } 262 263 int c0prev = 0; 264 int c0, c1; 265 schar_get_first_two_codepoints(buf[i], &c0, &c1); 266 267 for (; i < cols; i++) { 268 int c0next, c1next; 269 schar_get_first_two_codepoints(i + 1 < cols ? buf[i + 1] : 0, &c0next, &c1next); 270 271 if (!ARABIC_CHAR(c0)) { 272 goto next; 273 } 274 275 int c1new = c1; 276 int c0new = arabic_shape(c0, &c1new, c0next, c1next, c0prev); 277 278 if (c0new == c0 && c1new == c1) { 279 goto next; // unchanged 280 } 281 282 char scbuf[MAX_SCHAR_SIZE]; 283 schar_get(scbuf, buf[i]); 284 285 char scbuf_new[MAX_SCHAR_SIZE]; 286 size_t len = (size_t)utf_char2bytes(c0new, scbuf_new); 287 if (c1new) { 288 len += (size_t)utf_char2bytes(c1new, scbuf_new + len); 289 } 290 291 int off = utf_char2len(c0) + (c1 ? utf_char2len(c1) : 0); 292 size_t rest = strlen(scbuf + off); 293 if (rest + len + 1 > MAX_SCHAR_SIZE) { 294 // Too bigly, discard one code-point. 295 // This should be enough as c0 cannot grow more than from 2 to 4 bytes 296 // (base arabic to extended arabic) 297 rest -= (size_t)utf_cp_bounds(scbuf + off, scbuf + off + rest - 1).begin_off + 1; 298 } 299 memcpy(scbuf_new + len, scbuf + off, rest); 300 buf[i] = schar_from_buf(scbuf_new, len + rest); 301 302 next: 303 c0prev = c0; 304 c0 = c0next; 305 c1 = c1next; 306 } 307 } 308 309 /// clear a line in the grid starting at "off" until "width" characters 310 /// are cleared. 311 void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid) 312 { 313 for (int col = 0; col < width; col++) { 314 grid->chars[off + (size_t)col] = schar_from_ascii(' '); 315 } 316 int fill = valid ? 0 : -1; 317 memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); 318 memset(grid->vcols + off, -1, (size_t)width * sizeof(colnr_T)); 319 } 320 321 void grid_invalidate(ScreenGrid *grid) 322 { 323 memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols); 324 } 325 326 static bool grid_invalid_row(ScreenGrid *grid, int row) 327 { 328 return grid->attrs[grid->line_offset[row]] < 0; 329 } 330 331 /// Get a single character directly from grid.chars 332 /// 333 /// @param[out] attrp set to the character's attribute (optional) 334 schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp) 335 { 336 // safety check 337 if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) { 338 return NUL; 339 } 340 341 size_t off = grid->line_offset[row] + (size_t)col; 342 if (attrp != NULL) { 343 *attrp = grid->attrs[off]; 344 } 345 return grid->chars[off]; 346 } 347 348 static ScreenGrid *grid_line_grid = NULL; 349 static int grid_line_row = -1; 350 static int grid_line_coloff = 0; 351 static int grid_line_maxcol = 0; 352 static int grid_line_first = INT_MAX; 353 static int grid_line_last = 0; 354 static int grid_line_clear_to = 0; 355 static int grid_line_bg_attr = 0; 356 static int grid_line_clear_attr = 0; 357 static int grid_line_flags = 0; 358 359 /// Start a group of grid_line_puts calls that builds a single grid line. 360 /// 361 /// Must be matched with a grid_line_flush call before moving to 362 /// another line. 363 void grid_line_start(GridView *view, int row) 364 { 365 int col = 0; 366 ScreenGrid *grid = grid_adjust(view, &row, &col); 367 screengrid_line_start(grid, row, col); 368 } 369 370 void screengrid_line_start(ScreenGrid *grid, int row, int col) 371 { 372 grid_line_maxcol = grid->cols; 373 assert(grid_line_grid == NULL); 374 grid_line_row = row; 375 grid_line_grid = grid; 376 grid_line_coloff = col; 377 grid_line_first = (int)linebuf_size; 378 grid_line_maxcol = MIN(grid_line_maxcol, grid->cols - grid_line_coloff); 379 grid_line_last = 0; 380 grid_line_clear_to = 0; 381 grid_line_bg_attr = 0; 382 grid_line_clear_attr = 0; 383 grid_line_flags = 0; 384 385 assert((size_t)grid_line_maxcol <= linebuf_size); 386 387 if (full_screen && (rdb_flags & kOptRdbFlagInvalid)) { 388 assert(linebuf_char); 389 // Current batch must not depend on previous contents of linebuf_char. 390 // Set invalid values which will cause assertion failures later if they are used. 391 memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size); 392 memset(linebuf_attr, 0xFF, sizeof(sattr_T) * linebuf_size); 393 } 394 } 395 396 /// Get present char from current rendered screen line 397 /// 398 /// This indicates what already is on screen, not the pending render buffer. 399 /// 400 /// @return char or space if out of bounds 401 schar_T grid_line_getchar(int col, int *attr) 402 { 403 if (col < grid_line_maxcol) { 404 col += grid_line_coloff; 405 size_t off = grid_line_grid->line_offset[grid_line_row] + (size_t)col; 406 if (attr != NULL) { 407 *attr = grid_line_grid->attrs[off]; 408 } 409 return grid_line_grid->chars[off]; 410 } else { 411 // NUL is a very special value (right-half of double width), space is True Neutralâ„¢ 412 return schar_from_ascii(' '); 413 } 414 } 415 416 void grid_line_put_schar(int col, schar_T schar, int attr) 417 { 418 assert(grid_line_grid); 419 if (col >= grid_line_maxcol) { 420 return; 421 } 422 423 linebuf_char[col] = schar; 424 linebuf_attr[col] = attr; 425 426 grid_line_first = MIN(grid_line_first, col); 427 // TODO(bfredl): Y U NO DOUBLEWIDTH? 428 grid_line_last = MAX(grid_line_last, col + 1); 429 linebuf_vcol[col] = -1; 430 } 431 432 /// Put string "text" at "col" position relative to the grid line from the 433 /// recent grid_line_start() call. 434 /// 435 /// @param textlen length of string or -1 to use strlen(text) 436 /// Note: only outputs within one row! 437 /// 438 /// @return number of grid cells used 439 int grid_line_puts(int col, const char *text, int textlen, int attr) 440 { 441 const char *ptr = text; 442 int len = textlen; 443 444 assert(grid_line_grid); 445 446 int start_col = col; 447 448 const int max_col = grid_line_maxcol; 449 while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) { 450 // check if this is the first byte of a multibyte 451 int mbyte_blen; 452 if (len >= 0) { 453 int maxlen = (int)((text + len) - ptr); 454 mbyte_blen = utfc_ptr2len_len(ptr, maxlen); 455 if (mbyte_blen > maxlen) { 456 mbyte_blen = 1; 457 } 458 } else { 459 mbyte_blen = utfc_ptr2len(ptr); 460 } 461 int firstc; 462 schar_T schar = utfc_ptrlen2schar(ptr, mbyte_blen, &firstc); 463 int mbyte_cells = utf_ptr2cells_len(ptr, mbyte_blen); 464 if (mbyte_cells > 2 || schar == 0) { 465 mbyte_cells = 1; 466 schar = schar_from_char(0xFFFD); 467 } 468 469 if (col + mbyte_cells > max_col) { 470 // Only 1 cell left, but character requires 2 cells: 471 // display a '>' in the last column to avoid wrapping. 472 schar = schar_from_ascii('>'); 473 mbyte_cells = 1; 474 } 475 476 // When at the start of the text and overwriting the right half of a 477 // two-cell character in the same grid, truncate that into a '>'. 478 if (ptr == text && col > grid_line_first && col < grid_line_last 479 && linebuf_char[col] == 0) { 480 linebuf_char[col - 1] = schar_from_ascii('>'); 481 } 482 483 linebuf_char[col] = schar; 484 linebuf_attr[col] = attr; 485 linebuf_vcol[col] = -1; 486 if (mbyte_cells == 2) { 487 linebuf_char[col + 1] = 0; 488 linebuf_attr[col + 1] = attr; 489 linebuf_vcol[col + 1] = -1; 490 } 491 492 col += mbyte_cells; 493 ptr += mbyte_blen; 494 } 495 496 if (col > start_col) { 497 grid_line_first = MIN(grid_line_first, start_col); 498 grid_line_last = MAX(grid_line_last, col); 499 } 500 501 return col - start_col; 502 } 503 504 int grid_line_fill(int start_col, int end_col, schar_T sc, int attr) 505 { 506 end_col = MIN(end_col, grid_line_maxcol); 507 if (start_col >= end_col) { 508 return end_col; 509 } 510 511 for (int col = start_col; col < end_col; col++) { 512 linebuf_char[col] = sc; 513 linebuf_attr[col] = attr; 514 linebuf_vcol[col] = -1; 515 } 516 517 grid_line_first = MIN(grid_line_first, start_col); 518 grid_line_last = MAX(grid_line_last, end_col); 519 return end_col; 520 } 521 522 /// @param bg_attr applies to both the buffered line and the columns to clear 523 /// @param clear_attr applies only to the columns to clear 524 void grid_line_clear_end(int start_col, int end_col, int bg_attr, int clear_attr) 525 { 526 if (grid_line_first > start_col) { 527 grid_line_first = start_col; 528 grid_line_last = start_col; 529 } 530 grid_line_clear_to = end_col; 531 grid_line_bg_attr = bg_attr; 532 grid_line_clear_attr = clear_attr; 533 } 534 535 /// move the cursor to a position in a currently rendered line. 536 void grid_line_cursor_goto(int col) 537 { 538 ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col); 539 } 540 541 void grid_line_mirror(int width) 542 { 543 grid_line_clear_to = MAX(grid_line_last, grid_line_clear_to); 544 if (grid_line_first >= grid_line_clear_to) { 545 return; 546 } 547 linebuf_mirror(&grid_line_first, &grid_line_last, &grid_line_clear_to, width); 548 grid_line_flags |= SLF_RIGHTLEFT; 549 } 550 551 void linebuf_mirror(int *firstp, int *lastp, int *clearp, int width) 552 { 553 int first = *firstp; 554 int last = *lastp; 555 556 size_t n = (size_t)(last - first); 557 int mirror = width - 1; // Mirrors are more fun than television. 558 schar_T *scratch_char = (schar_T *)linebuf_scratch; 559 memcpy(scratch_char + first, linebuf_char + first, n * sizeof(schar_T)); 560 for (int col = first; col < last; col++) { 561 int rev = mirror - col; 562 if (col + 1 < last && scratch_char[col + 1] == 0) { 563 linebuf_char[rev - 1] = scratch_char[col]; 564 linebuf_char[rev] = 0; 565 col++; 566 } else { 567 linebuf_char[rev] = scratch_char[col]; 568 } 569 } 570 571 // for attr and vcol: assumes doublewidth chars are self-consistent 572 sattr_T *scratch_attr = (sattr_T *)linebuf_scratch; 573 memcpy(scratch_attr + first, linebuf_attr + first, n * sizeof(sattr_T)); 574 for (int col = first; col < last; col++) { 575 linebuf_attr[mirror - col] = scratch_attr[col]; 576 } 577 578 colnr_T *scratch_vcol = (colnr_T *)linebuf_scratch; 579 memcpy(scratch_vcol + first, linebuf_vcol + first, n * sizeof(colnr_T)); 580 for (int col = first; col < last; col++) { 581 linebuf_vcol[mirror - col] = scratch_vcol[col]; 582 } 583 584 *firstp = width - *clearp; 585 *clearp = width - first; 586 *lastp = width - last; 587 } 588 589 /// End a group of grid_line_puts calls and send the screen buffer to the UI layer. 590 void grid_line_flush(void) 591 { 592 ScreenGrid *grid = grid_line_grid; 593 grid_line_grid = NULL; 594 grid_line_clear_to = MAX(grid_line_last, grid_line_clear_to); 595 assert(grid_line_clear_to <= grid_line_maxcol); 596 if (grid_line_first >= grid_line_clear_to) { 597 return; 598 } 599 600 grid_put_linebuf(grid, grid_line_row, grid_line_coloff, grid_line_first, grid_line_last, 601 grid_line_clear_to, grid_line_bg_attr, grid_line_clear_attr, -1, 602 grid_line_flags); 603 } 604 605 /// flush grid line but only if on a valid row 606 /// 607 /// This is a stopgap until message.c has been refactored to behave 608 void grid_line_flush_if_valid_row(void) 609 { 610 if (grid_line_row < 0 || grid_line_row >= grid_line_grid->rows) { 611 if (rdb_flags & kOptRdbFlagInvalid) { 612 abort(); 613 } else { 614 grid_line_grid = NULL; 615 return; 616 } 617 } 618 grid_line_flush(); 619 } 620 621 void grid_clear(GridView *grid, int start_row, int end_row, int start_col, int end_col, int attr) 622 { 623 for (int row = start_row; row < end_row; row++) { 624 grid_line_start(grid, row); 625 end_col = MIN(end_col, grid_line_maxcol); 626 if (grid_line_row >= grid_line_grid->rows || start_col >= end_col) { 627 grid_line_grid = NULL; // TODO(bfredl): make callers behave instead 628 return; 629 } 630 grid_line_clear_end(start_col, end_col, attr, 0); 631 grid_line_flush(); 632 } 633 } 634 635 /// Check whether the given character needs redrawing: 636 /// - the (first byte of the) character is different 637 /// - the attributes are different 638 /// - the character is multi-byte and the next byte is different 639 /// - the character is two cells wide and the second cell differs. 640 static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int cols) 641 { 642 return (cols > 0 643 && ((linebuf_char[col] != grid->chars[off_to] 644 || linebuf_attr[col] != grid->attrs[off_to] 645 || (cols > 1 && linebuf_char[col + 1] == 0 646 && linebuf_char[col + 1] != grid->chars[off_to + 1])) 647 || exmode_active // TODO(bfredl): what in the actual fuck 648 || rdb_flags & kOptRdbFlagNodelta)); 649 } 650 651 /// Move one buffered line to the window grid, but only the characters that 652 /// have actually changed. Handle insert/delete character. 653 /// 654 /// @param coloff gives the first column on the grid for this line. 655 /// @param endcol gives the columns where valid characters are. 656 /// @param clear_width see SLF_RIGHTLEFT. 657 /// @param clear_attr combined with "bg_attr" for the columns to clear. 658 /// @param flags can have bits: 659 /// - SLF_RIGHTLEFT rightleft text, like a window with 'rightleft' option set: 660 /// - When false, clear columns "endcol" to "clear_width". 661 /// - When true, clear columns "col" to "endcol". 662 /// - SLF_WRAP hint to UI that "row" contains a line wrapped into the next row. 663 /// - SLF_INC_VCOL: 664 /// - When false, use "last_vcol" for grid->vcols[] of the columns to clear. 665 /// - When true, use an increasing sequence starting from "last_vcol + 1" for 666 /// grid->vcols[] of the columns to clear. 667 void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width, 668 int bg_attr, int clear_attr, colnr_T last_vcol, int flags) 669 { 670 bool redraw_next; // redraw_this for next character 671 bool clear_next = false; 672 assert(0 <= row && row < grid->rows); 673 // TODO(bfredl): check all callsites and eliminate 674 // Check for illegal col, just in case 675 if (endcol > grid->cols) { 676 endcol = grid->cols; 677 } 678 679 // Safety check. Avoids clang warnings down the call stack. 680 if (grid->chars == NULL || row >= grid->rows || coloff >= grid->cols) { 681 DLOG("invalid state, skipped"); 682 return; 683 } 684 685 bool invalid_row = grid != &default_grid && grid_invalid_row(grid, row) && col == 0; 686 size_t off_to = grid->line_offset[row] + (size_t)coloff; 687 const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols; 688 689 // When at the start of the text and overwriting the right half of a 690 // two-cell character in the same grid, truncate that into a '>'. 691 if (col > 0 && grid->chars[off_to + (size_t)col] == 0) { 692 linebuf_char[col - 1] = schar_from_ascii('>'); 693 linebuf_attr[col - 1] = grid->attrs[off_to + (size_t)col - 1]; 694 col--; 695 } 696 697 int clear_start = endcol; 698 if (flags & SLF_RIGHTLEFT) { 699 clear_start = col; 700 col = endcol; 701 endcol = clear_width; 702 clear_width = col; 703 } 704 705 if (p_arshape && !p_tbidi && endcol > col) { 706 line_do_arabic_shape(linebuf_char + col, endcol - col); 707 } 708 709 if (bg_attr) { 710 for (int c = col; c < endcol; c++) { 711 linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]); 712 } 713 } 714 715 redraw_next = grid_char_needs_redraw(grid, col, off_to + (size_t)col, endcol - col); 716 717 int start_dirty = -1; 718 int end_dirty = 0; 719 720 while (col < endcol) { 721 int char_cells = 1; // 1: normal char 722 // 2: occupies two display cells 723 if (col + 1 < endcol && linebuf_char[col + 1] == 0) { 724 char_cells = 2; 725 } 726 bool redraw_this = redraw_next; // Does character need redraw? 727 size_t off = off_to + (size_t)col; 728 redraw_next = grid_char_needs_redraw(grid, col + char_cells, 729 off + (size_t)char_cells, 730 endcol - col - char_cells); 731 732 if (redraw_this) { 733 if (start_dirty == -1) { 734 start_dirty = col; 735 } 736 end_dirty = col + char_cells; 737 // When writing a single-width character over a double-width 738 // character and at the end of the redrawn text, need to clear out 739 // the right half of the old character. 740 // Also required when writing the right half of a double-width 741 // char over the left half of an existing one 742 if (col + char_cells == endcol && off + (size_t)char_cells < max_off_to 743 && grid->chars[off + (size_t)char_cells] == NUL) { 744 clear_next = true; 745 } 746 747 grid->chars[off] = linebuf_char[col]; 748 if (char_cells == 2) { 749 grid->chars[off + 1] = linebuf_char[col + 1]; 750 } 751 752 grid->attrs[off] = linebuf_attr[col]; 753 // For simplicity set the attributes of second half of a 754 // double-wide character equal to the first half. 755 if (char_cells == 2) { 756 grid->attrs[off + 1] = linebuf_attr[col]; 757 } 758 } 759 760 grid->vcols[off] = linebuf_vcol[col]; 761 if (char_cells == 2) { 762 grid->vcols[off + 1] = linebuf_vcol[col + 1]; 763 } 764 765 col += char_cells; 766 } 767 768 if (clear_next) { 769 // Clear the second half of a double-wide character of which the left 770 // half was overwritten with a single-wide character. 771 grid->chars[off_to + (size_t)col] = schar_from_ascii(' '); 772 end_dirty++; 773 } 774 775 // When clearing the left half of a double-wide char also clear the right half. 776 if (off_to + (size_t)clear_width < max_off_to 777 && grid->chars[off_to + (size_t)clear_width] == 0) { 778 clear_width++; 779 } 780 781 int clear_dirty_start = -1, clear_end = -1; 782 if (flags & SLF_RIGHTLEFT) { 783 for (col = clear_width - 1; col >= clear_start; col--) { 784 size_t off = off_to + (size_t)col; 785 grid->vcols[off] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol; 786 } 787 } 788 clear_attr = hl_combine_attr(bg_attr, clear_attr); 789 // blank out the rest of the line 790 // TODO(bfredl): we could cache winline widths 791 for (col = clear_start; col < clear_width; col++) { 792 size_t off = off_to + (size_t)col; 793 if (grid->chars[off] != schar_from_ascii(' ') 794 || grid->attrs[off] != clear_attr 795 || rdb_flags & kOptRdbFlagNodelta) { 796 grid->chars[off] = schar_from_ascii(' '); 797 grid->attrs[off] = clear_attr; 798 if (clear_dirty_start == -1) { 799 clear_dirty_start = col; 800 } 801 clear_end = col + 1; 802 } 803 if (!(flags & SLF_RIGHTLEFT)) { 804 grid->vcols[off] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol; 805 } 806 } 807 808 if ((flags & SLF_RIGHTLEFT) && start_dirty != -1 && clear_dirty_start != -1) { 809 if (grid->throttled || clear_dirty_start >= start_dirty - 5) { 810 // cannot draw now or too small to be worth a separate "clear" event 811 start_dirty = clear_dirty_start; 812 } else { 813 ui_line(grid, row, invalid_row, coloff + clear_dirty_start, coloff + clear_dirty_start, 814 coloff + clear_end, clear_attr, flags & SLF_WRAP); 815 } 816 clear_end = end_dirty; 817 } else { 818 if (start_dirty == -1) { // clear only 819 start_dirty = clear_dirty_start; 820 end_dirty = clear_dirty_start; 821 } else if (clear_end < end_dirty) { // put only 822 clear_end = end_dirty; 823 } else { 824 end_dirty = endcol; 825 } 826 } 827 828 if (clear_end > start_dirty) { 829 if (!grid->throttled) { 830 ui_line(grid, row, invalid_row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end, 831 clear_attr, flags & SLF_WRAP); 832 } else if (grid->dirty_col) { 833 // TODO(bfredl): really get rid of the extra pseudo terminal in message.c 834 // by using a linebuf_char copy for "throttled message line" 835 if (clear_end > grid->dirty_col[row]) { 836 grid->dirty_col[row] = clear_end; 837 } 838 } 839 } 840 } 841 842 void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) 843 { 844 int new_row; 845 ScreenGrid ngrid = *grid; 846 assert(rows >= 0 && columns >= 0); 847 size_t ncells = (size_t)rows * (size_t)columns; 848 ngrid.chars = xmalloc(ncells * sizeof(schar_T)); 849 ngrid.attrs = xmalloc(ncells * sizeof(sattr_T)); 850 ngrid.vcols = xmalloc(ncells * sizeof(colnr_T)); 851 memset(ngrid.vcols, -1, ncells * sizeof(colnr_T)); 852 ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset)); 853 854 ngrid.rows = rows; 855 ngrid.cols = columns; 856 857 for (new_row = 0; new_row < ngrid.rows; new_row++) { 858 ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols; 859 860 grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid); 861 862 if (copy) { 863 // If the screen is not going to be cleared, copy as much as 864 // possible from the old screen to the new one and clear the rest 865 // (used when resizing the window at the "--more--" prompt or when 866 // executing an external command, for the GUI). 867 if (new_row < grid->rows && grid->chars != NULL) { 868 int len = MIN(grid->cols, ngrid.cols); 869 memmove(ngrid.chars + ngrid.line_offset[new_row], 870 grid->chars + grid->line_offset[new_row], 871 (size_t)len * sizeof(schar_T)); 872 memmove(ngrid.attrs + ngrid.line_offset[new_row], 873 grid->attrs + grid->line_offset[new_row], 874 (size_t)len * sizeof(sattr_T)); 875 memmove(ngrid.vcols + ngrid.line_offset[new_row], 876 grid->vcols + grid->line_offset[new_row], 877 (size_t)len * sizeof(colnr_T)); 878 } 879 } 880 } 881 grid_free(grid); 882 *grid = ngrid; 883 884 // Share a single scratch buffer for all grids, by 885 // ensuring it is as wide as the widest grid. 886 if (linebuf_size < (size_t)columns) { 887 xfree(linebuf_char); 888 xfree(linebuf_attr); 889 xfree(linebuf_vcol); 890 xfree(linebuf_scratch); 891 linebuf_char = xmalloc((size_t)columns * sizeof(schar_T)); 892 linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T)); 893 linebuf_vcol = xmalloc((size_t)columns * sizeof(colnr_T)); 894 linebuf_scratch = xmalloc((size_t)columns * sizeof(sscratch_T)); 895 linebuf_size = (size_t)columns; 896 } 897 } 898 899 void grid_free(ScreenGrid *grid) 900 { 901 xfree(grid->chars); 902 xfree(grid->attrs); 903 xfree(grid->vcols); 904 xfree(grid->line_offset); 905 906 grid->chars = NULL; 907 grid->attrs = NULL; 908 grid->vcols = NULL; 909 grid->line_offset = NULL; 910 } 911 912 #ifdef EXITFREE 913 /// Doesn't allow reinit, so must only be called by free_all_mem! 914 void grid_free_all_mem(void) 915 { 916 grid_free(&default_grid); 917 grid_free(&msg_grid); 918 XFREE_CLEAR(msg_grid.dirty_col); 919 xfree(linebuf_char); 920 xfree(linebuf_attr); 921 xfree(linebuf_vcol); 922 xfree(linebuf_scratch); 923 set_destroy(glyph, &glyph_cache); 924 } 925 #endif 926 927 /// (Re)allocates a window grid if size changed while in ext_multigrid mode. 928 /// Updates size, offsets and handle for the grid regardless. 929 /// 930 /// If "doclear" is true, don't try to copy from the old grid rather clear the 931 /// resized grid. 932 void win_grid_alloc(win_T *wp) 933 { 934 GridView *grid = &wp->w_grid; 935 ScreenGrid *grid_allocated = &wp->w_grid_alloc; 936 937 int total_rows = wp->w_height_outer; 938 int total_cols = wp->w_width_outer; 939 940 bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; 941 bool has_allocation = (grid_allocated->chars != NULL); 942 943 if (wp->w_view_height > wp->w_lines_size) { 944 wp->w_lines_valid = 0; 945 xfree(wp->w_lines); 946 wp->w_lines = xcalloc((size_t)wp->w_view_height + 1, sizeof(wline_T)); 947 wp->w_lines_size = wp->w_view_height; 948 } 949 950 bool was_resized = false; 951 if (want_allocation && (!has_allocation 952 || grid_allocated->rows != total_rows 953 || grid_allocated->cols != total_cols)) { 954 grid_alloc(grid_allocated, total_rows, total_cols, 955 wp->w_grid_alloc.valid, false); 956 grid_allocated->valid = true; 957 if (wp->w_floating && wp->w_config.border) { 958 wp->w_redr_border = true; 959 } 960 was_resized = true; 961 } else if (!want_allocation && has_allocation) { 962 // Single grid mode, all rendering will be redirected to default_grid. 963 // Only keep track of the size and offset of the window. 964 grid_free(grid_allocated); 965 grid_allocated->valid = false; 966 was_resized = true; 967 } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) { 968 grid_invalidate(grid_allocated); 969 grid_allocated->valid = true; 970 } 971 972 if (want_allocation) { 973 grid->target = grid_allocated; 974 grid->row_offset = wp->w_winrow_off; 975 grid->col_offset = wp->w_wincol_off; 976 } else { 977 grid->target = &default_grid; 978 grid->row_offset = wp->w_winrow + wp->w_winrow_off; 979 grid->col_offset = wp->w_wincol + wp->w_wincol_off; 980 } 981 982 // send grid resize event if: 983 // - a grid was just resized 984 // - screen_resize was called and all grid sizes must be sent 985 // - the UI wants multigrid event (necessary) 986 if ((resizing_screen || was_resized) && want_allocation) { 987 ui_call_grid_resize(grid_allocated->handle, 988 grid_allocated->cols, grid_allocated->rows); 989 ui_check_cursor_grid(grid_allocated->handle); 990 } 991 } 992 993 /// assign a handle to the grid. The grid need not be allocated. 994 void grid_assign_handle(ScreenGrid *grid) 995 { 996 static int last_grid_handle = DEFAULT_GRID_HANDLE; 997 998 // only assign a grid handle if not already 999 if (grid->handle == 0) { 1000 grid->handle = ++last_grid_handle; 1001 } 1002 } 1003 1004 /// insert lines on the screen and move the existing lines down 1005 /// 'line_count' is the number of lines to be inserted. 1006 /// 'end' is the line after the scrolled part. Normally it is Rows. 1007 /// 'col' is the column from with we start inserting. 1008 // 1009 /// 'row', 'col' and 'end' are relative to the start of the region. 1010 void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) 1011 { 1012 int j; 1013 unsigned temp; 1014 1015 if (line_count <= 0) { 1016 return; 1017 } 1018 1019 // Shift line_offset[] line_count down to reflect the inserted lines. 1020 // Clear the inserted lines. 1021 for (int i = 0; i < line_count; i++) { 1022 if (width != grid->cols) { 1023 // need to copy part of a line 1024 j = end - 1 - i; 1025 while ((j -= line_count) >= row) { 1026 linecopy(grid, j + line_count, j, col, width); 1027 } 1028 j += line_count; 1029 grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); 1030 } else { 1031 j = end - 1 - i; 1032 temp = (unsigned)grid->line_offset[j]; 1033 while ((j -= line_count) >= row) { 1034 grid->line_offset[j + line_count] = grid->line_offset[j]; 1035 } 1036 grid->line_offset[j + line_count] = temp; 1037 grid_clear_line(grid, temp, grid->cols, false); 1038 } 1039 } 1040 1041 if (!grid->throttled) { 1042 ui_call_grid_scroll(grid->handle, row, end, col, col + width, -line_count, 0); 1043 } 1044 } 1045 1046 /// delete lines on the screen and move lines up. 1047 /// 'end' is the line after the scrolled part. Normally it is Rows. 1048 /// When scrolling region used 'off' is the offset from the top for the region. 1049 /// 'row' and 'end' are relative to the start of the region. 1050 void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width) 1051 { 1052 int j; 1053 unsigned temp; 1054 1055 if (line_count <= 0) { 1056 return; 1057 } 1058 1059 // Now shift line_offset[] line_count up to reflect the deleted lines. 1060 // Clear the inserted lines. 1061 for (int i = 0; i < line_count; i++) { 1062 if (width != grid->cols) { 1063 // need to copy part of a line 1064 j = row + i; 1065 while ((j += line_count) <= end - 1) { 1066 linecopy(grid, j - line_count, j, col, width); 1067 } 1068 j -= line_count; 1069 grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false); 1070 } else { 1071 // whole width, moving the line pointers is faster 1072 j = row + i; 1073 temp = (unsigned)grid->line_offset[j]; 1074 while ((j += line_count) <= end - 1) { 1075 grid->line_offset[j - line_count] = grid->line_offset[j]; 1076 } 1077 grid->line_offset[j - line_count] = temp; 1078 grid_clear_line(grid, temp, grid->cols, false); 1079 } 1080 } 1081 1082 if (!grid->throttled) { 1083 ui_call_grid_scroll(grid->handle, row, end, col, col + width, line_count, 0); 1084 } 1085 } 1086 1087 /// @param overflow Number of cells to skip. 1088 static void grid_draw_bordertext(VirtText vt, int col, int winbl, const int *hl_attr, 1089 BorderTextType bt, int overflow) 1090 { 1091 int default_attr = hl_attr[bt == kBorderTextTitle ? HLF_BTITLE : HLF_BFOOTER]; 1092 if (overflow > 0) { 1093 grid_line_puts(1, "<", -1, hl_apply_winblend(winbl, default_attr)); 1094 col += 1; 1095 overflow += 1; 1096 } 1097 1098 for (size_t i = 0; i < kv_size(vt);) { 1099 int attr = -1; 1100 char *text = next_virt_text_chunk(vt, &i, &attr); 1101 if (text == NULL) { 1102 break; 1103 } 1104 if (attr == -1) { // No highlight specified. 1105 attr = default_attr; 1106 } 1107 // Skip characters from the beginning when title overflows available width. 1108 if (overflow > 0) { 1109 int cells = (int)mb_string2cells(text); 1110 // Skip entire chunk if overflow is larger than chunk width. 1111 if (overflow >= cells) { 1112 overflow -= cells; 1113 continue; 1114 } 1115 // Skip partial characters within the chunk. 1116 char *p = text; 1117 while (*p && overflow > 0) { 1118 overflow -= utf_ptr2cells(p); 1119 p += utfc_ptr2len(p); 1120 } 1121 text = p; 1122 } 1123 attr = hl_apply_winblend(winbl, attr); 1124 col += grid_line_puts(col, text, -1, attr); 1125 } 1126 } 1127 1128 static int get_bordertext_col(int total_col, int text_width, AlignTextPos align) 1129 { 1130 switch (align) { 1131 case kAlignLeft: 1132 return 1; 1133 case kAlignCenter: 1134 return MAX((total_col - text_width) / 2 + 1, 1); 1135 case kAlignRight: 1136 return MAX(total_col - text_width + 1, 1); 1137 } 1138 UNREACHABLE; 1139 } 1140 1141 /// draw border on floating window grid 1142 void grid_draw_border(ScreenGrid *grid, WinConfig *config, int *adj, int winbl, int *hl_attr) 1143 { 1144 int *attrs = config->border_attr; 1145 int default_adj[4] = { 1, 1, 1, 1 }; 1146 if (adj == NULL) { 1147 adj = default_adj; 1148 } 1149 schar_T chars[8]; 1150 if (!hl_attr) { 1151 hl_attr = hl_attr_active; 1152 } 1153 1154 for (int i = 0; i < 8; i++) { 1155 chars[i] = schar_from_str(config->border_chars[i]); 1156 } 1157 1158 int irow = grid->rows - adj[0] - adj[2]; 1159 int icol = grid->cols - adj[1] - adj[3]; 1160 1161 if (adj[0]) { 1162 screengrid_line_start(grid, 0, 0); 1163 if (adj[3]) { 1164 grid_line_put_schar(0, chars[0], attrs[0]); 1165 } 1166 1167 for (int i = 0; i < icol; i++) { 1168 grid_line_put_schar(i + adj[3], chars[1], attrs[1]); 1169 } 1170 1171 if (config->title) { 1172 int title_col = get_bordertext_col(icol, config->title_width, config->title_pos); 1173 grid_draw_bordertext(config->title_chunks, title_col, winbl, hl_attr, kBorderTextTitle, 1174 config->title_width - icol); 1175 } 1176 if (adj[1]) { 1177 grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); 1178 } 1179 grid_line_flush(); 1180 } 1181 1182 for (int i = 0; i < irow; i++) { 1183 if (adj[3]) { 1184 screengrid_line_start(grid, i + adj[0], 0); 1185 grid_line_put_schar(0, chars[7], attrs[7]); 1186 grid_line_flush(); 1187 } 1188 if (adj[1]) { 1189 int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3; 1190 screengrid_line_start(grid, i + adj[0], 0); 1191 grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]); 1192 grid_line_flush(); 1193 } 1194 } 1195 1196 if (adj[2]) { 1197 screengrid_line_start(grid, irow + adj[0], 0); 1198 if (adj[3]) { 1199 grid_line_put_schar(0, chars[6], attrs[6]); 1200 } 1201 1202 for (int i = 0; i < icol; i++) { 1203 int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5; 1204 grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]); 1205 } 1206 1207 if (config->footer) { 1208 int footer_col = get_bordertext_col(icol, config->footer_width, config->footer_pos); 1209 grid_draw_bordertext(config->footer_chunks, footer_col, winbl, hl_attr, kBorderTextFooter, 1210 config->footer_width - icol); 1211 } 1212 if (adj[1]) { 1213 grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); 1214 } 1215 grid_line_flush(); 1216 } 1217 } 1218 1219 static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) 1220 { 1221 unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col); 1222 unsigned off_from = (unsigned)(grid->line_offset[from] + (size_t)col); 1223 1224 memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); 1225 memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); 1226 memmove(grid->vcols + off_to, grid->vcols + off_from, (size_t)width * sizeof(colnr_T)); 1227 } 1228 1229 win_T *get_win_by_grid_handle(handle_T handle) 1230 { 1231 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 1232 if (wp->w_grid_alloc.handle == handle) { 1233 return wp; 1234 } 1235 } 1236 return NULL; 1237 } 1238 1239 /// Put a unicode character in a screen cell. 1240 schar_T schar_from_char(int c) 1241 { 1242 schar_T sc = 0; 1243 if (c >= 0x200000) { 1244 // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences 1245 c = 0xFFFD; 1246 } 1247 utf_char2bytes(c, (char *)&sc); 1248 return sc; 1249 }