vim-patch:9.0.1725: cursor pos wrong after concealed text with 'virtualedit'

Problem:    Wrong cursor position when clicking after concealed text
            with 'virtualedit'.
Solution:   Store virtual columns in ScreenCols[] instead of text
            columns, and always use coladvance() when clicking.

This also fixes incorrect curswant when clicking on a TAB, so now
Test_normal_click_on_ctrl_char() asserts the same results as the ones
before patch 9.0.0048.

closes: vim/vim#12808

e500ae8e29

Remove the mouse_adjust_click() function.

There is a difference in behavior with the old mouse_adjust_click()
approach: when clicking on the character immediately after concealed
text that is completely hidden, cursor is put on the clicked character
rather than at the start of the concealed text. The new behavior is
better, but it causes unnecessary scrolling in a functional test (which
is an existing issue unrelated to these patches), so adjust the test.

Now fully merged:
vim-patch:9.0.0177: cursor position wrong with 'virtualedit' and mouse click
This commit is contained in:
zeertzjq
2023-08-18 15:00:03 +08:00
parent b0dda500e9
commit 551998b7ee
9 changed files with 162 additions and 146 deletions

View File

@@ -2022,6 +2022,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
c = NUL; c = NUL;
} else { } else {
int c0; int c0;
char *prev_ptr = ptr;
// Get a character from the line itself. // Get a character from the line itself.
c0 = c = (uint8_t)(*ptr); c0 = c = (uint8_t)(*ptr);
@@ -2209,7 +2210,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
v = (ptr - line); v = (ptr - line);
if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) { if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0; spell_attr = 0;
char *prev_ptr = ptr - mb_l;
// do not calculate cap_col at the end of the line or when // do not calculate cap_col at the end of the line or when
// only white space is following // only white space is following
if (c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) { if (c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
@@ -2746,6 +2746,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
eol_attr = hl_combine_attr(wlv.cul_attr, eol_attr); eol_attr = hl_combine_attr(wlv.cul_attr, eol_attr);
} }
linebuf_attr[wlv.off] = eol_attr; linebuf_attr[wlv.off] = eol_attr;
linebuf_vcol[wlv.off] = MAXCOL;
if (wp->w_p_rl) { if (wp->w_p_rl) {
wlv.col--; wlv.col--;
wlv.off--; wlv.off--;
@@ -2832,6 +2833,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) { while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) {
schar_from_ascii(linebuf_char[wlv.off], ' '); schar_from_ascii(linebuf_char[wlv.off], ' ');
linebuf_vcol[wlv.off] = MAXCOL;
wlv.col += col_stride; wlv.col += col_stride;
if (draw_color_col) { if (draw_color_col) {
draw_color_col = advance_color_col(VCOL_HLC, &color_cols); draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
@@ -2866,6 +2868,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
while (wlv.col >= 0 && wlv.col < grid->cols) { while (wlv.col >= 0 && wlv.col < grid->cols) {
schar_from_ascii(linebuf_char[wlv.off], ' '); schar_from_ascii(linebuf_char[wlv.off], ' ');
linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol]; linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol];
linebuf_vcol[wlv.off] = wlv.vcol;
wlv.off += n; wlv.off += n;
wlv.vcol += n; wlv.vcol += n;
wlv.col += n; wlv.col += n;
@@ -2945,9 +2948,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// Skip characters that are left of the screen for 'nowrap'. // Skip characters that are left of the screen for 'nowrap'.
vcol_prev = wlv.vcol; vcol_prev = wlv.vcol;
if (wlv.draw_state < WL_LINE || n_skip <= 0) { if (wlv.draw_state < WL_LINE || n_skip <= 0) {
//
// Store the character. // Store the character.
//
if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
// A double-wide character is: put first half in left cell. // A double-wide character is: put first half in left cell.
wlv.off--; wlv.off--;
@@ -2965,6 +2966,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
linebuf_attr[wlv.off] = wlv.char_attr; linebuf_attr[wlv.off] = wlv.char_attr;
} }
linebuf_vcol[wlv.off] = wlv.vcol;
if (utf_char2cells(mb_c) > 1) { if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns. // Need to fill two screen columns.
wlv.off++; wlv.off++;
@@ -2980,6 +2983,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
if (wlv.tocol == wlv.vcol) { if (wlv.tocol == wlv.vcol) {
wlv.tocol++; wlv.tocol++;
} }
linebuf_vcol[wlv.off] = wlv.vcol;
if (wp->w_p_rl) { if (wp->w_p_rl) {
// now it's time to backup one cell // now it's time to backup one cell
wlv.off--; wlv.off--;

View File

@@ -78,6 +78,7 @@ void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
} }
int fill = valid ? 0 : -1; int fill = valid ? 0 : -1;
(void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
(void)memset(grid->vcols + off, -1, (size_t)width * sizeof(colnr_T));
} }
void grid_invalidate(ScreenGrid *grid) void grid_invalidate(ScreenGrid *grid)
@@ -196,6 +197,7 @@ void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr)
// TODO(bfredl): Y U NO DOUBLEWIDTH? // TODO(bfredl): Y U NO DOUBLEWIDTH?
put_dirty_last = MAX(put_dirty_last, col + 1); put_dirty_last = MAX(put_dirty_last, 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
@@ -324,9 +326,11 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int
schar_copy(grid->chars[off], buf); schar_copy(grid->chars[off], buf);
grid->attrs[off] = attr; grid->attrs[off] = attr;
grid->vcols[off] = -1;
if (mbyte_cells == 2) { if (mbyte_cells == 2) {
grid->chars[off + 1][0] = 0; grid->chars[off + 1][0] = 0;
grid->attrs[off + 1] = attr; grid->attrs[off + 1] = attr;
grid->vcols[off + 1] = -1;
} }
put_dirty_first = MIN(put_dirty_first, col); put_dirty_first = MIN(put_dirty_first, col);
put_dirty_last = MAX(put_dirty_last, col + mbyte_cells); put_dirty_last = MAX(put_dirty_last, col + mbyte_cells);
@@ -437,6 +441,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
} }
dirty_last = col + 1; dirty_last = col + 1;
} }
grid->vcols[off] = -1;
if (col == start_col) { if (col == start_col) {
schar_from_char(sc, c2); schar_from_char(sc, c2);
} }
@@ -620,13 +625,20 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
} }
grid->attrs[off_to] = linebuf_attr[off_from]; grid->attrs[off_to] = linebuf_attr[off_from];
grid->vcols[off_to] = linebuf_vcol[off_from];
// 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_to + 1] = linebuf_attr[off_from];
grid->vcols[off_to + 1] = linebuf_vcol[off_from + 1];
} }
} }
grid->vcols[off_to] = linebuf_vcol[off_from];
if (char_cells == 2) {
grid->vcols[off_to + 1] = linebuf_vcol[off_from];
}
off_to += (size_t)char_cells; off_to += (size_t)char_cells;
off_from += (size_t)char_cells; off_from += (size_t)char_cells;
col += char_cells; col += char_cells;
@@ -659,6 +671,7 @@ 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;
col++; col++;
off_to++; off_to++;
} }
@@ -690,6 +703,8 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
size_t ncells = (size_t)rows * (size_t)columns; size_t ncells = (size_t)rows * (size_t)columns;
ngrid.chars = xmalloc(ncells * sizeof(schar_T)); ngrid.chars = xmalloc(ncells * sizeof(schar_T));
ngrid.attrs = xmalloc(ncells * sizeof(sattr_T)); ngrid.attrs = xmalloc(ncells * sizeof(sattr_T));
ngrid.vcols = xmalloc(ncells * sizeof(colnr_T));
memset(ngrid.vcols, -1, ncells * sizeof(colnr_T));
ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset)); ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset));
ngrid.line_wraps = xmalloc((size_t)rows * sizeof(*ngrid.line_wraps)); ngrid.line_wraps = xmalloc((size_t)rows * sizeof(*ngrid.line_wraps));
@@ -715,6 +730,9 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
memmove(ngrid.attrs + ngrid.line_offset[new_row], memmove(ngrid.attrs + ngrid.line_offset[new_row],
grid->attrs + grid->line_offset[new_row], grid->attrs + grid->line_offset[new_row],
(size_t)len * sizeof(sattr_T)); (size_t)len * sizeof(sattr_T));
memmove(ngrid.vcols + ngrid.line_offset[new_row],
grid->vcols + grid->line_offset[new_row],
(size_t)len * sizeof(colnr_T));
} }
} }
} }
@@ -726,8 +744,10 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
if (linebuf_size < (size_t)columns) { if (linebuf_size < (size_t)columns) {
xfree(linebuf_char); xfree(linebuf_char);
xfree(linebuf_attr); xfree(linebuf_attr);
xfree(linebuf_vcol);
linebuf_char = xmalloc((size_t)columns * sizeof(schar_T)); linebuf_char = xmalloc((size_t)columns * sizeof(schar_T));
linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T)); linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T));
linebuf_vcol = xmalloc((size_t)columns * sizeof(colnr_T));
linebuf_size = (size_t)columns; linebuf_size = (size_t)columns;
} }
} }
@@ -736,11 +756,13 @@ void grid_free(ScreenGrid *grid)
{ {
xfree(grid->chars); xfree(grid->chars);
xfree(grid->attrs); xfree(grid->attrs);
xfree(grid->vcols);
xfree(grid->line_offset); xfree(grid->line_offset);
xfree(grid->line_wraps); xfree(grid->line_wraps);
grid->chars = NULL; grid->chars = NULL;
grid->attrs = NULL; grid->attrs = NULL;
grid->vcols = NULL;
grid->line_offset = NULL; grid->line_offset = NULL;
grid->line_wraps = NULL; grid->line_wraps = NULL;
} }
@@ -751,6 +773,7 @@ void grid_free_all_mem(void)
grid_free(&default_grid); grid_free(&default_grid);
xfree(linebuf_char); xfree(linebuf_char);
xfree(linebuf_attr); xfree(linebuf_attr);
xfree(linebuf_vcol);
} }
/// (Re)allocates a window grid if size changed while in ext_multigrid mode. /// (Re)allocates a window grid if size changed while in ext_multigrid mode.
@@ -939,6 +962,7 @@ static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T)); memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T)); memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
memmove(grid->vcols + off_to, grid->vcols + off_from, (size_t)width * sizeof(colnr_T));
} }
win_T *get_win_by_grid_handle(handle_T handle) win_T *get_win_by_grid_handle(handle_T handle)

