mirror of
https://github.com/neovim/neovim.git
synced 2026-04-19 22:10:45 +00:00
fix(marks): wrong line('w$', win) with conceal_lines (#37047)
Background:
Suppose a window has concealed lines, and sets conceallevel>2,
concealcursor="". The concealed lines are displayed if the window is
curwin and the cursor is on the those lines.
Problem:
line('w$', win) switches curwin to win, and then does validate_botline
for curwin. It computes botline assuming the concealed lines displayed,
resulting in a smaller value than the actual botline that the user sees.
Solution:
Evaluate line('w$', win) without switching curwin.
Apply similar changes to other functions that switches curwin.
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
This commit is contained in:
@@ -5260,14 +5260,17 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx)
|
|||||||
/// @param[in] dollar_lnum True when "$" is last line.
|
/// @param[in] dollar_lnum True when "$" is last line.
|
||||||
/// @param[out] ret_fnum Set to fnum for marks.
|
/// @param[out] ret_fnum Set to fnum for marks.
|
||||||
/// @param[in] charcol True to return character column.
|
/// @param[in] charcol True to return character column.
|
||||||
|
/// @param[in] wp Window for which to get the position.
|
||||||
///
|
///
|
||||||
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
|
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
|
||||||
pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum,
|
pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum,
|
||||||
const bool charcol)
|
const bool charcol, win_T *wp)
|
||||||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
static pos_T pos;
|
static pos_T pos;
|
||||||
|
|
||||||
|
buf_T *bp = wp->w_buffer;
|
||||||
|
|
||||||
// Argument can be [lnum, col, coladd].
|
// Argument can be [lnum, col, coladd].
|
||||||
if (tv->v_type == VAR_LIST) {
|
if (tv->v_type == VAR_LIST) {
|
||||||
bool error = false;
|
bool error = false;
|
||||||
@@ -5279,7 +5282,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
|
|||||||
|
|
||||||
// Get the line number.
|
// Get the line number.
|
||||||
pos.lnum = (linenr_T)tv_list_find_nr(l, 0, &error);
|
pos.lnum = (linenr_T)tv_list_find_nr(l, 0, &error);
|
||||||
if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) {
|
if (error || pos.lnum <= 0 || pos.lnum > bp->b_ml.ml_line_count) {
|
||||||
// Invalid line number.
|
// Invalid line number.
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -5291,9 +5294,9 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
|
|||||||
}
|
}
|
||||||
int len;
|
int len;
|
||||||
if (charcol) {
|
if (charcol) {
|
||||||
len = mb_charlen(ml_get(pos.lnum));
|
len = mb_charlen(ml_get_buf(bp, pos.lnum));
|
||||||
} else {
|
} else {
|
||||||
len = ml_get_len(pos.lnum);
|
len = ml_get_buf_len(bp, pos.lnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We accept "$" for the column number: last column.
|
// We accept "$" for the column number: last column.
|
||||||
@@ -5328,18 +5331,18 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
|
|||||||
pos.lnum = 0;
|
pos.lnum = 0;
|
||||||
if (name[0] == '.') {
|
if (name[0] == '.') {
|
||||||
// cursor
|
// cursor
|
||||||
pos = curwin->w_cursor;
|
pos = wp->w_cursor;
|
||||||
} else if (name[0] == 'v' && name[1] == NUL) {
|
} else if (name[0] == 'v' && name[1] == NUL) {
|
||||||
// Visual start
|
// Visual start
|
||||||
if (VIsual_active) {
|
if (VIsual_active && wp == curwin) {
|
||||||
pos = VIsual;
|
pos = VIsual;
|
||||||
} else {
|
} else {
|
||||||
pos = curwin->w_cursor;
|
pos = wp->w_cursor;
|
||||||
}
|
}
|
||||||
} else if (name[0] == '\'') {
|
} else if (name[0] == '\'') {
|
||||||
// mark
|
// mark
|
||||||
int mname = (uint8_t)name[1];
|
int mname = (uint8_t)name[1];
|
||||||
const fmark_T *const fm = mark_get(curbuf, curwin, NULL, kMarkAll, mname);
|
const fmark_T *const fm = mark_get(bp, wp, NULL, kMarkAll, mname);
|
||||||
if (fm == NULL || fm->mark.lnum <= 0) {
|
if (fm == NULL || fm->mark.lnum <= 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -5349,7 +5352,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
|
|||||||
}
|
}
|
||||||
if (pos.lnum != 0) {
|
if (pos.lnum != 0) {
|
||||||
if (charcol) {
|
if (charcol) {
|
||||||
pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col);
|
pos.col = buf_byteidx_to_charidx(bp, pos.lnum, pos.col);
|
||||||
}
|
}
|
||||||
return &pos;
|
return &pos;
|
||||||
}
|
}
|
||||||
@@ -5359,31 +5362,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
|
|||||||
if (name[0] == 'w' && dollar_lnum) {
|
if (name[0] == 'w' && dollar_lnum) {
|
||||||
// the "w_valid" flags are not reset when moving the cursor, but they
|
// the "w_valid" flags are not reset when moving the cursor, but they
|
||||||
// do matter for update_topline() and validate_botline().
|
// do matter for update_topline() and validate_botline().
|
||||||
check_cursor_moved(curwin);
|
check_cursor_moved(wp);
|
||||||
|
|
||||||
pos.col = 0;
|
pos.col = 0;
|
||||||
if (name[1] == '0') { // "w0": first visible line
|
if (name[1] == '0') { // "w0": first visible line
|
||||||
update_topline(curwin);
|
update_topline(wp);
|
||||||
// In silent Ex mode topline is zero, but that's not a valid line
|
// In silent Ex mode topline is zero, but that's not a valid line
|
||||||
// number; use one instead.
|
// number; use one instead.
|
||||||
pos.lnum = curwin->w_topline > 0 ? curwin->w_topline : 1;
|
pos.lnum = wp->w_topline > 0 ? wp->w_topline : 1;
|
||||||
return &pos;
|
return &pos;
|
||||||
} else if (name[1] == '$') { // "w$": last visible line
|
} else if (name[1] == '$') { // "w$": last visible line
|
||||||
validate_botline(curwin);
|
validate_botline(wp);
|
||||||
// In silent Ex mode botline is zero, return zero then.
|
// In silent Ex mode botline is zero, return zero then.
|
||||||
pos.lnum = curwin->w_botline > 0 ? curwin->w_botline - 1 : 0;
|
pos.lnum = wp->w_botline > 0 ? wp->w_botline - 1 : 0;
|
||||||
return &pos;
|
return &pos;
|
||||||
}
|
}
|
||||||
} else if (name[0] == '$') { // last column or line
|
} else if (name[0] == '$') { // last column or line
|
||||||
if (dollar_lnum) {
|
if (dollar_lnum) {
|
||||||
pos.lnum = curbuf->b_ml.ml_line_count;
|
pos.lnum = bp->b_ml.ml_line_count;
|
||||||
pos.col = 0;
|
pos.col = 0;
|
||||||
} else {
|
} else {
|
||||||
pos.lnum = curwin->w_cursor.lnum;
|
pos.lnum = wp->w_cursor.lnum;
|
||||||
if (charcol) {
|
if (charcol) {
|
||||||
pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr());
|
pos.col = (colnr_T)mb_charlen(ml_get_buf(bp, wp->w_cursor.lnum));
|
||||||
} else {
|
} else {
|
||||||
pos.col = get_cursor_line_len();
|
pos.col = ml_get_buf_len(bp, wp->w_cursor.lnum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &pos;
|
return &pos;
|
||||||
|
|||||||
@@ -670,33 +670,27 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switchwin_T switchwin;
|
win_T *wp = curwin;
|
||||||
bool winchanged = false;
|
|
||||||
|
|
||||||
if (argvars[1].v_type != VAR_UNKNOWN) {
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
||||||
// use the window specified in the second argument
|
// use the window specified in the second argument
|
||||||
tabpage_T *tp;
|
tabpage_T *tp;
|
||||||
win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[1]), &tp);
|
wp = win_id2wp_tp((int)tv_get_number(&argvars[1]), &tp);
|
||||||
if (wp == NULL || tp == NULL) {
|
if (wp == NULL || tp == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
check_cursor(wp);
|
||||||
if (switch_win_noblock(&switchwin, wp, tp, true) != OK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
check_cursor(curwin);
|
|
||||||
winchanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf_T *bp = wp->w_buffer;
|
||||||
colnr_T col = 0;
|
colnr_T col = 0;
|
||||||
int fnum = curbuf->b_fnum;
|
int fnum = bp->b_fnum;
|
||||||
pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol);
|
pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol, wp);
|
||||||
if (fp != NULL && fnum == curbuf->b_fnum) {
|
if (fp != NULL && fnum == bp->b_fnum) {
|
||||||
if (fp->col == MAXCOL) {
|
if (fp->col == MAXCOL) {
|
||||||
// '> can be MAXCOL, get the length of the line then
|
// '> can be MAXCOL, get the length of the line then
|
||||||
if (fp->lnum <= curbuf->b_ml.ml_line_count) {
|
if (fp->lnum <= bp->b_ml.ml_line_count) {
|
||||||
col = ml_get_len(fp->lnum) + 1;
|
col = ml_get_buf_len(bp, fp->lnum) + 1;
|
||||||
} else {
|
} else {
|
||||||
col = MAXCOL;
|
col = MAXCOL;
|
||||||
}
|
}
|
||||||
@@ -704,11 +698,11 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
|
|||||||
col = fp->col + 1;
|
col = fp->col + 1;
|
||||||
// col(".") when the cursor is on the NUL at the end of the line
|
// col(".") when the cursor is on the NUL at the end of the line
|
||||||
// because of "coladd" can be seen as an extra column.
|
// because of "coladd" can be seen as an extra column.
|
||||||
if (virtual_active(curwin) && fp == &curwin->w_cursor) {
|
if (virtual_active(wp) && fp == &wp->w_cursor) {
|
||||||
char *p = get_cursor_pos_ptr();
|
char *p = ml_get_buf(bp, wp->w_cursor.lnum) + wp->w_cursor.col;
|
||||||
if (curwin->w_cursor.coladd >=
|
if (wp->w_cursor.coladd >=
|
||||||
(colnr_T)win_chartabsize(curwin, p,
|
(colnr_T)win_chartabsize(wp, p,
|
||||||
curwin->w_virtcol - curwin->w_cursor.coladd)) {
|
wp->w_virtcol - wp->w_cursor.coladd)) {
|
||||||
int l;
|
int l;
|
||||||
if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
|
if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
|
||||||
col += l;
|
col += l;
|
||||||
@@ -718,10 +712,6 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
rettv->vval.v_number = col;
|
rettv->vval.v_number = col;
|
||||||
|
|
||||||
if (winchanged) {
|
|
||||||
restore_win_noblock(&switchwin, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "charcol()" function
|
/// "charcol()" function
|
||||||
@@ -2039,7 +2029,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool
|
|||||||
fp = &pos;
|
fp = &pos;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fp = var2fpos(&argvars[0], true, &fnum, charcol);
|
fp = var2fpos(&argvars[0], true, &fnum, charcol, curwin);
|
||||||
}
|
}
|
||||||
|
|
||||||
list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
|
list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
|
||||||
@@ -3935,22 +3925,18 @@ static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
tabpage_T *tp;
|
tabpage_T *tp;
|
||||||
win_T *wp = win_id2wp_tp(id, &tp);
|
win_T *wp = win_id2wp_tp(id, &tp);
|
||||||
if (wp != NULL && tp != NULL) {
|
if (wp != NULL && tp != NULL) {
|
||||||
switchwin_T switchwin;
|
// With 'splitkeep' != cursor and in diff mode, prevent that the
|
||||||
if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
|
// window scrolls and keep the topline.
|
||||||
// With 'splitkeep' != cursor and in diff mode, prevent that the
|
if (*p_spk != 'c' || (wp->w_p_diff && curwin->w_p_diff)) {
|
||||||
// window scrolls and keep the topline.
|
skip_update_topline = true;
|
||||||
if (*p_spk != 'c' || (curwin->w_p_diff && switchwin.sw_curwin->w_p_diff)) {
|
|
||||||
skip_update_topline = true;
|
|
||||||
}
|
|
||||||
check_cursor(curwin);
|
|
||||||
fp = var2fpos(&argvars[0], true, &fnum, false);
|
|
||||||
}
|
}
|
||||||
|
check_cursor(wp);
|
||||||
|
fp = var2fpos(&argvars[0], true, &fnum, false, wp);
|
||||||
skip_update_topline = false;
|
skip_update_topline = false;
|
||||||
restore_win_noblock(&switchwin, true);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use current window
|
// use current window
|
||||||
fp = var2fpos(&argvars[0], true, &fnum, false);
|
fp = var2fpos(&argvars[0], true, &fnum, false, curwin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fp != NULL) {
|
if (fp != NULL) {
|
||||||
@@ -7665,39 +7651,33 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
{
|
{
|
||||||
colnr_T vcol_start = 0;
|
colnr_T vcol_start = 0;
|
||||||
colnr_T vcol_end = 0;
|
colnr_T vcol_end = 0;
|
||||||
switchwin_T switchwin;
|
win_T *wp = curwin;
|
||||||
bool winchanged = false;
|
|
||||||
|
|
||||||
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
|
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
|
||||||
// use the window specified in the third argument
|
// use the window specified in the third argument
|
||||||
tabpage_T *tp;
|
tabpage_T *tp;
|
||||||
win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp);
|
wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp);
|
||||||
if (wp == NULL || tp == NULL) {
|
if (wp == NULL || tp == NULL) {
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
|
check_cursor(wp);
|
||||||
if (switch_win_noblock(&switchwin, wp, tp, true) != OK) {
|
|
||||||
goto theend;
|
|
||||||
}
|
|
||||||
|
|
||||||
check_cursor(curwin);
|
|
||||||
winchanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int fnum = curbuf->b_fnum;
|
buf_T *bp = wp->w_buffer;
|
||||||
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
|
int fnum = bp->b_fnum;
|
||||||
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
|
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false, wp);
|
||||||
&& fnum == curbuf->b_fnum) {
|
if (fp != NULL && fp->lnum <= bp->b_ml.ml_line_count
|
||||||
|
&& fnum == bp->b_fnum) {
|
||||||
// Limit the column to a valid value, getvvcol() doesn't check.
|
// Limit the column to a valid value, getvvcol() doesn't check.
|
||||||
if (fp->col < 0) {
|
if (fp->col < 0) {
|
||||||
fp->col = 0;
|
fp->col = 0;
|
||||||
} else {
|
} else {
|
||||||
const colnr_T len = ml_get_len(fp->lnum);
|
const colnr_T len = ml_get_buf_len(bp, fp->lnum);
|
||||||
if (fp->col > len) {
|
if (fp->col > len) {
|
||||||
fp->col = len;
|
fp->col = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end);
|
getvvcol(wp, fp, &vcol_start, NULL, &vcol_end);
|
||||||
vcol_start++;
|
vcol_start++;
|
||||||
vcol_end++;
|
vcol_end++;
|
||||||
}
|
}
|
||||||
@@ -7710,10 +7690,6 @@ theend:
|
|||||||
} else {
|
} else {
|
||||||
rettv->vval.v_number = vcol_end;
|
rettv->vval.v_number = vcol_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (winchanged) {
|
|
||||||
restore_win_noblock(&switchwin, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// "visualmode()" function
|
/// "visualmode()" function
|
||||||
|
|||||||
@@ -4229,7 +4229,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
|
|||||||
if (lnum <= 0 && did_emsg_before == did_emsg && tv->v_type != VAR_NUMBER) {
|
if (lnum <= 0 && did_emsg_before == did_emsg && tv->v_type != VAR_NUMBER) {
|
||||||
int fnum;
|
int fnum;
|
||||||
// No valid number, try using same function as line() does.
|
// No valid number, try using same function as line() does.
|
||||||
pos_T *const fp = var2fpos(tv, true, &fnum, false);
|
pos_T *const fp = var2fpos(tv, true, &fnum, false, curwin);
|
||||||
if (fp != NULL) {
|
if (fp != NULL) {
|
||||||
lnum = fp->lnum;
|
lnum = fp->lnum;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3571,6 +3571,42 @@ describe('extmark decorations', function()
|
|||||||
]],
|
]],
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('line("w$", win) considers conceal_lines', function()
|
||||||
|
api.nvim_buf_set_lines(0, 0, -1, true, { 'line 1', 'line 2', 'line 3' })
|
||||||
|
api.nvim_buf_set_extmark(0, ns, 0, 0, { conceal_lines = '' }) -- conceal line 1
|
||||||
|
|
||||||
|
local win = exec_lua(function()
|
||||||
|
local provider_ns = vim.api.nvim_create_namespace('test_f_line')
|
||||||
|
_G.line_w_dollar = {}
|
||||||
|
vim.api.nvim_set_decoration_provider(provider_ns, {
|
||||||
|
on_start = function()
|
||||||
|
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||||
|
table.insert(_G.line_w_dollar, { win, vim.fn.line('w$', win) })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local win = vim.api.nvim_open_win(0, false, {
|
||||||
|
relative = 'editor',
|
||||||
|
width = 20,
|
||||||
|
height = 1,
|
||||||
|
row = 0,
|
||||||
|
col = 0,
|
||||||
|
border = 'single',
|
||||||
|
})
|
||||||
|
vim.api.nvim_set_option_value('conceallevel', 2, { scope = 'local', win = win })
|
||||||
|
|
||||||
|
return win
|
||||||
|
end)
|
||||||
|
|
||||||
|
local line_w_dollar = exec_lua('return _G.line_w_dollar')
|
||||||
|
for _, win_line in ipairs(line_w_dollar) do
|
||||||
|
if win_line[1] == win then
|
||||||
|
eq(2, win_line[2])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('decorations: inline virtual text', function()
|
describe('decorations: inline virtual text', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user