refactor(grid): unify the two put-text-on-the-screen code paths

The screen grid refactors will continue until morale improves.
Jokes aside, this is quite a central installment in the series.

Before this refactor, there were two fundamentally distinct codepaths
for getting some text on the screen:

- the win_line() -> grid_put_linebuf() -> ui_line() call chain used for
  buffer text, with linebuf_char as a temporary scratch buffer
- the grid_line_start/grid_line_puts/grid_line_flush() -> ui_line()
  path used for every thing else: statuslines, messages and the command line.
  Here the grid->chars[] array itself doubles as a scratch buffer.

With this refactor, the later family of functions still exist, however
they now as well render to linebuf_char just like win_line() did, and
grid_put_linebuf() is called in the end to calculate delta changes.
This means we don't need any duplicate logic for delta calculations anymore.

Later down the line, it will be possible to share more logic operating
on this scratch buffer, like doing 'rightleft' reversal and arabic
shaping as a post-processing step.
This commit is contained in:
bfredl 2023-09-20 10:08:05 +02:00
parent af7d317f3f
commit e33269578b
7 changed files with 201 additions and 247 deletions

View File

@ -608,7 +608,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
grid_line_fill(clen, Columns, fillchar, attr); grid_line_fill(clen, Columns, fillchar, attr);
grid_line_flush(false); grid_line_flush();
} }
win_redraw_last_status(topframe); win_redraw_last_status(topframe);

View File

@ -1825,7 +1825,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
|| (number_only && wlv.draw_state > WL_STC)) || (number_only && wlv.draw_state > WL_STC))
&& wlv.filler_todo <= 0) { && wlv.filler_todo <= 0) {
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row);
grid_put_linebuf(grid, wlv.row, 0, wlv.col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); win_put_linebuf(wp, wlv.row, 0, wlv.col, -grid->cols, bg_attr, false);
// Pretend we have finished updating the window. Except when // Pretend we have finished updating the window. Except when
// 'cursorcolumn' is set. // 'cursorcolumn' is set.
if (wp->w_p_cuc) { if (wp->w_p_cuc) {
@ -2956,7 +2956,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl); wp->w_p_rl ? -1 : grid->cols, 0, wp->w_p_rl);
} }
draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row); draw_virt_text(wp, buf, win_col_offset, &wlv.col, wp->w_p_rl ? -1 : grid->cols, wlv.row);
grid_put_linebuf(grid, wlv.row, 0, wlv.col, grid->cols, wp->w_p_rl, wp, bg_attr, false); win_put_linebuf(wp, wlv.row, 0, wlv.col, grid->cols, bg_attr, false);
wlv.row++; wlv.row++;
// Update w_cline_height and w_cline_folded if the cursor line was // Update w_cline_height and w_cline_folded if the cursor line was
@ -3229,7 +3229,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
draw_virt_text(wp, buf, win_col_offset, &draw_col, wp->w_p_rl ? -1 : grid->cols, wlv.row); draw_virt_text(wp, buf, win_col_offset, &draw_col, wp->w_p_rl ? -1 : grid->cols, wlv.row);
} }
grid_put_linebuf(grid, wlv.row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap); win_put_linebuf(wp, wlv.row, 0, draw_col, grid->cols, bg_attr, wrap);
if (wrap) { if (wrap) {
ScreenGrid *current_grid = grid; ScreenGrid *current_grid = grid;
int current_row = wlv.row, dummy_col = 0; // dummy_col unused int current_row = wlv.row, dummy_col = 0; // dummy_col unused
@ -3297,3 +3297,37 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
xfree(wlv.saved_p_extra_free); xfree(wlv.saved_p_extra_free);
return wlv.row; return wlv.row;
} }
static void win_put_linebuf(win_T *wp, int row, int coloff, int endcol, int clear_width,
int bg_attr, bool wrap)
{
ScreenGrid *grid = &wp->w_grid;
// Take care of putting "<<<" on the first line for 'smoothscroll'.
if (row == 0 && wp->w_skipcol > 0
// do not overwrite the 'showbreak' text with "<<<"
&& *get_showbreak_value(wp) == NUL
// do not overwrite the 'listchars' "precedes" text with "<<<"
&& !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) {
int off = 0;
if (wp->w_p_nu && wp->w_p_rnu) {
// do not overwrite the line number, change "123 text" to "123<<<xt".
while (off < grid->cols && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
off++;
}
}
for (int i = 0; i < 3 && off < grid->cols; i++) {
if (off + 1 < grid->cols && linebuf_char[off + 1] == NUL) {
// When the first half of a double-width character is
// overwritten, change the second half to a space.
linebuf_char[off + 1] = schar_from_ascii(' ');
}
linebuf_char[off] = schar_from_ascii('<');
linebuf_attr[off] = HL_ATTR(HLF_AT);
off++;
}
}
grid_put_linebuf(grid, row, coloff, 0, endcol, clear_width, wp->w_p_rl, bg_attr, wrap, false);
}

