neovim/test/functional/ui/inccommand_spec.lua
bfredl e61228a214 fix(tests): needing two calls to setup a screen is cringe
Before calling "attach" a screen object is just a dummy container for
(row, col) values whose purpose is to be sent as part of the "attach"
function call anyway.

Just create the screen in an attached state directly. Keep the complete
(row, col, options) config together. It is still completely valid to
later detach and re-attach as needed, including to another session.
2024-11-14 12:40:57 +01:00

2940 lines
82 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local clear = n.clear
local command = n.command
local eq = t.eq
local eval = n.eval
local expect = n.expect
local feed = n.feed
local insert = n.insert
local fn = n.fn
local api = n.api
local neq = t.neq
local ok = t.ok
local retry = t.retry
local source = n.source
local poke_eventloop = n.poke_eventloop
local sleep = vim.uv.sleep
local testprg = n.testprg
local assert_alive = n.assert_alive
local default_text = [[
Inc substitution on
two lines
]]
local multiline_text = [[
1 2 3
A B C
4 5 6
X Y Z
7 8 9
]]
local multimatch_text = [[
a bdc eae a fgl lzia r
x
]]
local multibyte_text = [[
£ ¥ ѫѫ PEPPERS
£ ¥ ѫfѫ
a£ ѫ¥KOL
£ ¥ libm
£ ¥
]]
local long_multiline_text = [[
1 2 3
A B C
4 5 6
X Y Z
7 8 9
K L M
a b c
d e f
q r s
x y z
£ m n
t œ ¥
]]
local function common_setup(screen, inccommand, text)
if screen then
command('syntax on')
command('set nohlsearch')
command('hi Substitute guifg=red guibg=yellow')
screen:add_extra_attr_ids {
[100] = { underline = true },
[101] = { underline = true, foreground = Screen.colors.SlateBlue, bold = true },
vis = { background = Screen.colors.LightGrey },
}
end
command('set inccommand=' .. (inccommand or ''))
if text then
insert(text)
end
end
describe(':substitute, inccommand=split interactivity', function()
before_each(function()
clear()
common_setup(nil, 'split', default_text)
end)
-- Test the tests: verify that the `1==bufnr('$')` assertion
-- in the "no preview" tests (below) actually means something.
it('previews interactive cmdline', function()
feed(':%s/tw/MO/g')
retry(nil, 1000, function()
eq(2, eval("bufnr('$')"))
end)
end)
it('no preview if invoked by a script', function()
source('%s/tw/MO/g')
poke_eventloop()
eq(1, eval("bufnr('$')"))
-- sanity check: assert the buffer state
expect(default_text:gsub('tw', 'MO'))
end)
it('no preview if invoked by feedkeys()', function()
-- in a script...
source([[:call feedkeys(":%s/tw/MO/g\<CR>")]])
-- or interactively...
feed([[:call feedkeys(":%s/bs/BUU/g\<lt>CR>")<CR>]])
eq(1, eval("bufnr('$')"))
-- sanity check: assert the buffer state
expect(default_text:gsub('tw', 'MO'):gsub('bs', 'BUU'))
end)
end)
describe(":substitute, 'inccommand' preserves", function()
before_each(clear)
it('listed buffers (:ls)', function()
local screen = Screen.new(30, 10)
common_setup(screen, 'split', 'ABC')
feed(':%s/AB/BA/')
poke_eventloop()
feed('<CR>')
feed(':ls<CR>')
screen:expect([[
BAC |
{1:~ }|*3
{3: }|
:ls |
1 %a + "[No Name]" |
line 1 |
{6:Press ENTER or type command to}|
{6: continue}^ |
]])
end)
it("'[ and '] marks #26439", function()
local screen = Screen.new(30, 10)
common_setup(screen, 'nosplit', ('abc\ndef\n'):rep(50))
feed('ggyG')
local X = api.nvim_get_vvar('maxcol')
eq({ 0, 1, 1, 0 }, fn.getpos("'["))
eq({ 0, 101, X, 0 }, fn.getpos("']"))
feed(":'[,']s/def/")
poke_eventloop()
eq({ 0, 1, 1, 0 }, fn.getpos("'["))
eq({ 0, 101, X, 0 }, fn.getpos("']"))
feed('DEF/g')
poke_eventloop()
eq({ 0, 1, 1, 0 }, fn.getpos("'["))
eq({ 0, 101, X, 0 }, fn.getpos("']"))
feed('<CR>')
expect(('abc\nDEF\n'):rep(50))
end)
for _, case in pairs { '', 'split', 'nosplit' } do
it('various delimiters (inccommand=' .. case .. ')', function()
insert(default_text)
command('set inccommand=' .. case)
local delims = { '/', '#', ';', '%', ',', '@', '!' }
for _, delim in pairs(delims) do
feed(':%s' .. delim .. 'lines' .. delim .. 'LINES' .. delim .. 'g')
poke_eventloop()
feed('<CR>')
expect([[
Inc substitution on
two LINES
]])
command('undo')
end
end)
end
for _, case in pairs { '', 'split', 'nosplit' } do
it("'undolevels' (inccommand=" .. case .. ')', function()
command('set undolevels=139')
command('setlocal undolevels=34')
command('split') -- Show the buffer in multiple windows
command('set inccommand=' .. case)
insert('as')
feed(':%s/as/glork/')
poke_eventloop()
feed('<enter>')
eq(139, api.nvim_get_option_value('undolevels', { scope = 'global' }))
eq(34, api.nvim_get_option_value('undolevels', { buf = 0 }))
end)
end
for _, case in ipairs({ '', 'split', 'nosplit' }) do
it('empty undotree() (inccommand=' .. case .. ')', function()
command('set undolevels=1000')
command('set inccommand=' .. case)
local expected_undotree = eval('undotree()')
-- Start typing an incomplete :substitute command.
feed([[:%s/e/YYYY/g]])
poke_eventloop()
-- Cancel the :substitute.
feed([[<C-\><C-N>]])
-- The undo tree should be unchanged.
eq(expected_undotree, eval('undotree()'))
eq({}, eval('undotree()')['entries'])
end)
end
for _, case in ipairs({ '', 'split', 'nosplit' }) do
it('undotree() with branches (inccommand=' .. case .. ')', function()
command('set undolevels=1000')
command('set inccommand=' .. case)
-- Make some changes.
feed([[isome text 1<C-\><C-N>]])
feed([[osome text 2<C-\><C-N>]])
-- Add an undo branch.
feed([[u]])
-- More changes, more undo branches.
feed([[osome text 3<C-\><C-N>]])
feed([[AX<C-\><C-N>]])
feed([[...]])
feed([[uu]])
feed([[osome text 4<C-\><C-N>]])
feed([[u<C-R>u]])
feed([[osome text 5<C-\><C-N>]])
expect([[
some text 1
some text 3XX
some text 5]])
local expected_undotree = eval('undotree()')
eq(5, #expected_undotree['entries']) -- sanity
-- Start typing an incomplete :substitute command.
feed([[:%s/e/YYYY/g]])
poke_eventloop()
-- Cancel the :substitute.
feed([[<C-\><C-N>]])
-- The undo tree should be unchanged.
eq(expected_undotree, eval('undotree()'))
end)
end
for _, case in pairs { '', 'split', 'nosplit' } do
it('b:changedtick (inccommand=' .. case .. ')', function()
command('set inccommand=' .. case)
feed([[isome text 1<C-\><C-N>]])
feed([[osome text 2<C-\><C-N>]])
local expected_tick = eval('b:changedtick')
ok(expected_tick > 0)
expect([[
some text 1
some text 2]])
feed(':%s/e/XXX/')
poke_eventloop()
eq(expected_tick, eval('b:changedtick'))
end)
end
for _, case in ipairs({ '', 'split', 'nosplit' }) do
it('previous substitute string ~ (inccommand=' .. case .. ') #12109', function()
local screen = Screen.new(30, 10)
common_setup(screen, case, default_text)
feed(':%s/Inc/SUB<CR>')
expect([[
SUB substitution on
two lines
]])
feed(':%s/line/')
poke_eventloop()
feed('~')
poke_eventloop()
feed('<CR>')
expect([[
SUB substitution on
two SUBs
]])
feed(':%s/sti/')
poke_eventloop()
feed('~')
poke_eventloop()
feed('B')
poke_eventloop()
feed('<CR>')
expect([[
SUB subSUBBtution on
two SUBs
]])
feed(':%s/ion/NEW<CR>')
expect([[
SUB subSUBBtutNEW on
two SUBs
]])
feed(':%s/two/')
poke_eventloop()
feed('N')
poke_eventloop()
feed('~')
poke_eventloop()
feed('<CR>')
expect([[
SUB subSUBBtutNEW on
NNEW SUBs
]])
feed(':%s/bS/')
poke_eventloop()
feed('~')
poke_eventloop()
feed('W')
poke_eventloop()
feed('<CR>')
expect([[
SUB suNNEWWUBBtutNEW on
NNEW SUBs
]])
end)
end
end)
describe(":substitute, 'inccommand' preserves undo", function()
local cases = { '', 'split', 'nosplit' }
local substrings = {
{ ':%s/', '1' },
{ ':%s/', '1', '/' },
{ ':%s/', '1', '/', '<bs>' },
{ ':%s/', '1', '/', 'a' },
{ ':%s/', '1', '/', 'a', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x' },
{ ':%s/', '1', '/', 'a', 'x', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x', '<bs>', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x', '<bs>', '<bs>', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x', '/' },
{ ':%s/', '1', '/', 'a', 'x', '/', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x', '/', '<bs>', '/' },
{ ':%s/', '1', '/', 'a', 'x', '/', 'g' },
{ ':%s/', '1', '/', 'a', 'x', '/', 'g', '<bs>' },
{ ':%s/', '1', '/', 'a', 'x', '/', 'g', '<bs>', '<bs>' },
}
local function test_sub(substring, split, redoable)
command('bwipe!')
command('set inccommand=' .. split)
insert('1')
feed('o2<esc>')
command('undo')
feed('o3<esc>')
if redoable then
feed('o4<esc>')
command('undo')
end
for _, s in pairs(substring) do
feed(s)
end
poke_eventloop()
feed('<enter>')
command('undo')
feed('g-')
expect([[
1
2]])
feed('g+')
expect([[
1
3]])
end
local function test_notsub(substring, split, redoable)
command('bwipe!')
command('set inccommand=' .. split)
insert('1')
feed('o2<esc>')
command('undo')
feed('o3<esc>')
if redoable then
feed('o4<esc>')
command('undo')
end
for _, s in pairs(substring) do
feed(s)
end
poke_eventloop()
feed('<esc>')
feed('g-')
expect([[
1
2]])
feed('g+')
expect([[
1
3]])
if redoable then
feed('<c-r>')
expect([[
1
3
4]])
end
end
local function test_threetree(substring, split)
command('bwipe!')
command('set inccommand=' .. split)
insert('1')
feed('o2<esc>')
feed('o3<esc>')
feed('uu')
feed('oa<esc>')
feed('ob<esc>')
feed('uu')
feed('oA<esc>')
feed('oB<esc>')
-- This is the undo tree (x-Axis is timeline), we're at B now
-- ----------------A - B
-- /
-- | --------a - b
-- |/
-- 1 - 2 - 3
feed('2u')
for _, s in pairs(substring) do
feed(s)
poke_eventloop()
end
feed('<esc>')
expect([[
1]])
feed('g-')
expect([[
]])
feed('g+')
expect([[
1]])
feed('<c-r>')
expect([[
1
A]])
feed('g-') -- go to b
feed('2u')
for _, s in pairs(substring) do
feed(s)
poke_eventloop()
end
feed('<esc>')
feed('<c-r>')
expect([[
1
a]])
feed('g-') -- go to 3
feed('2u')
for _, s in pairs(substring) do
feed(s)
poke_eventloop()
end
feed('<esc>')
feed('<c-r>')
expect([[
1
2]])
end
before_each(clear)
it('at a non-leaf of the undo tree', function()
for _, case in pairs(cases) do
for _, str in pairs(substrings) do
for _, redoable in pairs({ true }) do
test_sub(str, case, redoable)
end
end
end
end)
it('at a leaf of the undo tree', function()
for _, case in pairs(cases) do
for _, str in pairs(substrings) do
for _, redoable in pairs({ false }) do
test_sub(str, case, redoable)
end
end
end
end)
it('when interrupting substitution', function()
for _, case in pairs(cases) do
for _, str in pairs(substrings) do
for _, redoable in pairs({ true, false }) do
test_notsub(str, case, redoable)
end
end
end
end)
it('in a complex undo scenario', function()
for _, case in pairs(cases) do
for _, str in pairs(substrings) do
test_threetree(str, case)
end
end
end)
it('with undolevels=0', function()
for _, case in pairs(cases) do
clear()
common_setup(nil, case, default_text)
command('set undolevels=0')
feed('1G0')
insert('X')
feed(':%s/tw/MO/')
poke_eventloop()
feed('<esc>')
command('undo')
expect(default_text)
command('undo')
expect(default_text:gsub('Inc', 'XInc'))
command('undo')
feed(':%s/tw/MO/g')
poke_eventloop()
feed('<CR>')
expect(default_text:gsub('tw', 'MO'))
command('undo')
expect(default_text)
command('undo')
expect(default_text:gsub('tw', 'MO'))
end
end)
it('with undolevels=1', function()
for _, case in pairs(cases) do
clear()
local screen = Screen.new(20, 10)
common_setup(screen, case, default_text)
screen:expect([[
Inc substitution on |
two lines |
^ |
{1:~ }|*6
|
]])
command('set undolevels=1')
feed('1G0')
insert('X')
feed('IY<esc>')
feed(':%s/tw/MO/')
poke_eventloop()
feed('<esc>')
feed('u')
expect(default_text:gsub('Inc', 'XInc'))
feed('u')
expect(default_text)
feed(':%s/tw/MO/g')
poke_eventloop()
feed('<enter>')
feed(':%s/MO/GO/g')
poke_eventloop()
feed('<enter>')
feed(':%s/GO/NO/g')
poke_eventloop()
feed('<enter>')
feed('u')
expect(default_text:gsub('tw', 'GO'))
feed('u')
expect(default_text:gsub('tw', 'MO'))
feed('u')
if case == 'split' then
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
end
end
end)
it('with undolevels=2', function()
for _, case in pairs(cases) do
clear()
local screen = Screen.new(20, 10)
common_setup(screen, case, default_text)
command('set undolevels=2')
feed('2GAx<esc>')
feed('Ay<esc>')
feed('Az<esc>')
feed(':%s/tw/AR')
poke_eventloop()
feed('<esc>')
feed('u')
expect(default_text:gsub('lines', 'linesxy'))
feed('u')
expect(default_text:gsub('lines', 'linesx'))
feed('u')
expect(default_text)
feed('u')
if case == 'split' then
screen:expect([[
Inc substitution on |
two line^s |
|
{1:~ }|*6
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
two line^s |
|
{1:~ }|*6
Already ...t change |
]])
end
feed(':%s/tw/MO/g')
poke_eventloop()
feed('<enter>')
feed(':%s/MO/GO/g')
poke_eventloop()
feed('<enter>')
feed(':%s/GO/NO/g')
poke_eventloop()
feed('<enter>')
feed(':%s/NO/LO/g')
poke_eventloop()
feed('<enter>')
feed('u')
expect(default_text:gsub('tw', 'NO'))
feed('u')
expect(default_text:gsub('tw', 'GO'))
feed('u')
expect(default_text:gsub('tw', 'MO'))
feed('u')
if case == 'split' then
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
end
end
end)
it('with undolevels=-1', function()
for _, case in pairs(cases) do
clear()
local screen = Screen.new(20, 10)
common_setup(screen, case, default_text)
command('set undolevels=-1')
feed(':%s/tw/MO/g')
poke_eventloop()
feed('<enter>')
feed('u')
if case == 'split' then
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*6
Already ...t change |
]])
end
-- repeat with an interrupted substitution
clear()
screen = Screen.new(20, 10)
common_setup(screen, case, default_text)
command('set undolevels=-1')
feed('1G')
feed('IL<esc>')
feed(':%s/tw/MO/g')
poke_eventloop()
feed('<esc>')
feed('u')
screen:expect([[
^LInc substitution on|
two lines |
|
{1:~ }|*6
Already ...t change |
]])
end
end)
end)
describe(':substitute, inccommand=split', function()
local screen
before_each(function()
clear()
screen = Screen.new(30, 15)
common_setup(screen, 'split', default_text .. default_text)
end)
it("preserves 'modified' buffer flag", function()
command('set nomodified')
feed(':%s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
Inc substitution on |
{20:tw}o lines |
|
{3:[No Name] }|
|2| {20:tw}o lines |
|4| {20:tw}o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw^ |
]])
feed([[<C-\><C-N>]]) -- Cancel the :substitute command.
eq(0, eval('&modified'))
end)
it('shows preview when cmd modifiers are present', function()
-- one modifier
feed(':keeppatterns %s/tw/to')
screen:expect { any = [[{20:to}o lines]] }
feed('<Esc>')
screen:expect { any = [[two lines]] }
-- multiple modifiers
feed(':keeppatterns silent %s/tw/to')
screen:expect { any = [[{20:to}o lines]] }
feed('<Esc>')
screen:expect { any = [[two lines]] }
-- non-modifier prefix
feed(':silent tabedit %s/tw/to')
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*9
:silent tabedit %s/tw/to^ |
]])
feed('<Esc>')
-- leading colons
feed(':::%s/tw/to')
screen:expect { any = [[{20:to}o lines]] }
feed('<Esc>')
screen:expect { any = [[two lines]] }
end)
it('ignores new-window modifiers when splitting the preview window', function()
-- one modifier
feed(':topleft %s/tw/to')
screen:expect([[
Inc substitution on |
{20:to}o lines |
Inc substitution on |
{20:to}o lines |
|
{3:[No Name] [+] }|
|2| {20:to}o lines |
|4| {20:to}o lines |
{1:~ }|*5
{2:[Preview] }|
:topleft %s/tw/to^ |
]])
feed('<Esc>')
screen:expect { any = [[two lines]] }
-- multiple modifiers
feed(':topleft vert %s/tw/to')
screen:expect([[
Inc substitution on |
{20:to}o lines |
Inc substitution on |
{20:to}o lines |
|
{3:[No Name] [+] }|
|2| {20:to}o lines |
|4| {20:to}o lines |
{1:~ }|*5
{2:[Preview] }|
:topleft vert %s/tw/to^ |
]])
feed('<Esc>')
screen:expect { any = [[two lines]] }
end)
it('shows split window when typing the pattern', function()
feed(':%s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
Inc substitution on |
{20:tw}o lines |
|
{3:[No Name] [+] }|
|2| {20:tw}o lines |
|4| {20:tw}o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw^ |
]])
end)
it('shows preview with empty replacement', function()
feed(':%s/tw/')
screen:expect([[
Inc substitution on |
o lines |
Inc substitution on |
o lines |
|
{3:[No Name] [+] }|
|2| o lines |
|4| o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw/^ |
]])
feed('x')
screen:expect([[
Inc substitution on |
{20:x}o lines |
Inc substitution on |
{20:x}o lines |
|
{3:[No Name] [+] }|
|2| {20:x}o lines |
|4| {20:x}o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw/x^ |
]])
feed('<bs>')
screen:expect([[
Inc substitution on |
o lines |
Inc substitution on |
o lines |
|
{3:[No Name] [+] }|
|2| o lines |
|4| o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw/^ |
]])
end)
it('shows split window when typing replacement', function()
feed(':%s/tw/XX')
screen:expect([[
Inc substitution on |
{20:XX}o lines |
Inc substitution on |
{20:XX}o lines |
|
{3:[No Name] [+] }|
|2| {20:XX}o lines |
|4| {20:XX}o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw/XX^ |
]])
end)
it('does not show split window for :s/', function()
feed('2gg')
feed(':s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
Inc substitution on |
two lines |
|
{1:~ }|*9
:s/tw^ |
]])
end)
it("'hlsearch' is active, 'cursorline' is not", function()
command('set hlsearch cursorline')
feed('gg')
-- Assert that 'cursorline' is active.
screen:expect([[
{21:^Inc substitution on }|
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*9
|
]])
feed(':%s/tw')
-- 'cursorline' is NOT active during preview.
screen:expect([[
Inc substitution on |
{20:tw}o lines |
Inc substitution on |
{20:tw}o lines |
|
{3:[No Name] [+] }|
|2| {20:tw}o lines |
|4| {20:tw}o lines |
{1:~ }|*5
{2:[Preview] }|
:%s/tw^ |
]])
end)
it('highlights the replacement text', function()
feed('ggO')
feed('M M M<esc>')
feed(':%s/M/123/g')
screen:expect([[
{20:123} {20:123} {20:123} |
Inc substitution on |
two lines |
Inc substitution on |
two lines |
{3:[No Name] [+] }|
|1| {20:123} {20:123} {20:123} |
{1:~ }|*6
{2:[Preview] }|
:%s/M/123/g^ |
]])
end)
it("highlights nothing when there's no match", function()
feed('gg')
feed(':%s/Inx')
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|
{1:~ }|*6
{2:[Preview] }|
:%s/Inx^ |
]])
end)
it('previews correctly when previewhight is small', function()
command('set cwh=3')
command('set hls')
feed('ggdG')
insert(string.rep('abc abc abc\n', 20))
feed(':%s/abc/MMM/g')
screen:expect([[
{20:MMM} {20:MMM} {20:MMM} |*9
{3:[No Name] [+] }|
| 1| {20:MMM} {20:MMM} {20:MMM} |
| 2| {20:MMM} {20:MMM} {20:MMM} |
| 3| {20:MMM} {20:MMM} {20:MMM} |
{2:[Preview] }|
:%s/abc/MMM/g^ |
]])
end)
it('actually replaces text', function()
feed(':%s/tw/XX/g')
poke_eventloop()
feed('<Enter>')
screen:expect([[
Inc substitution on |
XXo lines |
Inc substitution on |
^XXo lines |
|
{1:~ }|*9
:%s/tw/XX/g |
]])
end)
it('shows correct line numbers with many lines', function()
feed('gg')
feed('2yy')
feed('2000p')
command('1,1000s/tw/BB/g')
feed(':%s/tw/X')
screen:expect([[
Inc substitution on |
BBo lines |
Inc substitution on |
{20:X}o lines |
Inc substitution on |
{3:[No Name] [+] }|
|1001| {20:X}o lines |
|1003| {20:X}o lines |
|1005| {20:X}o lines |
|1007| {20:X}o lines |
|1009| {20:X}o lines |
|1011| {20:X}o lines |
|1013| {20:X}o lines |
{2:[Preview] }|
:%s/tw/X^ |
]])
end)
it('does not spam the buffer numbers', function()
-- The preview buffer is re-used (unless user deleted it), so buffer numbers
-- will not increase on each keystroke.
feed(':%s/tw/Xo/g')
-- Delete and re-type the g a few times.
feed('<BS>')
poke_eventloop()
feed('g')
poke_eventloop()
feed('<BS>')
poke_eventloop()
feed('g')
poke_eventloop()
feed('<CR>')
poke_eventloop()
feed(':vs tmp<enter>')
eq(3, fn.bufnr('$'))
end)
it('works with the n flag', function()
feed(':%s/tw/Mix/n')
poke_eventloop()
feed('<Enter>')
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
^ |
{1:~ }|*9
2 matches on 2 lines |
]])
end)
it("deactivates if 'redrawtime' is exceeded #5602", function()
-- prevent redraws from 'incsearch'
api.nvim_set_option_value('incsearch', false, {})
-- Assert that 'inccommand' is ENABLED initially.
eq('split', eval('&inccommand'))
-- Set 'redrawtime' to minimal value, to ensure timeout is triggered.
command('set redrawtime=1 nowrap')
-- Load a big file.
command('silent edit! test/functional/fixtures/bigfile_oneline.txt')
-- Start :substitute with a slow pattern.
feed([[:%s/B.*N/x]])
poke_eventloop()
-- Assert that 'inccommand' is DISABLED in cmdline mode.
eq('', eval('&inccommand'))
-- Assert that preview cleared (or never manifested).
screen:expect([[
0000;<control>;Cc;0;BN;;;;;N;N|
2F923;CJK COMPATIBILITY IDEOGR|
2F924;CJK COMPATIBILITY IDEOGR|
2F925;CJK COMPATIBILITY IDEOGR|
2F926;CJK COMPATIBILITY IDEOGR|
2F927;CJK COMPATIBILITY IDEOGR|
2F928;CJK COMPATIBILITY IDEOGR|
2F929;CJK COMPATIBILITY IDEOGR|
2F92A;CJK COMPATIBILITY IDEOGR|
2F92B;CJK COMPATIBILITY IDEOGR|
2F92C;CJK COMPATIBILITY IDEOGR|
2F92D;CJK COMPATIBILITY IDEOGR|
2F92E;CJK COMPATIBILITY IDEOGR|
2F92F;CJK COMPATIBILITY IDEOGR|
:%s/B.*N/x^ |
]])
-- Assert that 'inccommand' is again ENABLED after leaving cmdline mode.
feed([[<C-\><C-N>]])
eq('split', eval('&inccommand'))
end)
it("deactivates if 'foldexpr' is slow #9557", function()
insert([[
a
a
a
a
a
a
a
a
]])
source([[
function! Slowfold(lnum)
sleep 5m
return a:lnum % 3
endfun
]])
command('set redrawtime=1 inccommand=split')
command('set foldmethod=expr foldexpr=Slowfold(v:lnum)')
feed(':%s/a/bcdef')
-- Assert that 'inccommand' is DISABLED in cmdline mode.
retry(nil, nil, function()
eq('', eval('&inccommand'))
end)
-- Assert that 'inccommand' is again ENABLED after leaving cmdline mode.
feed([[<C-\><C-N>]])
retry(nil, nil, function()
eq('split', eval('&inccommand'))
end)
end)
it('clears preview if non-previewable command is edited #5585', function()
feed('gg')
-- Put a non-previewable command in history.
feed(":echo 'foo'<CR>")
-- Start an incomplete :substitute command.
feed(':1,2s/t/X')
screen:expect([[
Inc subs{20:X}itution on |
{20:X}wo lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|1| Inc subs{20:X}itution on |
|2| {20:X}wo lines |
{1:~ }|*5
{2:[Preview] }|
:1,2s/t/X^ |
]])
-- Select the previous command.
feed('<C-P>')
-- Assert that preview was cleared.
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*9
:echo 'foo'^ |
]])
end)
it([[preview changes correctly with c_CTRL-R_= and c_CTRL-\_e]], function()
feed('gg')
feed(':1,2s/t/X')
screen:expect([[
Inc subs{20:X}itution on |
{20:X}wo lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|1| Inc subs{20:X}itution on |
|2| {20:X}wo lines |
{1:~ }|*5
{2:[Preview] }|
:1,2s/t/X^ |
]])
feed([[<C-R>='Y']])
-- preview should be unchanged during c_CTRL-R_= editing
screen:expect([[
Inc subs{20:X}itution on |
{20:X}wo lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|1| Inc subs{20:X}itution on |
|2| {20:X}wo lines |
{1:~ }|*5
{2:[Preview] }|
={26:'Y'}^ |
]])
feed('<CR>')
-- preview should be changed by the result of the expression
screen:expect([[
Inc subs{20:XY}itution on |
{20:XY}wo lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|1| Inc subs{20:XY}itution on |
|2| {20:XY}wo lines |
{1:~ }|*5
{2:[Preview] }|
:1,2s/t/XY^ |
]])
feed([[<C-\>e'echo']])
-- preview should be unchanged during c_CTRL-\_e editing
screen:expect([[
Inc subs{20:XY}itution on |
{20:XY}wo lines |
Inc substitution on |
two lines |
|
{3:[No Name] [+] }|
|1| Inc subs{20:XY}itution on |
|2| {20:XY}wo lines |
{1:~ }|*5
{2:[Preview] }|
={26:'echo'}^ |
]])
feed('<CR>')
-- preview should be cleared if command is changed to a non-previewable one
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*9
:echo^ |
]])
end)
end)
describe('inccommand=nosplit', function()
local screen
before_each(function()
clear()
screen = Screen.new(20, 10)
common_setup(screen, 'nosplit', default_text .. default_text)
end)
it('works with :smagic, :snomagic', function()
command('set hlsearch')
insert('Line *.3.* here')
feed(':%smagic/3.*/X') -- start :smagic command
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
Line *.{20:X} |
{1:~ }|*4
:%smagic/3.*/X^ |
]])
feed([[<C-\><C-N>]]) -- cancel
feed(':%snomagic/3.*/X') -- start :snomagic command
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
Line *.{20:X} here |
{1:~ }|*4
:%snomagic/3.*/X^ |
]])
end)
it('shows preview when cmd modifiers are present', function()
-- one modifier
feed(':keeppatterns %s/tw/to')
screen:expect { any = [[{20:to}o lines]] }
feed('<Esc>')
screen:expect { any = [[two lines]] }
-- multiple modifiers
feed(':keeppatterns silent %s/tw/to')
screen:expect { any = [[{20:to}o lines]] }
feed('<Esc>')
screen:expect { any = [[two lines]] }
-- non-modifier prefix
feed(':silent tabedit %s/tw/to')
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*2
{3: }|
:silent tabedit %s/t|
w/to^ |
]])
end)
it('does not show window after toggling :set inccommand', function()
feed(':%s/tw/OKOK')
feed('<Esc>')
command('set icm=split')
feed(':%s/tw/OKOK')
feed('<Esc>')
command('set icm=nosplit')
feed(':%s/tw/OKOK')
poke_eventloop()
screen:expect([[
Inc substitution on |
{20:OKOK}o lines |
Inc substitution on |
{20:OKOK}o lines |
|
{1:~ }|*4
:%s/tw/OKOK^ |
]])
end)
it('never shows preview buffer', function()
command('set hlsearch')
feed(':%s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
Inc substitution on |
{20:tw}o lines |
|
{1:~ }|*4
:%s/tw^ |
]])
feed('/BM')
screen:expect([[
Inc substitution on |
{20:BM}o lines |
Inc substitution on |
{20:BM}o lines |
|
{1:~ }|*4
:%s/tw/BM^ |
]])
feed('/')
screen:expect([[
Inc substitution on |
{20:BM}o lines |
Inc substitution on |
{20:BM}o lines |
|
{1:~ }|*4
:%s/tw/BM/^ |
]])
feed('<enter>')
screen:expect([[
Inc substitution on |
BMo lines |
Inc substitution on |
^BMo lines |
|
{1:~ }|*4
:%s/tw/BM/ |
]])
end)
it('clears preview if non-previewable command is edited', function()
-- Put a non-previewable command in history.
feed(":echo 'foo'<CR>")
-- Start an incomplete :substitute command.
feed(':1,2s/t/X')
screen:expect([[
Inc subs{20:X}itution on |
{20:X}wo lines |
Inc substitution on |
two lines |
|
{1:~ }|*4
:1,2s/t/X^ |
]])
-- Select the previous command.
feed('<C-P>')
-- Assert that preview was cleared.
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*4
:echo 'foo'^ |
]])
end)
it('does not execute trailing bar-separated commands #7494', function()
feed(':%s/two/three/g|q!')
screen:expect([[
Inc substitution on |
{20:three} lines |
Inc substitution on |
{20:three} lines |
|
{1:~ }|*4
:%s/two/three/g|q!^ |
]])
eq(eval('v:null'), eval('v:exiting'))
end)
it('does not break bar-separated command #8796', function()
source([[
function! F()
if v:false | return | endif
endfun
]])
command('call timer_start(10, {-> F()}, {"repeat":-1})')
feed(':%s/')
sleep(20) -- Allow some timer activity.
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
|
{1:~ }|*4
:%s/^ |
]])
end)
end)
describe(":substitute, 'inccommand' with a failing expression", function()
local screen
local cases = { '', 'split', 'nosplit' }
local function refresh(case)
clear()
screen = Screen.new(20, 10)
common_setup(screen, case, default_text)
end
it('in the pattern does nothing', function()
for _, case in pairs(cases) do
refresh(case)
command('set inccommand=' .. case)
feed(':silent! %s/tw\\(/LARD/')
poke_eventloop()
feed('<enter>')
expect(default_text)
end
end)
it('in the replacement deletes the matches', function()
for _, case in pairs(cases) do
refresh(case)
local replacements = { "\\='LARD", '\\=xx_novar__xx' }
for _, repl in pairs(replacements) do
command('set inccommand=' .. case)
feed(':silent! %s/tw/' .. repl .. '/')
poke_eventloop()
feed('<enter>')
expect(default_text:gsub('tw', ''))
command('undo')
end
end
end)
it('in the range does not error #5912', function()
for _, case in pairs(cases) do
refresh(case)
feed(':100s/')
screen:expect([[
Inc substitution on |
two lines |
|
{1:~ }|*6
:100s/^ |
]])
feed('<enter>')
screen:expect([[
Inc substitution on |
two lines |
^ |
{1:~ }|*6
{9:E16: Invalid range} |
]])
end
end)
end)
describe("'inccommand' and :cnoremap", function()
local cases = { '', 'split', 'nosplit' }
local screen
local function refresh(case, visual)
clear()
screen = visual and Screen.new(80, 10) or nil
common_setup(screen, case, default_text)
end
it('work with remapped characters', function()
for _, case in pairs(cases) do
refresh(case)
local cmd = '%s/lines/LINES/g'
for i = 1, string.len(cmd) do
local c = string.sub(cmd, i, i)
command('cnoremap ' .. c .. ' ' .. c)
end
feed(':' .. cmd)
poke_eventloop()
feed('<CR>')
expect([[
Inc substitution on
two LINES
]])
end
end)
it('work when mappings move the cursor', function()
for _, case in pairs(cases) do
refresh(case)
command('cnoremap ,S LINES/<left><left><left><left><left><left>')
feed(':%s/lines/')
poke_eventloop()
feed(',S')
poke_eventloop()
feed('or three <enter>')
poke_eventloop()
expect([[
Inc substitution on
two or three LINES
]])
command('cnoremap ;S /X/<left><left><left>')
feed(':%s/')
poke_eventloop()
feed(';S')
poke_eventloop()
feed('I<enter>')
expect([[
Xnc substitution on
two or three LXNES
]])
command('cnoremap ,T //Y/<left><left><left>')
feed(':%s')
poke_eventloop()
feed(',T')
poke_eventloop()
feed('X<enter>')
expect([[
Ync substitution on
two or three LYNES
]])
command('cnoremap ;T s//Z/<left><left><left>')
feed(':%')
poke_eventloop()
feed(';T')
poke_eventloop()
feed('Y<enter>')
expect([[
Znc substitution on
two or three LZNES
]])
end
end)
it('still works with a broken mapping', function()
for _, case in pairs(cases) do
refresh(case, true)
command("cnoremap <expr> x execute('bwipeout!')[-1].'x'")
feed(':%s/tw/tox<enter>')
screen:expect { any = [[{9:^E565:]] }
feed('<c-c>')
-- error thrown b/c of the mapping
neq(nil, eval('v:errmsg'):find('^E565:'))
expect([[
Inc substitution on
toxo lines
]])
end
end)
it('work when temporarily moving the cursor', function()
for _, case in pairs(cases) do
refresh(case)
command("cnoremap <expr> x cursor(1, 1)[-1].'x'")
feed(':%s/tw/tox')
poke_eventloop()
feed('/g<enter>')
expect(default_text:gsub('tw', 'tox'))
end
end)
it("work when a mapping disables 'inccommand'", function()
for _, case in pairs(cases) do
refresh(case)
command("cnoremap <expr> x execute('set inccommand=')[-1]")
feed(':%s/tw/tox')
poke_eventloop()
feed('a/g<enter>')
expect(default_text:gsub('tw', 'toa'))
end
end)
it('work with a complex mapping', function()
for _, case in pairs(cases) do
refresh(case)
source([[cnoremap x <C-\>eextend(g:, {'fo': getcmdline()})
\.fo<CR><C-c>:new<CR>:bw!<CR>:<C-r>=remove(g:, 'fo')<CR>x]])
feed(':%s/tw/tox')
poke_eventloop()
feed('/<enter>')
expect(default_text:gsub('tw', 'tox'))
end
end)
end)
describe("'inccommand' autocommands", function()
before_each(clear)
-- keys are events to be tested
-- values are arrays like
-- { open = { 1 }, close = { 2, 3} }
-- which would mean that during the test below the event fires for
-- buffer 1 when opening the preview window, and for buffers 2 and 3
-- when closing the preview window
local eventsExpected = {
BufAdd = {},
BufDelete = {},
BufEnter = {},
BufFilePost = {},
BufFilePre = {},
BufHidden = {},
BufLeave = {},
BufNew = {},
BufNewFile = {},
BufRead = {},
BufReadCmd = {},
BufReadPre = {},
BufUnload = {},
BufWinEnter = {},
BufWinLeave = {},
BufWipeout = {},
BufWrite = {},
BufWriteCmd = {},
BufWritePost = {},
Syntax = {},
FileType = {},
WinEnter = {},
WinLeave = {},
CmdwinEnter = {},
CmdwinLeave = {},
}
local function bufferlist(q)
local s = ''
for _, buffer in pairs(q) do
s = s .. ', ' .. tostring(buffer)
end
return s
end
-- fill the table with default values
for event, _ in pairs(eventsExpected) do
eventsExpected[event].open = eventsExpected[event].open or {}
eventsExpected[event].close = eventsExpected[event].close or {}
end
local function register_autocmd(event)
api.nvim_set_var(event .. '_fired', {})
command('autocmd ' .. event .. ' * call add(g:' .. event .. "_fired, expand('<abuf>'))")
end
it('are not fired when splitting', function()
common_setup(nil, 'split', default_text)
local eventsObserved = {}
for event, _ in pairs(eventsExpected) do
eventsObserved[event] = {}
register_autocmd(event)
end
feed(':%s/tw')
for event, _ in pairs(eventsExpected) do
eventsObserved[event].open = api.nvim_get_var(event .. '_fired')
api.nvim_set_var(event .. '_fired', {})
end
feed('/<enter>')
for event, _ in pairs(eventsExpected) do
eventsObserved[event].close = api.nvim_get_var(event .. '_fired')
end
for event, _ in pairs(eventsExpected) do
eq(
event .. bufferlist(eventsExpected[event].open),
event .. bufferlist(eventsObserved[event].open)
)
eq(
event .. bufferlist(eventsExpected[event].close),
event .. bufferlist(eventsObserved[event].close)
)
end
end)
end)
describe("'inccommand' split windows", function()
local screen
local function refresh()
clear()
screen = Screen.new(40, 30)
common_setup(screen, 'split', default_text)
end
it('work after more splits', function()
refresh()
feed('gg')
command('vsplit')
command('split')
feed(':%s/tw')
screen:expect([[
Inc substitution on │Inc substitution on|
{20:tw}o lines │{20:tw}o lines |
│ |
{1:~ }│{1:~ }|*11
{3:[No Name] [+] }│{1:~ }|
Inc substitution on │{1:~ }|
{20:tw}o lines │{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|*2
{2:[No Name] [+] [No Name] [+] }|
|2| {20:tw}o lines |
{1:~ }|*6
{2:[Preview] }|
:%s/tw^ |
]])
feed('<esc>')
command('only')
command('split')
command('vsplit')
feed(':%s/tw')
screen:expect([[
Inc substitution on │Inc substitution on|
{20:tw}o lines │{20:tw}o lines |
│ |
{1:~ }│{1:~ }|*11
{3:[No Name] [+] }{2:[No Name] [+] }|
Inc substitution on |
{20:tw}o lines |
|
{1:~ }|*2
{2:[No Name] [+] }|
|2| {20:tw}o lines |
{1:~ }|*6
{2:[Preview] }|
:%s/tw^ |
]])
end)
local settings = {
'splitbelow',
'splitright',
'noequalalways',
'equalalways eadirection=ver',
'equalalways eadirection=hor',
'equalalways eadirection=both',
}
it('are not affected by various settings', function()
for _, setting in pairs(settings) do
refresh()
command('set ' .. setting)
feed(':%s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
|
{1:~ }|*17
{3:[No Name] [+] }|
|2| {20:tw}o lines |
{1:~ }|*6
{2:[Preview] }|
:%s/tw^ |
]])
end
end)
it("don't open if there's not enough room", function()
refresh()
screen:try_resize(40, 3)
feed('gg:%s/tw')
screen:expect([[
Inc substitution on |
{20:tw}o lines |
:%s/tw^ |
]])
end)
end)
describe("'inccommand' with 'gdefault'", function()
before_each(function()
clear()
end)
it('does not lock up #7244', function()
common_setup(nil, 'nosplit', '{')
command('set gdefault')
feed(':s/{\\n')
eq({ mode = 'c', blocking = false }, api.nvim_get_mode())
feed('/A<Enter>')
expect('A')
eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
end)
it('with multiline text and range, does not lock up #7244', function()
common_setup(nil, 'nosplit', '{\n\n{')
command('set gdefault')
feed(':%s/{\\n')
eq({ mode = 'c', blocking = false }, api.nvim_get_mode())
feed('/A<Enter>')
expect('A\nA')
eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
end)
it('does not crash on zero-width matches #7485', function()
common_setup(nil, 'split', default_text)
command('set gdefault')
feed('gg')
feed('Vj')
feed(':s/\\%V')
eq({ mode = 'c', blocking = false }, api.nvim_get_mode())
feed('<Esc>')
eq({ mode = 'n', blocking = false }, api.nvim_get_mode())
end)
it('removes highlights after abort for a zero-width match', function()
local screen = Screen.new(30, 5)
common_setup(screen, 'nosplit', default_text)
command('set gdefault')
feed(':%s/\\%1c/a/')
screen:expect([[
{20:a}Inc substitution on |
{20:a}two lines |
{20:a} |
{1:~ }|
:%s/\%1c/a/^ |
]])
feed('<Esc>')
screen:expect([[
Inc substitution on |
two lines |
^ |
{1:~ }|
|
]])
end)
end)
describe(':substitute', function()
local screen
before_each(function()
clear()
screen = Screen.new(30, 15)
end)
it('inccommand=split, highlights multiline substitutions', function()
common_setup(screen, 'split', multiline_text)
feed('gg')
feed(':%s/2\\_.*X')
screen:expect([[
1 {20:2 3} |
{20:A B C} |
{20:4 5 6} |
{20:X} Y Z |
7 8 9 |
{3:[No Name] [+] }|
|1| 1 {20:2 3} |
|2|{20: A B C} |
|3|{20: 4 5 6} |
|4|{20: X} Y Z |
{1:~ }|*3
{2:[Preview] }|
:%s/2\_.*X^ |
]])
feed('/MMM')
screen:expect([[
1 {20:MMM} Y Z |
7 8 9 |
|
{1:~ }|*2
{3:[No Name] [+] }|
|1| 1 {20:MMM} Y Z |
{1:~ }|*6
{2:[Preview] }|
:%s/2\_.*X/MMM^ |
]])
feed('\\rK\\rLLL')
screen:expect([[
1 {20:MMM} |
{20:K} |
{20:LLL} Y Z |
7 8 9 |
|
{3:[No Name] [+] }|
|1| 1 {20:MMM} |
|2|{20: K} |
|3|{20: LLL} Y Z |
{1:~ }|*4
{2:[Preview] }|
:%s/2\_.*X/MMM\rK\rLLL^ |
]])
end)
it('inccommand=nosplit, highlights multiline substitutions', function()
common_setup(screen, 'nosplit', multiline_text)
feed('gg')
feed(':%s/2\\_.*X/MMM')
screen:expect([[
1 {20:MMM} Y Z |
7 8 9 |
|
{1:~ }|*11
:%s/2\_.*X/MMM^ |
]])
feed('\\rK\\rLLL')
screen:expect([[
1 {20:MMM} |
{20:K} |
{20:LLL} Y Z |
7 8 9 |
|
{1:~ }|*9
:%s/2\_.*X/MMM\rK\rLLL^ |
]])
end)
it('inccommand=split, highlights multiple matches on a line', function()
common_setup(screen, 'split', multimatch_text)
command('set gdefault')
feed('gg')
feed(':%s/a/XLK')
screen:expect([[
{20:XLK} bdc e{20:XLK}e {20:XLK} fgl lzi{20:XLK} r|
x |
|
{1:~ }|*2
{3:[No Name] [+] }|
|1| {20:XLK} bdc e{20:XLK}e {20:XLK} fgl lzi{20:X}|
{20:LK} r |
{1:~ }|*5
{2:[Preview] }|
:%s/a/XLK^ |
]])
end)
it('inccommand=nosplit, highlights multiple matches on a line', function()
common_setup(screen, 'nosplit', multimatch_text)
command('set gdefault')
feed('gg')
feed(':%s/a/XLK')
screen:expect([[
{20:XLK} bdc e{20:XLK}e {20:XLK} fgl lzi{20:XLK} r|
x |
|
{1:~ }|*11
:%s/a/XLK^ |
]])
end)
it('inccommand=split, with \\zs', function()
common_setup(screen, 'split', multiline_text)
feed('gg')
feed(':%s/[0-9]\\n\\zs[A-Z]/OKO')
screen:expect([[
{20:OKO} B C |
4 5 6 |
{20:OKO} Y Z |
7 8 9 |
|
{3:[No Name] [+] }|
|1| 1 2 3 |
|2| {20:OKO} B C |
|3| 4 5 6 |
|4| {20:OKO} Y Z |
{1:~ }|*3
{2:[Preview] }|
:%s/[0-9]\n\zs[A-Z]/OKO^ |
]])
end)
it('inccommand=nosplit, with \\zs', function()
common_setup(screen, 'nosplit', multiline_text)
feed('gg')
feed(':%s/[0-9]\\n\\zs[A-Z]/OKO')
screen:expect([[
1 2 3 |
{20:OKO} B C |
4 5 6 |
{20:OKO} Y Z |
7 8 9 |
|
{1:~ }|*8
:%s/[0-9]\n\zs[A-Z]/OKO^ |
]])
end)
it('inccommand=split, substitutions of different length', function()
common_setup(screen, 'split', 'T T123 T2T TTT T090804\nx')
feed(':%s/T\\([0-9]\\+\\)/\\1\\1/g')
screen:expect([[
T {20:123123} {20:22}T TTT {20:090804090804} |
x |
{1:~ }|*3
{3:[No Name] [+] }|
|1| T {20:123123} {20:22}T TTT {20:090804090}|
{20:804} |
{1:~ }|*5
{2:[Preview] }|
:%s/T\([0-9]\+\)/\1\1/g^ |
]])
end)
it('inccommand=nosplit, substitutions of different length', function()
common_setup(screen, 'nosplit', 'T T123 T2T TTT T090804\nx')
feed(':%s/T\\([0-9]\\+\\)/\\1\\1/g')
screen:expect([[
T {20:123123} {20:22}T TTT {20:090804090804} |
x |
{1:~ }|*12
:%s/T\([0-9]\+\)/\1\1/g^ |
]])
end)
it('inccommand=split, contraction of lines', function()
local text = [[
T T123 T T123 T2T TT T23423424
x
afa Q
adf la;lkd R
alx
]]
common_setup(screen, 'split', text)
feed(':%s/[QR]\\n')
screen:expect([[
afa {20:Q} |
adf la;lkd {20:R} |
alx |
|
{1:~ }|
{3:[No Name] [+] }|
|3| afa {20:Q} |
|4|{20: }adf la;lkd {20:R} |
|5|{20: }alx |
{1:~ }|*4
{2:[Preview] }|
:%s/[QR]\n^ |
]])
feed('/KKK')
screen:expect([[
T T123 T T123 T2T TT T23423424|
x |
afa {20:KKK}adf la;lkd {20:KKK}alx |
|
{1:~ }|
{3:[No Name] [+] }|
|3| afa {20:KKK}adf la;lkd {20:KKK}alx |
{1:~ }|*6
{2:[Preview] }|
:%s/[QR]\n/KKK^ |
]])
end)
it('inccommand=nosplit, contraction of lines', function()
local text = [[
T T123 T T123 T2T TT T23423424
x
afa Q
adf la;lkd R
alx
]]
common_setup(screen, 'nosplit', text)
feed(':%s/[QR]\\n/KKK')
screen:expect([[
T T123 T T123 T2T TT T23423424|
x |
afa {20:KKK}adf la;lkd {20:KKK}alx |
|
{1:~ }|*10
:%s/[QR]\n/KKK^ |
]])
end)
it('inccommand=split, contraction of two subsequent NL chars', function()
local text = [[
AAA AA
BBB BB
CCC CC
]]
-- This used to crash, but more than 20 highlight entries are required
-- to reproduce it (so that the marktree has multiple nodes)
common_setup(screen, 'split', string.rep(text, 10))
feed(':%s/\\n\\n/<c-v><c-m>/g')
screen:expect {
grid = [[
CCC CC |
AAA AA |
BBB BB |
CCC CC |
|
{3:[No Name] [+] }|
| 1| AAA AA |
| 2|{20: }BBB BB |
| 3|{20: }CCC CC |
| 4|{20: }AAA AA |
| 5|{20: }BBB BB |
| 6|{20: }CCC CC |
| 7|{20: }AAA AA |
{2:[Preview] }|
:%s/\n\n/{18:^M}/g^ |
]],
}
assert_alive()
end)
it('inccommand=nosplit, contraction of two subsequent NL chars', function()
local text = [[
AAA AA
BBB BB
CCC CC
]]
common_setup(screen, 'nosplit', string.rep(text, 10))
feed(':%s/\\n\\n/<c-v><c-m>/g')
screen:expect {
grid = [[
CCC CC |
AAA AA |
BBB BB |
CCC CC |
AAA AA |
BBB BB |
CCC CC |
AAA AA |
BBB BB |
CCC CC |
AAA AA |
BBB BB |
CCC CC |
|
:%s/\n\n/{18:^M}/g^ |
]],
}
assert_alive()
end)
it('inccommand=split, multibyte text', function()
common_setup(screen, 'split', multibyte_text)
feed(':%s/£.*ѫ/X¥¥')
screen:expect([[
a{20:X¥¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{1:~ }|
{3:[No Name] [+] }|
|1| {20:X¥¥} PEPPERS |
|2| {20:X¥¥} |
|3| a{20:X¥¥}¥KOL |
{1:~ }|*4
{2:[Preview] }|
:%s/£.*ѫ/X¥¥^ |
]])
feed('\\ra££ ¥')
screen:expect([[
a{20:X¥¥} |
{20:a££ ¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{3:[No Name] [+] }|
|1| {20:X¥¥} |
|2|{20: a££ ¥} PEPPERS |
|3| {20:X¥¥} |
|4|{20: a££ ¥} |
|5| a{20:X¥¥} |
|6|{20: a££ ¥}¥KOL |
{1:~ }|
{2:[Preview] }|
:%s/£.*ѫ/X¥¥\ra££ ¥^ |
]])
end)
it('inccommand=nosplit, multibyte text', function()
common_setup(screen, 'nosplit', multibyte_text)
feed(':%s/£.*ѫ/X¥¥')
screen:expect([[
{20:X¥¥} PEPPERS |
{20:X¥¥} |
a{20:X¥¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{1:~ }|*8
:%s/£.*ѫ/X¥¥^ |
]])
feed('\\ra££ ¥')
screen:expect([[
{20:X¥¥} |
{20:a££ ¥} PEPPERS |
{20:X¥¥} |
{20:a££ ¥} |
a{20:X¥¥} |
{20:a££ ¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{1:~ }|*5
:%s/£.*ѫ/X¥¥\ra££ ¥^ |
]])
end)
it('inccommand=split, small cmdwinheight', function()
common_setup(screen, 'split', long_multiline_text)
command('set cmdwinheight=2')
feed(':%s/[a-z]')
screen:expect([[
X Y Z |
7 8 9 |
K L M |
{20:a} b c |
{20:d} e f |
{20:q} r s |
{20:x} y z |
£ {20:m} n |
{20:t} œ ¥ |
|
{3:[No Name] [+] }|
| 7| {20:a} b c |
| 8| {20:d} e f |
{2:[Preview] }|
:%s/[a-z]^ |
]])
feed('/JLKR £')
screen:expect([[
X Y Z |
7 8 9 |
K L M |
{20:JLKR £} b c |
{20:JLKR £} e f |
{20:JLKR £} r s |
{20:JLKR £} y z |
£ {20:JLKR £} n |
{20:JLKR £} œ ¥ |
|
{3:[No Name] [+] }|
| 7| {20:JLKR £} b c |
| 8| {20:JLKR £} e f |
{2:[Preview] }|
:%s/[a-z]/JLKR £^ |
]])
feed('\\rѫ ab \\rXXXX')
screen:expect([[
7 8 9 |
K L M |
{20:JLKR £} |
{20:ѫ ab } |
{20:XXXX} b c |
{20:JLKR £} |
{20:ѫ ab } |
{20:XXXX} e f |
{20:JLKR £} |
{20:ѫ ab } |
{3:[No Name] [+] }|
| 7| {20:JLKR £} |
{3: }|
:%s/[a-z]/JLKR £\rѫ ab \rXXX|
X^ |
]])
end)
it('inccommand=split, large cmdwinheight', function()
common_setup(screen, 'split', long_multiline_text)
command('set cmdwinheight=11')
feed(':%s/. .$')
screen:expect([[
t {20:œ ¥} |
{3:[No Name] [+] }|
| 1| 1 {20:2 3} |
| 2| A {20:B C} |
| 3| 4 {20:5 6} |
| 4| X {20:Y Z} |
| 5| 7 {20:8 9} |
| 6| K {20:L M} |
| 7| a {20:b c} |
| 8| d {20:e f} |
| 9| q {20:r s} |
|10| x {20:y z} |
|11| £ {20:m n} |
{2:[Preview] }|
:%s/. .$^ |
]])
feed('/ YYY')
screen:expect([[
t {20: YYY} |
{3:[No Name] [+] }|
| 1| 1 {20: YYY} |
| 2| A {20: YYY} |
| 3| 4 {20: YYY} |
| 4| X {20: YYY} |
| 5| 7 {20: YYY} |
| 6| K {20: YYY} |
| 7| a {20: YYY} |
| 8| d {20: YYY} |
| 9| q {20: YYY} |
|10| x {20: YYY} |
|11| £ {20: YYY} |
{2:[Preview] }|
:%s/. .$/ YYY^ |
]])
feed('\\r KKK')
screen:expect([[
a {20: YYY} |
{3:[No Name] [+] }|
| 1| 1 {20: YYY} |
| 2|{20: KKK} |
| 3| A {20: YYY} |
| 4|{20: KKK} |
| 5| 4 {20: YYY} |
| 6|{20: KKK} |
| 7| X {20: YYY} |
| 8|{20: KKK} |
| 9| 7 {20: YYY} |
|10|{20: KKK} |
|11| K {20: YYY} |
{2:[Preview] }|
:%s/. .$/ YYY\r KKK^ |
]])
end)
it('inccommand=split, lookaround', function()
common_setup(screen, 'split', 'something\neverything\nsomeone')
feed([[:%s/\(some\)\@<lt>=thing/one/]])
screen:expect([[
some{20:one} |
everything |
someone |
{1:~ }|*2
{3:[No Name] [+] }|
|1| some{20:one} |
{1:~ }|*6
{2:[Preview] }|
:%s/\(some\)\@<=thing/one/^ |
]])
feed('<C-c>')
feed('gg')
poke_eventloop()
feed([[:%s/\(some\)\@<lt>!thing/one/]])
screen:expect([[
something |
every{20:one} |
someone |
{1:~ }|*2
{3:[No Name] [+] }|
|2| every{20:one} |
{1:~ }|*6
{2:[Preview] }|
:%s/\(some\)\@<!thing/one/^ |
]])
feed([[<C-c>]])
poke_eventloop()
feed([[:%s/some\(thing\)\@=/every/]])
screen:expect([[
{20:every}thing |
everything |
someone |
{1:~ }|*2
{3:[No Name] [+] }|
|1| {20:every}thing |
{1:~ }|*6
{2:[Preview] }|
:%s/some\(thing\)\@=/every/^ |
]])
feed([[<C-c>]])
poke_eventloop()
feed([[:%s/some\(thing\)\@!/every/]])
screen:expect([[
something |
everything |
{20:every}one |
{1:~ }|*2
{3:[No Name] [+] }|
|3| {20:every}one |
{1:~ }|*6
{2:[Preview] }|
:%s/some\(thing\)\@!/every/^ |
]])
end)
it("doesn't prompt to swap cmd range", function()
screen:try_resize(50, 8) -- wide to avoid hit-enter prompt
common_setup(screen, 'split', default_text)
feed(':2,1s/tw/MO/g')
-- substitution preview should have been made, without prompting
screen:expect([[
{20:MO}o lines |
{3:[No Name] [+] }|
|2| {20:MO}o lines |
{1:~ }|*3
{2:[Preview] }|
:2,1s/tw/MO/g^ |
]])
-- but should be prompted on hitting enter
feed('<CR>')
screen:expect([[
{20:MO}o lines |
{3:[No Name] [+] }|
|2| {20:MO}o lines |
{1:~ }|*3
{2:[Preview] }|
{6:Backwards range given, OK to swap (y/n)?}^ |
]])
feed('y')
screen:expect([[
Inc substitution on |
^MOo lines |
|
{1:~ }|*4
{6:Backwards range given, OK to swap (y/n)?}y |
]])
end)
end)
it(':substitute with inccommand during :terminal activity', function()
if t.skip_fragile(pending) then
return
end
retry(2, 40000, function()
clear()
local screen = Screen.new(30, 15)
command('set cmdwinheight=3')
feed(([[:terminal "%s" REP 5000 xxx<cr>]]):format(testprg('shell-test')))
command('file term')
feed('G') -- Follow :terminal output.
command('new')
common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz')
command('wincmd =')
feed('gg')
feed(':%s/foo/ZZZ')
sleep(20) -- Allow some terminal activity.
poke_eventloop()
screen:sleep(0)
screen:expect_unchanged()
end)
end)
it(':substitute with inccommand, timer-induced :redraw #9777', function()
clear()
local screen = Screen.new(30, 12)
command('set cmdwinheight=3')
command('call timer_start(10, {-> execute("redraw")}, {"repeat":-1})')
command('call timer_start(10, {-> execute("redrawstatus")}, {"repeat":-1})')
common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz')
feed('gg')
feed(':%s/foo/ZZZ')
sleep(20) -- Allow some timer activity.
screen:expect([[
{20:ZZZ} bar baz |
bar baz fox |
bar {20:ZZZ} baz |
{1:~ }|*3
{3:[No Name] [+] }|
|1| {20:ZZZ} bar baz |
|3| bar {20:ZZZ} baz |
{1:~ }|
{2:[Preview] }|
:%s/foo/ZZZ^ |
]])
-- Also with nvim__redraw()
command('call timer_start(10, {-> nvim__redraw(#{flush:1})}, {"repeat":-1})')
command('call timer_start(10, {-> nvim__redraw(#{statusline:1})}, {"repeat":-1})')
sleep(20) -- Allow some timer activity.
screen:expect_unchanged()
end)
it(':substitute with inccommand, allows :redraw before first separator is typed #18857', function()
clear()
local screen = Screen.new(30, 6)
common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz')
command('hi! link NormalFloat CursorLine')
local float_buf = api.nvim_create_buf(false, true)
api.nvim_open_win(float_buf, false, {
relative = 'editor',
height = 1,
width = 5,
row = 3,
col = 0,
focusable = false,
})
feed(':')
screen:expect([[
foo bar baz |
bar baz fox |
bar foo baz |
{21: }{1: }|
{1:~ }|
:^ |
]])
feed('%s')
screen:expect([[
foo bar baz |
bar baz fox |
bar foo baz |
{21: }{1: }|
{1:~ }|
:%s^ |
]])
api.nvim_buf_set_lines(float_buf, 0, -1, true, { 'foo' })
command('redraw')
screen:expect([[
foo bar baz |
bar baz fox |
bar foo baz |
{21:foo }{1: }|
{1:~ }|
:%s^ |
]])
end)
it(':substitute with inccommand, does not crash if range contains invalid marks', function()
clear()
local screen = Screen.new(30, 6)
common_setup(screen, 'split', 'test')
feed([[:'a,'bs]])
screen:expect([[
test |
{1:~ }|*4
:'a,'bs^ |
]])
-- v:errmsg shouldn't be set either before the first separator is typed
eq('', eval('v:errmsg'))
feed('/')
screen:expect([[
test |
{1:~ }|*4
:'a,'bs/^ |
]])
end)
it(':substitute with inccommand, no unnecessary redraw if preview is not shown', function()
clear()
local screen = Screen.new(60, 6)
common_setup(screen, 'split', 'test')
feed(':ls<CR>')
screen:expect([[
test |
{1:~ }|
{3: }|
:ls |
1 %a + "[No Name]" line 1 |
{6:Press ENTER or type command to continue}^ |
]])
feed(':s')
-- no unnecessary redraw, so messages are still shown
screen:expect([[
test |
{1:~ }|
{3: }|
:ls |
1 %a + "[No Name]" line 1 |
:s^ |
]])
feed('o')
screen:expect([[
test |
{1:~ }|
{3: }|
:ls |
1 %a + "[No Name]" line 1 |
:so^ |
]])
feed('<BS>')
screen:expect([[
test |
{1:~ }|
{3: }|
:ls |
1 %a + "[No Name]" line 1 |
:s^ |
]])
feed('/test')
-- now inccommand is shown, so screen is redrawn
screen:expect([[
{20:test} |
{1:~ }|*4
:s/test^ |
]])
end)
it(":substitute doesn't crash with inccommand, if undo is empty #12932", function()
clear()
local screen = Screen.new(10, 5)
command('set undolevels=-1')
common_setup(screen, 'split', 'test')
feed(':%s/test')
sleep(100)
feed('/')
sleep(100)
feed('f')
screen:expect([[
{20:f} |
{1:~ }|*3
:%s/test/f^ |
]])
assert_alive()
end)
it(':substitute with inccommand works properly if undo is not synced #20029', function()
clear()
local screen = Screen.new(30, 6)
common_setup(screen, 'nosplit', 'foo\nbar\nbaz')
api.nvim_set_keymap('x', '<F2>', '<Esc>`<Oaaaaa asdf<Esc>`>obbbbb asdf<Esc>V`<k:s/asdf/', {})
feed('gg0<C-V>lljj<F2>')
screen:expect([[
aaaaa |
foo |
bar |
baz |
bbbbb |
:'<,'>s/asdf/^ |
]])
feed('hjkl')
screen:expect([[
aaaaa {20:hjkl} |
foo |
bar |
baz |
bbbbb {20:hjkl} |
:'<,'>s/asdf/hjkl^ |
]])
feed('<CR>')
expect([[
aaaaa hjkl
foo
bar
baz
bbbbb hjkl]])
feed('u')
expect([[
foo
bar
baz]])
end)
it(':substitute with inccommand does not unexpectedly change viewport #25697', function()
clear()
local screen = Screen.new(45, 5)
common_setup(screen, 'nosplit', long_multiline_text)
command('vnew | tabnew | tabclose')
screen:expect([[
^ │£ m n |
{1:~ }│t œ ¥ |
{1:~ }│ |
{3:[No Name] }{2:[No Name] [+] }|
|
]])
feed(':s/')
screen:expect([[
│£ m n |
{1:~ }│t œ ¥ |
{1:~ }│ |
{3:[No Name] }{2:[No Name] [+] }|
:s/^ |
]])
feed('<Esc>')
screen:expect([[
^ │£ m n |
{1:~ }│t œ ¥ |
{1:~ }│ |
{3:[No Name] }{2:[No Name] [+] }|
|
]])
end)
it('long :%s/ with inccommand does not collapse cmdline', function()
clear()
local screen = Screen.new(10, 5)
common_setup(screen, 'nosplit')
feed(
':%s/AAAAAAA',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A',
'A'
)
screen:expect([[
|
{3: }|
:%s/AAAAAAAA|
AAAAAAAAAAAA|
AAAAAAA^ |
]])
end)
it("with 'inccommand' typing invalid `={expr}` does not show error", function()
clear()
local screen = Screen.new(30, 6)
common_setup(screen, 'nosplit')
feed(':edit `=`')
screen:expect([[
|
{1:~ }|*4
:edit `=`^ |
]])
end)
it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", function()
clear()
common_setup(nil, 'nosplit')
feed(':filter s')
assert_alive()
feed(' ')
assert_alive()
feed('h')
assert_alive()
feed('i')
assert_alive()
end)
it("'inccommand' cannot be changed during preview #23136", function()
clear()
local screen = Screen.new(30, 6)
common_setup(screen, 'nosplit', 'foo\nbar\nbaz')
source([[
function! IncCommandToggle()
let prev = &inccommand
if &inccommand ==# 'split'
set inccommand=nosplit
elseif &inccommand ==# 'nosplit'
set inccommand=split
elseif &inccommand ==# ''
set inccommand=nosplit
else
throw 'unknown inccommand'
endif
return " \<BS>"
endfun
cnoremap <expr> <C-E> IncCommandToggle()
]])
feed(':%s/foo/bar<C-E><C-E><C-E>')
assert_alive()
end)
it("'inccommand' value can be changed multiple times #27086", function()
clear()
local screen = Screen.new(30, 20)
common_setup(screen, 'split', 'foo1\nfoo2\nfoo3')
for _ = 1, 3 do
feed(':%s/foo/bar')
screen:expect([[
{20:bar}1 |
{20:bar}2 |
{20:bar}3 |
{1:~ }|*7
{3:[No Name] [+] }|
|1| {20:bar}1 |
|2| {20:bar}2 |
|3| {20:bar}3 |
{1:~ }|*4
{2:[Preview] }|
:%s/foo/bar^ |
]])
feed('<Esc>')
command('set inccommand=nosplit')
feed(':%s/foo/bar')
screen:expect([[
{20:bar}1 |
{20:bar}2 |
{20:bar}3 |
{1:~ }|*16
:%s/foo/bar^ |
]])
feed('<Esc>')
command('set inccommand=split')
end
end)
it("'inccommand' disables preview if preview buffer can't be created #27086", function()
clear()
api.nvim_buf_set_name(0, '[Preview]')
local screen = Screen.new(30, 20)
common_setup(screen, 'split', 'foo1\nfoo2\nfoo3')
eq('split', api.nvim_get_option_value('inccommand', {}))
feed(':%s/foo/bar')
screen:expect([[
{20:bar}1 |
{20:bar}2 |
{20:bar}3 |
{1:~ }|*16
:%s/foo/bar^ |
]])
eq('nosplit', api.nvim_get_option_value('inccommand', {}))
end)