View File

@@ -27,6 +27,7 @@ EXTERN bool resizing_screen INIT(= 0);
EXTERN schar_T *linebuf_char INIT(= NULL); EXTERN schar_T *linebuf_char INIT(= NULL);
EXTERN sattr_T *linebuf_attr INIT(= NULL); EXTERN sattr_T *linebuf_attr INIT(= NULL);
EXTERN colnr_T *linebuf_vcol INIT(= NULL);
// Low-level functions to manipulate individual character cells on the // Low-level functions to manipulate individual character cells on the
// screen grid. // screen grid.

View File

@@ -5,6 +5,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "nvim/pos.h"
#include "nvim/types.h" #include "nvim/types.h"
#define MAX_MCO 6 // fixed value for 'maxcombine' #define MAX_MCO 6 // fixed value for 'maxcombine'
@@ -37,9 +38,14 @@ enum {
/// screen is cleared, the cells should be filled with a single whitespace char. /// screen is cleared, the cells should be filled with a single whitespace char.
/// ///
/// attrs[] contains the highlighting attribute for each cell. /// attrs[] contains the highlighting attribute for each cell.
/// line_offset[n] is the offset from chars[] and attrs[] for the ///
/// start of line 'n'. These offsets are in general not linear, as full screen /// vcols[] countain the virtual columns in the line. -1 means not available
/// scrolling is implemented by rotating the offsets in the line_offset array. /// (below last line), MAXCOL means after the end of the line.
///
/// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start
/// of line 'n'. These offsets are in general not linear, as full screen scrolling
/// is implemented by rotating the offsets in the line_offset array.
///
/// line_wraps[] is an array of boolean flags indicating if the screen line /// line_wraps[] is an array of boolean flags indicating if the screen line
/// wraps to the next line. It can only be true if a window occupies the entire /// wraps to the next line. It can only be true if a window occupies the entire
/// screen width. /// screen width.
@@ -49,6 +55,7 @@ struct ScreenGrid {
schar_T *chars; schar_T *chars;
sattr_T *attrs; sattr_T *attrs;
colnr_T *vcols;
size_t *line_offset; size_t *line_offset;
char *line_wraps; char *line_wraps;
@@ -106,7 +113,7 @@ struct ScreenGrid {
bool comp_disabled; bool comp_disabled;
}; };
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
false, 0, 0, NULL, false, true, 0, \ false, 0, 0, NULL, false, true, 0, \
0, 0, 0, 0, 0, false } 0, 0, 0, 0, 0, false }