View File

@ -771,20 +771,20 @@ static void win_redr_border(win_T *wp)
if (adj[1]) { if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[2], attrs[2]); grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
} }
grid_line_flush(false); grid_line_flush();
} }
for (int i = 0; i < irow; i++) { for (int i = 0; i < irow; i++) {
if (adj[3]) { if (adj[3]) {
grid_line_start(grid, i + adj[0]); grid_line_start(grid, i + adj[0]);
grid_line_put_schar(0, chars[7], attrs[7]); grid_line_put_schar(0, chars[7], attrs[7]);
grid_line_flush(false); grid_line_flush();
} }
if (adj[1]) { if (adj[1]) {
int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3; int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
grid_line_start(grid, i + adj[0]); grid_line_start(grid, i + adj[0]);
grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]); grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
grid_line_flush(false); grid_line_flush();
} }
} }
@ -807,7 +807,7 @@ static void win_redr_border(win_T *wp)
if (adj[1]) { if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[4], attrs[4]); grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
} }
grid_line_flush(false); grid_line_flush();
} }
} }
@ -1098,7 +1098,7 @@ int showmode(void)
&& !(p_ch == 0 && !ui_has(kUIMessages))) { && !(p_ch == 0 && !ui_has(kUIMessages))) {
grid_line_start(&msg_grid_adj, Rows - 1); grid_line_start(&msg_grid_adj, Rows - 1);
win_redr_ruler(ruler_win); win_redr_ruler(ruler_win);
grid_line_flush(false); grid_line_flush();
} }
redraw_cmdline = false; redraw_cmdline = false;
@ -1370,25 +1370,25 @@ static void draw_sep_connectors_win(win_T *wp)
bool bot_left = !(win_at_bottom || win_at_left); bool bot_left = !(win_at_bottom || win_at_left);
bool bot_right = !(win_at_bottom || win_at_right); bool bot_right = !(win_at_bottom || win_at_right);
if (top_left || top_right) { if (top_left) {
grid_line_start(&default_grid, wp->w_winrow - 1); grid_line_start(&default_grid, wp->w_winrow - 1);
if (top_left) { grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl);
grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl); grid_line_flush();
}
if (top_right) {
grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl);
}
grid_line_flush(false);
} }
if (bot_left || bot_right) { if (top_right) {
grid_line_start(&default_grid, wp->w_winrow - 1);
grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl);
grid_line_flush();
}
if (bot_left) {
grid_line_start(&default_grid, W_ENDROW(wp)); grid_line_start(&default_grid, W_ENDROW(wp));
if (bot_left) { grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl);
grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl); grid_line_flush();
} }
if (bot_right) { if (bot_right) {
grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl); grid_line_start(&default_grid, W_ENDROW(wp));
} grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl);
grid_line_flush(false); grid_line_flush();
} }
} }
@ -2394,8 +2394,11 @@ static void win_update(win_T *wp, DecorProviders *providers)
int symbol = wp->w_p_fcs_chars.lastline; int symbol = wp->w_p_fcs_chars.lastline;
// Last line isn't finished: Display "@@@" at the end. // Last line isn't finished: Display "@@@" at the end.
grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, // TODO(bfredl): this display ">@@@" when ">" was a left-halve
MAX(start_col, 0), wp->w_grid.cols, symbol, symbol, at_attr); // maybe "@@@@" is preferred when this happens.
grid_line_start(&wp->w_grid, wp->w_grid.rows - 1);
grid_line_fill(MAX(start_col, 0), wp->w_grid.cols, symbol, at_attr);
grid_line_flush();
set_empty_rows(wp, srow); set_empty_rows(wp, srow);
wp->w_botline = lnum; wp->w_botline = lnum;
} else { } else {

View File

@ -190,21 +190,16 @@ void grid_invalidate(ScreenGrid *grid)
(void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols); (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
} }
bool grid_invalid_row(ScreenGrid *grid, int row) static bool grid_invalid_row(ScreenGrid *grid, int row)
{ {
return grid->attrs[grid->line_offset[row]] < 0; 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) ? 2 : 1;
}
/// Return number of display cells for char at grid->chars[off]. /// Return number of display cells for char at grid->chars[off].
/// We make sure that the offset used is less than "max_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) static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off)
{ {
return line_off2cells(grid->chars, off, max_off); return (off + 1 < max_off && grid->chars[off + 1] == 0) ? 2 : 1;
} }
/// Return true if the character at "row"/"col" on the screen is the left side /// Return true if the character at "row"/"col" on the screen is the left side
@ -261,18 +256,12 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp)
schar_get(bytes, grid->chars[off]); schar_get(bytes, grid->chars[off]);
} }
static bool check_grid(ScreenGrid *grid, int row, int col) static ScreenGrid *grid_line_grid = NULL;
{ static int grid_line_row = -1;
grid_adjust(&grid, &row, &col); static int grid_line_coloff = 0;
// Safety check. The check for negative row and column is to fix issue static int grid_line_maxcol = 0;
// vim/vim#4102. TODO(neovim): find out why row/col could be negative. static int grid_line_first = INT_MAX;
if (grid->chars == NULL static int grid_line_last = 0;
|| row >= grid->rows || row < 0
|| col >= grid->cols || col < 0) {
return false;
}
return true;
}
/// put string 'text' on the window grid at position 'row' and 'col', with /// put string 'text' on the window grid at position 'row' and 'col', with
/// attributes 'attr', and update contents of 'grid' /// attributes 'attr', and update contents of 'grid'
@ -280,26 +269,32 @@ static bool check_grid(ScreenGrid *grid, int row, int col)
/// Note: only outputs within one row! /// Note: only outputs within one row!
int grid_puts(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) int grid_puts(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr)
{ {
if (!check_grid(grid, row, col)) { grid_line_start(grid, row);
// 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.
int off_col = grid_line_coloff + col;
if (grid_line_grid->chars == NULL
|| grid_line_row >= grid_line_grid->rows || grid_line_row < 0
|| off_col >= grid_line_grid->cols || off_col < 0) {
if (rdb_flags & RDB_INVALID) { if (rdb_flags & RDB_INVALID) {
abort(); abort();
} else {
grid_line_grid = NULL;
return 0;
} }
return 0;
} }
grid_line_start(grid, row);
int len = grid_line_puts(col, text, textlen, attr); int len = grid_line_puts(col, text, textlen, attr);
grid_line_flush(true); if (grid_line_last > grid_line_first) {
// TODO(bfredl): this is bullshit. message.c should manage its own cursor movements
int col_pos = MIN(grid_line_coloff + grid_line_last, grid_line_grid->cols - 1);
ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col_pos);
}
grid_line_flush();
return len; return len;
} }
static ScreenGrid *grid_line_grid = NULL;
static int grid_line_row = -1;
static int grid_line_coloff = 0;
static int grid_line_first = INT_MAX;
static int grid_line_last = 0;
static bool grid_line_was_invalid = false;
/// Start a group of grid_line_puts calls that builds a single grid line. /// Start a group of grid_line_puts calls that builds a single grid line.
/// ///
/// Must be matched with a grid_line_flush call before moving to /// Must be matched with a grid_line_flush call before moving to
@ -308,56 +303,46 @@ void grid_line_start(ScreenGrid *grid, int row)
{ {
int col = 0; int col = 0;
grid_adjust(&grid, &row, &col); grid_adjust(&grid, &row, &col);
assert(grid_line_row == -1); assert(grid_line_grid == NULL);
grid_line_row = row; grid_line_row = row;
grid_line_grid = grid; grid_line_grid = grid;
grid_line_coloff = col; grid_line_coloff = col;
// TODO(bfredl): ugly hackaround, will be fixed in STAGE 2 grid_line_first = (int)linebuf_size;
grid_line_was_invalid = grid != &default_grid && grid_invalid_row(grid, row); grid_line_maxcol = grid->cols - grid_line_coloff;
grid_line_last = 0;
} }
void grid_line_put_schar(int col, schar_T schar, int attr) void grid_line_put_schar(int col, schar_T schar, int attr)
{ {
assert(grid_line_row >= 0); assert(grid_line_grid);
ScreenGrid *grid = grid_line_grid;
size_t off = grid->line_offset[grid_line_row] + (size_t)col; linebuf_char[col] = schar;
if (grid->attrs[off] != attr || grid->chars[off] != schar || rdb_flags & RDB_NODELTA) { linebuf_attr[col] = attr;
grid->chars[off] = schar;
grid->attrs[off] = attr;
grid_line_first = MIN(grid_line_first, col); grid_line_first = MIN(grid_line_first, col);
// TODO(bfredl): Y U NO DOUBLEWIDTH? // TODO(bfredl): Y U NO DOUBLEWIDTH?
grid_line_last = MAX(grid_line_last, col + 1); grid_line_last = MAX(grid_line_last, col + 1);
} linebuf_vcol[col] = -1;
grid->vcols[off] = -1;
} }
/// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// like grid_puts(), but output "text[len]". When "len" is -1 output up to
/// a NUL. /// a NUL.
int grid_line_puts(int col, const char *text, int textlen, int attr) int grid_line_puts(int col, const char *text, int textlen, int attr)
{ {
size_t off;
const char *ptr = text; const char *ptr = text;
int len = textlen; int len = textlen;
int c; int c;
size_t max_off;
int u8cc[MAX_MCO]; int u8cc[MAX_MCO];
bool clear_next_cell = false;
int prev_c = 0; // previous Arabic character int prev_c = 0; // previous Arabic character
int pc, nc, nc1; int pc, nc, nc1;
int pcc[MAX_MCO]; int pcc[MAX_MCO];
assert(grid_line_row >= 0); assert(grid_line_grid);
ScreenGrid *grid = grid_line_grid;
int row = grid_line_row;
col += grid_line_coloff;
off = grid->line_offset[row] + (size_t)col;
int start_col = col; int start_col = col;
max_off = grid->line_offset[row] + (size_t)grid->cols; int max_col = grid_line_maxcol;
while (col < grid->cols while (col < max_col
&& (len < 0 || (int)(ptr - text) < len) && (len < 0 || (int)(ptr - text) < len)
&& *ptr != NUL) { && *ptr != NUL) {
c = (unsigned char)(*ptr); c = (unsigned char)(*ptr);
@ -394,7 +379,7 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
} else { } else {
prev_c = u8c; prev_c = u8c;
} }
if (col + mbyte_cells > grid->cols) { if (col + mbyte_cells > max_col) {
// Only 1 cell left, but character requires 2 cells: // Only 1 cell left, but character requires 2 cells:
// display a '>' in the last column to avoid wrapping. */ // display a '>' in the last column to avoid wrapping. */
c = '>'; c = '>';
@ -408,55 +393,29 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
// an edge case, treat it as such.. // an edge case, treat it as such..
buf = schar_from_cc(u8c, u8cc); buf = schar_from_cc(u8c, u8cc);
int need_redraw = grid->chars[off] != buf // When at the start of the text and overwriting the right half of a
|| (mbyte_cells == 2 && grid->chars[off + 1] != 0) // two-cell character in the same grid, truncate that into a '>'.
|| grid->attrs[off] != attr if (ptr == text && col > grid_line_first && col < grid_line_last
|| exmode_active && linebuf_char[col] == 0) {
|| rdb_flags & RDB_NODELTA; linebuf_char[col - 1] = schar_from_ascii('>');
}
if (need_redraw) {
// When at the end of the text and overwriting a two-cell linebuf_char[col] = buf;
// character with a one-cell character, need to clear the next linebuf_attr[col] = attr;
// cell. Also when overwriting the left half of a two-cell char linebuf_vcol[col] = -1;
// with the right half of a two-cell char. Do this only once if (mbyte_cells == 2) {
// (utf8_off2cells() may return 2 on the right half). linebuf_char[col + 1] = 0;
if (clear_next_cell) { linebuf_attr[col + 1] = attr;
clear_next_cell = false; linebuf_vcol[col + 1] = -1;
} 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) {
grid->chars[off - 1] = schar_from_ascii('>');
}
grid->chars[off] = buf;
grid->attrs[off] = attr;
grid->vcols[off] = -1;
if (mbyte_cells == 2) {
grid->chars[off + 1] = 0;
grid->attrs[off + 1] = attr;
grid->vcols[off + 1] = -1;
}
grid_line_first = MIN(grid_line_first, col);
grid_line_last = MAX(grid_line_last, col + mbyte_cells);
} }
off += (size_t)mbyte_cells;
col += mbyte_cells; col += mbyte_cells;
ptr += mbyte_blen; ptr += mbyte_blen;
if (clear_next_cell) { }
// This only happens at the end, display one space next.
ptr = " "; if (col > start_col) {
len = -1; grid_line_first = MIN(grid_line_first, start_col);
} grid_line_last = MAX(grid_line_last, col);
} }
return col - start_col; return col - start_col;
@ -464,56 +423,30 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
void grid_line_fill(int start_col, int end_col, int c, int attr) void grid_line_fill(int start_col, int end_col, int c, int attr)
{ {
ScreenGrid *grid = grid_line_grid;
size_t lineoff = grid->line_offset[grid_line_row];
start_col += grid_line_coloff;
end_col += grid_line_coloff;
schar_T sc = schar_from_char(c); schar_T sc = schar_from_char(c);
for (int col = start_col; col < end_col; col++) { for (int col = start_col; col < end_col; col++) {
size_t off = lineoff + (size_t)col; linebuf_char[col] = sc;
if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { linebuf_attr[col] = attr;
grid->chars[off] = sc; linebuf_vcol[col] = -1;
grid->attrs[off] = attr;
grid_line_first = MIN(grid_line_first, col);
grid_line_last = MAX(grid_line_last, col + 1);
}
grid->vcols[off] = -1;
} }
grid_line_first = MIN(grid_line_first, start_col);
grid_line_last = MAX(grid_line_last, end_col);
} }
/// End a group of grid_line_puts calls and send the screen buffer to the UI layer. /// End a group of grid_line_puts calls and send the screen buffer to the UI layer.
/// void grid_line_flush(void)
/// @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_line_flush(bool set_cursor)
{ {
assert(grid_line_row != -1); ScreenGrid *grid = grid_line_grid;
if (grid_line_first < grid_line_last) {
// When drawing over the right half of a double-wide char clear out the
// left half. Only needed in a terminal.
if (grid_line_was_invalid && grid_line_first == 0) {
// redraw the previous cell, make it empty
grid_line_first = -1;
}
if (set_cursor) {
ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row,
MIN(grid_line_last, grid_line_grid->cols - 1));
}
if (!grid_line_grid->throttled) {
ui_line(grid_line_grid, grid_line_row, grid_line_first, grid_line_last,
grid_line_last, 0, false);
} else if (grid_line_grid->dirty_col) {
if (grid_line_last > grid_line_grid->dirty_col[grid_line_row]) {
grid_line_grid->dirty_col[grid_line_row] = grid_line_last;
}
}
grid_line_first = INT_MAX;
grid_line_last = 0;
}
grid_line_row = -1;
grid_line_grid = NULL; grid_line_grid = NULL;
if (!(grid_line_first < grid_line_last)) {
return;
}
int row = grid_line_row;
bool invalid_row = grid != &default_grid && grid_invalid_row(grid, row) && grid_line_first == 0;
grid_put_linebuf(grid, row, grid_line_coloff, grid_line_first, grid_line_last, grid_line_last,
false, 0, false, invalid_row);
} }
/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" /// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col"
@ -607,13 +540,14 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
/// - the attributes are different /// - the attributes are different
/// - the character is multi-byte and the next byte is different /// - the character is multi-byte and the next byte is different
/// - the character is two cells wide and the second cell differs. /// - 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) static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int cols)
{ {
return (cols > 0 return (cols > 0
&& ((linebuf_char[off_from] != grid->chars[off_to] && ((linebuf_char[col] != grid->chars[off_to]
|| linebuf_attr[off_from] != grid->attrs[off_to] || linebuf_attr[col] != grid->attrs[off_to]
|| (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1 || (cols > 1 && linebuf_char[col + 1] == 0
&& linebuf_char[off_from + 1] != grid->chars[off_to + 1])) && linebuf_char[col + 1] != grid->chars[off_to + 1]))
|| exmode_active // TODO(bfredl): what in the actual fuck
|| rdb_flags & RDB_NODELTA)); || rdb_flags & RDB_NODELTA));
} }
@ -623,30 +557,27 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_
/// "endcol" gives the columns where valid characters are. /// "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 /// "clear_width" is the width of the window. It's > 0 if the rest of the line
/// needs to be cleared, negative otherwise. /// needs to be cleared, negative otherwise.
/// "rlflag" is true in a rightleft window: /// "rl" is true for rightleft text, like a window with 'rightleft' option set
/// When true and "clear_width" > 0, clear columns 0 to "endcol" /// When true and "clear_width" > 0, clear columns 0 to "endcol"
/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width" /// 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 /// If "wrap" is true, then hint to the UI that "row" contains a line
/// which has wrapped into the next row. /// which has wrapped into the next row.
void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width,
int rlflag, win_T *wp, int bg_attr, bool wrap) int rl, int bg_attr, bool wrap, bool invalid_row)
{ {
int col = 0;
bool redraw_next; // redraw_this for next character bool redraw_next; // redraw_this for next character
bool clear_next = false; bool clear_next = false;
bool topline = row == 0;
int char_cells; // 1: normal char int char_cells; // 1: normal char
// 2: occupies two display cells // 2: occupies two display cells
int start_dirty = -1, end_dirty = 0; int start_dirty = -1, end_dirty = 0;
assert(row < grid->rows); assert(0 <= row && row < grid->rows);
// TODO(bfredl): check all callsites and eliminate // TODO(bfredl): check all callsites and eliminate
// Check for illegal col, just in case // Check for illegal col, just in case
if (endcol > grid->cols) { if (endcol > grid->cols) {
endcol = grid->cols; endcol = grid->cols;
} }
const size_t max_off_from = (size_t)grid->cols;
grid_adjust(&grid, &row, &coloff); grid_adjust(&grid, &row, &coloff);
// Safety check. Avoids clang warnings down the call stack. // Safety check. Avoids clang warnings down the call stack.
@ -655,45 +586,21 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
return; return;
} }
size_t off_from = 0;
size_t off_to = grid->line_offset[row] + (size_t)coloff; size_t off_to = grid->line_offset[row] + (size_t)coloff;
const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols; const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols;
// Take care of putting "<<<" on the first line for 'smoothscroll'. // When at the start of the text and overwriting the right half of a
if (topline && wp->w_skipcol > 0 // two-cell character in the same grid, truncate that into a '>'.
// do not overwrite the 'showbreak' text with "<<<" if (col > 0 && grid->chars[off_to + (size_t)col] == 0) {
&& *get_showbreak_value(wp) == NUL linebuf_char[col - 1] = schar_from_ascii('>');
// do not overwrite the 'listchars' "precedes" text with "<<<" col--;
&& !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) {
size_t off = 0;
size_t skip = 0;
if (wp->w_p_nu && wp->w_p_rnu) {
// do not overwrite the line number, change "123 text" to
// "123<<<xt".
while (skip < max_off_from && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
off++;
skip++;
}
}
for (size_t i = 0; i < 3 && i + skip < max_off_from; i++) {
if (line_off2cells(linebuf_char, off, max_off_from) > 1) {
// When the first half of a double-width character is
// overwritten, change the second half to a space.
linebuf_char[off + 1] = schar_from_ascii(' ');
}
linebuf_char[off] = schar_from_ascii('<');
linebuf_attr[off] = HL_ATTR(HLF_AT);
off++;
}
} }
if (rlflag) { if (rl) {
// Clear rest first, because it's left of the text. // Clear rest first, because it's left of the text.
if (clear_width > 0) { if (clear_width > 0) {
while (col <= endcol && grid->chars[off_to] == schar_from_ascii(' ') while (col <= endcol && grid->chars[off_to + (size_t)col] == schar_from_ascii(' ')
&& grid->attrs[off_to] == bg_attr) { && grid->attrs[off_to + (size_t)col] == bg_attr) {
off_to++;
col++; col++;
} }
if (col <= endcol) { if (col <= endcol) {
@ -701,28 +608,26 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
} }
} }
col = endcol + 1; 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); endcol = (clear_width > 0 ? clear_width : -clear_width);
} }
if (bg_attr) { if (bg_attr) {
assert(off_from == (size_t)col);
for (int c = col; c < endcol; c++) { for (int c = col; c < endcol; c++) {
linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]); linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]);
} }
} }
redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col); redraw_next = grid_char_needs_redraw(grid, col, (size_t)col + off_to, endcol - col);
while (col < endcol) { while (col < endcol) {
char_cells = 1; char_cells = 1;
if (col + 1 < endcol) { if (col + 1 < endcol && linebuf_char[col + 1] == 0) {
char_cells = line_off2cells(linebuf_char, off_from, max_off_from); char_cells = 2;
} }
bool redraw_this = redraw_next; // Does character need redraw? bool redraw_this = redraw_next; // Does character need redraw?
redraw_next = grid_char_needs_redraw(grid, off_from + (size_t)char_cells, size_t off = (size_t)col + off_to;
off_to + (size_t)char_cells, redraw_next = grid_char_needs_redraw(grid, col + char_cells,
off + (size_t)char_cells,
endcol - col - char_cells); endcol - col - char_cells);
if (redraw_this) { if (redraw_this) {
@ -737,53 +642,52 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
// char over the left half of an existing one // char over the left half of an existing one
if (col + char_cells == endcol if (col + char_cells == endcol
&& ((char_cells == 1 && ((char_cells == 1
&& grid_off2cells(grid, off_to, max_off_to) > 1) && grid_off2cells(grid, off, max_off_to) > 1)
|| (char_cells == 2 || (char_cells == 2
&& grid_off2cells(grid, off_to, max_off_to) == 1 && grid_off2cells(grid, off, max_off_to) == 1
&& grid_off2cells(grid, off_to + 1, max_off_to) > 1))) { && grid_off2cells(grid, off + 1, max_off_to) > 1))) {
clear_next = true; clear_next = true;
} }
grid->chars[off_to] = linebuf_char[off_from]; grid->chars[off] = linebuf_char[col];
if (char_cells == 2) { if (char_cells == 2) {
grid->chars[off_to + 1] = linebuf_char[off_from + 1]; grid->chars[off + 1] = linebuf_char[col + 1];
} }
grid->attrs[off_to] = linebuf_attr[off_from]; grid->attrs[off] = linebuf_attr[col];
// For simplicity set the attributes of second half of a // For simplicity set the attributes of second half of a
// double-wide character equal to the first half. // double-wide character equal to the first half.
if (char_cells == 2) { if (char_cells == 2) {
grid->attrs[off_to + 1] = linebuf_attr[off_from]; grid->attrs[off + 1] = linebuf_attr[col];
} }
} }
grid->vcols[off_to] = linebuf_vcol[off_from]; grid->vcols[off] = linebuf_vcol[col];
if (char_cells == 2) { if (char_cells == 2) {
grid->vcols[off_to + 1] = linebuf_vcol[off_from + 1]; grid->vcols[off + 1] = linebuf_vcol[col + 1];
} }
off_to += (size_t)char_cells;
off_from += (size_t)char_cells;
col += char_cells; col += char_cells;
} }
if (clear_next) { if (clear_next) {
// Clear the second half of a double-wide character of which the left // Clear the second half of a double-wide character of which the left
// half was overwritten with a single-wide character. // half was overwritten with a single-wide character.
grid->chars[off_to] = schar_from_ascii(' '); grid->chars[(size_t)col + off_to] = schar_from_ascii(' ');
end_dirty++; end_dirty++;
} }
int clear_end = -1; int clear_end = -1;
if (clear_width > 0 && !rlflag) { if (clear_width > 0 && !rl) {
// blank out the rest of the line // blank out the rest of the line
// TODO(bfredl): we could cache winline widths // TODO(bfredl): we could cache winline widths
while (col < clear_width) { while (col < clear_width) {
if (grid->chars[off_to] != schar_from_ascii(' ') size_t off = (size_t)col + off_to;
|| grid->attrs[off_to] != bg_attr if (grid->chars[off] != schar_from_ascii(' ')
|| grid->attrs[off] != bg_attr
|| rdb_flags & RDB_NODELTA) { || rdb_flags & RDB_NODELTA) {
grid->chars[off_to] = schar_from_ascii(' '); grid->chars[off] = schar_from_ascii(' ');
grid->attrs[off_to] = bg_attr; grid->attrs[off] = bg_attr;
if (start_dirty == -1) { if (start_dirty == -1) {
start_dirty = col; start_dirty = col;
end_dirty = col; end_dirty = col;
@ -792,9 +696,8 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
} }
clear_end = col + 1; clear_end = col + 1;
} }
grid->vcols[off_to] = MAXCOL; grid->vcols[off] = MAXCOL;
col++; col++;
off_to++;
} }
} }
@ -805,8 +708,22 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
start_dirty = end_dirty; start_dirty = end_dirty;
} }
if (clear_end > start_dirty) { if (clear_end > start_dirty) {
ui_line(grid, row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end, if (!grid->throttled) {
bg_attr, wrap); int start_pos = coloff + start_dirty;
// When drawing over the right half of a double-wide char clear out the
// left half. Only needed in a terminal.
if (invalid_row && start_pos == 0) {
start_pos = -1;
}
ui_line(grid, row, start_pos, coloff + end_dirty, coloff + clear_end,
bg_attr, wrap);
} else if (grid->dirty_col) {
// TODO(bfredl): really get rid of the extra psuedo terminal in message.c
// by using a linebuf_char copy for "throttled message line"
if (clear_end > grid->dirty_col[row]) {
grid->dirty_col[row] = clear_end;
}
}
} }
} }

