fix(api): more intuitive cursor updates in nvim_buf_set_text

Fixes #22526
This commit is contained in:
Sergey Slipchenko 2023-09-11 08:16:03 +04:00
parent 2b475cb5cc
commit d22172f36b
No known key found for this signature in database
GPG Key ID: 375ECA0AF0329782
6 changed files with 790 additions and 47 deletions

View File

@ -2438,6 +2438,8 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire
lines.
Prefer |nvim_put()| if you want to insert text at the cursor position.
Attributes: ~
not allowed when |textlock| is active
@ -2451,6 +2453,7 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
See also: ~
• |nvim_buf_set_lines()|
• |nvim_put()|
nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
Sets a buffer-scoped (b:) variable

View File

@ -621,6 +621,7 @@ function vim.api.nvim_buf_set_option(buffer, name, value) end
--- range, use `replacement = {}`.
--- Prefer `nvim_buf_set_lines()` if you are only adding or deleting entire
--- lines.
--- Prefer `nvim_put()` if you want to insert text at the cursor position.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param start_row integer First line index

View File

@ -454,23 +454,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end)
-- Some LSP servers are depending on the VSCode behavior.
-- The VSCode will re-locate the cursor position after applying TextEdit so we also do it.
local is_current_buf = api.nvim_get_current_buf() == bufnr or bufnr == 0
local cursor = (function()
if not is_current_buf then
return {
row = -1,
col = -1,
}
end
local cursor = api.nvim_win_get_cursor(0)
return {
row = cursor[1] - 1,
col = cursor[2],
}
end)()
-- save and restore local marks since they get deleted by nvim_buf_set_lines
local marks = {}
for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
@ -480,7 +463,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
-- Apply text edits.
local is_cursor_fixed = false
local has_eol_text_edit = false
for _, text_edit in ipairs(text_edits) do
-- Normalize line ending
@ -527,20 +509,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
e.end_col = math.min(last_line_len, e.end_col)
api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
-- Fix cursor position.
local row_count = (e.end_row - e.start_row) + 1
if e.end_row < cursor.row then
cursor.row = cursor.row + (#e.text - row_count)
is_cursor_fixed = true
elseif e.end_row == cursor.row and e.end_col <= cursor.col then
cursor.row = cursor.row + (#e.text - row_count)
cursor.col = #e.text[#e.text] + (cursor.col - e.end_col)
if #e.text == 1 then
cursor.col = cursor.col + e.start_col
end
is_cursor_fixed = true
end
end
end
@ -560,16 +528,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end
-- Apply fixed cursor position.
if is_cursor_fixed then
local is_valid_cursor = true
is_valid_cursor = is_valid_cursor and cursor.row < max
is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, cursor.row) or '')
if is_valid_cursor then
api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col })
end
end
-- Remove final line if needed
local fix_eol = has_eol_text_edit
fix_eol = fix_eol and (vim.bo[bufnr].eol or (vim.bo[bufnr].fixeol and not vim.bo[bufnr].binary))

View File