View File

@@ -1329,15 +1329,15 @@ retnomove:
} }
} }
colnr_T col_from_screen = -1;
int mouse_fold_flags = 0;
mouse_check_grid(&col_from_screen, &mouse_fold_flags);
// compute the position in the buffer line from the posn on the screen // compute the position in the buffer line from the posn on the screen
if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) {
mouse_past_bottom = true; mouse_past_bottom = true;
} }
if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) {
col = mouse_adjust_click(curwin, row, col);
}
// Start Visual mode before coladvance(), for when 'sel' != "old" // Start Visual mode before coladvance(), for when 'sel' != "old"
if ((flags & MOUSE_MAY_VIS) && !VIsual_active) { if ((flags & MOUSE_MAY_VIS) && !VIsual_active) {
VIsual = old_cursor; VIsual = old_cursor;
@@ -1352,6 +1352,10 @@ retnomove:
} }
} }
if (col_from_screen >= 0) {
col = col_from_screen;
}
curwin->w_curswant = col; curwin->w_curswant = col;
curwin->w_set_curswant = false; // May still have been true curwin->w_set_curswant = false; // May still have been true
if (coladvance(col) == FAIL) { // Mouse click beyond end of line if (coladvance(col) == FAIL) { // Mouse click beyond end of line
@@ -1369,14 +1373,14 @@ retnomove:
count |= CURSOR_MOVED; // Cursor has moved count |= CURSOR_MOVED; // Cursor has moved
} }
count |= mouse_check_fold(); count |= mouse_fold_flags;
return count; return count;
} }
// Compute the position in the buffer line from the posn on the screen in /// Compute the position in the buffer line from the posn on the screen in
// window "win". /// window "win".
// Returns true if the position is below the last line. /// Returns true if the position is below the last line.
bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
{ {
int col = *colp; int col = *colp;
@@ -1573,9 +1577,7 @@ static void set_mouse_topline(win_T *wp)
orig_topfill = wp->w_topfill; orig_topfill = wp->w_topfill;
} }
///
/// Return length of line "lnum" for horizontal scrolling. /// Return length of line "lnum" for horizontal scrolling.
///
static colnr_T scroll_line_len(linenr_T lnum) static colnr_T scroll_line_len(linenr_T lnum)
{ {
colnr_T col = 0; colnr_T col = 0;
@@ -1663,116 +1665,9 @@ bool mouse_scroll_horiz(int dir)
return set_leftcol(leftcol); return set_leftcol(leftcol);
} }
/// Adjusts the clicked column position when 'conceallevel' > 0 /// Check clicked cell on its grid
static int mouse_adjust_click(win_T *wp, int row, int col) static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
{ FUNC_ATTR_NONNULL_ALL
if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0
&& wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) {
return col;
}
// `col` is the position within the current line that is highlighted by the
// cursor without consideration for concealed characters. The current line is
// scanned *up to* `col`, nudging it left or right when concealed characters
// are encountered.
//
// win_chartabsize() is used to keep track of the virtual column position
// relative to the line's bytes. For example: if col == 9 and the line
// starts with a tab that's 8 columns wide, we would want the cursor to be
// highlighting the second byte, not the ninth.
linenr_T lnum = wp->w_cursor.lnum;
// Make a copy of the line, because syntax matching may free it.
char *line = xstrdup(ml_get(lnum));
char *ptr = line;
char *ptr_end;
char *ptr_row_offset = line; // Where we begin adjusting `ptr_end`
// Find the offset where scanning should begin.
int offset = wp->w_leftcol;
if (row > 0) {
offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) -
wp->w_leftcol + wp->w_skipcol);
}
int vcol;
if (offset) {
// Skip everything up to an offset since nvim takes care of displaying the
// correct portion of the line when horizontally scrolling.
// When 'wrap' is enabled, only the row (of the wrapped line) needs to be
// checked for concealed characters.
vcol = 0;
while (vcol < offset && *ptr != NUL) {
vcol += win_chartabsize(curwin, ptr, vcol);
ptr += utfc_ptr2len(ptr);
}
ptr_row_offset = ptr;
}
// Align `ptr_end` with `col`
vcol = offset;
ptr_end = ptr_row_offset;
while (vcol < col && *ptr_end != NUL) {
vcol += win_chartabsize(curwin, ptr_end, vcol);
ptr_end += utfc_ptr2len(ptr_end);
}
int prev_matchid;
int nudge = 0;
vcol = offset;
#define INCR() nudge++; ptr_end += utfc_ptr2len(ptr_end)
#define DECR() nudge--; ptr_end -= utfc_ptr2len(ptr_end)
while (ptr < ptr_end && *ptr != NUL) {
int cwidth = win_chartabsize(curwin, ptr, vcol);
vcol += cwidth;
if (cwidth > 1 && *ptr == '\t' && nudge > 0) {
// A tab will "absorb" any previous adjustments.
cwidth = MIN(cwidth, nudge);
while (cwidth > 0) {
DECR();
cwidth--;
}
}
int matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
if (matchid != 0) {
if (wp->w_p_cole == 3) {
INCR();
} else {
if (!(row > 0 && ptr == ptr_row_offset)
&& (wp->w_p_cole == 1 || (wp->w_p_cole == 2
&& (wp->w_p_lcs_chars.conceal != NUL
|| syn_get_sub_char() != NUL)))) {
// At least one placeholder character will be displayed.
DECR();
}
prev_matchid = matchid;
while (prev_matchid == matchid && *ptr != NUL) {
INCR();
ptr += utfc_ptr2len(ptr);
matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
}
continue;
}
}
ptr += utfc_ptr2len(ptr);
}
xfree(line);
return col + nudge;
}
// Check clicked cell is foldcolumn
int mouse_check_fold(void)
{ {
int click_grid = mouse_grid; int click_grid = mouse_grid;
int click_row = mouse_row; int click_row = mouse_row;
@@ -1780,7 +1675,8 @@ int mouse_check_fold(void)
int mouse_char = ' '; int mouse_char = ' ';
int max_row = Rows; int max_row = Rows;
int max_col = Columns; int max_col = Columns;
int multigrid = ui_has(kUIMultigrid); bool multigrid = ui_has(kUIMultigrid);
colnr_T col_from_screen = -1;
win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col);
if (wp && multigrid) { if (wp && multigrid) {
@@ -1792,14 +1688,46 @@ int mouse_check_fold(void)
&& mouse_col >= 0 && mouse_col < max_col) { && mouse_col >= 0 && mouse_col < max_col) {
ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
int fdc = win_fdccol_count(wp); int fdc = win_fdccol_count(wp);
int row = multigrid && mouse_grid == 0 ? click_row : mouse_row; int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
int col = multigrid && mouse_grid == 0 ? click_col : mouse_col; int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
if (gp->chars != NULL) {
const size_t off = gp->line_offset[use_row] + (size_t)use_col;
// Only use vcols[] after the window was redrawn. Mainly matters
// for tests, a user would not click before redrawing.
if (wp->w_redr_type == 0) {
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[use_row];
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;
}
}
*vcolp = gp->vcols[off_r] + (int)(off - off_r);
} else {
// Shouldn't normally happen
*vcolp = MAXCOL;
}
} else if (col_from_screen >= 0) {
// Use the virtual column from vcols[], it is accurate also after
// concealed characters.
*vcolp = col_from_screen;
}
// Remember the character under the mouse, might be one of foldclose or // Remember the character under the mouse, might be one of foldclose or
// foldopen fillchars in the fold column. // foldopen fillchars in the fold column.
if (gp->chars != NULL) { mouse_char = utf_ptr2char((char *)gp->chars[off]);
mouse_char = utf_ptr2char((char *)gp->chars[gp->line_offset[row]
+ (unsigned)col]);
} }
// Check for position outside of the fold column. // Check for position outside of the fold column.
@@ -1810,10 +1738,8 @@ int mouse_check_fold(void)
} }
if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
return MOUSE_FOLD_OPEN; *flagsp |= MOUSE_FOLD_OPEN;
} else if (mouse_char != ' ') { } else if (mouse_char != ' ') {
return MOUSE_FOLD_CLOSE; *flagsp |= MOUSE_FOLD_CLOSE;
} }
return 0;
} }

