mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
fix(edit): backspace adds extra spaces for inline virtual text (#28005)
Problem: Backspace adds extra spaces for inline virtual text. Solution: Ignore virtual text and wrapping when backspacing.
This commit is contained in:
parent
a7bbda121d
commit
14839c5d18
103
src/nvim/edit.c
103
src/nvim/edit.c
@ -3673,23 +3673,6 @@ static void ins_del(void)
|
|||||||
AppendCharToRedobuff(K_DEL);
|
AppendCharToRedobuff(K_DEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete one character for ins_bs().
|
|
||||||
static void ins_bs_one(colnr_T *vcolp)
|
|
||||||
{
|
|
||||||
dec_cursor();
|
|
||||||
getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL);
|
|
||||||
if (State & REPLACE_FLAG) {
|
|
||||||
// Don't delete characters before the insert point when in
|
|
||||||
// Replace mode
|
|
||||||
if (curwin->w_cursor.lnum != Insstart.lnum
|
|
||||||
|| curwin->w_cursor.col >= Insstart.col) {
|
|
||||||
replace_do_bs(-1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
del_char(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle Backspace, delete-word and delete-line in Insert mode.
|
/// Handle Backspace, delete-word and delete-line in Insert mode.
|
||||||
///
|
///
|
||||||
/// @param c character that was typed
|
/// @param c character that was typed
|
||||||
@ -3846,42 +3829,69 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
|
|||||||
// Handle deleting one 'shiftwidth' or 'softtabstop'.
|
// Handle deleting one 'shiftwidth' or 'softtabstop'.
|
||||||
if (mode == BACKSPACE_CHAR
|
if (mode == BACKSPACE_CHAR
|
||||||
&& ((p_sta && in_indent)
|
&& ((p_sta && in_indent)
|
||||||
|| ((get_sts_value() != 0
|
|| ((get_sts_value() != 0 || tabstop_count(curbuf->b_p_vsts_array))
|
||||||
|| tabstop_count(curbuf->b_p_vsts_array))
|
|
||||||
&& curwin->w_cursor.col > 0
|
&& curwin->w_cursor.col > 0
|
||||||
&& (*(get_cursor_pos_ptr() - 1) == TAB
|
&& (*(get_cursor_pos_ptr() - 1) == TAB
|
||||||
|| (*(get_cursor_pos_ptr() - 1) == ' '
|
|| (*(get_cursor_pos_ptr() - 1) == ' '
|
||||||
&& (!*inserted_space_p || arrow_used)))))) {
|
&& (!*inserted_space_p || arrow_used)))))) {
|
||||||
colnr_T vcol;
|
|
||||||
colnr_T want_vcol;
|
|
||||||
|
|
||||||
*inserted_space_p = false;
|
*inserted_space_p = false;
|
||||||
// Compute the virtual column where we want to be. Since
|
|
||||||
// 'showbreak' may get in the way, need to get the last column of
|
bool const use_ts = !curwin->w_p_list || curwin->w_p_lcs_chars.tab1;
|
||||||
// the previous character.
|
char *const line = ml_get_buf(curbuf, curwin->w_cursor.lnum);
|
||||||
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
|
char *const end_ptr = line + curwin->w_cursor.col;
|
||||||
colnr_T start_vcol = vcol;
|
|
||||||
dec_cursor();
|
colnr_T vcol = 0;
|
||||||
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
|
colnr_T space_vcol = 0;
|
||||||
inc_cursor();
|
StrCharInfo sci = utf_ptr2StrCharInfo(line);
|
||||||
if (p_sta && in_indent) {
|
StrCharInfo space_sci = sci;
|
||||||
int ts = get_sw_value(curbuf);
|
bool prev_space = false;
|
||||||
want_vcol = (want_vcol / ts) * ts;
|
while (sci.ptr < end_ptr) {
|
||||||
|
bool cur_space = ascii_iswhite(sci.chr.value);
|
||||||
|
if (!prev_space && cur_space) {
|
||||||
|
space_sci = sci;
|
||||||
|
space_vcol = vcol;
|
||||||
|
}
|
||||||
|
vcol += charsize_nowrap(curbuf, use_ts, vcol, sci.chr.value);
|
||||||
|
sci = utfc_next(sci);
|
||||||
|
prev_space = cur_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
colnr_T want_vcol = vcol - 1;
|
||||||
|
if (want_vcol <= 0) {
|
||||||
|
want_vcol = 0;
|
||||||
|
} else if (p_sta && in_indent) {
|
||||||
|
want_vcol = want_vcol - want_vcol % get_sw_value(curbuf);
|
||||||
} else {
|
} else {
|
||||||
want_vcol = tabstop_start(want_vcol,
|
want_vcol = tabstop_start(want_vcol, get_sts_value(), curbuf->b_p_vsts_array);
|
||||||
get_sts_value(),
|
|
||||||
curbuf->b_p_vsts_array);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete characters until we are at or before want_vcol
|
while (true) {
|
||||||
while (vcol > want_vcol && curwin->w_cursor.col > 0
|
int size = charsize_nowrap(curbuf, use_ts, space_vcol, space_sci.chr.value);
|
||||||
&& (cc = (uint8_t)(*(get_cursor_pos_ptr() - 1)), ascii_iswhite(cc))) {
|
if (space_vcol + size > want_vcol) {
|
||||||
ins_bs_one(&vcol);
|
break;
|
||||||
|
}
|
||||||
|
space_vcol += size;
|
||||||
|
space_sci = utfc_next(space_sci);
|
||||||
|
}
|
||||||
|
colnr_T const want_col = (int)(space_sci.ptr - line);
|
||||||
|
|
||||||
|
// Delete characters until we are at or before want_col.
|
||||||
|
while (curwin->w_cursor.col > want_col) {
|
||||||
|
dec_cursor();
|
||||||
|
if (State & REPLACE_FLAG) {
|
||||||
|
// Don't delete characters before the insert point when in Replace mode.
|
||||||
|
if (curwin->w_cursor.lnum != Insstart.lnum
|
||||||
|
|| curwin->w_cursor.col >= Insstart.col) {
|
||||||
|
replace_do_bs(-1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
del_char(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert extra spaces until we are at want_vcol
|
// Insert extra spaces until we are at want_vcol.
|
||||||
while (vcol < want_vcol) {
|
for (; space_vcol < want_vcol; space_vcol++) {
|
||||||
// Remember the first char we inserted
|
// Remember the first char we inserted.
|
||||||
if (curwin->w_cursor.lnum == Insstart_orig.lnum
|
if (curwin->w_cursor.lnum == Insstart_orig.lnum
|
||||||
&& curwin->w_cursor.col < Insstart_orig.col) {
|
&& curwin->w_cursor.col < Insstart_orig.col) {
|
||||||
Insstart_orig.col = curwin->w_cursor.col;
|
Insstart_orig.col = curwin->w_cursor.col;
|
||||||
@ -3895,13 +3905,6 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
|
|||||||
replace_push(NUL);
|
replace_push(NUL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are now back where we started delete one character. Can
|
|
||||||
// happen when using 'sts' and 'linebreak'.
|
|
||||||
if (vcol >= start_vcol) {
|
|
||||||
ins_bs_one(&vcol);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Delete up to starting point, start of line or previous word.
|
// Delete up to starting point, start of line or previous word.
|
||||||
|
@ -375,6 +375,18 @@ CharSize charsize_fast(CharsizeArg *csarg, colnr_T const vcol, int32_t const cur
|
|||||||
return charsize_fast_impl(csarg->win, csarg->use_tabstop, vcol, cur_char);
|
return charsize_fast_impl(csarg->win, csarg->use_tabstop, vcol, cur_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the number of cells taken up on the screen at given virtual column.
|
||||||
|
int charsize_nowrap(buf_T *buf, bool use_tabstop, colnr_T vcol, int32_t cur_char)
|
||||||
|
{
|
||||||
|
if (cur_char == TAB && use_tabstop) {
|
||||||
|
return tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
|
||||||
|
} else if (cur_char < 0) {
|
||||||
|
return kInvalidByteCells;
|
||||||
|
} else {
|
||||||
|
return char2cells(cur_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that virtual column "vcol" is in the rightmost column of window "wp".
|
/// Check that virtual column "vcol" is in the rightmost column of window "wp".
|
||||||
///
|
///
|
||||||
/// @param wp window
|
/// @param wp window
|
||||||
|
@ -8,6 +8,7 @@ local command = helpers.command
|
|||||||
local eq = helpers.eq
|
local eq = helpers.eq
|
||||||
local eval = helpers.eval
|
local eval = helpers.eval
|
||||||
local curbuf_contents = helpers.curbuf_contents
|
local curbuf_contents = helpers.curbuf_contents
|
||||||
|
local api = helpers.api
|
||||||
|
|
||||||
describe('insert-mode', function()
|
describe('insert-mode', function()
|
||||||
before_each(function()
|
before_each(function()
|
||||||
@ -221,4 +222,146 @@ describe('insert-mode', function()
|
|||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('backspace', function()
|
||||||
|
local function set_lines(line_b, line_e, ...)
|
||||||
|
api.nvim_buf_set_lines(0, line_b, line_e, true, { ... })
|
||||||
|
end
|
||||||
|
local function s(count)
|
||||||
|
return (' '):rep(count)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_cols(expected_cols)
|
||||||
|
local cols = { { helpers.fn.col('.'), helpers.fn.virtcol('.') } }
|
||||||
|
for _ = 2, #expected_cols do
|
||||||
|
feed('<BS>')
|
||||||
|
table.insert(cols, { helpers.fn.col('.'), helpers.fn.virtcol('.') })
|
||||||
|
end
|
||||||
|
eq(expected_cols, cols)
|
||||||
|
end
|
||||||
|
|
||||||
|
it('works with tabs and spaces', function()
|
||||||
|
local screen = Screen.new(30, 2)
|
||||||
|
screen:attach()
|
||||||
|
command('setl ts=4 sw=4')
|
||||||
|
set_lines(0, 1, '\t' .. s(4) .. '\t' .. s(9) .. '\t a')
|
||||||
|
feed('$i')
|
||||||
|
test_cols({
|
||||||
|
{ 18, 26 },
|
||||||
|
{ 17, 25 },
|
||||||
|
{ 15, 21 },
|
||||||
|
{ 11, 17 },
|
||||||
|
{ 7, 13 },
|
||||||
|
{ 6, 9 },
|
||||||
|
{ 2, 5 },
|
||||||
|
{ 1, 1 },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with varsofttabstop', function()
|
||||||
|
local screen = Screen.new(30, 2)
|
||||||
|
screen:attach()
|
||||||
|
command('setl vsts=6,2,5,3')
|
||||||
|
set_lines(0, 1, 'a\t' .. s(4) .. '\t a')
|
||||||
|
feed('$i')
|
||||||
|
test_cols({
|
||||||
|
{ 9, 18 },
|
||||||
|
{ 8, 17 },
|
||||||
|
{ 8, 14 },
|
||||||
|
{ 3, 9 },
|
||||||
|
{ 7, 7 },
|
||||||
|
{ 2, 2 },
|
||||||
|
{ 1, 1 },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with tab as ^I', function()
|
||||||
|
local screen = Screen.new(30, 2)
|
||||||
|
screen:attach()
|
||||||
|
command('set list listchars=space:.')
|
||||||
|
command('setl ts=4 sw=4')
|
||||||
|
set_lines(0, 1, '\t' .. s(4) .. '\t' .. s(9) .. '\t a')
|
||||||
|
feed('$i')
|
||||||
|
test_cols({
|
||||||
|
{ 18, 21 },
|
||||||
|
{ 15, 17 },
|
||||||
|
{ 11, 13 },
|
||||||
|
{ 7, 9 },
|
||||||
|
{ 4, 5 },
|
||||||
|
{ 1, 1 },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works in replace mode', function()
|
||||||
|
local screen = Screen.new(50, 2)
|
||||||
|
screen:attach()
|
||||||
|
command('setl ts=8 sw=8 sts=8')
|
||||||
|
set_lines(0, 1, '\t' .. s(4) .. '\t' .. s(9) .. '\t a')
|
||||||
|
feed('$R')
|
||||||
|
test_cols({
|
||||||
|
{ 18, 34 },
|
||||||
|
{ 17, 33 },
|
||||||
|
{ 15, 25 },
|
||||||
|
{ 7, 17 },
|
||||||
|
{ 2, 9 },
|
||||||
|
{ 1, 8 }, -- last screen cell of first tab is at vcol 8
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with breakindent', function()
|
||||||
|
local screen = Screen.new(17, 4)
|
||||||
|
screen:attach()
|
||||||
|
command('setl ts=4 sw=4 bri briopt=min:5')
|
||||||
|
set_lines(0, 1, '\t' .. s(4) .. '\t' .. s(9) .. '\t a')
|
||||||
|
feed('$i')
|
||||||
|
test_cols({
|
||||||
|
{ 18, 50 },
|
||||||
|
{ 17, 49 },
|
||||||
|
{ 15, 33 },
|
||||||
|
{ 11, 17 },
|
||||||
|
{ 7, 13 },
|
||||||
|
{ 6, 9 },
|
||||||
|
{ 2, 5 },
|
||||||
|
{ 1, 1 },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('works with inline virtual text', function()
|
||||||
|
local screen = Screen.new(50, 2)
|
||||||
|
screen:attach()
|
||||||
|
command('setl ts=4 sw=4')
|
||||||
|
set_lines(0, 1, '\t' .. s(4) .. '\t' .. s(9) .. '\t a')
|
||||||
|
local ns = api.nvim_create_namespace('')
|
||||||
|
local vt_opts = { virt_text = { { 'text' } }, virt_text_pos = 'inline' }
|
||||||
|
api.nvim_buf_set_extmark(0, ns, 0, 2, vt_opts)
|
||||||
|
feed('$i')
|
||||||
|
test_cols({
|
||||||
|
{ 18, 30 },
|
||||||
|
{ 17, 29 },
|
||||||
|
{ 15, 25 },
|
||||||
|
{ 11, 21 },
|
||||||
|
{ 7, 17 },
|
||||||
|
{ 6, 13 },
|
||||||
|
{ 2, 9 },
|
||||||
|
{ 1, 5 },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("works with 'revins'", function()
|
||||||
|
local screen = Screen.new(30, 3)
|
||||||
|
screen:attach()
|
||||||
|
command('setl ts=4 sw=4 revins')
|
||||||
|
set_lines(0, 1, ('a'):rep(16), s(3) .. '\t' .. s(4) .. '\t a')
|
||||||
|
feed('j$i')
|
||||||
|
test_cols({
|
||||||
|
{ 11, 14 },
|
||||||
|
{ 10, 13 },
|
||||||
|
{ 9, 9 },
|
||||||
|
{ 5, 5 },
|
||||||
|
{ 1, 1 },
|
||||||
|
{ 1, 1 }, -- backspace on empty line does nothing
|
||||||
|
})
|
||||||
|
eq(2, api.nvim_win_get_cursor(0)[1])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user