diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-03-17 07:26:39 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-17 07:26:39 +0800 |
commit | d114dbe9f79c1382298b04319b7ded88e95e3ee8 (patch) | |
tree | a3ed5239b72332bef8e42afa2b9ba095c74d7afe | |
parent | 34b57508a78b0a980e898ee35d950db0a90368ca (diff) | |
download | rneovim-d114dbe9f79c1382298b04319b7ded88e95e3ee8.tar.gz rneovim-d114dbe9f79c1382298b04319b7ded88e95e3ee8.tar.bz2 rneovim-d114dbe9f79c1382298b04319b7ded88e95e3ee8.zip |
vim-patch:9.1.0184: Cursor pos wrong when clicking with conceal and wrap (#27890)
Problem: Cursor position wrong when clicking with conceal and wrap.
Solution: Use the virtual column of the last char for ScreenCols[] in
boguscols. Remove use of MAXCOL in ScreenCols[]. Rename
third argument of wlv_screen_line() to "clear_end" as that's
clearer what it does (zeertzjq).
related: 14192
closes: vim/vim#14200
https://github.com/vim/vim/commit/d0c1b7723f7e73763597af2f97a53d94ab7ed020
Rename win_put_linebuf() to wlv_put_linebuf().
-rw-r--r-- | src/nvim/drawline.c | 34 | ||||
-rw-r--r-- | src/nvim/grid.c | 45 | ||||
-rw-r--r-- | src/nvim/grid.h | 8 | ||||
-rw-r--r-- | src/nvim/mouse.c | 28 | ||||
-rw-r--r-- | test/old/testdir/test_conceal.vim | 180 |
5 files changed, 170 insertions, 125 deletions
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index ffe2253cf1..816eb8a674 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -300,6 +300,7 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int if (vt->pos == kVPosEndOfLine && do_eol) { state->eol_col = col + 1; } + // TODO(zeertzjq): set values in linebuf_vcol[] *end_col = MAX(*end_col, col); } if (!vt || !(vt->flags & kVTRepeatLinebreak)) { @@ -1549,7 +1550,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s // When only updating the columns and that's done, stop here. if (col_rows > 0) { - win_put_linebuf(wp, wlv.row, wlv.off, wlv.off, bg_attr, false); + wlv_put_linebuf(wp, &wlv, wlv.off, false, bg_attr, 0); // Need to update more screen lines if: // - 'statuscolumn' needs to be drawn, or // - LineNrAbove or LineNrBelow is used, or @@ -1606,7 +1607,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && lnum == wp->w_cursor.lnum && wlv.vcol >= wp->w_virtcol) { draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); // don't clear anything after wlv.col - win_put_linebuf(wp, wlv.row, wlv.col, wlv.col, bg_attr, false); + wlv_put_linebuf(wp, &wlv, wlv.col, false, bg_attr, 0); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -2539,7 +2540,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s : wlv.char_attr; linebuf_attr[wlv.off] = eol_attr; - linebuf_vcol[wlv.off] = MAXCOL; + linebuf_vcol[wlv.off] = wlv.vcol; wlv.col++; wlv.off++; wlv.vcol++; @@ -2595,7 +2596,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s while (wlv.col < grid->cols) { linebuf_char[wlv.off] = schar_from_ascii(' '); - linebuf_vcol[wlv.off] = MAXCOL; + linebuf_vcol[wlv.off] = wlv.vcol; wlv.col++; advance_color_col(&wlv, vcol_hlc(wlv)); @@ -2638,7 +2639,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0); } draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row); - win_put_linebuf(wp, wlv.row, wlv.col, grid->cols, bg_attr, false); + // Set increasing virtual columns in grid->vcols[] to set correct curswant + // (or "coladd" for 'virtualedit') when clicking after end of line. + wlv_put_linebuf(wp, &wlv, wlv.col, true, bg_attr, SLF_INC_VCOL); wlv.row++; // Update w_cline_height and w_cline_folded if the cursor line was @@ -2869,7 +2872,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s while (draw_col < grid->cols) { linebuf_char[wlv.off] = schar_from_char(' '); linebuf_attr[wlv.off] = attr; - linebuf_vcol[wlv.off] = MAXCOL; // TODO(zeertzjq): this is wrong + linebuf_vcol[wlv.off] = wlv.vcol - 1; wlv.off++; draw_col++; } @@ -2882,7 +2885,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row); } - win_put_linebuf(wp, wlv.row, draw_col, grid->cols, bg_attr, wrap); + wlv_put_linebuf(wp, &wlv, draw_col, true, bg_attr, wrap ? SLF_WRAP : 0); if (wrap) { ScreenGrid *current_grid = grid; int current_row = wlv.row; @@ -2942,18 +2945,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s return wlv.row; } -static void win_put_linebuf(win_T *wp, int row, int endcol, int clear_width, int bg_attr, bool wrap) +/// Call grid_put_linebuf() using values from "wlv". +/// Also takes care of putting "<<<" on the first line for 'smoothscroll' +/// when 'showbreak' is not set. +/// +/// @param clear_end clear until the end of the screen line. +/// @param flags for grid_put_linebuf(), but shouldn't contain SLF_RIGHTLEFT. +static void wlv_put_linebuf(win_T *wp, const winlinevars_T *wlv, int endcol, bool clear_end, + int bg_attr, int flags) { ScreenGrid *grid = &wp->w_grid; int startcol = 0; + int clear_width = clear_end ? grid->cols : endcol; + assert(!(flags & SLF_RIGHTLEFT)); if (wp->w_p_rl) { linebuf_mirror(&startcol, &endcol, &clear_width, grid->cols); + flags |= SLF_RIGHTLEFT; } // Take care of putting "<<<" on the first line for 'smoothscroll'. - if (row == 0 && wp->w_skipcol > 0 + if (wlv->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 "<<<" @@ -2978,7 +2991,8 @@ static void win_put_linebuf(win_T *wp, int row, int endcol, int clear_width, int } } + int row = wlv->row; int coloff = 0; grid_adjust(&grid, &row, &coloff); - grid_put_linebuf(grid, row, coloff, startcol, endcol, clear_width, wp->w_p_rl, bg_attr, wrap); + grid_put_linebuf(grid, row, coloff, startcol, endcol, clear_width, bg_attr, wlv->vcol - 1, flags); } diff --git a/src/nvim/grid.c b/src/nvim/grid.c index e386853022..e911e6ad21 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -1,3 +1,6 @@ +// Low-level functions to manipulate individual character cells on the +// screen grid. +// // Most of the routines in this file perform screen (grid) manipulations. The // given operation is performed physically on the screen. The corresponding // change is also made to the internal screen image. In this way, the editor @@ -338,7 +341,7 @@ static int grid_line_first = INT_MAX; static int grid_line_last = 0; static int grid_line_clear_to = 0; static int grid_line_clear_attr = 0; -static bool grid_line_rl = false; +static int grid_line_flags = 0; /// Start a group of grid_line_puts calls that builds a single grid line. /// @@ -358,7 +361,7 @@ void grid_line_start(ScreenGrid *grid, int row) grid_line_last = 0; grid_line_clear_to = 0; grid_line_clear_attr = 0; - grid_line_rl = false; + grid_line_flags = 0; assert((size_t)grid_line_maxcol <= linebuf_size); @@ -515,7 +518,7 @@ void grid_line_mirror(void) return; } linebuf_mirror(&grid_line_first, &grid_line_last, &grid_line_clear_to, grid_line_maxcol); - grid_line_rl = true; + grid_line_flags |= SLF_RIGHTLEFT; } void linebuf_mirror(int *firstp, int *lastp, int *clearp, int maxcol) @@ -568,7 +571,7 @@ void grid_line_flush(void) } grid_put_linebuf(grid, grid_line_row, grid_line_coloff, grid_line_first, grid_line_last, - grid_line_clear_to, grid_line_rl, grid_line_clear_attr, false); + grid_line_clear_to, grid_line_clear_attr, -1, grid_line_flags); } /// flush grid line but only if on a valid row @@ -619,17 +622,21 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int /// 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. -/// "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 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. +/// +/// @param coloff gives the first column on the grid for this line. +/// @param endcol gives the columns where valid characters are. +/// @param clear_width see SLF_RIGHTLEFT. +/// @param flags can have bits: +/// - SLF_RIGHTLEFT rightleft text, like a window with 'rightleft' option set: +/// - When false, clear columns "endcol" to "clear_width". +/// - When true, clear columns "col" to "endcol". +/// - SLF_WRAP hint to UI that "row" contains a line wrapped into the next row. +/// - SLF_INC_VCOL: +/// - When false, use "last_vcol" for grid->vcols[] of the columns to clear. +/// - When true, use an increasing sequence starting from "last_vcol + 1" for +/// grid->vcols[] of the columns to clear. void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width, - bool rl, int bg_attr, bool wrap) + int bg_attr, colnr_T last_vcol, int flags) { bool redraw_next; // redraw_this for next character bool clear_next = false; @@ -659,7 +666,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol } int clear_start = endcol; - if (rl) { + if (flags & SLF_RIGHTLEFT) { clear_start = col; col = endcol; endcol = clear_width; @@ -758,17 +765,17 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol } clear_end = col + 1; } - grid->vcols[off] = MAXCOL; + grid->vcols[off] = (flags & SLF_INC_VCOL) ? ++last_vcol : last_vcol; col++; } - if (rl && start_dirty != -1 && clear_dirty_start != -1) { + if ((flags & SLF_RIGHTLEFT) && start_dirty != -1 && clear_dirty_start != -1) { if (grid->throttled || clear_dirty_start >= start_dirty - 5) { // cannot draw now or too small to be worth a separate "clear" event start_dirty = clear_dirty_start; } else { ui_line(grid, row, invalid_row, coloff + clear_dirty_start, coloff + clear_dirty_start, - coloff + clear_end, bg_attr, wrap); + coloff + clear_end, bg_attr, flags & SLF_WRAP); } clear_end = end_dirty; } else { @@ -785,7 +792,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol if (clear_end > start_dirty) { if (!grid->throttled) { ui_line(grid, row, invalid_row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end, - bg_attr, wrap); + bg_attr, flags & SLF_WRAP); } else if (grid->dirty_col) { // TODO(bfredl): really get rid of the extra pseudo terminal in message.c // by using a linebuf_char copy for "throttled message line" diff --git a/src/nvim/grid.h b/src/nvim/grid.h index 7506f0fc9d..25144cdc2c 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -27,8 +27,12 @@ EXTERN sattr_T *linebuf_attr INIT( = NULL); EXTERN colnr_T *linebuf_vcol INIT( = NULL); EXTERN char *linebuf_scratch INIT( = NULL); -// Low-level functions to manipulate individual character cells on the -// screen grid. +/// flags for grid_put_linebuf() +enum { + SLF_RIGHTLEFT = 1, + SLF_WRAP = 2, + SLF_INC_VCOL = 4, +}; /// Put a ASCII character in a screen cell. /// diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d82ba58918..a2b4042f30 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1883,33 +1883,7 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp) const size_t off = gp->line_offset[click_row] + (size_t)click_col; colnr_T col_from_screen = gp->vcols[off]; - if (col_from_screen == MAXCOL) { - // When clicking after end of line, still need to set correct curswant - size_t off_l = gp->line_offset[click_row] + (size_t)start_col; - if (gp->vcols[off_l] < MAXCOL) { - // Binary search to find last char in line - size_t off_r = off; - while (off_l < off_r) { - size_t off_m = (off_l + off_r + 1) / 2; - if (gp->vcols[off_m] < MAXCOL) { - off_l = off_m; - } else { - off_r = off_m - 1; - } - } - colnr_T eol_vcol = gp->vcols[off_r]; - assert(eol_vcol < MAXCOL); - if (eol_vcol < 0) { - // Empty line or whole line before w_leftcol, - // with columns before buffer text - eol_vcol = curwin->w_leftcol - 1; - } - *vcolp = eol_vcol + (int)(off - off_r); - } else { - // Empty line or whole line before w_leftcol - *vcolp = click_col - start_col + curwin->w_leftcol; - } - } else if (col_from_screen >= 0) { + if (col_from_screen >= 0) { // Use the virtual column from vcols[], it is accurate also after // concealed characters. *vcolp = col_from_screen; diff --git a/test/old/testdir/test_conceal.vim b/test/old/testdir/test_conceal.vim index 52b0661f85..a03ec21e79 100644 --- a/test/old/testdir/test_conceal.vim +++ b/test/old/testdir/test_conceal.vim @@ -387,77 +387,123 @@ func Test_conceal_eol() endfunc func Test_conceal_mouse_click() - enew! + call NewWindow(10, 40) set mouse=a setlocal conceallevel=2 concealcursor=nc syn match Concealed "this" conceal hi link Concealed Search - call setline(1, 'conceal this click here') - redraw - call assert_equal(['conceal click here '], ScreenLines(1, 20)) - - " click on the space between "this" and "click" puts cursor there - call Ntest_setmouse(1, 9) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 13, 0, 13], getcurpos()) - " click on 'h' of "here" puts cursor there - call Ntest_setmouse(1, 16) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 20, 0, 20], getcurpos()) - " click on 'e' of "here" puts cursor there - call Ntest_setmouse(1, 19) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 23], getcurpos()) - " click after end of line puts cursor on 'e' without 'virtualedit' - call Ntest_setmouse(1, 20) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 24], getcurpos()) - call Ntest_setmouse(1, 21) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 25], getcurpos()) - call Ntest_setmouse(1, 22) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 26], getcurpos()) - call Ntest_setmouse(1, 31) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 35], getcurpos()) - call Ntest_setmouse(1, 32) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 36], getcurpos()) - - set virtualedit=all - redraw - " click on the space between "this" and "click" puts cursor there - call Ntest_setmouse(1, 9) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 13, 0, 13], getcurpos()) - " click on 'h' of "here" puts cursor there - call Ntest_setmouse(1, 16) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 20, 0, 20], getcurpos()) - " click on 'e' of "here" puts cursor there - call Ntest_setmouse(1, 19) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 23, 0, 23], getcurpos()) - " click after end of line puts cursor there with 'virtualedit' - call Ntest_setmouse(1, 20) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 24, 0, 24], getcurpos()) - call Ntest_setmouse(1, 21) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 24, 1, 25], getcurpos()) - call Ntest_setmouse(1, 22) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 24, 2, 26], getcurpos()) - call Ntest_setmouse(1, 31) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 24, 11, 35], getcurpos()) - call Ntest_setmouse(1, 32) - call feedkeys("\<LeftMouse>", "tx") - call assert_equal([0, 1, 24, 12, 36], getcurpos()) - - bwipe! - set mouse& virtualedit& + + " Test with both 'nocursorline' and 'cursorline', as they use two different + " code paths to set virtual columns for the cells to clear. + for cul in [v:false, v:true] + let &l:cursorline = cul + + call setline(1, 'conceal this click here') + call assert_equal([ + \ 'conceal click here ', + \ ], ScreenLines(1, 40)) + + " Click on the space between "this" and "click" puts cursor there. + call Ntest_setmouse(1, 9) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 13, 0, 13], getcurpos()) + " Click on 'h' of "here" puts cursor there. + call Ntest_setmouse(1, 16) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 20, 0, 20], getcurpos()) + " Click on 'e' of "here" puts cursor there. + call Ntest_setmouse(1, 19) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 23], getcurpos()) + " Click after end of line puts cursor on 'e' without 'virtualedit'. + call Ntest_setmouse(1, 20) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 24], getcurpos()) + call Ntest_setmouse(1, 21) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 25], getcurpos()) + call Ntest_setmouse(1, 22) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 26], getcurpos()) + call Ntest_setmouse(1, 31) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 35], getcurpos()) + call Ntest_setmouse(1, 32) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 36], getcurpos()) + + set virtualedit=all + redraw + " Click on the space between "this" and "click" puts cursor there. + call Ntest_setmouse(1, 9) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 13, 0, 13], getcurpos()) + " Click on 'h' of "here" puts cursor there. + call Ntest_setmouse(1, 16) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 20, 0, 20], getcurpos()) + " Click on 'e' of "here" puts cursor there. + call Ntest_setmouse(1, 19) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 23, 0, 23], getcurpos()) + " Click after end of line puts cursor there with 'virtualedit'. + call Ntest_setmouse(1, 20) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 24, 0, 24], getcurpos()) + call Ntest_setmouse(1, 21) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 24, 1, 25], getcurpos()) + call Ntest_setmouse(1, 22) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 24, 2, 26], getcurpos()) + call Ntest_setmouse(1, 31) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 24, 11, 35], getcurpos()) + call Ntest_setmouse(1, 32) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 24, 12, 36], getcurpos()) + set virtualedit& + + " Test with a wrapped line. + call setline(1, ['conceal this click here']->repeat(3)->join()) + call assert_equal([ + \ 'conceal click here conceal cli ', + \ 'ck here conceal click here ', + \ ], ScreenLines([1, 2], 40)) + " Click on boguscols puts cursor on the last char of a screen line. + for col in range(33, 40) + call Ntest_setmouse(1, col) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 40, 0, 40], getcurpos()) + endfor + + " Also test with the last char of a screen line concealed. + setlocal number signcolumn=yes + call assert_equal([ + \ ' 1 conceal click here conceal ', + \ ' click here conceal click h ', + \ ' ere ', + \ ], ScreenLines([1, 3], 40)) + call Ntest_setmouse(1, 34) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 32, 0, 32], getcurpos()) + call Ntest_setmouse(2, 7) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 37, 0, 37], getcurpos()) + " Click on boguscols puts cursor on the last char of a screen line. + for col in range(35, 40) + call Ntest_setmouse(1, col) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 34, 0, 34], getcurpos()) + call Ntest_setmouse(2, col) + call feedkeys("\<LeftMouse>", "tx") + call assert_equal([0, 1, 68, 0, 68], getcurpos()) + endfor + setlocal number& signcolumn& + endfor + + call CloseWindow() + set mouse& endfunc " Test that cursor is drawn at the correct column when it is after end of the |