View File

@ -2087,7 +2087,7 @@ static void display_showcmd(void)
// clear the rest of an old message by outputting up to SHOWCMD_COLS spaces // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces
grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG)); grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG));
grid_line_flush(false); grid_line_flush();
} }
/// When "check" is false, prepare for commands that scroll the window. /// When "check" is false, prepare for commands that scroll the window.

View File

@ -655,7 +655,7 @@ void pum_redraw(void)
i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
} }
} }
grid_line_flush(false); grid_line_flush();
row++; row++;
} }
} }

View File

@ -171,7 +171,7 @@ void win_redr_status(win_T *wp)
} }
} }
grid_line_flush(false); grid_line_flush();
} }
// May need to draw the character below the vertical separator. // May need to draw the character below the vertical separator.
@ -449,7 +449,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
grid_line_fill(col, maxcol, fillchar, curattr); grid_line_fill(col, maxcol, fillchar, curattr);
if (!draw_ruler) { if (!draw_ruler) {
grid_line_flush(false); grid_line_flush();
} }
// Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
@ -861,7 +861,7 @@ void draw_tabline(void)
}; };
} }
grid_line_flush(false); grid_line_flush();
} }
// Reset the flag here again, in case evaluating 'tabline' causes it to be // Reset the flag here again, in case evaluating 'tabline' causes it to be