diff options
author | bfredl <bjorn.linse@gmail.com> | 2022-05-08 13:04:18 +0200 |
---|---|---|
committer | bfredl <bjorn.linse@gmail.com> | 2022-05-08 13:22:09 +0200 |
commit | df41d884a7b788bb38060e732f1a7abb08de7b1b (patch) | |
tree | c7687eef00b35bdc02ba37437a70b7c37dda4026 | |
parent | 0d3f17a6c317b26cdc319b48e25e1574f3a0e9fd (diff) | |
download | rneovim-df41d884a7b788bb38060e732f1a7abb08de7b1b.tar.gz rneovim-df41d884a7b788bb38060e732f1a7abb08de7b1b.tar.bz2 rneovim-df41d884a7b788bb38060e732f1a7abb08de7b1b.zip |
refactor(grid): move out grid_* functions from screen.c
Also normalize some types. use "size_t" for unsigned array offsets.
Fix -Wconversion issues missed as screen.c is missing this check.
-rw-r--r-- | src/nvim/ex_getln.c | 2 | ||||
-rw-r--r-- | src/nvim/grid.c | 708 | ||||
-rw-r--r-- | src/nvim/grid.h | 57 | ||||
-rw-r--r-- | src/nvim/grid_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/main.c | 1 | ||||
-rw-r--r-- | src/nvim/memory.c | 2 | ||||
-rw-r--r-- | src/nvim/screen.c | 738 | ||||
-rw-r--r-- | src/nvim/screen.h | 13 | ||||
-rw-r--r-- | src/nvim/ui_compositor.c | 2 | ||||
-rw-r--r-- | src/nvim/window.c | 4 |
10 files changed, 777 insertions, 752 deletions
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 1932c02cf2..d4cea15d20 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3664,7 +3664,7 @@ static void cursorcmd(void) static void cmd_cursor_goto(int row, int col) { ScreenGrid *grid = &msg_grid_adj; - screen_adjust_grid(&grid, &row, &col); + grid_adjust(&grid, &row, &col); ui_grid_cursor_goto(grid->handle, row, col); } diff --git a/src/nvim/grid.c b/src/nvim/grid.c new file mode 100644 index 0000000000..b150d3819e --- /dev/null +++ b/src/nvim/grid.c @@ -0,0 +1,708 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/arabic.h" +#include "nvim/highlight.h" +#include "nvim/vim.h" +#include "nvim/grid.h" +#include "nvim/ui.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "grid.c.generated.h" +#endif + +// temporary buffer for rendering a single screenline, so it can be +// compared with previous contents to calculate smallest delta. +// Per-cell attributes +static size_t linebuf_size = 0; + + +/// Determine if dedicated window grid should be used or the default_grid +/// +/// If UI did not request multigrid support, draw all windows on the +/// default_grid. +/// +/// NB: this function can only been used with window grids in a context where +/// win_grid_alloc already has been called! +/// +/// If the default_grid is used, adjust window relative positions to global +/// screen positions. +void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off) +{ + if ((*grid)->target) { + *row_off += (*grid)->row_offset; + *col_off += (*grid)->col_offset; + *grid = (*grid)->target; + } +} + +/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. +int schar_from_cc(char_u *p, int c, int u8cc[MAX_MCO]) +{ + int len = utf_char2bytes(c, p); + for (int i = 0; i < MAX_MCO; i++) { + if (u8cc[i] == 0) { + break; + } + len += utf_char2bytes(u8cc[i], p + len); + } + p[len] = 0; + return len; +} + +/// clear a line in the grid starting at "off" until "width" characters +/// are cleared. +void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid) +{ + for (int col = 0; col < width; col++) { + schar_from_ascii(grid->chars[off + (size_t)col], ' '); + } + int fill = valid ? 0 : -1; + (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); +} + +void grid_invalidate(ScreenGrid *grid) +{ + (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)(grid->Rows * grid->Columns)); +} + +bool grid_invalid_row(ScreenGrid *grid, int row) +{ + return grid->attrs[grid->line_offset[row]] < 0; +} + +static int line_off2cells(schar_T *line, size_t off, size_t max_off) +{ + return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1; +} + +/// Return number of display cells for char at grid->chars[off]. +/// We make sure that the offset used is less than "max_off". +static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off) +{ + return line_off2cells(grid->chars, off, max_off); +} + +/// Return true if the character at "row"/"col" on the screen is the left side +/// of a double-width character. +/// +/// Caller must make sure "row" and "col" are not invalid! +bool grid_lefthalve(ScreenGrid *grid, int row, int col) +{ + grid_adjust(&grid, &row, &col); + + return grid_off2cells(grid, grid->line_offset[row] + (size_t)col, + grid->line_offset[row] + (size_t)grid->Columns) > 1; +} + +/// Correct a position on the screen, if it's the right half of a double-wide +/// char move it to the left half. Returns the corrected column. +int grid_fix_col(ScreenGrid *grid, int col, int row) +{ + int coloff = 0; + grid_adjust(&grid, &row, &coloff); + + col += coloff; + if (grid->chars != NULL && col > 0 + && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) { + return col - 1 - coloff; + } + return col - coloff; +} + +/// output a single character directly to the grid +void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) +{ + char_u buf[MB_MAXBYTES + 1]; + + buf[utf_char2bytes(c, buf)] = NUL; + grid_puts(grid, buf, row, col, attr); +} + +/// get a single character directly from grid.chars into "bytes[]". +/// Also return its attribute in *attrp; +void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp) +{ + size_t off; + + grid_adjust(&grid, &row, &col); + + // safety check + if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) { + off = grid->line_offset[row] + (size_t)col; + *attrp = grid->attrs[off]; + schar_copy(bytes, grid->chars[off]); + } +} + + +/// put string '*text' on the window grid at position 'row' and 'col', with +/// attributes 'attr', and update chars[] and attrs[]. +/// Note: only outputs within one row, message is truncated at grid boundary! +/// Note: if grid, row and/or col is invalid, nothing is done. +void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) +{ + grid_puts_len(grid, text, -1, row, col, attr); +} + +static ScreenGrid *put_dirty_grid = NULL; +static int put_dirty_row = -1; +static int put_dirty_first = INT_MAX; +static int put_dirty_last = 0; + +/// Start a group of grid_puts_len calls that builds a single grid line. +/// +/// Must be matched with a grid_puts_line_flush call before moving to +/// another line. +void grid_puts_line_start(ScreenGrid *grid, int row) +{ + int col = 0; // unused + grid_adjust(&grid, &row, &col); + assert(put_dirty_row == -1); + put_dirty_row = row; + put_dirty_grid = grid; +} + +void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +{ + assert(put_dirty_row == row); + size_t off = grid->line_offset[row] + (size_t)col; + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + schar_copy(grid->chars[off], schar); + grid->attrs[off] = attr; + + put_dirty_first = MIN(put_dirty_first, col); + // TODO(bfredl): Y U NO DOUBLEWIDTH? + put_dirty_last = MAX(put_dirty_last, col + 1); + } +} + +/// like grid_puts(), but output "text[len]". When "len" is -1 output up to +/// a NUL. +void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col, int attr) +{ + size_t off; + char_u *ptr = text; + int len = textlen; + int c; + size_t max_off; + int mbyte_blen = 1; + int mbyte_cells = 1; + int u8c = 0; + int u8cc[MAX_MCO]; + bool clear_next_cell = false; + int prev_c = 0; // previous Arabic character + int pc, nc, nc1; + int pcc[MAX_MCO]; + int need_redraw; + bool do_flush = false; + + grid_adjust(&grid, &row, &col); + + // Safety check. The check for negative row and column is to fix issue + // vim/vim#4102. TODO(neovim): find out why row/col could be negative. + if (grid->chars == NULL + || row >= grid->Rows || row < 0 + || col >= grid->Columns || col < 0) { + return; + } + + if (put_dirty_row == -1) { + grid_puts_line_start(grid, row); + do_flush = true; + } else { + if (grid != put_dirty_grid || row != put_dirty_row) { + abort(); + } + } + off = grid->line_offset[row] + (size_t)col; + + // When drawing over the right half of a double-wide char clear out the + // left half. Only needed in a terminal. + if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { + // redraw the previous cell, make it empty + put_dirty_first = -1; + put_dirty_last = MAX(put_dirty_last, 1); + } + + max_off = grid->line_offset[row] + (size_t)grid->Columns; + while (col < grid->Columns + && (len < 0 || (int)(ptr - text) < len) + && *ptr != NUL) { + c = *ptr; + // check if this is the first byte of a multibyte + if (len > 0) { + mbyte_blen = utfc_ptr2len_len(ptr, (int)((text + len) - ptr)); + } else { + mbyte_blen = utfc_ptr2len((char *)ptr); + } + if (len >= 0) { + u8c = utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)); + } else { + u8c = utfc_ptr2char(ptr, u8cc); + } + mbyte_cells = utf_char2cells(u8c); + if (p_arshape && !p_tbidi && arabic_char(u8c)) { + // Do Arabic shaping. + if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) { + // Past end of string to be displayed. + nc = NUL; + nc1 = NUL; + } else { + nc = utfc_ptr2char_len(ptr + mbyte_blen, pcc, + (int)((text + len) - ptr - mbyte_blen)); + nc1 = pcc[0]; + } + pc = prev_c; + prev_c = u8c; + u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc); + } else { + prev_c = u8c; + } + if (col + mbyte_cells > grid->Columns) { + // Only 1 cell left, but character requires 2 cells: + // display a '>' in the last column to avoid wrapping. */ + c = '>'; + u8c = '>'; + u8cc[0] = 0; + mbyte_cells = 1; + } + + schar_T buf; + schar_from_cc(buf, u8c, u8cc); + + + need_redraw = schar_cmp(grid->chars[off], buf) + || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) + || grid->attrs[off] != attr + || exmode_active; + + if (need_redraw) { + // When at the end of the text and overwriting a two-cell + // character with a one-cell character, need to clear the next + // cell. Also when overwriting the left half of a two-cell char + // with the right half of a two-cell char. Do this only once + // (utf8_off2cells() may return 2 on the right half). + if (clear_next_cell) { + clear_next_cell = false; + } else if ((len < 0 ? ptr[mbyte_blen] == NUL : ptr + mbyte_blen >= text + len) + && ((mbyte_cells == 1 + && grid_off2cells(grid, off, max_off) > 1) + || (mbyte_cells == 2 + && grid_off2cells(grid, off, max_off) == 1 + && grid_off2cells(grid, off + 1, max_off) > 1))) { + clear_next_cell = true; + } + + // When at the start of the text and overwriting the right half of a + // two-cell character in the same grid, truncate that into a '>'. + if (ptr == text && col > 0 && grid->chars[off][0] == 0) { + grid->chars[off - 1][0] = '>'; + grid->chars[off - 1][1] = 0; + } + + schar_copy(grid->chars[off], buf); + grid->attrs[off] = attr; + if (mbyte_cells == 2) { + grid->chars[off + 1][0] = 0; + grid->attrs[off + 1] = attr; + } + put_dirty_first = MIN(put_dirty_first, col); + put_dirty_last = MAX(put_dirty_last, col + mbyte_cells); + } + + off += (size_t)mbyte_cells; + col += mbyte_cells; + ptr += mbyte_blen; + if (clear_next_cell) { + // This only happens at the end, display one space next. + ptr = (char_u *)" "; + len = -1; + } + } + + if (do_flush) { + grid_puts_line_flush(true); + } +} + +/// End a group of grid_puts_len calls and send the screen buffer to the UI +/// layer. +/// +/// @param set_cursor Move the visible cursor to the end of the changed region. +/// This is a workaround for not yet refactored code paths +/// and shouldn't be used in new code. +void grid_puts_line_flush(bool set_cursor) +{ + assert(put_dirty_row != -1); + if (put_dirty_first < put_dirty_last) { + if (set_cursor) { + ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, + MIN(put_dirty_last, put_dirty_grid->Columns - 1)); + } + if (!put_dirty_grid->throttled) { + ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, + put_dirty_last, 0, false); + } else if (put_dirty_grid->dirty_col) { + if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { + put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; + } + } + put_dirty_first = INT_MAX; + put_dirty_last = 0; + } + put_dirty_row = -1; + put_dirty_grid = NULL; +} + +/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" +/// to "end_col" (exclusive) with character "c1" in first column followed by +/// "c2" in the other columns. Use attributes "attr". +void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1, + int c2, int attr) +{ + schar_T sc; + + int row_off = 0, col_off = 0; + grid_adjust(&grid, &row_off, &col_off); + start_row += row_off; + end_row += row_off; + start_col += col_off; + end_col += col_off; + + // safety check + if (end_row > grid->Rows) { + end_row = grid->Rows; + } + if (end_col > grid->Columns) { + end_col = grid->Columns; + } + + // nothing to do + if (start_row >= end_row || start_col >= end_col) { + return; + } + + for (int row = start_row; row < end_row; row++) { + // When drawing over the right half of a double-wide char clear + // out the left half. When drawing over the left half of a + // double wide-char clear out the right half. Only needed in a + // terminal. + if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { + grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); + } + if (end_col < grid->Columns + && grid_fix_col(grid, end_col, row) != end_col) { + grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); + } + + // if grid was resized (in ext_multigrid mode), the UI has no redraw updates + // for the newly resized grid. It is better mark everything as dirty and + // send all the updates. + int dirty_first = INT_MAX; + int dirty_last = 0; + + int col = start_col; + schar_from_char(sc, c1); + size_t lineoff = grid->line_offset[row]; + for (col = start_col; col < end_col; col++) { + size_t off = lineoff + (size_t)col; + if (schar_cmp(grid->chars[off], sc) + || grid->attrs[off] != attr) { + schar_copy(grid->chars[off], sc); + grid->attrs[off] = attr; + if (dirty_first == INT_MAX) { + dirty_first = col; + } + dirty_last = col + 1; + } + if (col == start_col) { + schar_from_char(sc, c2); + } + } + if (dirty_last > dirty_first) { + // TODO(bfredl): support a cleared suffix even with a batched line? + if (put_dirty_row == row) { + put_dirty_first = MIN(put_dirty_first, dirty_first); + put_dirty_last = MAX(put_dirty_last, dirty_last); + } else if (grid->throttled) { + // Note: assumes msg_grid is the only throttled grid + assert(grid == &msg_grid); + int dirty = 0; + if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') { + dirty = dirty_last; + } else if (c1 != ' ') { + dirty = dirty_first + 1; + } + if (grid->dirty_col && dirty > grid->dirty_col[row]) { + grid->dirty_col[row] = dirty; + } + } else { + int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); + ui_line(grid, row, dirty_first, last, dirty_last, attr, false); + } + } + + if (end_col == grid->Columns) { + grid->line_wraps[row] = false; + } + } +} + +/// Check whether the given character needs redrawing: +/// - the (first byte of the) character is different +/// - the attributes are different +/// - the character is multi-byte and the next byte is different +/// - the character is two cells wide and the second cell differs. +static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols) +{ + return (cols > 0 + && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to]) + || linebuf_attr[off_from] != grid->attrs[off_to] + || (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1 + && schar_cmp(linebuf_char[off_from + 1], + grid->chars[off_to + 1]))) + || rdb_flags & RDB_NODELTA)); +} + +/// Move one buffered line to the window grid, but only the characters that +/// have actually changed. Handle insert/delete character. +/// "coloff" gives the first column on the grid for this line. +/// "endcol" gives the columns where valid characters are. +/// "clear_width" is the width of the window. It's > 0 if the rest of the line +/// needs to be cleared, negative otherwise. +/// "rlflag" is TRUE in a rightleft window: +/// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" +/// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" +/// If "wrap" is true, then hint to the UI that "row" contains a line +/// which has wrapped into the next row. +void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, + int rlflag, win_T *wp, int bg_attr, bool wrap) +{ + size_t max_off_from; + size_t max_off_to; + int col = 0; + bool redraw_this; // Does character need redraw? + bool redraw_next; // redraw_this for next character + bool clear_next = false; + int char_cells; // 1: normal char + // 2: occupies two display cells + int start_dirty = -1, end_dirty = 0; + + // TODO(bfredl): check all callsites and eliminate + // Check for illegal row and col, just in case + if (row >= grid->Rows) { + row = grid->Rows - 1; + } + if (endcol > grid->Columns) { + endcol = grid->Columns; + } + + grid_adjust(&grid, &row, &coloff); + + // Safety check. Avoids clang warnings down the call stack. + if (grid->chars == NULL || row >= grid->Rows || coloff >= grid->Columns) { + DLOG("invalid state, skipped"); + return; + } + + size_t off_from = 0; + size_t off_to = grid->line_offset[row] + (size_t)coloff; + max_off_from = linebuf_size; + max_off_to = grid->line_offset[row] + (size_t)grid->Columns; + + if (rlflag) { + // Clear rest first, because it's left of the text. + if (clear_width > 0) { + while (col <= endcol && grid->chars[off_to][0] == ' ' + && grid->chars[off_to][1] == NUL + && grid->attrs[off_to] == bg_attr) { + off_to++; + col++; + } + if (col <= endcol) { + grid_fill(grid, row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', bg_attr); + } + } + col = endcol + 1; + off_to = grid->line_offset[row] + (size_t)col + (size_t)coloff; + off_from += (size_t)col; + endcol = (clear_width > 0 ? clear_width : -clear_width); + } + + if (bg_attr) { + for (int c = col; c < endcol; c++) { + linebuf_attr[off_from + (size_t)c] = + hl_combine_attr(bg_attr, linebuf_attr[off_from + (size_t)c]); + } + } + + redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col); + + while (col < endcol) { + char_cells = 1; + if (col + 1 < endcol) { + char_cells = line_off2cells(linebuf_char, off_from, max_off_from); + } + redraw_this = redraw_next; + redraw_next = grid_char_needs_redraw(grid, off_from + (size_t)char_cells, + off_to + (size_t)char_cells, + endcol - col - char_cells); + + if (redraw_this) { + if (start_dirty == -1) { + start_dirty = col; + } + end_dirty = col + char_cells; + // When writing a single-width character over a double-width + // character and at the end of the redrawn text, need to clear out + // the right half of the old character. + // Also required when writing the right half of a double-width + // char over the left half of an existing one + if (col + char_cells == endcol + && ((char_cells == 1 + && grid_off2cells(grid, off_to, max_off_to) > 1) + || (char_cells == 2 + && grid_off2cells(grid, off_to, max_off_to) == 1 + && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) { + clear_next = true; + } + + schar_copy(grid->chars[off_to], linebuf_char[off_from]); + if (char_cells == 2) { + schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]); + } + + grid->attrs[off_to] = linebuf_attr[off_from]; + // For simplicity set the attributes of second half of a + // double-wide character equal to the first half. + if (char_cells == 2) { + grid->attrs[off_to + 1] = linebuf_attr[off_from]; + } + } + + off_to += (size_t)char_cells; + off_from += (size_t)char_cells; + col += char_cells; + } + + if (clear_next) { + // Clear the second half of a double-wide character of which the left + // half was overwritten with a single-wide character. + schar_from_ascii(grid->chars[off_to], ' '); + end_dirty++; + } + + int clear_end = -1; + if (clear_width > 0 && !rlflag) { + // blank out the rest of the line + // TODO(bfredl): we could cache winline widths + while (col < clear_width) { + if (grid->chars[off_to][0] != ' ' + || grid->chars[off_to][1] != NUL + || grid->attrs[off_to] != bg_attr) { + grid->chars[off_to][0] = ' '; + grid->chars[off_to][1] = NUL; + grid->attrs[off_to] = bg_attr; + if (start_dirty == -1) { + start_dirty = col; + end_dirty = col; + } else if (clear_end == -1) { + end_dirty = endcol; + } + clear_end = col + 1; + } + col++; + off_to++; + } + } + + if (clear_width > 0 || wp->w_width != grid->Columns) { + // If we cleared after the end of the line, it did not wrap. + // For vsplit, line wrapping is not possible. + grid->line_wraps[row] = false; + } + + if (clear_end < end_dirty) { + clear_end = end_dirty; + } + if (start_dirty == -1) { + start_dirty = end_dirty; + } + if (clear_end > start_dirty) { + ui_line(grid, row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end, + bg_attr, wrap); + } +} + +void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) +{ + int new_row; + ScreenGrid new = *grid; + assert(rows >= 0 && columns >= 0); + size_t ncells = (size_t)rows * (size_t)columns; + new.chars = xmalloc(ncells * sizeof(schar_T)); + new.attrs = xmalloc(ncells * sizeof(sattr_T)); + new.line_offset = xmalloc((size_t)rows * sizeof(*new.line_offset)); + new.line_wraps = xmalloc((size_t)rows * sizeof(*new.line_wraps)); + + new.Rows = rows; + new.Columns = columns; + + for (new_row = 0; new_row < new.Rows; new_row++) { + new.line_offset[new_row] = (size_t)new_row * (size_t)new.Columns; + new.line_wraps[new_row] = false; + + grid_clear_line(&new, new.line_offset[new_row], columns, valid); + + if (copy) { + // If the screen is not going to be cleared, copy as much as + // possible from the old screen to the new one and clear the rest + // (used when resizing the window at the "--more--" prompt or when + // executing an external command, for the GUI). + if (new_row < grid->Rows && grid->chars != NULL) { + int len = MIN(grid->Columns, new.Columns); + memmove(new.chars + new.line_offset[new_row], + grid->chars + grid->line_offset[new_row], + (size_t)len * sizeof(schar_T)); + memmove(new.attrs + new.line_offset[new_row], + grid->attrs + grid->line_offset[new_row], + (size_t)len * sizeof(sattr_T)); + } + } + } + grid_free(grid); + *grid = new; + + // Share a single scratch buffer for all grids, by + // ensuring it is as wide as the widest grid. + if (linebuf_size < (size_t)columns) { + xfree(linebuf_char); + xfree(linebuf_attr); + linebuf_char = xmalloc((size_t)columns * sizeof(schar_T)); + linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T)); + linebuf_size = (size_t)columns; + } +} + +void grid_free(ScreenGrid *grid) +{ + xfree(grid->chars); + xfree(grid->attrs); + xfree(grid->line_offset); + xfree(grid->line_wraps); + + grid->chars = NULL; + grid->attrs = NULL; + grid->line_offset = NULL; + grid->line_wraps = NULL; +} + +/// Doesn't allow reinit, so must only be called by free_all_mem! +void grid_free_all_mem(void) +{ + grid_free(&default_grid); + xfree(linebuf_char); + xfree(linebuf_attr); +} + diff --git a/src/nvim/grid.h b/src/nvim/grid.h new file mode 100644 index 0000000000..12a5bf95bb --- /dev/null +++ b/src/nvim/grid.h @@ -0,0 +1,57 @@ +#ifndef NVIM_GRID_H +#define NVIM_GRID_H + +#include <stdbool.h> + +#include "nvim/ascii.h" +#include "nvim/grid_defs.h" +#include "nvim/buffer_defs.h" + +/// By default, all windows are drawn on a single rectangular grid, represented by +/// this ScreenGrid instance. In multigrid mode each window will have its own +/// grid, then this is only used for global screen elements that hasn't been +/// externalized. +/// +/// Note: before the screen is initialized and when out of memory these can be +/// NULL. +EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT); + +#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid + +EXTERN schar_T *linebuf_char INIT(= NULL); +EXTERN sattr_T *linebuf_attr INIT(= NULL); + +// Low-level functions to manipulate individual character cells on the +// screen grid. + +/// Put a ASCII character in a screen cell. +static inline void schar_from_ascii(char_u *p, const char c) +{ + p[0] = (char_u)c; + p[1] = 0; +} + +/// Put a unicode character in a screen cell. +static inline int schar_from_char(char_u *p, int c) +{ + int len = utf_char2bytes(c, p); + p[len] = NUL; + return len; +} + +/// compare the contents of two screen cells. +static inline int schar_cmp(char_u *sc1, char_u *sc2) +{ + return strncmp((char *)sc1, (char *)sc2, sizeof(schar_T)); +} + +/// copy the contents of screen cell `sc2` into cell `sc1` +static inline void schar_copy(char_u *sc1, char_u *sc2) +{ + xstrlcpy((char *)sc1, (char *)sc2, sizeof(schar_T)); +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "grid.h.generated.h" +#endif +#endif // NVIM_GRID_H diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 69eca5d123..2516ea52a7 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -50,7 +50,7 @@ struct ScreenGrid { schar_T *chars; sattr_T *attrs; - unsigned *line_offset; + size_t *line_offset; char_u *line_wraps; // last column that was drawn (not cleared with the default background). diff --git a/src/nvim/main.c b/src/nvim/main.c index 3f9e875d74..9beaa0686f 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -37,6 +37,7 @@ # include <locale.h> #endif #include "nvim/garray.h" +#include "nvim/grid.h" #include "nvim/log.h" #include "nvim/mark.h" #include "nvim/mbyte.h" diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 19144a5dce..8a65188f22 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -696,7 +696,7 @@ void free_all_mem(void) } // free screenlines (can't display anything now!) - screen_free_all_mem(); + grid_free_all_mem(); clear_hl_tables(false); list_free_log(); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f3e627cc65..968397fbfa 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -130,13 +130,6 @@ #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ -// temporary buffer for rendering a single screenline, so it can be -// compared with previous contents to calculate smallest delta. -// Per-cell attributes -static size_t linebuf_size = 0; -static schar_T *linebuf_char = NULL; -static sattr_T *linebuf_attr = NULL; - static match_T search_hl; // used for 'hlsearch' highlight matching StlClickDefinition *tab_page_click_defs = NULL; @@ -4221,7 +4214,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (wrap) { ScreenGrid *current_grid = grid; int current_row = row, dummy_col = 0; // dummy_col unused - screen_adjust_grid(¤t_grid, ¤t_row, &dummy_col); + grid_adjust(¤t_grid, ¤t_row, &dummy_col); // Force a redraw of the first column of the next line. current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1; @@ -4387,25 +4380,6 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, return col; } -/// Determine if dedicated window grid should be used or the default_grid -/// -/// If UI did not request multigrid support, draw all windows on the -/// default_grid. -/// -/// NB: this function can only been used with window grids in a context where -/// win_grid_alloc already has been called! -/// -/// If the default_grid is used, adjust window relative positions to global -/// screen positions. -void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) -{ - if ((*grid)->target) { - *row_off += (*grid)->row_offset; - *col_off += (*grid)->col_offset; - *grid = (*grid)->target; - } -} - // Return true if CursorLineSign highlight is to be used. static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) { @@ -4535,196 +4509,6 @@ static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_att } -/* - * Check whether the given character needs redrawing: - * - the (first byte of the) character is different - * - the attributes are different - * - the character is multi-byte and the next byte is different - * - the character is two cells wide and the second cell differs. - */ -static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to, int cols) -{ - return (cols > 0 - && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to]) - || linebuf_attr[off_from] != grid->attrs[off_to] - || (line_off2cells(linebuf_char, off_from, off_from + cols) > 1 - && schar_cmp(linebuf_char[off_from + 1], - grid->chars[off_to + 1]))) - || rdb_flags & RDB_NODELTA)); -} - -/// Move one buffered line to the window grid, but only the characters that -/// have actually changed. Handle insert/delete character. -/// "coloff" gives the first column on the grid for this line. -/// "endcol" gives the columns where valid characters are. -/// "clear_width" is the width of the window. It's > 0 if the rest of the line -/// needs to be cleared, negative otherwise. -/// "rlflag" is TRUE in a rightleft window: -/// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" -/// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" -/// If "wrap" is true, then hint to the UI that "row" contains a line -/// which has wrapped into the next row. -static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, - int rlflag, win_T *wp, int bg_attr, bool wrap) -{ - unsigned off_from; - unsigned off_to; - unsigned max_off_from; - unsigned max_off_to; - int col = 0; - bool redraw_this; // Does character need redraw? - bool redraw_next; // redraw_this for next character - bool clear_next = false; - int char_cells; // 1: normal char - // 2: occupies two display cells - int start_dirty = -1, end_dirty = 0; - - // TODO(bfredl): check all callsites and eliminate - // Check for illegal row and col, just in case - if (row >= grid->Rows) { - row = grid->Rows - 1; - } - if (endcol > grid->Columns) { - endcol = grid->Columns; - } - - screen_adjust_grid(&grid, &row, &coloff); - - // Safety check. Avoids clang warnings down the call stack. - if (grid->chars == NULL || row >= grid->Rows || coloff >= grid->Columns) { - DLOG("invalid state, skipped"); - return; - } - - off_from = 0; - off_to = grid->line_offset[row] + coloff; - max_off_from = linebuf_size; - max_off_to = grid->line_offset[row] + grid->Columns; - - if (rlflag) { - // Clear rest first, because it's left of the text. - if (clear_width > 0) { - while (col <= endcol && grid->chars[off_to][0] == ' ' - && grid->chars[off_to][1] == NUL - && grid->attrs[off_to] == bg_attr) { - ++off_to; - ++col; - } - if (col <= endcol) { - grid_fill(grid, row, row + 1, col + coloff, endcol + coloff + 1, - ' ', ' ', bg_attr); - } - } - col = endcol + 1; - off_to = grid->line_offset[row] + col + coloff; - off_from += col; - endcol = (clear_width > 0 ? clear_width : -clear_width); - } - - if (bg_attr) { - for (int c = col; c < endcol; c++) { - linebuf_attr[off_from + c] = - hl_combine_attr(bg_attr, linebuf_attr[off_from + c]); - } - } - - redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col); - - while (col < endcol) { - char_cells = 1; - if (col + 1 < endcol) { - char_cells = line_off2cells(linebuf_char, off_from, max_off_from); - } - redraw_this = redraw_next; - redraw_next = grid_char_needs_redraw(grid, off_from + char_cells, - off_to + char_cells, - endcol - col - char_cells); - - if (redraw_this) { - if (start_dirty == -1) { - start_dirty = col; - } - end_dirty = col + char_cells; - // When writing a single-width character over a double-width - // character and at the end of the redrawn text, need to clear out - // the right half of the old character. - // Also required when writing the right half of a double-width - // char over the left half of an existing one - if (col + char_cells == endcol - && ((char_cells == 1 - && grid_off2cells(grid, off_to, max_off_to) > 1) - || (char_cells == 2 - && grid_off2cells(grid, off_to, max_off_to) == 1 - && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) { - clear_next = true; - } - - schar_copy(grid->chars[off_to], linebuf_char[off_from]); - if (char_cells == 2) { - schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]); - } - - grid->attrs[off_to] = linebuf_attr[off_from]; - // For simplicity set the attributes of second half of a - // double-wide character equal to the first half. - if (char_cells == 2) { - grid->attrs[off_to + 1] = linebuf_attr[off_from]; - } - } - - off_to += char_cells; - off_from += char_cells; - col += char_cells; - } - - if (clear_next) { - // Clear the second half of a double-wide character of which the left - // half was overwritten with a single-wide character. - schar_from_ascii(grid->chars[off_to], ' '); - end_dirty++; - } - - int clear_end = -1; - if (clear_width > 0 && !rlflag) { - // blank out the rest of the line - // TODO(bfredl): we could cache winline widths - while (col < clear_width) { - if (grid->chars[off_to][0] != ' ' - || grid->chars[off_to][1] != NUL - || grid->attrs[off_to] != bg_attr) { - grid->chars[off_to][0] = ' '; - grid->chars[off_to][1] = NUL; - grid->attrs[off_to] = bg_attr; - if (start_dirty == -1) { - start_dirty = col; - end_dirty = col; - } else if (clear_end == -1) { - end_dirty = endcol; - } - clear_end = col + 1; - } - col++; - off_to++; - } - } - - if (clear_width > 0 || wp->w_width != grid->Columns) { - // If we cleared after the end of the line, it did not wrap. - // For vsplit, line wrapping is not possible. - grid->line_wraps[row] = false; - } - - if (clear_end < end_dirty) { - clear_end = end_dirty; - } - if (start_dirty == -1) { - start_dirty = end_dirty; - } - if (clear_end > start_dirty) { - ui_line(grid, row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end, - bg_attr, wrap); - } -} /* * Mirror text "str" for right-left displaying. @@ -5676,334 +5460,6 @@ static void win_redr_border(win_T *wp) } } -// Low-level functions to manipulate individual character cells on the -// screen grid. - -/// Put a ASCII character in a screen cell. -static void schar_from_ascii(char_u *p, const char c) -{ - p[0] = c; - p[1] = 0; -} - -/// Put a unicode character in a screen cell. -static int schar_from_char(char_u *p, int c) -{ - int len = utf_char2bytes(c, p); - p[len] = NUL; - return len; -} - -/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. -static int schar_from_cc(char_u *p, int c, int u8cc[MAX_MCO]) -{ - int len = utf_char2bytes(c, p); - for (int i = 0; i < MAX_MCO; i++) { - if (u8cc[i] == 0) { - break; - } - len += utf_char2bytes(u8cc[i], p + len); - } - p[len] = 0; - return len; -} - -/// compare the contents of two screen cells. -static int schar_cmp(char_u *sc1, char_u *sc2) -{ - return STRNCMP(sc1, sc2, sizeof(schar_T)); -} - -/// copy the contents of screen cell `sc2` into cell `sc1` -static void schar_copy(char_u *sc1, char_u *sc2) -{ - STRLCPY(sc1, sc2, sizeof(schar_T)); -} - -static int line_off2cells(schar_T *line, size_t off, size_t max_off) -{ - return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1; -} - -/// Return number of display cells for char at grid->chars[off]. -/// We make sure that the offset used is less than "max_off". -static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off) -{ - return line_off2cells(grid->chars, off, max_off); -} - -/// Return true if the character at "row"/"col" on the screen is the left side -/// of a double-width character. -/// -/// Caller must make sure "row" and "col" are not invalid! -bool grid_lefthalve(ScreenGrid *grid, int row, int col) -{ - screen_adjust_grid(&grid, &row, &col); - - return grid_off2cells(grid, grid->line_offset[row] + col, - grid->line_offset[row] + grid->Columns) > 1; -} - -/// Correct a position on the screen, if it's the right half of a double-wide -/// char move it to the left half. Returns the corrected column. -int grid_fix_col(ScreenGrid *grid, int col, int row) -{ - int coloff = 0; - screen_adjust_grid(&grid, &row, &coloff); - - col += coloff; - if (grid->chars != NULL && col > 0 - && grid->chars[grid->line_offset[row] + col][0] == 0) { - return col - 1 - coloff; - } - return col - coloff; -} - -/// output a single character directly to the grid -void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) -{ - char_u buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, buf, row, col, attr); -} - -/// get a single character directly from grid.chars into "bytes[]". -/// Also return its attribute in *attrp; -void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp) -{ - unsigned off; - - screen_adjust_grid(&grid, &row, &col); - - // safety check - if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) { - off = grid->line_offset[row] + col; - *attrp = grid->attrs[off]; - schar_copy(bytes, grid->chars[off]); - } -} - - -/// put string '*text' on the window grid at position 'row' and 'col', with -/// attributes 'attr', and update chars[] and attrs[]. -/// Note: only outputs within one row, message is truncated at grid boundary! -/// Note: if grid, row and/or col is invalid, nothing is done. -void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) -{ - grid_puts_len(grid, text, -1, row, col, attr); -} - -static ScreenGrid *put_dirty_grid = NULL; -static int put_dirty_row = -1; -static int put_dirty_first = INT_MAX; -static int put_dirty_last = 0; - -/// Start a group of grid_puts_len calls that builds a single grid line. -/// -/// Must be matched with a grid_puts_line_flush call before moving to -/// another line. -void grid_puts_line_start(ScreenGrid *grid, int row) -{ - int col = 0; // unused - screen_adjust_grid(&grid, &row, &col); - assert(put_dirty_row == -1); - put_dirty_row = row; - put_dirty_grid = grid; -} - -void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) -{ - assert(put_dirty_row == row); - unsigned int off = grid->line_offset[row] + col; - if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { - schar_copy(grid->chars[off], schar); - grid->attrs[off] = attr; - - put_dirty_first = MIN(put_dirty_first, col); - // TODO(bfredl): Y U NO DOUBLEWIDTH? - put_dirty_last = MAX(put_dirty_last, col + 1); - } -} - -/// like grid_puts(), but output "text[len]". When "len" is -1 output up to -/// a NUL. -void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col, int attr) -{ - unsigned off; - char_u *ptr = text; - int len = textlen; - int c; - unsigned max_off; - int mbyte_blen = 1; - int mbyte_cells = 1; - int u8c = 0; - int u8cc[MAX_MCO]; - int clear_next_cell = FALSE; - int prev_c = 0; // previous Arabic character - int pc, nc, nc1; - int pcc[MAX_MCO]; - int need_redraw; - bool do_flush = false; - - screen_adjust_grid(&grid, &row, &col); - - // Safety check. The check for negative row and column is to fix issue - // vim/vim#4102. TODO(neovim): find out why row/col could be negative. - if (grid->chars == NULL - || row >= grid->Rows || row < 0 - || col >= grid->Columns || col < 0) { - return; - } - - if (put_dirty_row == -1) { - grid_puts_line_start(grid, row); - do_flush = true; - } else { - if (grid != put_dirty_grid || row != put_dirty_row) { - abort(); - } - } - off = grid->line_offset[row] + col; - - // When drawing over the right half of a double-wide char clear out the - // left half. Only needed in a terminal. - if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { - // redraw the previous cell, make it empty - put_dirty_first = -1; - put_dirty_last = MAX(put_dirty_last, 1); - } - - max_off = grid->line_offset[row] + grid->Columns; - while (col < grid->Columns - && (len < 0 || (int)(ptr - text) < len) - && *ptr != NUL) { - c = *ptr; - // check if this is the first byte of a multibyte - if (len > 0) { - mbyte_blen = utfc_ptr2len_len(ptr, (int)((text + len) - ptr)); - } else { - mbyte_blen = utfc_ptr2len((char *)ptr); - } - if (len >= 0) { - u8c = utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)); - } else { - u8c = utfc_ptr2char(ptr, u8cc); - } - mbyte_cells = utf_char2cells(u8c); - if (p_arshape && !p_tbidi && arabic_char(u8c)) { - // Do Arabic shaping. - if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) { - // Past end of string to be displayed. - nc = NUL; - nc1 = NUL; - } else { - nc = utfc_ptr2char_len(ptr + mbyte_blen, pcc, - (int)((text + len) - ptr - mbyte_blen)); - nc1 = pcc[0]; - } - pc = prev_c; - prev_c = u8c; - u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc); - } else { - prev_c = u8c; - } - if (col + mbyte_cells > grid->Columns) { - // Only 1 cell left, but character requires 2 cells: - // display a '>' in the last column to avoid wrapping. */ - c = '>'; - u8c = '>'; - u8cc[0] = 0; - mbyte_cells = 1; - } - - schar_T buf; - schar_from_cc(buf, u8c, u8cc); - - - need_redraw = schar_cmp(grid->chars[off], buf) - || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) - || grid->attrs[off] != attr - || exmode_active; - - if (need_redraw) { - // When at the end of the text and overwriting a two-cell - // character with a one-cell character, need to clear the next - // cell. Also when overwriting the left half of a two-cell char - // with the right half of a two-cell char. Do this only once - // (utf8_off2cells() may return 2 on the right half). - if (clear_next_cell) { - clear_next_cell = false; - } else if ((len < 0 ? ptr[mbyte_blen] == NUL - : ptr + mbyte_blen >= text + len) - && ((mbyte_cells == 1 - && grid_off2cells(grid, off, max_off) > 1) - || (mbyte_cells == 2 - && grid_off2cells(grid, off, max_off) == 1 - && grid_off2cells(grid, off + 1, max_off) > 1))) { - clear_next_cell = true; - } - - // When at the start of the text and overwriting the right half of a - // two-cell character in the same grid, truncate that into a '>'. - if (ptr == text && col > 0 && grid->chars[off][0] == 0) { - grid->chars[off - 1][0] = '>'; - grid->chars[off - 1][1] = 0; - } - - schar_copy(grid->chars[off], buf); - grid->attrs[off] = attr; - if (mbyte_cells == 2) { - grid->chars[off + 1][0] = 0; - grid->attrs[off + 1] = attr; - } - put_dirty_first = MIN(put_dirty_first, col); - put_dirty_last = MAX(put_dirty_last, col + mbyte_cells); - } - - off += mbyte_cells; - col += mbyte_cells; - ptr += mbyte_blen; - if (clear_next_cell) { - // This only happens at the end, display one space next. - ptr = (char_u *)" "; - len = -1; - } - } - - if (do_flush) { - grid_puts_line_flush(true); - } -} - -/// End a group of grid_puts_len calls and send the screen buffer to the UI -/// layer. -/// -/// @param set_cursor Move the visible cursor to the end of the changed region. -/// This is a workaround for not yet refactored code paths -/// and shouldn't be used in new code. -void grid_puts_line_flush(bool set_cursor) -{ - assert(put_dirty_row != -1); - if (put_dirty_first < put_dirty_last) { - if (set_cursor) { - ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, - MIN(put_dirty_last, put_dirty_grid->Columns - 1)); - } - if (!put_dirty_grid->throttled) { - ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, - put_dirty_last, 0, false); - } else if (put_dirty_grid->dirty_col) { - if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { - put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; - } - } - put_dirty_first = INT_MAX; - put_dirty_last = 0; - } - put_dirty_row = -1; - put_dirty_grid = NULL; -} /* * Prepare for 'hlsearch' highlighting. @@ -6030,99 +5486,6 @@ static void end_search_hl(void) } -/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" -/// to "end_col" (exclusive) with character "c1" in first column followed by -/// "c2" in the other columns. Use attributes "attr". -void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1, - int c2, int attr) -{ - schar_T sc; - - int row_off = 0, col_off = 0; - screen_adjust_grid(&grid, &row_off, &col_off); - start_row += row_off; - end_row += row_off; - start_col += col_off; - end_col += col_off; - - // safety check - if (end_row > grid->Rows) { - end_row = grid->Rows; - } - if (end_col > grid->Columns) { - end_col = grid->Columns; - } - - // nothing to do - if (start_row >= end_row || start_col >= end_col) { - return; - } - - for (int row = start_row; row < end_row; row++) { - // When drawing over the right half of a double-wide char clear - // out the left half. When drawing over the left half of a - // double wide-char clear out the right half. Only needed in a - // terminal. - if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); - } - if (end_col < grid->Columns - && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); - } - - // if grid was resized (in ext_multigrid mode), the UI has no redraw updates - // for the newly resized grid. It is better mark everything as dirty and - // send all the updates. - int dirty_first = INT_MAX; - int dirty_last = 0; - - int col = start_col; - schar_from_char(sc, c1); - int lineoff = grid->line_offset[row]; - for (col = start_col; col < end_col; col++) { - int off = lineoff + col; - if (schar_cmp(grid->chars[off], sc) - || grid->attrs[off] != attr) { - schar_copy(grid->chars[off], sc); - grid->attrs[off] = attr; - if (dirty_first == INT_MAX) { - dirty_first = col; - } - dirty_last = col + 1; - } - if (col == start_col) { - schar_from_char(sc, c2); - } - } - if (dirty_last > dirty_first) { - // TODO(bfredl): support a cleared suffix even with a batched line? - if (put_dirty_row == row) { - put_dirty_first = MIN(put_dirty_first, dirty_first); - put_dirty_last = MAX(put_dirty_last, dirty_last); - } else if (grid->throttled) { - // Note: assumes msg_grid is the only throttled grid - assert(grid == &msg_grid); - int dirty = 0; - if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') { - dirty = dirty_last; - } else if (c1 != ' ') { - dirty = dirty_first + 1; - } - if (grid->dirty_col && dirty > grid->dirty_col[row]) { - grid->dirty_col[row] = dirty; - } - } else { - int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); - ui_line(grid, row, dirty_first, last, dirty_last, attr, false); - } - } - - if (end_col == grid->Columns) { - grid->line_wraps[row] = false; - } - } -} /// Check if there should be a delay. Used before clearing or redrawing the /// screen or the command line. @@ -6319,77 +5682,6 @@ retry: resizing = false; } -void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) -{ - int new_row; - ScreenGrid new = *grid; - assert(rows >= 0 && columns >= 0); - size_t ncells = (size_t)rows * columns; - new.chars = xmalloc(ncells * sizeof(schar_T)); - new.attrs = xmalloc(ncells * sizeof(sattr_T)); - new.line_offset = xmalloc((size_t)(rows * sizeof(unsigned))); - new.line_wraps = xmalloc((size_t)(rows * sizeof(char_u))); - - new.Rows = rows; - new.Columns = columns; - - for (new_row = 0; new_row < new.Rows; new_row++) { - new.line_offset[new_row] = new_row * new.Columns; - new.line_wraps[new_row] = false; - - grid_clear_line(&new, new.line_offset[new_row], columns, valid); - - if (copy) { - // If the screen is not going to be cleared, copy as much as - // possible from the old screen to the new one and clear the rest - // (used when resizing the window at the "--more--" prompt or when - // executing an external command, for the GUI). - if (new_row < grid->Rows && grid->chars != NULL) { - int len = MIN(grid->Columns, new.Columns); - memmove(new.chars + new.line_offset[new_row], - grid->chars + grid->line_offset[new_row], - (size_t)len * sizeof(schar_T)); - memmove(new.attrs + new.line_offset[new_row], - grid->attrs + grid->line_offset[new_row], - (size_t)len * sizeof(sattr_T)); - } - } - } - grid_free(grid); - *grid = new; - - // Share a single scratch buffer for all grids, by - // ensuring it is as wide as the widest grid. - if (linebuf_size < (size_t)columns) { - xfree(linebuf_char); - xfree(linebuf_attr); - linebuf_char = xmalloc(columns * sizeof(schar_T)); - linebuf_attr = xmalloc(columns * sizeof(sattr_T)); - linebuf_size = columns; - } -} - -void grid_free(ScreenGrid *grid) -{ - xfree(grid->chars); - xfree(grid->attrs); - xfree(grid->line_offset); - xfree(grid->line_wraps); - - grid->chars = NULL; - grid->attrs = NULL; - grid->line_offset = NULL; - grid->line_wraps = NULL; -} - -/// Doesn't allow reinit, so must only be called by free_all_mem! -void screen_free_all_mem(void) -{ - grid_free(&default_grid); - xfree(linebuf_char); - xfree(linebuf_attr); -} - /// Clear tab_page_click_defs table /// /// @param[out] tpcd Table to clear. @@ -6457,28 +5749,6 @@ void screenclear(void) } } -/// clear a line in the grid starting at "off" until "width" characters -/// are cleared. -void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) -{ - for (int col = 0; col < width; col++) { - schar_from_ascii(grid->chars[off + col], ' '); - } - int fill = valid ? 0 : -1; - (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); -} - -void grid_invalidate(ScreenGrid *grid) -{ - (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns); -} - -bool grid_invalid_row(ScreenGrid *grid, int row) -{ - return grid->attrs[grid->line_offset[row]] < 0; -} - - /// Copy part of a grid line for vertically split window. static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) { @@ -6510,7 +5780,7 @@ void setcursor(void) && vim_isprintc(gchar_cursor())) ? 2 : 1); } - screen_adjust_grid(&grid, &row, &col); + grid_adjust(&grid, &row, &col); ui_grid_cursor_goto(grid->handle, row, col); } } @@ -6563,7 +5833,7 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, unsigned temp; int row_off = 0; - screen_adjust_grid(&grid, &row_off, &col); + grid_adjust(&grid, &row_off, &col); row += row_off; end += row_off; @@ -6612,7 +5882,7 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, unsigned temp; int row_off = 0; - screen_adjust_grid(&grid, &row_off, &col); + grid_adjust(&grid, &row_off, &col); row += row_off; end += row_off; diff --git a/src/nvim/screen.h b/src/nvim/screen.h index 33b713e756..3afbaa5eb6 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -4,7 +4,7 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" -#include "nvim/grid_defs.h" +#include "nvim/grid.h" #include "nvim/pos.h" #include "nvim/types.h" @@ -27,17 +27,6 @@ typedef enum { WC_BOTTOM_RIGHT, } WindowCorner; -/// By default, all windows are drawn on a single rectangular grid, represented by -/// this ScreenGrid instance. In multigrid mode each window will have its own -/// grid, then this is only used for global screen elements that hasn't been -/// externalized. -/// -/// Note: before the screen is initialized and when out of memory these can be -/// NULL. -EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT); - -#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid - // Maximum columns for terminal highlight attributes #define TERM_ATTRS_MAX 1024 diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 60ad8609a4..f76158ff12 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -635,7 +635,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, // ideally win_update() should keep track of this itself and not scroll // the invalid space. if (curgrid->attrs[curgrid->line_offset[r - curgrid->comp_row] - + left - curgrid->comp_col] >= 0) { + + (size_t)left - (size_t)curgrid->comp_col] >= 0) { compose_line(r, left, right, 0); } } diff --git a/src/nvim/window.c b/src/nvim/window.c index 9cf1e3eca1..bbae2bfaaf 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -806,7 +806,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) col += parent->w_wincol; ScreenGrid *grid = &parent->w_grid; int row_off = 0, col_off = 0; - screen_adjust_grid(&grid, &row_off, &col_off); + grid_adjust(&grid, &row_off, &col_off); row += row_off; col += col_off; } @@ -877,7 +877,7 @@ void ui_ext_win_position(win_T *wp) if (win) { grid = &win->w_grid; int row_off = 0, col_off = 0; - screen_adjust_grid(&grid, &row_off, &col_off); + grid_adjust(&grid, &row_off, &col_off); row += row_off; col += col_off; if (c.bufpos.lnum >= 0) { |