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=[[ - | + ^ | | | |