View File

@@ -1089,7 +1089,6 @@ describe('ui/mouse/input', function()
command([[setlocal concealcursor=ni nowrap shiftwidth=2 tabstop=4 list listchars=tab:>-]]) command([[setlocal concealcursor=ni nowrap shiftwidth=2 tabstop=4 list listchars=tab:>-]])
command([[syntax region X0 matchgroup=X1 start=/\*/ end=/\*/ concealends contains=X2]]) command([[syntax region X0 matchgroup=X1 start=/\*/ end=/\*/ concealends contains=X2]])
command([[syntax match X2 /cats/ conceal cchar=X contained]]) command([[syntax match X2 /cats/ conceal cchar=X contained]])
-- No heap-use-after-free with multi-line syntax pattern #24317
command([[syntax match X3 /\n\@<=x/ conceal cchar=>]]) command([[syntax match X3 /\n\@<=x/ conceal cchar=>]])
command([[highlight link X0 Normal]]) command([[highlight link X0 Normal]])
command([[highlight link X1 NonText]]) command([[highlight link X1 NonText]])
@@ -1497,7 +1496,6 @@ describe('ui/mouse/input', function()
]]) ]])
end) -- level 2 - wrapped end) -- level 2 - wrapped
it('(level 3) click on non-wrapped lines', function() it('(level 3) click on non-wrapped lines', function()
feed_command('let &conceallevel=3', 'echo') feed_command('let &conceallevel=3', 'echo')
@@ -1535,6 +1533,7 @@ describe('ui/mouse/input', function()
]]) ]])
feed('<esc><LeftMouse><20,2>') feed('<esc><LeftMouse><20,2>')
feed('zH') -- FIXME: unnecessary horizontal scrolling
screen:expect([[ screen:expect([[
Section{0:>>--->--->---}t1 | Section{0:>>--->--->---}t1 |
{0:>--->--->---} t2 t3 t4 | {0:>--->--->---} t2 t3 t4 |

View File

@@ -349,9 +349,56 @@ func Test_conceal_mouse_click()
call Ntest_setmouse(1, 16) call Ntest_setmouse(1, 16)
call feedkeys("\<LeftMouse>", "tx") call feedkeys("\<LeftMouse>", "tx")
call assert_equal([0, 1, 20, 0, 20], getcurpos()) 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 " Nvim: redraw_for_cursorcolumn() redraws for conceal
" 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 without '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! bwipe!
set mouse& set mouse& virtualedit&
endfunc endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -4062,13 +4062,13 @@ func Test_normal_click_on_ctrl_char()
call assert_equal([0, 1, 1, 0, 1], getcurpos()) call assert_equal([0, 1, 1, 0, 1], getcurpos())
call Ntest_setmouse(1, 2) call Ntest_setmouse(1, 2)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 2, 0, 8], getcurpos()) call assert_equal([0, 1, 2, 0, 2], getcurpos())
call Ntest_setmouse(1, 3) call Ntest_setmouse(1, 3)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 2, 0, 8], getcurpos()) call assert_equal([0, 1, 2, 0, 3], getcurpos())
call Ntest_setmouse(1, 7) call Ntest_setmouse(1, 7)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 2, 0, 8], getcurpos()) call assert_equal([0, 1, 2, 0, 7], getcurpos())
call Ntest_setmouse(1, 8) call Ntest_setmouse(1, 8)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 2, 0, 8], getcurpos()) call assert_equal([0, 1, 2, 0, 8], getcurpos())
@@ -4080,13 +4080,13 @@ func Test_normal_click_on_ctrl_char()
call assert_equal([0, 1, 4, 0, 10], getcurpos()) call assert_equal([0, 1, 4, 0, 10], getcurpos())
call Ntest_setmouse(1, 11) call Ntest_setmouse(1, 11)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 4, 0, 10], getcurpos()) call assert_equal([0, 1, 4, 0, 11], getcurpos())
call Ntest_setmouse(1, 12) call Ntest_setmouse(1, 12)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 5, 0, 12], getcurpos()) call assert_equal([0, 1, 5, 0, 12], getcurpos())
call Ntest_setmouse(1, 13) call Ntest_setmouse(1, 13)
call feedkeys("\<LeftMouse>", 'xt') call feedkeys("\<LeftMouse>", 'xt')
call assert_equal([0, 1, 5, 0, v:maxcol], getcurpos()) call assert_equal([0, 1, 5, 0, 13], getcurpos())
bwipe! bwipe!
let &mouse = save_mouse let &mouse = save_mouse

View File

@@ -586,6 +586,12 @@ func Test_virtualedit_mouse()
call Ntest_setmouse(1, 9) call Ntest_setmouse(1, 9)
call feedkeys("\<LeftMouse>", "xt") call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 6, 0, 9], getcurpos()) call assert_equal([0, 1, 6, 0, 9], getcurpos())
call Ntest_setmouse(1, 12)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 9, 0, 12], getcurpos())
call Ntest_setmouse(1, 13)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 10, 0, 13], getcurpos())
call Ntest_setmouse(1, 15) call Ntest_setmouse(1, 15)
call feedkeys("\<LeftMouse>", "xt") call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 10, 2, 15], getcurpos()) call assert_equal([0, 1, 10, 2, 15], getcurpos())