diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 29354a4f07..f9bdf6843a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -963,17 +963,17 @@ static void print_spaces(TUIData *tui, int width) } } -/// Move cursor to the position given by `row` and `col` and print the character in `cell`. -/// This allows the grid and the host terminal to assume different widths of ambiguous-width chars. +/// Move cursor to the position given by `row` and `col` and print the char in `cell`. +/// Allows grid and host terminal to assume different widths of ambiguous-width chars. /// -/// @param is_doublewidth whether the character is double-width on the grid. -/// If true and the character is ambiguous-width, clear two cells. +/// @param is_doublewidth whether the char is double-width on the grid. +/// If true and the char is ambiguous-width, clear two cells. static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool is_doublewidth) { UGrid *grid = &tui->grid; if (grid->row == -1 && cell->data == NUL) { - // If cursor needs to repositioned and there is nothing to print, don't move cursor. + // If cursor needs repositioning and there is nothing to print, don't move cursor. return; } @@ -981,10 +981,14 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool char buf[MAX_SCHAR_SIZE]; schar_get(buf, cell->data); - bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf)); - if (is_ambiwidth && is_doublewidth) { + int c = utf_ptr2char(buf); + bool is_ambiwidth = utf_ambiguous_width(c); + if (is_doublewidth && (is_ambiwidth || utf_char2cells(c) == 1)) { + // If the server used setcellwidths() to treat a single-width char as double-width, + // it needs to be treated like an ambiguous-width char. + is_ambiwidth = true; // Clear the two screen cells. - // If the character is single-width in the host terminal it won't change the second cell. + // If the char is single-width in host terminal it won't change the second cell. update_attrs(tui, cell->attr); print_spaces(tui, 2); cursor_goto(tui, row, col); @@ -993,7 +997,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool print_cell(tui, buf, cell->attr); if (is_ambiwidth) { - // Force repositioning cursor after printing an ambiguous-width character. + // Force repositioning cursor after printing an ambiguous-width char. grid->row = -1; } } diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index e4f26d85c8..7f74bf8176 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1650,7 +1650,7 @@ describe('TUI', function() eq(expected, rv) end) - it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function() + it('allows grid to assume wider ambiwidth chars than host terminal', function() child_session:request( 'nvim_buf_set_lines', 0, @@ -1694,6 +1694,50 @@ describe('TUI', function() screen:expect(singlewidth_screen) end) + it('allows grid to assume wider non-ambiwidth chars than host terminal', function() + child_session:request( + 'nvim_buf_set_lines', + 0, + 0, + -1, + true, + { ('✓'):rep(60), ('✓'):rep(60) } + ) + child_session:request('nvim_set_option_value', 'cursorline', true, {}) + child_session:request('nvim_set_option_value', 'list', true, {}) + child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) + feed_data('gg') + local singlewidth_screen = [[ + {13:✓}{12:✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| + {12:✓✓✓✓✓✓✓✓✓✓}{15:$}{12: }| + ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓| + ✓✓✓✓✓✓✓✓✓✓{4:$} | + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]] + -- When grid assumes "✓" to be double-width but host terminal assumes it to be single-width, + -- the second cell of "✓" is a space and the attributes of the "✓" are applied to it. + local doublewidth_screen = [[ + {13:✓}{12: ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| + {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| + {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }{15:$}{12: }| + ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ {4:@@@@}| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]] + screen:expect(singlewidth_screen) + child_session:request('nvim_set_option_value', 'ambiwidth', 'double', {}) + screen:expect_unchanged() + child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2713, 0x2713, 2 } } }) + screen:expect(doublewidth_screen) + child_session:request('nvim_set_option_value', 'ambiwidth', 'single', {}) + screen:expect_unchanged() + child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2713, 0x2713, 1 } } }) + screen:expect(singlewidth_screen) + end) + it('draws correctly when cursor_address overflows #21643', function() t.skip(is_os('mac'), 'FIXME: crashes/errors on macOS') screen:try_resize(77, 855)