@ -504,7 +504,10 @@ end:
///
/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
/// Prefer |nvim_put()| if you want to insert text at the cursor position.
///
/// @see |nvim_buf_set_lines()|
/// @see |nvim_put()|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@ -725,11 +728,12 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
FOR_ALL_TAB_WINDOWS(tp, win) {
if (win->w_buffer == buf) {
// adjust cursor like an extmark ( i e it was inside last_part_len)
if (win->w_cursor.lnum == end_row && win->w_cursor.col > end_col) {
win->w_cursor.col -= col_extent - (colnr_T)last_item.size;
if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
(colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
} else {
fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
}
fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
}
}
@ -1339,6 +1343,79 @@ static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
invalidate_botline(win);
}
/// Fix cursor position after replacing text
/// between (start_row, start_col) and (end_row, end_col).
///
/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row.
static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row,
colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row)
{
colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1;
colnr_T end_row_change_start = new_rows == 1 ? start_col : 0;
colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row;
// check if cursor is after replaced range or not
if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) {
// if cursor is after replaced range, it's shifted
// to keep it's position the same, relative to end_col
linenr_T old_rows = end_row - start_row + 1;
win->w_cursor.lnum += new_rows - old_rows;
win->w_cursor.col += end_row_change_end - end_col;
} else {
// if cursor is inside replaced range
// and the new range got smaller,
// it's shifted to keep it inside the new range
//
// if cursor is before range or range did not
// got smaller, position is not changed
colnr_T old_coladd = win->w_cursor.coladd;
// it's easier to work with a single value here.
// col and coladd are fixed by a later call
// to check_cursor_col_win when necessary
win->w_cursor.col += win->w_cursor.coladd;
win->w_cursor.coladd = 0;
linenr_T new_end_row = start_row + new_rows - 1;
// make sure cursor row is in the new row range
if (win->w_cursor.lnum > new_end_row) {
win->w_cursor.lnum = new_end_row;
// don't simply move cursor up, but to the end
// of new_end_row, if it's not at or after
// it already (in case virtualedit is active)
// column might be additionally adjusted below
// to keep it inside col range if needed
colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row));
if (win->w_cursor.col < len) {
win->w_cursor.col = len;
}
}
// if cursor is at the last row and
// it wasn't after eol before, move it exactly
// to end_row_change_end
if (win->w_cursor.lnum == new_end_row
&& win->w_cursor.col > end_row_change_end && old_coladd == 0) {
win->w_cursor.col = end_row_change_end;
// make sure cursor is inside range, not after it,
// except when doing so would move it before new range
if (win->w_cursor.col - mode_col_adj >= end_row_change_start) {
win->w_cursor.col -= mode_col_adj;
}
}
}
check_cursor_col_win(win);
changed_cline_bef_curs(win);
invalidate_botline(win);
}
/// Initialise a string array either:
/// - on the Lua stack (as a table) (if lstate is not NULL)
/// - as an API array object (if lstate is NULL).

View File

