From a58bb215449cee65b965b9094e9e996ddfe78315 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 5 Oct 2023 14:44:13 +0200 Subject: [PATCH] refactor(grid): get rid of unbatched grid_puts and grid_putchar This finalizes the long running refactor from the old TUI-focused grid implementation where text-drawing cursor was not separated from the visible cursor. Still, the pattern of setting cursor position together with updating a line was convenient. Introduce grid_line_cursor_goto() to still allow this but now being explicit about it. Only having batched drawing functions makes code involving drawing a bit longer. But it is better to be explicit, and this highlights cases where multiple small redraws can be grouped together. This was the case for most of the changed places (messages, lastline, and :intro) --- src/nvim/drawline.c | 1 + src/nvim/drawscreen.c | 18 ++-- src/nvim/edit.c | 38 ++++--- src/nvim/eval/funcs.c | 6 +- src/nvim/grid.c | 156 ++++++++++----------------- src/nvim/message.c | 14 +-- src/nvim/statusline.c | 4 +- src/nvim/ui_compositor.c | 2 +- src/nvim/version.c | 16 ++- test/functional/ui/messages_spec.lua | 2 +- 10 files changed, 105 insertions(+), 152 deletions(-) diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index eef427a7b8..e5d2ba7550 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -3330,5 +3330,6 @@ static void win_put_linebuf(win_T *wp, int row, int coloff, int endcol, int clea } } + grid_adjust(&grid, &row, &coloff); grid_put_linebuf(grid, row, coloff, 0, endcol, clear_width, wp->w_p_rl, bg_attr, wrap, false); } diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index a0cbc60f25..e32a556daa 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -2382,26 +2382,20 @@ static void win_update(win_T *wp, DecorProviders *providers) wp->w_botline = lnum; wp->w_filler_rows = wp->w_grid.rows - srow; } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" - int scr_row = wp->w_grid.rows - 1; - int symbol = wp->w_p_fcs_chars.lastline; - char fillbuf[12]; // 2 characters of 6 bytes - int charlen = utf_char2bytes(symbol, &fillbuf[0]); - utf_char2bytes(symbol, &fillbuf[charlen]); - // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr); + grid_line_start(&wp->w_grid, wp->w_grid.rows - 1); + grid_line_fill(0, MIN(wp->w_grid.cols, 3), wp->w_p_fcs_chars.lastline, at_attr); + grid_line_fill(3, wp->w_grid.cols, ' ', at_attr); + grid_line_flush(); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" - int start_col = wp->w_grid.cols - 3; - int symbol = wp->w_p_fcs_chars.lastline; - // Last line isn't finished: Display "@@@" at the end. // TODO(bfredl): this display ">@@@" when ">" was a left-halve // 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_fill(MAX(wp->w_grid.cols - 3, 0), wp->w_grid.cols, + wp->w_p_fcs_chars.lastline, at_attr); grid_line_flush(); set_empty_rows(wp, srow); wp->w_botline = lnum; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 58cface37f..4593748c25 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1412,11 +1412,11 @@ static void ins_ctrl_v(void) // Put a character directly onto the screen. It's not stored in a buffer. // Used while handling CTRL-K, CTRL-V, etc. in Insert mode. static int pc_status; -#define PC_STATUS_UNSET 0 // pc_bytes was not set -#define PC_STATUS_RIGHT 1 // right half of double-wide char -#define PC_STATUS_LEFT 2 // left half of double-wide char -#define PC_STATUS_SET 3 // pc_bytes was filled -static char pc_bytes[MB_MAXBYTES + 1]; // saved bytes +#define PC_STATUS_UNSET 0 // nothing was put on screen +#define PC_STATUS_RIGHT 1 // right half of double-wide char +#define PC_STATUS_LEFT 2 // left half of double-wide char +#define PC_STATUS_SET 3 // pc_schar was filled +static schar_T pc_schar; // saved char static int pc_attr; static int pc_row; static int pc_col; @@ -1433,31 +1433,34 @@ void edit_putchar(int c, bool highlight) attr = 0; } pc_row = curwin->w_wrow; - pc_col = 0; pc_status = PC_STATUS_UNSET; + grid_line_start(&curwin->w_grid, pc_row); if (curwin->w_p_rl) { - pc_col += curwin->w_grid.cols - 1 - curwin->w_wcol; - const int fix_col = grid_fix_col(&curwin->w_grid, pc_col, pc_row); + pc_col = curwin->w_grid.cols - 1 - curwin->w_wcol; - if (fix_col != pc_col) { - grid_putchar(&curwin->w_grid, ' ', pc_row, fix_col, attr); + if (grid_line_getchar(pc_col, NULL) == NUL) { + grid_line_put_schar(pc_col - 1, schar_from_ascii(' '), attr); curwin->w_wcol--; pc_status = PC_STATUS_RIGHT; } } else { - pc_col += curwin->w_wcol; - if (grid_lefthalve(&curwin->w_grid, pc_row, pc_col)) { + pc_col = curwin->w_wcol; + + if (grid_line_getchar(pc_col + 1, NULL) == NUL) { + // pc_col is the left half of a double-width char pc_status = PC_STATUS_LEFT; } } // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { - // TODO(bfredl): save the schar_T instead - grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); + pc_schar = grid_line_getchar(pc_col, &pc_attr); pc_status = PC_STATUS_SET; } - grid_putchar(&curwin->w_grid, c, pc_row, pc_col, attr); + + char buf[MB_MAXBYTES + 1]; + grid_line_puts(pc_col, buf, utf_char2bytes(c, buf), attr); + grid_line_flush(); } } @@ -1537,7 +1540,10 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, -1, pc_row, pc_col, pc_attr); + // TODO(bfredl): this could be smarter and also handle the dubyawidth case + grid_line_start(&curwin->w_grid, pc_row); + grid_line_put_schar(pc_col, pc_schar, pc_attr); + grid_line_flush(); } } } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 4dd3f193e6..eb2e2fb1e2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -6885,7 +6885,7 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) c = -1; } else { char buf[MB_MAXBYTES + 1]; - grid_getbytes(grid, row, col, buf, NULL); + schar_get(buf, grid_getchar(grid, row, col, NULL)); c = utf_ptr2char(buf); } rettv->vval.v_number = c; @@ -6906,7 +6906,7 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } char buf[MB_MAXBYTES + 1]; - grid_getbytes(grid, row, col, buf, NULL); + schar_get(buf, grid_getchar(grid, row, col, NULL)); int pcc[MAX_MCO]; int c = utfc_ptr2char(buf, pcc); int composing_len = 0; @@ -6951,7 +6951,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr } char buf[MB_MAXBYTES + 1]; - grid_getbytes(grid, row, col, buf, NULL); + schar_get(buf, grid_getchar(grid, row, col, NULL)); rettv->vval.v_string = xstrdup(buf); } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 7a707407d2..2eab158bc4 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -201,65 +201,23 @@ static bool grid_invalid_row(ScreenGrid *grid, int row) return grid->attrs[grid->line_offset[row]] < 0; } -/// 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 (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 -/// 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->cols) > 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) { - 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 buf[MB_MAXBYTES + 1]; - - grid_puts(grid, buf, utf_char2bytes(c, buf), row, col, attr); -} - /// Get a single character directly from grid.chars into "bytes", which must /// have a size of "MB_MAXBYTES + 1". /// If "attrp" is not NULL, return the character's attribute in "*attrp". -void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp) +schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp) { grid_adjust(&grid, &row, &col); // safety check if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) { - return; + return NUL; } size_t off = grid->line_offset[row] + (size_t)col; if (attrp != NULL) { *attrp = grid->attrs[off]; } - schar_get(bytes, grid->chars[off]); + return grid->chars[off]; } static ScreenGrid *grid_line_grid = NULL; @@ -269,38 +227,6 @@ static int grid_line_maxcol = 0; static int grid_line_first = INT_MAX; static int grid_line_last = 0; -/// put string 'text' on the window grid at position 'row' and 'col', with -/// attributes 'attr', and update contents of 'grid' -/// @param textlen length of string or -1 to use strlen(text) -/// Note: only outputs within one row! -int grid_puts(ScreenGrid *grid, const char *text, int textlen, int row, int col, int attr) -{ - 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) { - abort(); - } else { - grid_line_grid = NULL; - return 0; - } - } - - int len = grid_line_puts(col, text, textlen, attr); - 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; -} - /// 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 @@ -318,6 +244,25 @@ void grid_line_start(ScreenGrid *grid, int row) grid_line_last = 0; } +/// Get present char from current rendered screen line +/// +/// This indicates what already is on screen, not the pending render buffer. +/// +/// @return char or space if out of bounds +schar_T grid_line_getchar(int col, int *attr) +{ + if (col < grid_line_maxcol) { + size_t off = grid_line_grid->line_offset[grid_line_row] + (size_t)col; + if (attr != NULL) { + *attr = grid_line_grid->attrs[off]; + } + return grid_line_grid->chars[off]; + } else { + // NUL is a very special value (right-half of double width), space is True Neutralâ„¢ + return schar_from_ascii(' '); + } +} + void grid_line_put_schar(int col, schar_T schar, int attr) { assert(grid_line_grid); @@ -331,8 +276,13 @@ void grid_line_put_schar(int col, schar_T schar, int attr) linebuf_vcol[col] = -1; } -/// like grid_puts(), but output "text[len]". When "len" is -1 output up to -/// a NUL. +/// Put string "text" at "col" position relative to the grid line from the +/// recent grid_line_start() call. +/// +/// @param textlen length of string or -1 to use strlen(text) +/// Note: only outputs within one row! +/// +/// @return number of grid cells used int grid_line_puts(int col, const char *text, int textlen, int attr) { const char *ptr = text; @@ -435,8 +385,16 @@ void grid_line_fill(int start_col, int end_col, int c, int attr) linebuf_attr[col] = attr; linebuf_vcol[col] = -1; } - grid_line_first = MIN(grid_line_first, start_col); - grid_line_last = MAX(grid_line_last, end_col); + if (start_col < end_col) { + grid_line_first = MIN(grid_line_first, start_col); + grid_line_last = MAX(grid_line_last, end_col); + } +} + +/// move the cursor to a position in a currently rendered line. +void grid_line_cursor_goto(int col) +{ + ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col); } /// End a group of grid_line_puts calls and send the screen buffer to the UI layer. @@ -500,27 +458,29 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int } for (int row = start_row; row < end_row; row++) { + int dirty_first = INT_MAX; + int dirty_last = 0; + size_t lineoff = grid->line_offset[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(grid, " ", 1, row, start_col - 1, 0); + if (start_col > 0 && grid->chars[lineoff + (size_t)start_col] == NUL) { + size_t off = lineoff + (size_t)start_col - 1; + grid->chars[off] = schar_from_ascii(' '); + grid->attrs[off] = attr; + dirty_first = start_col - 1; } - if (end_col < grid->cols - && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts(grid, " ", 1, row, end_col, 0); + if (end_col < grid->cols && grid->chars[lineoff + (size_t)end_col] == NUL) { + size_t off = lineoff + (size_t)end_col; + grid->chars[off] = schar_from_ascii(' '); + grid->attrs[off] = attr; + dirty_last = end_col + 1; } - // 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; sc = schar_from_char(c1); - size_t lineoff = grid->line_offset[row]; for (col = start_col; col < end_col; col++) { size_t off = lineoff + (size_t)col; if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { @@ -600,8 +560,6 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol endcol = grid->cols; } - grid_adjust(&grid, &row, &coloff); - // Safety check. Avoids clang warnings down the call stack. if (grid->chars == NULL || row >= grid->rows || coloff >= grid->cols) { DLOG("invalid state, skipped"); @@ -662,12 +620,8 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol // 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, max_off_to) > 1) - || (char_cells == 2 - && grid_off2cells(grid, off, max_off_to) == 1 - && grid_off2cells(grid, off + 1, max_off_to) > 1))) { + if (col + char_cells == endcol && off + (size_t)char_cells < max_off_to + && grid->chars[off + (size_t)char_cells] == NUL) { clear_next = true; } diff --git a/src/nvim/message.c b/src/nvim/message.c index 97402276b2..67266b325c 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2959,15 +2959,15 @@ void os_msg(const char *str) void msg_moremsg(int full) { - int attr; - char *s = _("-- More --"); - - attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); - grid_puts(&msg_grid_adj, s, -1, Rows - 1, 0, attr); + int attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); + grid_line_start(&msg_grid_adj, Rows - 1); + int len = grid_line_puts(0, _("-- More --"), -1, attr); if (full) { - grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), -1, - Rows - 1, vim_strsize(s), attr); + len += grid_line_puts(len, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + -1, attr); } + grid_line_cursor_goto(len); + grid_line_flush(); } /// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD, diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 1529b63812..fb467093ad 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -182,7 +182,9 @@ void win_redr_status(win_T *wp) attr = win_hl_attr(wp, HLF_C); fillchar = wp->w_p_fcs_chars.vert; } - grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr); + grid_line_start(&default_grid, W_ENDROW(wp)); + grid_line_put_schar(W_ENDCOL(wp), schar_from_char(fillchar), attr); + grid_line_flush(); } busy = false; } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 0f26e269ae..8c16380f1f 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -532,7 +532,7 @@ void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endco compose_debug(row, row + 1, startcol, clearcol, dbghl_composed, true); compose_line(row, startcol, clearcol, flags); } else { - compose_debug(row, row + 1, startcol, endcol, dbghl_normal, false); + compose_debug(row, row + 1, startcol, endcol, dbghl_normal, endcol >= clearcol); compose_debug(row, row + 1, endcol, clearcol, dbghl_clear, true); #ifndef NDEBUG for (int i = 0; i < endcol - startcol; i++) { diff --git a/src/nvim/version.c b/src/nvim/version.c index 20bbcb2f8a..c3bfad4706 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2827,7 +2827,7 @@ void intro_message(int colon) } if (*mesg != NUL) { - do_intro_line(row, mesg, 0); + do_intro_line((int)row, mesg, 0); } row++; @@ -2838,14 +2838,13 @@ void intro_message(int colon) } } -static void do_intro_line(long row, char *mesg, int attr) +static void do_intro_line(int row, char *mesg, int attr) { char *p; int l; - int clen; // Center the message horizontally. - long col = vim_strsize(mesg); + int col = vim_strsize(mesg); col = (Columns - col) / 2; @@ -2853,21 +2852,18 @@ static void do_intro_line(long row, char *mesg, int attr) col = 0; } + grid_line_start(&default_grid, row); // Split up in parts to highlight <> items differently. for (p = mesg; *p != NUL; p += l) { - clen = 0; - for (l = 0; p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); l++) { - clen += ptr2cells(p + l); l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); - grid_puts(&default_grid, p, l, (int)row, (int)col, - *p == '<' ? HL_ATTR(HLF_8) : attr); - col += clen; + col += grid_line_puts(col, p, l, *p == '<' ? HL_ATTR(HLF_8) : attr); } + grid_line_flush(); } /// ":intro": clear screen, display intro screen and wait for return. diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 46a42e5beb..8a13796c04 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1366,7 +1366,7 @@ describe('ui/ext_messages', function() feed(":intro") screen:expect{grid=[[ - | + ^ | | | |