mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #5782 'Visual-mode put from @. register'
This commit is contained in:
commit
043d8ba422
@ -1517,10 +1517,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
|
|||||||
coladvance(curwin->w_curswant);
|
coladvance(curwin->w_curswant);
|
||||||
}
|
}
|
||||||
cap->count0 = redo_VIsual_count;
|
cap->count0 = redo_VIsual_count;
|
||||||
if (redo_VIsual_count != 0)
|
cap->count1 = (cap->count0 == 0 ? 1 : cap->count0);
|
||||||
cap->count1 = redo_VIsual_count;
|
|
||||||
else
|
|
||||||
cap->count1 = 1;
|
|
||||||
} else if (VIsual_active) {
|
} else if (VIsual_active) {
|
||||||
if (!gui_yank) {
|
if (!gui_yank) {
|
||||||
/* Save the current VIsual area for '< and '> marks, and "gv" */
|
/* Save the current VIsual area for '< and '> marks, and "gv" */
|
||||||
@ -7727,16 +7724,22 @@ static void nv_put(cmdarg_T *cap)
|
|||||||
savereg = copy_register(regname);
|
savereg = copy_register(regname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now delete the selected text. */
|
// To place the cursor correctly after a blockwise put, and to leave the
|
||||||
cap->cmdchar = 'd';
|
// text in the correct position when putting over a selection with
|
||||||
cap->nchar = NUL;
|
// 'virtualedit' and past the end of the line, we use the 'c' operator in
|
||||||
cap->oap->regname = NUL;
|
// do_put(), which requires the visual selection to still be active.
|
||||||
nv_operator(cap);
|
if (!VIsual_active || VIsual_mode == 'V' || regname != '.') {
|
||||||
do_pending_operator(cap, 0, false);
|
// Now delete the selected text.
|
||||||
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
|
cap->cmdchar = 'd';
|
||||||
|
cap->nchar = NUL;
|
||||||
|
cap->oap->regname = NUL;
|
||||||
|
nv_operator(cap);
|
||||||
|
do_pending_operator(cap, 0, false);
|
||||||
|
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
|
||||||
|
|
||||||
/* delete PUT_LINE_BACKWARD; */
|
// delete PUT_LINE_BACKWARD;
|
||||||
cap->oap->regname = regname;
|
cap->oap->regname = regname;
|
||||||
|
}
|
||||||
|
|
||||||
/* When deleted a linewise Visual area, put the register as
|
/* When deleted a linewise Visual area, put the register as
|
||||||
* lines to avoid it joined with the next line. When deletion was
|
* lines to avoid it joined with the next line. When deletion was
|
||||||
|
118
src/nvim/ops.c
118
src/nvim/ops.c
@ -2637,12 +2637,79 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
* special characters (newlines, etc.).
|
* special characters (newlines, etc.).
|
||||||
*/
|
*/
|
||||||
if (regname == '.') {
|
if (regname == '.') {
|
||||||
(void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') :
|
bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V');
|
||||||
(count == -1 ? 'O' : 'i')), count, FALSE);
|
|
||||||
/* Putting the text is done later, so can't really move the cursor to
|
// PUT_LINE has special handling below which means we use 'i' to start.
|
||||||
* the next character. Use "l" to simulate it. */
|
char command_start_char = non_linewise_vis ? 'c' :
|
||||||
if ((flags & PUT_CURSEND) && gchar_cursor() != NUL)
|
(flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i'));
|
||||||
stuffcharReadbuff('l');
|
|
||||||
|
// To avoid 'autoindent' on linewise puts, create a new line with `:put _`.
|
||||||
|
if (flags & PUT_LINE) {
|
||||||
|
do_put('_', NULL, dir, 1, PUT_LINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If given a count when putting linewise, we stuff the readbuf with the
|
||||||
|
// dot register 'count' times split by newlines.
|
||||||
|
if (flags & PUT_LINE) {
|
||||||
|
stuffcharReadbuff(command_start_char);
|
||||||
|
for (; count > 0; count--) {
|
||||||
|
(void)stuff_inserted(NUL, 1, count != 1);
|
||||||
|
if (count != 1) {
|
||||||
|
// To avoid 'autoindent' affecting the text, use Ctrl_U to remove any
|
||||||
|
// whitespace. Can't just insert Ctrl_U into readbuf1, this would go
|
||||||
|
// back to the previous line in the case of 'noautoindent' and
|
||||||
|
// 'backspace' includes "eol". So we insert a dummy space for Ctrl_U
|
||||||
|
// to consume.
|
||||||
|
stuffReadbuff((char_u *)"\n ");
|
||||||
|
stuffcharReadbuff(Ctrl_U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(void)stuff_inserted(command_start_char, count, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Putting the text is done later, so can't move the cursor to the next
|
||||||
|
// character. Simulate it with motion commands after the insert.
|
||||||
|
if (flags & PUT_CURSEND) {
|
||||||
|
if (flags & PUT_LINE) {
|
||||||
|
stuffReadbuff((char_u *)"j0");
|
||||||
|
} else {
|
||||||
|
// Avoid ringing the bell from attempting to move into the space after
|
||||||
|
// the current line. We can stuff the readbuffer with "l" if:
|
||||||
|
// 1) 'virtualedit' is "all" or "onemore"
|
||||||
|
// 2) We are not at the end of the line
|
||||||
|
// 3) We are not (one past the end of the line && on the last line)
|
||||||
|
// This allows a visual put over a selection one past the end of the
|
||||||
|
// line joining the current line with the one below.
|
||||||
|
|
||||||
|
// curwin->w_cursor.col marks the byte position of the cursor in the
|
||||||
|
// currunt line. It increases up to a max of
|
||||||
|
// STRLEN(ml_get(curwin->w_cursor.lnum)). With 'virtualedit' and the
|
||||||
|
// cursor past the end of the line, curwin->w_cursor.coladd is
|
||||||
|
// incremented instead of curwin->w_cursor.col.
|
||||||
|
char_u *cursor_pos = get_cursor_pos_ptr();
|
||||||
|
bool one_past_line = (*cursor_pos == NUL);
|
||||||
|
bool eol = false;
|
||||||
|
if (!one_past_line) {
|
||||||
|
eol = (*(cursor_pos + mb_ptr2len(cursor_pos)) == NUL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE);
|
||||||
|
bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum
|
||||||
|
&& one_past_line;
|
||||||
|
if (ve_allows || !(eol || eof)) {
|
||||||
|
stuffcharReadbuff('l');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (flags & PUT_LINE) {
|
||||||
|
stuffReadbuff((char_u *)"g'[");
|
||||||
|
}
|
||||||
|
|
||||||
|
// So the 'u' command restores cursor position after ".p, save the cursor
|
||||||
|
// position now (though not saving any text).
|
||||||
|
if (command_start_char == 'a') {
|
||||||
|
u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2831,14 +2898,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
else
|
else
|
||||||
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
|
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
|
||||||
|
|
||||||
if (has_mbyte)
|
// move to start of next multi-byte character
|
||||||
/* move to start of next multi-byte character */
|
curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
|
||||||
curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr());
|
col++;
|
||||||
else if (c != TAB || ve_flags != VE_ALL)
|
} else {
|
||||||
++curwin->w_cursor.col;
|
|
||||||
++col;
|
|
||||||
} else
|
|
||||||
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
|
getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2);
|
||||||
|
}
|
||||||
|
|
||||||
col += curwin->w_cursor.coladd;
|
col += curwin->w_cursor.coladd;
|
||||||
if (ve_flags == VE_ALL
|
if (ve_flags == VE_ALL
|
||||||
@ -2892,8 +2957,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
bd.startspaces = incr - bd.endspaces;
|
bd.startspaces = incr - bd.endspaces;
|
||||||
--bd.textcol;
|
--bd.textcol;
|
||||||
delcount = 1;
|
delcount = 1;
|
||||||
if (has_mbyte)
|
bd.textcol -= (*mb_head_off)(oldp, oldp + bd.textcol);
|
||||||
bd.textcol -= (*mb_head_off)(oldp, oldp + bd.textcol);
|
|
||||||
if (oldp[bd.textcol] != TAB) {
|
if (oldp[bd.textcol] != TAB) {
|
||||||
/* Only a Tab can be split into spaces. Other
|
/* Only a Tab can be split into spaces. Other
|
||||||
* characters will have to be moved to after the
|
* characters will have to be moved to after the
|
||||||
@ -2975,21 +3039,13 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
// if type is kMTCharWise, FORWARD is the same as BACKWARD on the next
|
// if type is kMTCharWise, FORWARD is the same as BACKWARD on the next
|
||||||
// char
|
// char
|
||||||
if (dir == FORWARD && gchar_cursor() != NUL) {
|
if (dir == FORWARD && gchar_cursor() != NUL) {
|
||||||
if (has_mbyte) {
|
int bytelen = (*mb_ptr2len)(get_cursor_pos_ptr());
|
||||||
int bytelen = (*mb_ptr2len)(get_cursor_pos_ptr());
|
|
||||||
|
|
||||||
/* put it on the next of the multi-byte character. */
|
// put it on the next of the multi-byte character.
|
||||||
col += bytelen;
|
col += bytelen;
|
||||||
if (yanklen) {
|
if (yanklen) {
|
||||||
curwin->w_cursor.col += bytelen;
|
curwin->w_cursor.col += bytelen;
|
||||||
curbuf->b_op_end.col += bytelen;
|
curbuf->b_op_end.col += bytelen;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
++col;
|
|
||||||
if (yanklen) {
|
|
||||||
++curwin->w_cursor.col;
|
|
||||||
++curbuf->b_op_end.col;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
curbuf->b_op_start = curwin->w_cursor;
|
curbuf->b_op_start = curwin->w_cursor;
|
||||||
@ -3027,7 +3083,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
}
|
}
|
||||||
if (VIsual_active)
|
if (VIsual_active)
|
||||||
lnum++;
|
lnum++;
|
||||||
} while (VIsual_active && lnum <= curbuf->b_visual.vi_end.lnum);
|
} while (VIsual_active
|
||||||
|
&& (lnum <= curbuf->b_visual.vi_end.lnum
|
||||||
|
|| lnum <= curbuf->b_visual.vi_start.lnum));
|
||||||
|
|
||||||
if (VIsual_active) { /* reset lnum to the last visual line */
|
if (VIsual_active) { /* reset lnum to the last visual line */
|
||||||
lnum--;
|
lnum--;
|
||||||
|
@ -13,6 +13,8 @@ local check_logs = global_helpers.check_logs
|
|||||||
local neq = global_helpers.neq
|
local neq = global_helpers.neq
|
||||||
local eq = global_helpers.eq
|
local eq = global_helpers.eq
|
||||||
local ok = global_helpers.ok
|
local ok = global_helpers.ok
|
||||||
|
local map = global_helpers.map
|
||||||
|
local filter = global_helpers.filter
|
||||||
|
|
||||||
local start_dir = lfs.currentdir()
|
local start_dir = lfs.currentdir()
|
||||||
local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim'
|
local nvim_prog = os.getenv('NVIM_PROG') or 'build/bin/nvim'
|
||||||
@ -570,6 +572,8 @@ return function(after_each)
|
|||||||
neq = neq,
|
neq = neq,
|
||||||
expect = expect,
|
expect = expect,
|
||||||
ok = ok,
|
ok = ok,
|
||||||
|
map = map,
|
||||||
|
filter = filter,
|
||||||
nvim = nvim,
|
nvim = nvim,
|
||||||
nvim_async = nvim_async,
|
nvim_async = nvim_async,
|
||||||
nvim_prog = nvim_prog,
|
nvim_prog = nvim_prog,
|
||||||
|
936
test/functional/normal/put_spec.lua
Normal file
936
test/functional/normal/put_spec.lua
Normal file
@ -0,0 +1,936 @@
|
|||||||
|
local Screen = require('test.functional.ui.screen')
|
||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local clear = helpers.clear
|
||||||
|
local insert = helpers.insert
|
||||||
|
local feed = helpers.feed
|
||||||
|
local expect = helpers.expect
|
||||||
|
local eq = helpers.eq
|
||||||
|
local map = helpers.map
|
||||||
|
local filter = helpers.filter
|
||||||
|
local execute = helpers.execute
|
||||||
|
local curbuf_contents = helpers.curbuf_contents
|
||||||
|
local funcs = helpers.funcs
|
||||||
|
local dedent = helpers.dedent
|
||||||
|
local getreg = funcs.getreg
|
||||||
|
|
||||||
|
local function reset()
|
||||||
|
clear()
|
||||||
|
insert([[
|
||||||
|
Line of words 1
|
||||||
|
Line of words 2]])
|
||||||
|
execute('goto 1')
|
||||||
|
feed('itest_string.<esc>u')
|
||||||
|
funcs.setreg('a', 'test_stringa', 'V')
|
||||||
|
funcs.setreg('b', 'test_stringb\ntest_stringb\ntest_stringb', 'b')
|
||||||
|
funcs.setreg('"', 'test_string"', 'v')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We check the last inserted register ". in each of these tests because it is
|
||||||
|
-- implemented completely differently in do_put().
|
||||||
|
-- It is implemented differently so that control characters and imap'ped
|
||||||
|
-- characters work in the same manner when pasted as when inserted.
|
||||||
|
describe('put command', function()
|
||||||
|
-- Put a call to clear() here to force the connection to the server.
|
||||||
|
-- This means we can use the funcs.*() functions while mangling text before
|
||||||
|
-- the actual tests are run.
|
||||||
|
clear()
|
||||||
|
before_each(reset)
|
||||||
|
|
||||||
|
local function visual_marks_zero()
|
||||||
|
for _,v in pairs(funcs.getpos("'<")) do
|
||||||
|
if v ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _,v in pairs(funcs.getpos("'>")) do
|
||||||
|
if v ~= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- {{{ Where test definitions are run
|
||||||
|
local function run_test_variations(test_variations, extra_setup)
|
||||||
|
reset()
|
||||||
|
if extra_setup then extra_setup() end
|
||||||
|
local init_contents = curbuf_contents()
|
||||||
|
local init_cursorpos = funcs.getcurpos()
|
||||||
|
local assert_no_change = function (exception_table, after_undo)
|
||||||
|
expect(init_contents)
|
||||||
|
-- When putting the ". register forwards, undo doesn't move
|
||||||
|
-- the cursor back to where it was before.
|
||||||
|
-- This is because it uses the command character 'a' to
|
||||||
|
-- start the insert, and undo after that leaves the cursor
|
||||||
|
-- one place to the right (unless we were at the end of the
|
||||||
|
-- line when we pasted).
|
||||||
|
if not (exception_table.undo_position and after_undo) then
|
||||||
|
eq(funcs.getcurpos(), init_cursorpos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, test in pairs(test_variations) do
|
||||||
|
it(test.description, function()
|
||||||
|
if extra_setup then extra_setup() end
|
||||||
|
local orig_dotstr = funcs.getreg('.')
|
||||||
|
helpers.ok(visual_marks_zero())
|
||||||
|
-- Make sure every test starts from the same conditions
|
||||||
|
assert_no_change(test.exception_table, false)
|
||||||
|
local was_cli = test.test_action()
|
||||||
|
test.test_assertions(test.exception_table, false)
|
||||||
|
-- Check that undo twice puts us back to the original conditions
|
||||||
|
-- (i.e. puts the cursor and text back to before)
|
||||||
|
feed('u')
|
||||||
|
assert_no_change(test.exception_table, true)
|
||||||
|
|
||||||
|
-- Should not have changed the ". register
|
||||||
|
-- If we paste the ". register with a count we can't avoid
|
||||||
|
-- changing this register, hence avoid this check.
|
||||||
|
if not test.exception_table.dot_reg_changed then
|
||||||
|
eq(funcs.getreg('.'), orig_dotstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Doing something, undoing it, and then redoing it should
|
||||||
|
-- leave us in the same state as just doing it once.
|
||||||
|
-- For :ex actions we want '@:', for normal actions we want '.'
|
||||||
|
|
||||||
|
-- The '.' redo doesn't work for visual put so just exit if
|
||||||
|
-- it was tested.
|
||||||
|
-- We check that visual put was used by checking if the '< and
|
||||||
|
-- '> marks were changed.
|
||||||
|
if not visual_marks_zero() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if test.exception_table.undo_position then
|
||||||
|
funcs.setpos('.', init_cursorpos)
|
||||||
|
end
|
||||||
|
if was_cli then
|
||||||
|
feed('@:')
|
||||||
|
else
|
||||||
|
feed('.')
|
||||||
|
end
|
||||||
|
|
||||||
|
test.test_assertions(test.exception_table, true)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end -- run_test_variations()
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
local function create_test_defs(test_defs, command_base, command_creator, -- {{{
|
||||||
|
expect_base, expect_creator)
|
||||||
|
local rettab = {}
|
||||||
|
local exceptions
|
||||||
|
for _, v in pairs(test_defs) do
|
||||||
|
if v[4] then
|
||||||
|
exceptions = v[4]
|
||||||
|
else
|
||||||
|
exceptions = {}
|
||||||
|
end
|
||||||
|
table.insert(rettab,
|
||||||
|
{
|
||||||
|
test_action = command_creator(command_base, v[1]),
|
||||||
|
test_assertions = expect_creator(expect_base, v[2]),
|
||||||
|
description = v[3],
|
||||||
|
exception_table = exceptions,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
return rettab
|
||||||
|
end -- create_test_defs() }}}
|
||||||
|
|
||||||
|
local function find_cursor_position(expect_string) -- {{{
|
||||||
|
-- There must only be one occurance of the character 'x' in
|
||||||
|
-- expect_string.
|
||||||
|
-- This function removes that occurance, and returns the position that
|
||||||
|
-- it was in.
|
||||||
|
-- This returns the cursor position that would leave the 'x' in that
|
||||||
|
-- place if we feed 'ix<esc>' and the string existed before it.
|
||||||
|
for linenum, line in pairs(funcs.split(expect_string, '\n', 1)) do
|
||||||
|
local column = line:find('x')
|
||||||
|
if column then
|
||||||
|
return {linenum, column}, expect_string:gsub('x', '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end -- find_cursor_position() }}}
|
||||||
|
|
||||||
|
-- Action function creators {{{
|
||||||
|
local function create_p_action(test_map, substitution)
|
||||||
|
local temp_val = test_map:gsub('p', substitution)
|
||||||
|
return function()
|
||||||
|
feed(temp_val)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_put_action(command_base, substitution)
|
||||||
|
local temp_val = command_base:gsub('put', substitution)
|
||||||
|
return function()
|
||||||
|
execute(temp_val)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Expect function creator {{{
|
||||||
|
local function expect_creator(conversion_function, expect_base, conversion_table)
|
||||||
|
local temp_expect_string = conversion_function(expect_base, conversion_table)
|
||||||
|
local cursor_position, expect_string = find_cursor_position(temp_expect_string)
|
||||||
|
return function(exception_table, after_redo)
|
||||||
|
expect(expect_string)
|
||||||
|
|
||||||
|
-- Have to use getcurpos() instead of curwinmeths.get_cursor() in
|
||||||
|
-- order to account for virtualedit.
|
||||||
|
-- We always want the curswant element in getcurpos(), which is
|
||||||
|
-- sometimes different to the column element in
|
||||||
|
-- curwinmeths.get_cursor().
|
||||||
|
-- NOTE: The ".gp command leaves the cursor after the pasted text
|
||||||
|
-- when running, but does not when the command is redone with the
|
||||||
|
-- '.' command.
|
||||||
|
if not (exception_table.redo_position and after_redo) then
|
||||||
|
local actual_position = funcs.getcurpos()
|
||||||
|
eq(cursor_position, {actual_position[2], actual_position[5]})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end -- expect_creator() }}}
|
||||||
|
|
||||||
|
-- Test definitions {{{
|
||||||
|
local function copy_def(def)
|
||||||
|
local rettab = { '', {}, '', nil }
|
||||||
|
rettab[1] = def[1]
|
||||||
|
for k,v in pairs(def[2]) do
|
||||||
|
rettab[2][k] = v
|
||||||
|
end
|
||||||
|
rettab[3] = def[3]
|
||||||
|
if def[4] then
|
||||||
|
rettab[4] = {}
|
||||||
|
for k,v in pairs(def[4]) do
|
||||||
|
rettab[4][k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return rettab
|
||||||
|
end
|
||||||
|
|
||||||
|
local normal_command_defs = {
|
||||||
|
{
|
||||||
|
'p',
|
||||||
|
{cursor_after = false, put_backwards = false, dot_register = false},
|
||||||
|
'pastes after cursor with p',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'gp',
|
||||||
|
{cursor_after = true, put_backwards = false, dot_register = false},
|
||||||
|
'leaves cursor after text with gp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'".p',
|
||||||
|
{cursor_after = false, put_backwards = false, dot_register = true},
|
||||||
|
'works with the ". register',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'".gp',
|
||||||
|
{cursor_after = true, put_backwards = false, dot_register = true},
|
||||||
|
'gp works with the ". register',
|
||||||
|
{redo_position = true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'P',
|
||||||
|
{cursor_after = false, put_backwards = true, dot_register = false},
|
||||||
|
'pastes before cursor with P',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'gP',
|
||||||
|
{cursor_after = true, put_backwards = true, dot_register = false},
|
||||||
|
'gP pastes before cursor and leaves cursor after text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'".P',
|
||||||
|
{cursor_after = false, put_backwards = true, dot_register = true},
|
||||||
|
'P works with ". register',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'".gP',
|
||||||
|
{cursor_after = true, put_backwards = true, dot_register = true},
|
||||||
|
'gP works with ". register',
|
||||||
|
{redo_position = true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Add a definition applying a count for each definition above.
|
||||||
|
-- Could do this for each transformation (p -> P, p -> gp etc), but I think
|
||||||
|
-- it's neater this way (balance between being explicit and too verbose).
|
||||||
|
for i = 1,#normal_command_defs do
|
||||||
|
local cur = normal_command_defs[i]
|
||||||
|
|
||||||
|
-- Make modified copy of current definition that includes a count.
|
||||||
|
local newdef = copy_def(cur)
|
||||||
|
newdef[2].count = 2
|
||||||
|
cur[2].count = 1
|
||||||
|
newdef[1] = '2' .. newdef[1]
|
||||||
|
newdef[3] = 'double ' .. newdef[3]
|
||||||
|
|
||||||
|
if cur[2].dot_register then
|
||||||
|
if not cur[4] then
|
||||||
|
newdef[4] = {}
|
||||||
|
end
|
||||||
|
newdef[4].dot_reg_changed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
normal_command_defs[#normal_command_defs + 1] = newdef
|
||||||
|
end
|
||||||
|
|
||||||
|
local ex_command_defs = {
|
||||||
|
{
|
||||||
|
'put',
|
||||||
|
{put_backwards = false, dot_register = false},
|
||||||
|
'pastes linewise forwards with :put',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'put!',
|
||||||
|
{put_backwards = true, dot_register = false},
|
||||||
|
'pastes linewise backwards with :put!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'put .',
|
||||||
|
{put_backwards = false, dot_register = true},
|
||||||
|
'pastes linewise with the dot register',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'put! .',
|
||||||
|
{put_backwards = true, dot_register = true},
|
||||||
|
'pastes linewise backwards with the dot register',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function non_dotdefs(def_table)
|
||||||
|
return filter(function(d) return not d[2].dot_register end, def_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Conversion functions {{{
|
||||||
|
local function convert_characterwise(expect_base, conversion_table,
|
||||||
|
virtualedit_end, visual_put)
|
||||||
|
expect_base = dedent(expect_base)
|
||||||
|
-- There is no difference between 'P' and 'p' when VIsual_active
|
||||||
|
if not visual_put then
|
||||||
|
if conversion_table.put_backwards then
|
||||||
|
-- Special case for virtualedit at the end of a line.
|
||||||
|
local replace_string
|
||||||
|
if not virtualedit_end then
|
||||||
|
replace_string = 'test_stringx"%1'
|
||||||
|
else
|
||||||
|
replace_string = 'test_stringx"'
|
||||||
|
end
|
||||||
|
expect_base = expect_base:gsub('(.)test_stringx"', replace_string)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if conversion_table.count > 1 then
|
||||||
|
local rep_string = 'test_string"'
|
||||||
|
local extra_puts = rep_string:rep(conversion_table.count - 1)
|
||||||
|
expect_base = expect_base:gsub('test_stringx"', extra_puts .. 'test_stringx"')
|
||||||
|
end
|
||||||
|
if conversion_table.cursor_after then
|
||||||
|
expect_base = expect_base:gsub('test_stringx"', 'test_string"x')
|
||||||
|
end
|
||||||
|
if conversion_table.dot_register then
|
||||||
|
expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
|
||||||
|
end
|
||||||
|
return expect_base
|
||||||
|
end -- convert_characterwise()
|
||||||
|
|
||||||
|
local function make_back(string)
|
||||||
|
local prev_line
|
||||||
|
local rettab = {}
|
||||||
|
local string_found = false
|
||||||
|
for _, line in pairs(funcs.split(string, '\n', 1)) do
|
||||||
|
if line:find('test_string') then
|
||||||
|
string_found = true
|
||||||
|
table.insert(rettab, line)
|
||||||
|
else
|
||||||
|
if string_found then
|
||||||
|
if prev_line then
|
||||||
|
table.insert(rettab, prev_line)
|
||||||
|
prev_line = nil
|
||||||
|
end
|
||||||
|
table.insert(rettab, line)
|
||||||
|
else
|
||||||
|
table.insert(rettab, prev_line)
|
||||||
|
prev_line = line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- In case there are no lines after the text that was put.
|
||||||
|
if prev_line and string_found then
|
||||||
|
table.insert(rettab, prev_line)
|
||||||
|
end
|
||||||
|
return table.concat(rettab, '\n')
|
||||||
|
end -- make_back()
|
||||||
|
|
||||||
|
local function convert_linewise(expect_base, conversion_table, _, use_a, indent)
|
||||||
|
expect_base = dedent(expect_base)
|
||||||
|
if conversion_table.put_backwards then
|
||||||
|
expect_base = make_back(expect_base)
|
||||||
|
end
|
||||||
|
local p_str = 'test_string"'
|
||||||
|
if use_a then
|
||||||
|
p_str = 'test_stringa'
|
||||||
|
end
|
||||||
|
|
||||||
|
if conversion_table.dot_register then
|
||||||
|
expect_base = expect_base:gsub('x' .. p_str, 'xtest_string.')
|
||||||
|
p_str = 'test_string.'
|
||||||
|
end
|
||||||
|
|
||||||
|
if conversion_table.cursor_after then
|
||||||
|
expect_base = expect_base:gsub('x' .. p_str .. '\n', p_str .. '\nx')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The 'indent' argument is only used here because a single put with an
|
||||||
|
-- indent doesn't require special handling. It doesn't require special
|
||||||
|
-- handling because the cursor is never put before the indent, hence
|
||||||
|
-- the modification of 'test_stringx"' gives the same overall answer as
|
||||||
|
-- modifying ' test_stringx"'.
|
||||||
|
|
||||||
|
-- Only happens when using normal mode command actions.
|
||||||
|
if conversion_table.count and conversion_table.count > 1 then
|
||||||
|
if not indent then
|
||||||
|
indent = ''
|
||||||
|
end
|
||||||
|
local rep_string = indent .. p_str .. '\n'
|
||||||
|
local extra_puts = rep_string:rep(conversion_table.count - 1)
|
||||||
|
local orig_string, new_string
|
||||||
|
if conversion_table.cursor_after then
|
||||||
|
orig_string = indent .. p_str .. '\nx'
|
||||||
|
new_string = extra_puts .. orig_string
|
||||||
|
else
|
||||||
|
orig_string = indent .. 'x' .. p_str .. '\n'
|
||||||
|
new_string = orig_string .. extra_puts
|
||||||
|
end
|
||||||
|
expect_base = expect_base:gsub(orig_string, new_string)
|
||||||
|
end
|
||||||
|
return expect_base
|
||||||
|
end
|
||||||
|
|
||||||
|
local function put_x_last(orig_line, p_str)
|
||||||
|
local prev_end, cur_end, cur_start = 0, 0, 0
|
||||||
|
while cur_start do
|
||||||
|
prev_end = cur_end
|
||||||
|
cur_start, cur_end = orig_line:find(p_str, prev_end)
|
||||||
|
end
|
||||||
|
-- Assume (because that is the only way I call it) that p_str matches
|
||||||
|
-- the pattern 'test_string.'
|
||||||
|
return orig_line:sub(1, prev_end - 1) .. 'x' .. orig_line:sub(prev_end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function convert_blockwise(expect_base, conversion_table, visual,
|
||||||
|
use_b, trailing_whitespace)
|
||||||
|
expect_base = dedent(expect_base)
|
||||||
|
local p_str = 'test_string"'
|
||||||
|
if use_b then
|
||||||
|
p_str = 'test_stringb'
|
||||||
|
end
|
||||||
|
|
||||||
|
if conversion_table.dot_register then
|
||||||
|
expect_base = expect_base:gsub('(x?)' .. p_str, '%1test_string.')
|
||||||
|
-- Looks strange, but the dot is a special character in the pattern
|
||||||
|
-- and a literal character in the replacement.
|
||||||
|
expect_base = expect_base:gsub('test_stringx.', 'test_stringx.')
|
||||||
|
p_str = 'test_string.'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No difference between 'p' and 'P' in visual mode.
|
||||||
|
if not visual then
|
||||||
|
if conversion_table.put_backwards then
|
||||||
|
-- One for the line where the cursor is left, one for all other
|
||||||
|
-- lines.
|
||||||
|
expect_base = expect_base:gsub('([^x])' .. p_str, p_str .. '%1')
|
||||||
|
expect_base = expect_base:gsub('([^x])x' .. p_str, 'x' .. p_str .. '%1')
|
||||||
|
if not trailing_whitespace then
|
||||||
|
expect_base = expect_base:gsub(' \n', '\n')
|
||||||
|
expect_base = expect_base:gsub(' $', '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if conversion_table.count and conversion_table.count > 1 then
|
||||||
|
local p_pattern = p_str:gsub('%.', '%%.')
|
||||||
|
expect_base = expect_base:gsub(p_pattern,
|
||||||
|
p_str:rep(conversion_table.count))
|
||||||
|
expect_base = expect_base:gsub('test_stringx([b".])',
|
||||||
|
p_str:rep(conversion_table.count - 1)
|
||||||
|
.. '%0')
|
||||||
|
end
|
||||||
|
|
||||||
|
if conversion_table.cursor_after then
|
||||||
|
if not visual then
|
||||||
|
local prev_line
|
||||||
|
local rettab = {}
|
||||||
|
local prev_in_block = false
|
||||||
|
for _, line in pairs(funcs.split(expect_base, '\n', 1)) do
|
||||||
|
if line:find('test_string') then
|
||||||
|
if prev_line then
|
||||||
|
prev_line = prev_line:gsub('x', '')
|
||||||
|
table.insert(rettab, prev_line)
|
||||||
|
end
|
||||||
|
prev_line = line
|
||||||
|
prev_in_block = true
|
||||||
|
else
|
||||||
|
if prev_in_block then
|
||||||
|
prev_line = put_x_last(prev_line, p_str)
|
||||||
|
table.insert(rettab, prev_line)
|
||||||
|
prev_in_block = false
|
||||||
|
end
|
||||||
|
table.insert(rettab, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if prev_line and prev_in_block then
|
||||||
|
table.insert(rettab, put_x_last(prev_line, p_str))
|
||||||
|
end
|
||||||
|
|
||||||
|
expect_base = table.concat(rettab, '\n')
|
||||||
|
else
|
||||||
|
expect_base = expect_base:gsub('x(.)', '%1x')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return expect_base
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Convenience functions {{{
|
||||||
|
local function run_normal_mode_tests(test_string, base_map, extra_setup,
|
||||||
|
virtualedit_end, selection_string)
|
||||||
|
local function convert_closure(e, c)
|
||||||
|
return convert_characterwise(e, c, virtualedit_end, selection_string)
|
||||||
|
end
|
||||||
|
local function expect_normal_creator(expect_base, conversion_table)
|
||||||
|
local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
|
||||||
|
return function(exception_table, after_redo)
|
||||||
|
test_expect(exception_table, after_redo)
|
||||||
|
if selection_string then
|
||||||
|
eq(getreg('"'), selection_string)
|
||||||
|
else
|
||||||
|
eq(getreg('"'), 'test_string"')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
normal_command_defs,
|
||||||
|
base_map,
|
||||||
|
create_p_action,
|
||||||
|
test_string,
|
||||||
|
expect_normal_creator
|
||||||
|
),
|
||||||
|
extra_setup
|
||||||
|
)
|
||||||
|
end -- run_normal_mode_tests()
|
||||||
|
|
||||||
|
local function convert_linewiseer(expect_base, conversion_table)
|
||||||
|
return expect_creator(convert_linewise, expect_base, conversion_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_linewise_tests(expect_base, base_command, extra_setup)
|
||||||
|
local linewise_test_defs = create_test_defs(
|
||||||
|
ex_command_defs, base_command,
|
||||||
|
create_put_action, expect_base, convert_linewiseer)
|
||||||
|
run_test_variations(linewise_test_defs, extra_setup)
|
||||||
|
end -- run_linewise_tests()
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Actual tests
|
||||||
|
describe('default pasting', function()
|
||||||
|
local expect_string = [[
|
||||||
|
Ltest_stringx"ine of words 1
|
||||||
|
Line of words 2]]
|
||||||
|
run_normal_mode_tests(expect_string, 'p')
|
||||||
|
|
||||||
|
run_linewise_tests([[
|
||||||
|
Line of words 1
|
||||||
|
xtest_string"
|
||||||
|
Line of words 2]],
|
||||||
|
'put'
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('linewise register', function()
|
||||||
|
-- put with 'p'
|
||||||
|
local local_ex_command_defs = non_dotdefs(normal_command_defs)
|
||||||
|
local base_expect_string = [[
|
||||||
|
Line of words 1
|
||||||
|
xtest_stringa
|
||||||
|
Line of words 2]]
|
||||||
|
local function local_convert_linewise(expect_base, conversion_table)
|
||||||
|
return convert_linewise(expect_base, conversion_table, nil, true)
|
||||||
|
end
|
||||||
|
local function expect_lineput(expect_base, conversion_table)
|
||||||
|
return expect_creator(local_convert_linewise, expect_base, conversion_table)
|
||||||
|
end
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
local_ex_command_defs,
|
||||||
|
'"ap',
|
||||||
|
create_p_action,
|
||||||
|
base_expect_string,
|
||||||
|
expect_lineput
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- put with :put
|
||||||
|
local linewise_put_defs = non_dotdefs(ex_command_defs)
|
||||||
|
base_expect_string = [[
|
||||||
|
Line of words 1
|
||||||
|
xtest_stringa
|
||||||
|
Line of words 2]]
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
linewise_put_defs,
|
||||||
|
'put a', create_put_action,
|
||||||
|
base_expect_string, convert_linewiseer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('blockwise register', function()
|
||||||
|
local blockwise_put_defs = non_dotdefs(normal_command_defs)
|
||||||
|
local test_base = [[
|
||||||
|
Lxtest_stringbine of words 1
|
||||||
|
Ltest_stringbine of words 2
|
||||||
|
test_stringb]]
|
||||||
|
|
||||||
|
local function expect_block_creator(expect_base, conversion_table)
|
||||||
|
return expect_creator(function(e,c) return convert_blockwise(e,c,nil,true) end,
|
||||||
|
expect_base, conversion_table)
|
||||||
|
end
|
||||||
|
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
blockwise_put_defs,
|
||||||
|
'"bp',
|
||||||
|
create_p_action,
|
||||||
|
test_base,
|
||||||
|
expect_block_creator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('adds correct indentation when put with [p and ]p', function()
|
||||||
|
feed('G>>"a]pix<esc>')
|
||||||
|
-- luacheck: ignore
|
||||||
|
expect([[
|
||||||
|
Line of words 1
|
||||||
|
Line of words 2
|
||||||
|
xtest_stringa]])
|
||||||
|
feed('uu"a[pix<esc>')
|
||||||
|
-- luacheck: ignore
|
||||||
|
expect([[
|
||||||
|
Line of words 1
|
||||||
|
xtest_stringa
|
||||||
|
Line of words 2]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('linewise paste with autoindent', function()
|
||||||
|
-- luacheck: ignore
|
||||||
|
run_linewise_tests([[
|
||||||
|
Line of words 1
|
||||||
|
Line of words 2
|
||||||
|
xtest_string"]],
|
||||||
|
'put'
|
||||||
|
,
|
||||||
|
function()
|
||||||
|
funcs.setline('$', ' Line of words 2')
|
||||||
|
-- Set curswant to '8' to be at the end of the tab character
|
||||||
|
-- This is where the cursor is put back after the 'u' command.
|
||||||
|
funcs.setpos('.', {0, 2, 1, 0, 8})
|
||||||
|
execute('set autoindent')
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('put inside tabs with virtualedit', function()
|
||||||
|
local test_string = [[
|
||||||
|
Line of words 1
|
||||||
|
test_stringx" Line of words 2]]
|
||||||
|
run_normal_mode_tests(test_string, 'p', function()
|
||||||
|
funcs.setline('$', ' Line of words 2')
|
||||||
|
execute('set virtualedit=all')
|
||||||
|
funcs.setpos('.', {0, 2, 1, 2, 3})
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('put after the line with virtualedit', function()
|
||||||
|
local test_string = [[
|
||||||
|
Line of words 1 test_stringx"
|
||||||
|
Line of words 2]]
|
||||||
|
run_normal_mode_tests(test_string, 'p', function()
|
||||||
|
funcs.setline('$', ' Line of words 2')
|
||||||
|
execute('set virtualedit=all')
|
||||||
|
funcs.setpos('.', {0, 1, 16, 1, 17})
|
||||||
|
end, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('Visual put', function()
|
||||||
|
describe('basic put', function()
|
||||||
|
local test_string = [[
|
||||||
|
test_stringx" words 1
|
||||||
|
Line of words 2]]
|
||||||
|
run_normal_mode_tests(test_string, 'v2ep', nil, nil, 'Line of')
|
||||||
|
end)
|
||||||
|
describe('over trailing newline', function()
|
||||||
|
local test_string = 'Line of test_stringx"Line of words 2'
|
||||||
|
run_normal_mode_tests(test_string, 'v$p', function()
|
||||||
|
funcs.setpos('.', {0, 1, 9, 0, 9})
|
||||||
|
end,
|
||||||
|
nil,
|
||||||
|
'words 1\n')
|
||||||
|
end)
|
||||||
|
describe('linewise mode', function()
|
||||||
|
local test_string = [[
|
||||||
|
xtest_string"
|
||||||
|
Line of words 2]]
|
||||||
|
local function expect_vis_linewise(expect_base, conversion_table)
|
||||||
|
return expect_creator(function(e, c)
|
||||||
|
return convert_linewise(e, c, nil, nil)
|
||||||
|
end,
|
||||||
|
expect_base, conversion_table)
|
||||||
|
end
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
normal_command_defs,
|
||||||
|
'Vp',
|
||||||
|
create_p_action,
|
||||||
|
test_string,
|
||||||
|
expect_vis_linewise
|
||||||
|
),
|
||||||
|
function() funcs.setpos('.', {0, 1, 1, 0, 1}) end
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('with whitespace at bol', function()
|
||||||
|
local function expect_vis_lineindented(expect_base, conversion_table)
|
||||||
|
local test_expect = expect_creator(function(e, c)
|
||||||
|
return convert_linewise(e, c, nil, nil, ' ')
|
||||||
|
end,
|
||||||
|
expect_base, conversion_table)
|
||||||
|
return function(exception_table, after_redo)
|
||||||
|
test_expect(exception_table, after_redo)
|
||||||
|
eq(getreg('"'), 'Line of words 1\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local base_expect_string = [[
|
||||||
|
xtest_string"
|
||||||
|
Line of words 2]]
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
normal_command_defs,
|
||||||
|
'Vp',
|
||||||
|
create_p_action,
|
||||||
|
base_expect_string,
|
||||||
|
expect_vis_lineindented
|
||||||
|
),
|
||||||
|
function()
|
||||||
|
feed('i test_string.<esc>u')
|
||||||
|
funcs.setreg('"', ' test_string"', 'v')
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('blockwise visual mode', function()
|
||||||
|
local test_base = [[
|
||||||
|
test_stringx"e of words 1
|
||||||
|
test_string"e of words 2]]
|
||||||
|
|
||||||
|
local function expect_block_creator(expect_base, conversion_table)
|
||||||
|
local test_expect = expect_creator(function(e, c)
|
||||||
|
return convert_blockwise(e, c, true)
|
||||||
|
end, expect_base, conversion_table)
|
||||||
|
return function(e,c)
|
||||||
|
test_expect(e,c)
|
||||||
|
eq(getreg('"'), 'Lin\nLin')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local select_down_test_defs = create_test_defs(
|
||||||
|
normal_command_defs,
|
||||||
|
'<C-v>jllp',
|
||||||
|
create_p_action,
|
||||||
|
test_base,
|
||||||
|
expect_block_creator
|
||||||
|
)
|
||||||
|
run_test_variations(select_down_test_defs)
|
||||||
|
|
||||||
|
|
||||||
|
-- Undo and redo of a visual block put leave the cursor in the top
|
||||||
|
-- left of the visual block area no matter where the cursor was
|
||||||
|
-- when it started.
|
||||||
|
local undo_redo_no = map(function(table)
|
||||||
|
local rettab = copy_def(table)
|
||||||
|
if not rettab[4] then
|
||||||
|
rettab[4] = {}
|
||||||
|
end
|
||||||
|
rettab[4].undo_position = true
|
||||||
|
rettab[4].redo_position = true
|
||||||
|
return rettab
|
||||||
|
end,
|
||||||
|
normal_command_defs)
|
||||||
|
|
||||||
|
-- Selection direction doesn't matter
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
undo_redo_no,
|
||||||
|
'<C-v>kllp',
|
||||||
|
create_p_action,
|
||||||
|
test_base,
|
||||||
|
expect_block_creator
|
||||||
|
),
|
||||||
|
function() funcs.setpos('.', {0, 2, 1, 0, 1}) end
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('blockwise cursor after undo', function()
|
||||||
|
-- A bit of a hack of the reset above.
|
||||||
|
-- In the tests that selection direction doesn't matter, we
|
||||||
|
-- don't check the undo/redo position because it doesn't fit
|
||||||
|
-- the same pattern as everything else.
|
||||||
|
-- Here we fix this by directly checking the undo/redo position
|
||||||
|
-- in the test_assertions of our test definitions.
|
||||||
|
local function assertion_creator(_,_)
|
||||||
|
return function(_,_)
|
||||||
|
feed('u')
|
||||||
|
-- Have to use feed('u') here to set curswant, because
|
||||||
|
-- ex_undo() doesn't do that.
|
||||||
|
eq(funcs.getcurpos(), {0, 1, 1, 0, 1})
|
||||||
|
feed('<C-r>')
|
||||||
|
eq(funcs.getcurpos(), {0, 1, 1, 0, 1})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
run_test_variations(
|
||||||
|
create_test_defs(
|
||||||
|
undo_redo_no,
|
||||||
|
'<C-v>kllp',
|
||||||
|
create_p_action,
|
||||||
|
test_base,
|
||||||
|
assertion_creator
|
||||||
|
),
|
||||||
|
function() funcs.setpos('.', {0, 2, 1, 0, 1}) end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
describe("with 'virtualedit'", function()
|
||||||
|
describe('splitting a tab character', function()
|
||||||
|
local base_expect_string = [[
|
||||||
|
Line of words 1
|
||||||
|
test_stringx" Line of words 2]]
|
||||||
|
run_normal_mode_tests(
|
||||||
|
base_expect_string,
|
||||||
|
'vp',
|
||||||
|
function()
|
||||||
|
funcs.setline('$', ' Line of words 2')
|
||||||
|
execute('set virtualedit=all')
|
||||||
|
funcs.setpos('.', {0, 2, 1, 2, 3})
|
||||||
|
end,
|
||||||
|
nil,
|
||||||
|
' '
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
describe('after end of line', function()
|
||||||
|
local base_expect_string = [[
|
||||||
|
Line of words 1 test_stringx"
|
||||||
|
Line of words 2]]
|
||||||
|
run_normal_mode_tests(
|
||||||
|
base_expect_string,
|
||||||
|
'vp',
|
||||||
|
function()
|
||||||
|
execute('set virtualedit=all')
|
||||||
|
funcs.setpos('.', {0, 1, 16, 2, 18})
|
||||||
|
end,
|
||||||
|
true,
|
||||||
|
' '
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('. register special tests', function()
|
||||||
|
before_each(reset)
|
||||||
|
it('applies control character actions', function()
|
||||||
|
feed('i<C-t><esc>u')
|
||||||
|
expect([[
|
||||||
|
Line of words 1
|
||||||
|
Line of words 2]])
|
||||||
|
feed('".p')
|
||||||
|
expect([[
|
||||||
|
Line of words 1
|
||||||
|
Line of words 2]])
|
||||||
|
feed('u1go<C-v>j".p')
|
||||||
|
eq([[
|
||||||
|
ine of words 1
|
||||||
|
ine of words 2]], curbuf_contents())
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function bell_test(actions, should_ring)
|
||||||
|
local screen = Screen.new()
|
||||||
|
screen:attach()
|
||||||
|
helpers.ok(not screen.bell and not screen.visualbell)
|
||||||
|
actions()
|
||||||
|
helpers.wait()
|
||||||
|
screen:wait(function()
|
||||||
|
if should_ring then
|
||||||
|
if not screen.bell and not screen.visualbell then
|
||||||
|
return 'Bell was not rung after action'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if screen.bell or screen.visualbell then
|
||||||
|
return 'Bell was rung after action'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
screen:detach()
|
||||||
|
end
|
||||||
|
|
||||||
|
it('should not ring the bell with gp at end of line', function()
|
||||||
|
bell_test(function() feed('$".gp') end)
|
||||||
|
|
||||||
|
-- Even if the last character is a multibyte character.
|
||||||
|
reset()
|
||||||
|
funcs.setline(1, 'helloม')
|
||||||
|
bell_test(function() feed('$".gp') end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should not ring the bell with gp and end of file', function()
|
||||||
|
funcs.setpos('.', {0, 2, 1, 0})
|
||||||
|
bell_test(function() feed('$vl".gp') end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should ring the bell when deleting if not appropriate', function()
|
||||||
|
execute('goto 2')
|
||||||
|
feed('i<bs><esc>')
|
||||||
|
expect([[
|
||||||
|
ine of words 1
|
||||||
|
Line of words 2]])
|
||||||
|
bell_test(function() feed('".P') end, true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should restore cursor position after undo of ".p', function()
|
||||||
|
local origpos = funcs.getcurpos()
|
||||||
|
feed('".pu')
|
||||||
|
eq(origpos, funcs.getcurpos())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should be unaffected by 'autoindent' with V\".2p", function()
|
||||||
|
execute('set autoindent')
|
||||||
|
feed('i test_string.<esc>u')
|
||||||
|
feed('V".2p')
|
||||||
|
expect([[
|
||||||
|
test_string.
|
||||||
|
test_string.
|
||||||
|
Line of words 2]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
@ -91,6 +91,24 @@ local function tmpname()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function map(func, tab)
|
||||||
|
local rettab = {}
|
||||||
|
for k, v in pairs(tab) do
|
||||||
|
rettab[k] = func(v)
|
||||||
|
end
|
||||||
|
return rettab
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(filter_func, tab)
|
||||||
|
local rettab = {}
|
||||||
|
for _, entry in pairs(tab) do
|
||||||
|
if filter_func(entry) then
|
||||||
|
table.insert(rettab, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return rettab
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
eq = eq,
|
eq = eq,
|
||||||
neq = neq,
|
neq = neq,
|
||||||
@ -98,4 +116,6 @@ return {
|
|||||||
check_logs = check_logs,
|
check_logs = check_logs,
|
||||||
uname = uname,
|
uname = uname,
|
||||||
tmpname = tmpname,
|
tmpname = tmpname,
|
||||||
|
map = map,
|
||||||
|
filter = filter,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user