@ -848,6 +848,710 @@ describe('api/buf', function()
eq({1, 4}, meths.win_get_cursor(win2))
end)
describe('when text is being added right at cursor position #22526', function()
it('updates the cursor position in NORMAL mode', function()
insert([[
abcd]])
-- position the cursor on 'c'
curwin('set_cursor', {1, 2})
-- add 'xxx' before 'c'
set_text(0, 2, 0, 2, {'xxx'})
eq({'abxxxcd'}, get_lines(0, -1, true))
-- cursor should be on 'c'
eq({1, 5}, curwin('get_cursor'))
end)
it('updates the cursor position only in non-current window when in INSERT mode', function()
insert([[
abcd]])
-- position the cursor on 'c'
curwin('set_cursor', {1, 2})
-- open vertical split
feed('<c-w>v')
-- get into INSERT mode to treat cursor
-- as being after 'b', not on 'c'
feed('i')
-- add 'xxx' between 'b' and 'c'
set_text(0, 2, 0, 2, {'xxx'})
eq({'abxxxcd'}, get_lines(0, -1, true))
-- in the current window cursor should stay after 'b'
eq({1, 2}, curwin('get_cursor'))
-- quit INSERT mode
feed('<esc>')
-- close current window
feed('<c-w>c')
-- in another window cursor should be on 'c'
eq({1, 5}, curwin('get_cursor'))
end)
end)
describe('when text is being deleted right at cursor position', function()
it('leaves cursor at the same position in NORMAL mode', function()
insert([[
abcd]])
-- position the cursor on 'b'
curwin('set_cursor', {1, 1})
-- delete 'b'
set_text(0, 1, 0, 2, {})
eq({'acd'}, get_lines(0, -1, true))
-- cursor is now on 'c'
eq({1, 1}, curwin('get_cursor'))
end)
it('leaves cursor at the same position in INSERT mode in current and non-current window', function()
insert([[
abcd]])
-- position the cursor on 'b'
curwin('set_cursor', {1, 1})
-- open vertical split
feed('<c-w>v')
-- get into INSERT mode to treat cursor
-- as being after 'a', not on 'b'
feed('i')
-- delete 'b'
set_text(0, 1, 0, 2, {})
eq({'acd'}, get_lines(0, -1, true))
-- cursor in the current window should stay after 'a'
eq({1, 1}, curwin('get_cursor'))
-- quit INSERT mode
feed('<esc>')
-- close current window
feed('<c-w>c')
-- cursor in non-current window should stay on 'c'
eq({1, 1}, curwin('get_cursor'))
end)
end)
describe('when cursor is inside replaced row range', function()
it('keeps cursor at the same position if cursor is at start_row, but before start_col', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on ' ' before 'first'
curwin('set_cursor', {1, 14})
set_text(0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should stay at the same position
eq({1, 14}, curwin('get_cursor'))
end)
it('keeps cursor at the same position if cursor is at start_row and column is still valid', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'f' in 'first'
curwin('set_cursor', {1, 15})
set_text(0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should stay at the same position
eq({1, 15}, curwin('get_cursor'))
end)
it('adjusts cursor column to keep it valid if start_row got smaller', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 't' in 'first'
curwin('set_cursor', {1, 19})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'This should be last' }, get_lines(0, -1, true))
-- cursor should end up on 't' in 'last'
eq({1, 18}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 18}, cursor)
end)
it('adjusts cursor column to keep it valid if start_row got smaller in INSERT mode', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 't' in 'first'
curwin('set_cursor', {1, 19})
-- enter INSERT mode to treat cursor as being after 't'
feed('a')
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'This should be last' }, get_lines(0, -1, true))
-- cursor should end up after 't' in 'last'
eq({1, 19}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 19}, cursor)
end)
it('adjusts cursor column to keep it valid in a row after start_row if it got smaller', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'w' in 'want'
curwin('set_cursor', {2, 31})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'1',
'then 2',
'and then',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be 1',
'then 2',
'and then the last one',
}, get_lines(0, -1, true))
-- cursor column should end up at the end of a row
eq({2, 5}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 5}, cursor)
end)
it('adjusts cursor column to keep it valid in a row after start_row if it got smaller in INSERT mode', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'w' in 'want'
curwin('set_cursor', {2, 31})
-- enter INSERT mode
feed('a')
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'1',
'then 2',
'and then',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be 1',
'then 2',
'and then the last one',
}, get_lines(0, -1, true))
-- cursor column should end up at the end of a row
eq({2, 6}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 6}, cursor)
end)
it('adjusts cursor line and column to keep it inside replacement range', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'n' in 'finally'
curwin('set_cursor', {3, 6})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should end up on 'y' in 'hopefully'
-- to stay in the range, because it got smaller
eq({2, 12}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 12}, cursor)
end)
it('adjusts cursor line and column if replacement is empty', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'r' in 'there'
curwin('set_cursor', {2, 8})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'This should be the last one' }, get_lines(0, -1, true))
-- cursor should end up on the next column after deleted range
eq({1, 15}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 15}, cursor)
end)
it('adjusts cursor line and column if replacement is empty and start_col == 0', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'r' in 'there'
curwin('set_cursor', {2, 8})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'finally the last one' }, get_lines(0, -1, true))
-- cursor should end up in column 0
eq({1, 0}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 0}, cursor)
end)
it('adjusts cursor column if replacement ends at cursor row, after cursor column', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'y' in 'finally'
curwin('set_cursor', {3, 10})
set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
eq({
'This should be 1',
'this 2',
'and then the last one',
}, get_lines(0, -1, true))
-- cursor should end up on 'n' in 'then'
eq({3, 7}, curwin('get_cursor'))
end)
it('adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'y' at 'finally'
curwin('set_cursor', {3, 10})
-- enter INSERT mode to treat cursor as being between 'l' and 'y'
feed('i')
set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
eq({
'This should be 1',
'this 2',
'and then the last one',
}, get_lines(0, -1, true))
-- cursor should end up after 'n' in 'then'
eq({3, 8}, curwin('get_cursor'))
end)
it('adjusts cursor column if replacement is inside of a single line', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'y' in 'finally'
curwin('set_cursor', {3, 10})
set_text(2, 4, 2, 11, { 'then' })
eq({
'This should be first',
'then there is a line we do not want',
'and then the last one',
}, get_lines(0, -1, true))
-- cursor should end up on 'n' in 'then'
eq({3, 7}, curwin('get_cursor'))
end)
it('does not move cursor column after end of a line', function()
insert([[
This should be the only line here
!!!]])
-- position cursor on the last '1'
curwin('set_cursor', {2, 2})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'This should be the only line here' }, get_lines(0, -1, true))
-- cursor should end up on '!'
eq({1, 32}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 32}, cursor)
end)
it('does not move cursor column before start of a line', function()
insert('\n!!!')
-- position cursor on the last '1'
curwin('set_cursor', {2, 2})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ '' }, get_lines(0, -1, true))
-- cursor should end up on '!'
eq({1, 0}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 0}, cursor)
end)
describe('with virtualedit', function()
it('adjusts cursor line and column to keep it inside replacement range if cursor is not after eol', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position cursor on 't' in 'want'
curwin('set_cursor', {2, 34})
-- turn on virtualedit
command('set virtualedit=all')
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should end up on 'y' in 'hopefully'
-- to stay in the range
eq({2, 12}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 12}, cursor)
-- coladd should be 0
eq(0, exec_lua([[
return vim.fn.winsaveview().coladd
]]))
end)
it('does not change cursor screen column when cursor is after eol and row got shorter', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position cursor on 't' in 'want'
curwin('set_cursor', {2, 34})
-- turn on virtualedit
command('set virtualedit=all')
-- move cursor after eol
exec_lua([[
vim.fn.winrestview({ coladd = 5 })
]])
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should end up at eol of a new row
eq({2, 26}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 26}, cursor)
-- coladd should be increased so that cursor stays in the same screen column
eq(13, exec_lua([[
return vim.fn.winsaveview().coladd
]]))
end)
it('does not change cursor screen column when cursor is after eol and row got longer', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position cursor on 't' in 'first'
curwin('set_cursor', {1, 19})
-- turn on virtualedit
command('set virtualedit=all')
-- move cursor after eol
exec_lua([[
vim.fn.winrestview({ coladd = 21 })
]])
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should end up at eol of a new row
eq({1, 38}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 38}, cursor)
-- coladd should be increased so that cursor stays in the same screen column
eq(2, exec_lua([[
return vim.fn.winsaveview().coladd
]]))
end)
it('does not change cursor screen column when cursor is after eol and row extended past cursor column', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position cursor on 't' in 'first'
curwin('set_cursor', {1, 19})
-- turn on virtualedit
command('set virtualedit=all')
-- move cursor after eol just a bit
exec_lua([[
vim.fn.winrestview({ coladd = 3 })
]])
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should stay at the same screen column
eq({1, 22}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 22}, cursor)
-- coladd should become 0
eq(0, exec_lua([[
return vim.fn.winsaveview().coladd
]]))
end)
it('does not change cursor screen column when cursor is after eol and row range decreased', function()
insert([[
This should be first
then there is a line we do not want
and one more
and finally the last one]])
-- position cursor on 'e' in 'more'
curwin('set_cursor', {3, 11})
-- turn on virtualedit
command('set virtualedit=all')
-- move cursor after eol
exec_lua([[
vim.fn.winrestview({ coladd = 28 })
]])
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, {
'the line we do not want',
'but hopefully',
})
return vim.api.nvim_win_get_cursor(0)
]])
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should end up at eol of a new row
eq({2, 26}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({2, 26}, cursor)
-- coladd should be increased so that cursor stays in the same screen column
eq(13, exec_lua([[
return vim.fn.winsaveview().coladd
]]))
end)
end)
end)
describe('when cursor is at end_row and after end_col', function()
it('adjusts cursor column when only a newline is added or deleted', function()
insert([[
first line
second
line]])
-- position the cursor on 'i'
curwin('set_cursor', {3, 2})
set_text(1, 6, 2, 0, {})
eq({'first line', 'second line'}, get_lines(0, -1, true))
-- cursor should stay on 'i'
eq({2, 8}, curwin('get_cursor'))
-- add a newline back
set_text(1, 6, 1, 6, {'', ''})
eq({'first line', 'second', ' line'}, get_lines(0, -1, true))
-- cursor should return back to the original position
eq({3, 2}, curwin('get_cursor'))
end)
it('adjusts cursor column if the range is not bound to either start or end of a line', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 'h' in 'the'
curwin('set_cursor', {3, 13})
set_text(0, 14, 2, 11, {})
eq({'This should be the last one'}, get_lines(0, -1, true))
-- cursor should stay on 'h'
eq({1, 16}, curwin('get_cursor'))
-- add deleted lines back
set_text(0, 14, 0, 14, {
' first',
'then there is a line we do not want',
'and finally',
})
eq({
'This should be first',
'then there is a line we do not want',
'and finally the last one',
}, get_lines(0, -1, true))
-- cursor should return back to the original position
eq({3, 13}, curwin('get_cursor'))
end)
it('adjusts cursor column if replacing lines in range, not just deleting and adding', function()
insert([[
This should be first
then there is a line we do not want
and finally the last one]])
-- position the cursor on 's' in 'last'
curwin('set_cursor', {3, 18})
set_text(0, 15, 2, 11, {
'the line we do not want',
'but hopefully',
})
eq({
'This should be the line we do not want',
'but hopefully the last one',
}, get_lines(0, -1, true))
-- cursor should stay on 's'
eq({2, 20}, curwin('get_cursor'))
set_text(0, 15, 1, 13, {
'first',
'then there is a line we do not want',
'and finally',
})
eq({
'This should be first',
'then there is a line we do not want',
'and finally the last one',
}, get_lines(0, -1, true))
-- cursor should return back to the original position
eq({3, 18}, curwin('get_cursor'))
end)
it('does not move cursor column after end of a line', function()
insert([[
This should be the only line here
]])
-- position cursor at the empty line
curwin('set_cursor', {2, 0})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ 'This should be the only line here!' }, get_lines(0, -1, true))
-- cursor should end up on '!'
eq({1, 33}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 33}, cursor)
end)
it('does not move cursor column before start of a line', function()
insert('\n')
eq({ '', '' }, get_lines(0, -1, true))
-- position cursor on the last '1'
curwin('set_cursor', {2, 2})
local cursor = exec_lua([[
vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''})
return vim.api.nvim_win_get_cursor(0)
]])
eq({ '' }, get_lines(0, -1, true))
-- cursor should end up on '!'
eq({1, 0}, curwin('get_cursor'))
-- immediate call to nvim_win_get_cursor should have returned the same position
eq({1, 0}, cursor)
end)
end)
it('can handle NULs', function()
set_text(0, 0, 0, 0, {'ab\0cd'})
eq('ab\0cd', curbuf_depr('get_line', 0))

View File

@ -1787,7 +1787,7 @@ describe('LSP', function()
eq({
'First line of text';
}, buf_lines(1))
eq({ 1, 6 }, funcs.nvim_win_get_cursor(0))
eq({ 1, 17 }, funcs.nvim_win_get_cursor(0))
end)
it('fix the cursor row', function()