neovim/test/functional/ui/inccommand_spec.lua
zeertzjq 05faa8f30a
test: make expect_unchanged() less confusing (#22255)
Problem:
The sleep before collecting the initial screen state is confusing and
may lead to unexpected success if it comes after a blocking RPC call.

Solution:
Remove that sleep and add an "intermediate" argument.
2023-02-15 07:26:55 +08:00

3116 lines
94 KiB
Lua

local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local curbufmeths = helpers.curbufmeths
local eq = helpers.eq
local eval = helpers.eval
local feed_command = helpers.feed_command
local expect = helpers.expect
local feed = helpers.feed
local insert = helpers.insert
local meths = helpers.meths
local neq = helpers.neq
local ok = helpers.ok
local retry = helpers.retry
local source = helpers.source
local poke_eventloop = helpers.poke_eventloop
local nvim = helpers.nvim
local sleep = helpers.sleep
local testprg = helpers.testprg
local assert_alive = helpers.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:attach()
screen:set_default_attr_ids({
[1] = {foreground = Screen.colors.Fuchsia},
[2] = {foreground = Screen.colors.Brown, bold = true},
[3] = {foreground = Screen.colors.SlateBlue},
[4] = {bold = true, foreground = Screen.colors.SlateBlue},
[5] = {foreground = Screen.colors.DarkCyan},
[6] = {bold = true},
[7] = {underline = true, bold = true, foreground = Screen.colors.SlateBlue},
[8] = {foreground = Screen.colors.Slateblue, underline = true},
[9] = {background = Screen.colors.Yellow},
[10] = {reverse = true},
[11] = {reverse = true, bold=true},
[12] = {foreground = Screen.colors.Red, background = Screen.colors.Yellow},
[13] = {bold = true, foreground = Screen.colors.SeaGreen},
[14] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[15] = {bold=true, foreground=Screen.colors.Blue},
[16] = {background=Screen.colors.Grey90}, -- cursorline
[17] = {foreground = Screen.colors.Blue1},
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_command("%s/AB/BA/")
feed_command("ls")
screen:expect([[
BAC |
{15:~ }|
{15:~ }|
{15:~ }|
{11: }|
:ls |
1 %a + "[No Name]" |
line 1 |
{13:Press ENTER or type command to}|
{13: continue}^ |
]])
end)
for _, case in pairs{"", "split", "nosplit"} do
it("various delimiters (inccommand="..case..")", function()
insert(default_text)
feed_command("set inccommand=" .. case)
local delims = { '/', '#', ';', '%', ',', '@', '!' }
for _,delim in pairs(delims) do
feed_command("%s"..delim.."lines"..delim.."LINES"..delim.."g")
expect([[
Inc substitution on
two LINES
]])
feed_command("undo")
end
end)
end
for _, case in pairs{"", "split", "nosplit"} do
it("'undolevels' (inccommand="..case..")", function()
feed_command("set undolevels=139")
feed_command("setlocal undolevels=34")
feed_command("set inccommand=" .. case)
insert("as")
feed(":%s/as/glork/<enter>")
eq(meths.get_option('undolevels'), 139)
eq(curbufmeths.get_option('undolevels'), 34)
end)
end
for _, case in ipairs({"", "split", "nosplit"}) do
it("empty undotree() (inccommand="..case..")", function()
feed_command("set undolevels=1000")
feed_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()
feed_command("set undolevels=1000")
feed_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()
feed_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/ax",
":%s/1/ax<bs>",
":%s/1/ax<bs><bs>",
":%s/1/ax<bs><bs><bs>",
":%s/1/ax/",
":%s/1/ax/<bs>",
":%s/1/ax/<bs>/",
":%s/1/ax/g",
":%s/1/ax/g<bs>",
":%s/1/ax/g<bs><bs>"
}
local function test_sub(substring, split, redoable)
command('bwipe!')
feed_command("set inccommand=" .. split)
insert("1")
feed("o2<esc>")
feed_command("undo")
feed("o3<esc>")
if redoable then
feed("o4<esc>")
feed_command("undo")
end
feed(substring.. "<enter>")
feed_command("undo")
feed("g-")
expect([[
1
2]])
feed("g+")
expect([[
1
3]])
end
local function test_notsub(substring, split, redoable)
command('bwipe!')
feed_command("set inccommand=" .. split)
insert("1")
feed("o2<esc>")
feed_command("undo")
feed("o3<esc>")
if redoable then
feed("o4<esc>")
feed_command("undo")
end
feed(substring .. "<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!')
feed_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")
feed(substring .. "<esc>")
expect([[
1]])
feed("g-")
expect([[
]])
feed("g+")
expect([[
1]])
feed("<c-r>")
expect([[
1
A]])
feed("g-") -- go to b
feed("2u")
feed(substring .. "<esc>")
feed("<c-r>")
expect([[
1
a]])
feed("g-") -- go to 3
feed("2u")
feed(substring .. "<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)
feed_command("set undolevels=0")
feed("1G0")
insert("X")
feed(":%s/tw/MO/<esc>")
feed_command("undo")
expect(default_text)
feed_command("undo")
expect(default_text:gsub("Inc", "XInc"))
feed_command("undo")
feed_command("%s/tw/MO/g")
expect(default_text:gsub("tw", "MO"))
feed_command("undo")
expect(default_text)
feed_command("undo")
expect(default_text:gsub("tw", "MO"))
end
end)
it('with undolevels=1', function()
local screen = Screen.new(20,10)
for _, case in pairs(cases) do
clear()
common_setup(screen, case, default_text)
screen:expect([[
Inc substitution on |
two lines |
^ |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
|
]])
feed_command("set undolevels=1")
feed("1G0")
insert("X")
feed("IY<esc>")
feed(":%s/tw/MO/<esc>")
-- feed_command("undo") here would cause "Press ENTER".
feed("u")
expect(default_text:gsub("Inc", "XInc"))
feed("u")
expect(default_text)
feed(":%s/tw/MO/g<enter>")
feed(":%s/MO/GO/g<enter>")
feed(":%s/GO/NO/g<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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
end
end
end)
it('with undolevels=2', function()
local screen = Screen.new(20,10)
for _, case in pairs(cases) do
clear()
common_setup(screen, case, default_text)
feed_command("set undolevels=2")
feed("2GAx<esc>")
feed("Ay<esc>")
feed("Az<esc>")
feed(":%s/tw/AR<esc>")
-- feed_command("undo") here would cause "Press ENTER".
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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
two line^s |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
end
feed(":%s/tw/MO/g<enter>")
feed(":%s/MO/GO/g<enter>")
feed(":%s/GO/NO/g<enter>")
feed(":%s/NO/LO/g<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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
end
end
end)
it('with undolevels=-1', function()
local screen = Screen.new(20,10)
for _, case in pairs(cases) do
clear()
common_setup(screen, case, default_text)
feed_command("set undolevels=-1")
feed(":%s/tw/MO/g<enter>")
-- feed_command("undo") here will result in a "Press ENTER" prompt
feed("u")
if case == "split" then
screen:expect([[
Inc substitution on |
^MOo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
else
screen:expect([[
Inc substitution on |
^MOo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
end
-- repeat with an interrupted substitution
clear()
common_setup(screen, case, default_text)
feed_command("set undolevels=-1")
feed("1G")
feed("IL<esc>")
feed(":%s/tw/MO/g<esc>")
feed("u")
screen:expect([[
^LInc substitution on|
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
Already ...t change |
]])
end
end)
end)
describe(":substitute, inccommand=split", function()
local screen = Screen.new(30,15)
before_each(function()
clear()
common_setup(screen, "split", default_text .. default_text)
end)
it("preserves 'modified' buffer flag", function()
feed_command("set nomodified")
feed(":%s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
{11:[No Name] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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=[[{12:to}o lines]]}
feed('<Esc>')
screen:expect{any=[[two lines]]}
-- multiple modifiers
feed(':keeppatterns silent %s/tw/to')
screen:expect{any=[[{12: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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:silent tabedit %s/tw/to^ |
]])
feed('<Esc>')
-- leading colons
feed(':::%s/tw/to')
screen:expect{any=[[{12: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 |
{12:to}o lines |
Inc substitution on |
{12:to}o lines |
|
{11:[No Name] [+] }|
|2| {12:to}o lines |
|4| {12:to}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
{12:to}o lines |
Inc substitution on |
{12:to}o lines |
|
{11:[No Name] [+] }|
|2| {12:to}o lines |
|4| {12:to}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw/^ |
]])
feed("x")
screen:expect([[
Inc substitution on |
{12:x}o lines |
Inc substitution on |
{12:x}o lines |
|
{11:[No Name] [+] }|
|2| {12:x}o lines |
|4| {12:x}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw/x^ |
]])
feed("<bs>")
screen:expect([[
Inc substitution on |
o lines |
Inc substitution on |
o lines |
|
{11:[No Name] [+] }|
|2| o lines |
|4| o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw/^ |
]])
end)
it('shows split window when typing replacement', function()
feed(":%s/tw/XX")
screen:expect([[
Inc substitution on |
{12:XX}o lines |
Inc substitution on |
{12:XX}o lines |
|
{11:[No Name] [+] }|
|2| {12:XX}o lines |
|4| {12:XX}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw/XX^ |
]])
end)
it('does not show split window for :s/', function()
feed("2gg")
feed(":s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
Inc substitution on |
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:s/tw^ |
]])
end)
it("'hlsearch' is active, 'cursorline' is not", function()
feed_command("set hlsearch cursorline")
feed("gg")
-- Assert that 'cursorline' is active.
screen:expect([[
{16:^Inc substitution on }|
two lines |
Inc substitution on |
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:set hlsearch cursorline |
]])
feed(":%s/tw")
-- 'cursorline' is NOT active during preview.
screen:expect([[
Inc substitution on |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
|4| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw^ |
]])
end)
it('highlights the replacement text', function()
feed('ggO')
feed('M M M<esc>')
feed(':%s/M/123/g')
screen:expect([[
{12:123} {12:123} {12:123} |
Inc substitution on |
two lines |
Inc substitution on |
two lines |
{11:[No Name] [+] }|
|1| {12:123} {12:123} {12:123} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
|
{11:[No Name] [+] }|
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/Inx^ |
]])
end)
it('previews correctly when previewhight is small', function()
feed_command('set cwh=3')
feed_command('set hls')
feed('ggdG')
insert(string.rep('abc abc abc\n', 20))
feed(':%s/abc/MMM/g')
screen:expect([[
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{12:MMM} {12:MMM} {12:MMM} |
{11:[No Name] [+] }|
| 1| {12:MMM} {12:MMM} {12:MMM} |
| 2| {12:MMM} {12:MMM} {12:MMM} |
| 3| {12:MMM} {12:MMM} {12:MMM} |
{10:[Preview] }|
:%s/abc/MMM/g^ |
]])
end)
it('actually replaces text', function()
feed(":%s/tw/XX/g<Enter>")
screen:expect([[
Inc substitution on |
XXo lines |
Inc substitution on |
^XXo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw/XX/g |
]])
end)
it('shows correct line numbers with many lines', function()
feed("gg")
feed("2yy")
feed("2000p")
feed_command("1,1000s/tw/BB/g")
feed(":%s/tw/X")
screen:expect([[
BBo lines |
Inc substitution on |
{12:X}o lines |
Inc substitution on |
{12:X}o lines |
{11:[No Name] [+] }|
|1001| {12:X}o lines |
|1003| {12:X}o lines |
|1005| {12:X}o lines |
|1007| {12:X}o lines |
|1009| {12:X}o lines |
|1011| {12:X}o lines |
|1013| {12:X}o lines |
{10:[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, helpers.call('bufnr', '$'))
end)
it('works with the n flag', function()
feed(":%s/tw/Mix/n<Enter>")
screen:expect([[
Inc substitution on |
two lines |
Inc substitution on |
two lines |
^ |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
2 matches on 2 lines |
]])
end)
it("deactivates if 'redrawtime' is exceeded #5602", function()
-- prevent redraws from 'incsearch'
meths.set_option('incsearch', false)
-- Assert that 'inccommand' is ENABLED initially.
eq("split", eval("&inccommand"))
-- Set 'redrawtime' to minimal value, to ensure timeout is triggered.
feed_command("set redrawtime=1 nowrap")
-- Load a big file.
feed_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_command("echo 'foo'")
-- Start an incomplete :substitute command.
feed(":1,2s/t/X")
screen:expect([[
Inc subs{12:X}itution on |
{12:X}wo lines |
Inc substitution on |
two lines |
|
{11:[No Name] [+] }|
|1| Inc subs{12:X}itution on |
|2| {12:X}wo lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
: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{12:X}itution on |
{12:X}wo lines |
Inc substitution on |
two lines |
|
{11:[No Name] [+] }|
|1| Inc subs{12:X}itution on |
|2| {12:X}wo lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:1,2s/t/X^ |
]])
feed([[<C-R>='Y']])
-- preview should be unchanged during c_CTRL-R_= editing
screen:expect([[
Inc subs{12:X}itution on |
{12:X}wo lines |
Inc substitution on |
two lines |
|
{11:[No Name] [+] }|
|1| Inc subs{12:X}itution on |
|2| {12:X}wo lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
={1:'Y'}^ |
]])
feed('<CR>')
-- preview should be changed by the result of the expression
screen:expect([[
Inc subs{12:XY}itution on |
{12:XY}wo lines |
Inc substitution on |
two lines |
|
{11:[No Name] [+] }|
|1| Inc subs{12:XY}itution on |
|2| {12:XY}wo lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:1,2s/t/XY^ |
]])
feed([[<C-\>e'echo']])
-- preview should be unchanged during c_CTRL-\_e editing
screen:expect([[
Inc subs{12:XY}itution on |
{12:XY}wo lines |
Inc substitution on |
two lines |
|
{11:[No Name] [+] }|
|1| Inc subs{12:XY}itution on |
|2| {12:XY}wo lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
={1:'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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:echo^ |
]])
end)
end)
describe("inccommand=nosplit", function()
local screen = Screen.new(20,10)
before_each(function()
clear()
common_setup(screen, "nosplit", default_text .. default_text)
end)
it("works with :smagic, :snomagic", function()
feed_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 *.{12:X} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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 *.{12:X} here |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%snomagic/3.*/X^ |
]])
end)
it("shows preview when cmd modifiers are present", function()
-- one modifier
feed(':keeppatterns %s/tw/to')
screen:expect{any=[[{12:to}o lines]]}
feed('<Esc>')
screen:expect{any=[[two lines]]}
-- multiple modifiers
feed(':keeppatterns silent %s/tw/to')
screen:expect{any=[[{12: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 |
|
{15:~ }|
{15:~ }|
{11: }|
: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 |
{12:OKOK}o lines |
Inc substitution on |
{12:OKOK}o lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw/OKOK^ |
]])
end)
it('never shows preview buffer', function()
feed_command("set hlsearch")
feed(":%s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
Inc substitution on |
{12:tw}o lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw^ |
]])
feed("/BM")
screen:expect([[
Inc substitution on |
{12:BM}o lines |
Inc substitution on |
{12:BM}o lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw/BM^ |
]])
feed("/")
screen:expect([[
Inc substitution on |
{12:BM}o lines |
Inc substitution on |
{12:BM}o lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw/BM/^ |
]])
feed("<enter>")
screen:expect([[
Inc substitution on |
BMo lines |
Inc substitution on |
^BMo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/tw/BM/ |
]])
end)
it("clears preview if non-previewable command is edited", function()
-- Put a non-previewable command in history.
feed_command("echo 'foo'")
-- Start an incomplete :substitute command.
feed(":1,2s/t/X")
screen:expect([[
Inc subs{12:X}itution on |
{12:X}wo lines |
Inc substitution on |
two lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
: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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:echo 'foo'^ |
]])
end)
it("does not execute trailing bar-separated commands #7494", function()
feed(':%s/two/three/g|q!')
screen:expect([[
Inc substitution on |
{12:three} lines |
Inc substitution on |
{12:three} lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/^ |
]])
end)
end)
describe(":substitute, 'inccommand' with a failing expression", function()
local screen = Screen.new(20,10)
local cases = { "", "split", "nosplit" }
local function refresh(case)
clear()
common_setup(screen, case, default_text)
end
it('in the pattern does nothing', function()
for _, case in pairs(cases) do
refresh(case)
feed_command("set inccommand=" .. case)
feed(":silent! %s/tw\\(/LARD/<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
feed_command("set inccommand=" .. case)
feed(":silent! %s/tw/" .. repl .. "/<enter>")
expect(default_text:gsub("tw", ""))
feed_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 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:100s/^ |
]])
feed('<enter>')
screen:expect([[
Inc substitution on |
two lines |
^ |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{14: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(50,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)
feed_command("cnoremap ".. c .. " " .. c)
end
feed_command(cmd)
expect([[
Inc substitution on
two LINES
]])
end
end)
it('work when mappings move the cursor', function()
for _, case in pairs(cases) do
refresh(case)
feed_command("cnoremap ,S LINES/<left><left><left><left><left><left>")
feed(":%s/lines/,Sor three <enter>")
expect([[
Inc substitution on
two or three LINES
]])
feed_command("cnoremap ;S /X/<left><left><left>")
feed(":%s/;SI<enter>")
expect([[
Xnc substitution on
two or three LXNES
]])
feed_command("cnoremap ,T //Y/<left><left><left>")
feed(":%s,TX<enter>")
expect([[
Ync substitution on
two or three LYNES
]])
feed_command("cnoremap ;T s//Z/<left><left><left>")
feed(":%;TY<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)
feed_command("cnoremap <expr> x execute('bwipeout!')[-1].'x'")
feed(":%s/tw/tox<enter>")
screen:expect{any=[[{14:^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)
feed_command("cnoremap <expr> x cursor(1, 1)[-1].'x'")
feed(":%s/tw/tox/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)
feed_command("cnoremap <expr> x execute('set inccommand=')[-1]")
feed(":%s/tw/toxa/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")
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(t)
local s = ""
for _, buffer in pairs(t) 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)
meths.set_var(event .. "_fired", {})
feed_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 = meths.get_var(event .. "_fired")
meths.set_var(event .. "_fired", {})
end
feed("/<enter>")
for event, _ in pairs(eventsExpected) do
eventsObserved[event].close = meths.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")
feed_command("vsplit")
feed_command("split")
feed(":%s/tw")
screen:expect([[
Inc substitution on │Inc substitution on|
{12:tw}o lines │{12:tw}o lines |
│ |
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{11:[No Name] [+] }│{15:~ }|
Inc substitution on │{15:~ }|
{12:tw}o lines │{15:~ }|
│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{10:[No Name] [+] [No Name] [+] }|
|2| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/tw^ |
]])
feed("<esc>")
feed_command("only")
feed_command("split")
feed_command("vsplit")
feed(":%s/tw")
screen:expect([[
Inc substitution on │Inc substitution on|
{12:tw}o lines │{12:tw}o lines |
│ |
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{15:~ }│{15:~ }|
{11:[No Name] [+] }{10:[No Name] [+] }|
Inc substitution on |
{12:tw}o lines |
|
{15:~ }|
{15:~ }|
{10:[No Name] [+] }|
|2| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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()
feed_command("set " .. setting)
feed(":%s/tw")
screen:expect([[
Inc substitution on |
{12:tw}o lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|2| {12:tw}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
{12: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}, nvim("get_mode"))
feed("/A<Enter>")
expect("A")
eq({mode='n', blocking=false}, 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}, nvim("get_mode"))
feed("/A<Enter>")
expect("A\nA")
eq({mode='n', blocking=false}, 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}, nvim("get_mode"))
feed("<Esc>")
eq({mode='n', blocking=false}, 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([[
{12:a}Inc substitution on |
{12:a}two lines |
{12:a} |
{15:~ }|
:%s/\%1c/a/^ |
]])
feed("<Esc>")
screen:expect([[
Inc substitution on |
two lines |
^ |
{15:~ }|
|
]])
end)
end)
describe(":substitute", function()
local screen = Screen.new(30,15)
before_each(function()
clear()
end)
it("inccommand=split, highlights multiline substitutions", function()
common_setup(screen, "split", multiline_text)
feed("gg")
feed(":%s/2\\_.*X")
screen:expect([[
1 {12:2 3} |
{12:A B C} |
{12:4 5 6} |
{12:X} Y Z |
7 8 9 |
{11:[No Name] [+] }|
|1| 1 {12:2 3} |
|2|{12: A B C} |
|3|{12: 4 5 6} |
|4|{12: X} Y Z |
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/2\_.*X^ |
]])
feed("/MMM")
screen:expect([[
1 {12:MMM} Y Z |
7 8 9 |
|
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| 1 {12:MMM} Y Z |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/2\_.*X/MMM^ |
]])
feed("\\rK\\rLLL")
screen:expect([[
1 {12:MMM} |
{12:K} |
{12:LLL} Y Z |
7 8 9 |
|
{11:[No Name] [+] }|
|1| 1 {12:MMM} |
|2|{12: K} |
|3|{12: LLL} Y Z |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 {12:MMM} Y Z |
7 8 9 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/2\_.*X/MMM^ |
]])
feed("\\rK\\rLLL")
screen:expect([[
1 {12:MMM} |
{12:K} |
{12:LLL} Y Z |
7 8 9 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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([[
{12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r|
x |
|
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| {12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:X}|
{12:LK} r |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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([[
{12:XLK} bdc e{12:XLK}e {12:XLK} fgl lzi{12:XLK} r|
x |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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([[
{12:OKO} B C |
4 5 6 |
{12:OKO} Y Z |
7 8 9 |
|
{11:[No Name] [+] }|
|1| 1 2 3 |
|2| {12:OKO} B C |
|3| 4 5 6 |
|4| {12:OKO} Y Z |
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 |
{12:OKO} B C |
4 5 6 |
{12:OKO} Y Z |
7 8 9 |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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 {12:123123} {12:22}T TTT {12:090804090804} |
x |
{15:~ }|
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| T {12:123123} {12:22}T TTT {12:090804090}|
{12:804} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 {12:123123} {12:22}T TTT {12:090804090804} |
x |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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 {12:Q} |
adf la;lkd {12:R} |
alx |
|
{15:~ }|
{11:[No Name] [+] }|
|3| afa {12:Q} |
|4|{12: }adf la;lkd {12:R} |
|5|{12: }alx |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/[QR]\n^ |
]])
feed("/KKK")
screen:expect([[
T T123 T T123 T2T TT T23423424|
x |
afa {12:KKK}adf la;lkd {12:KKK}alx |
|
{15:~ }|
{11:[No Name] [+] }|
|3| afa {12:KKK}adf la;lkd {12:KKK}alx |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[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 {12:KKK}adf la;lkd {12:KKK}alx |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/[QR]\n/KKK^ |
]])
end)
it("inccommand=split, contraction of two subsequent NL chars", function()
-- luacheck: push ignore 611
local text = [[
AAA AA
BBB BB
CCC CC
]]
-- luacheck: pop
-- 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 |
|
{11:[No Name] [+] }|
| 1| AAA AA |
| 2|{12: }BBB BB |
| 3|{12: }CCC CC |
| 4|{12: }AAA AA |
| 5|{12: }BBB BB |
| 6|{12: }CCC CC |
| 7|{12: }AAA AA |
{10:[Preview] }|
:%s/\n\n/{17:^M}/g^ |
]]}
assert_alive()
end)
it("inccommand=nosplit, contraction of two subsequent NL chars", function()
-- luacheck: push ignore 611
local text = [[
AAA AA
BBB BB
CCC CC
]]
-- luacheck: pop
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/{17:^M}/g^ |
]]}
assert_alive()
end)
it("inccommand=split, multibyte text", function()
common_setup(screen, "split", multibyte_text)
feed(":%s/£.*ѫ/X¥¥")
screen:expect([[
a{12:X¥¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{15:~ }|
{11:[No Name] [+] }|
|1| {12:X¥¥} PEPPERS |
|2| {12:X¥¥} |
|3| a{12:X¥¥}¥KOL |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/£.*ѫ/X¥¥^ |
]])
feed("\\ra££ ¥")
screen:expect([[
a{12:X¥¥} |
{12:a££ ¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{11:[No Name] [+] }|
|1| {12:X¥¥} |
|2|{12: a££ ¥} PEPPERS |
|3| {12:X¥¥} |
|4|{12: a££ ¥} |
|5| a{12:X¥¥} |
|6|{12: a££ ¥}¥KOL |
{15:~ }|
{10:[Preview] }|
:%s/£.*ѫ/X¥¥\ra££ ¥^ |
]])
end)
it("inccommand=nosplit, multibyte text", function()
common_setup(screen, "nosplit", multibyte_text)
feed(":%s/£.*ѫ/X¥¥")
screen:expect([[
{12:X¥¥} PEPPERS |
{12:X¥¥} |
a{12:X¥¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%s/£.*ѫ/X¥¥^ |
]])
feed("\\ra££ ¥")
screen:expect([[
{12:X¥¥} |
{12:a££ ¥} PEPPERS |
{12:X¥¥} |
{12:a££ ¥} |
a{12:X¥¥} |
{12:a££ ¥}¥KOL |
£ ¥ libm |
£ ¥ |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:%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 |
{12:a} b c |
{12:d} e f |
{12:q} r s |
{12:x} y z |
£ {12:m} n |
{12:t} œ ¥ |
|
{11:[No Name] [+] }|
| 7| {12:a} b c |
| 8| {12:d} e f |
{10:[Preview] }|
:%s/[a-z]^ |
]])
feed("/JLKR £")
screen:expect([[
X Y Z |
7 8 9 |
K L M |
{12:JLKR £} b c |
{12:JLKR £} e f |
{12:JLKR £} r s |
{12:JLKR £} y z |
£ {12:JLKR £} n |
{12:JLKR £} œ ¥ |
|
{11:[No Name] [+] }|
| 7| {12:JLKR £} b c |
| 8| {12:JLKR £} e f |
{10:[Preview] }|
:%s/[a-z]/JLKR £^ |
]])
feed("\\rѫ ab \\rXXXX")
screen:expect([[
7 8 9 |
K L M |
{12:JLKR £} |
{12:ѫ ab } |
{12:XXXX} b c |
{12:JLKR £} |
{12:ѫ ab } |
{12:XXXX} e f |
{12:JLKR £} |
{12:ѫ ab } |
{11:[No Name] [+] }|
| 7| {12:JLKR £} |
{11: }|
:%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 {12:œ ¥} |
{11:[No Name] [+] }|
| 1| 1 {12:2 3} |
| 2| A {12:B C} |
| 3| 4 {12:5 6} |
| 4| X {12:Y Z} |
| 5| 7 {12:8 9} |
| 6| K {12:L M} |
| 7| a {12:b c} |
| 8| d {12:e f} |
| 9| q {12:r s} |
|10| x {12:y z} |
|11| £ {12:m n} |
{10:[Preview] }|
:%s/. .$^ |
]])
feed("/ YYY")
screen:expect([[
t {12: YYY} |
{11:[No Name] [+] }|
| 1| 1 {12: YYY} |
| 2| A {12: YYY} |
| 3| 4 {12: YYY} |
| 4| X {12: YYY} |
| 5| 7 {12: YYY} |
| 6| K {12: YYY} |
| 7| a {12: YYY} |
| 8| d {12: YYY} |
| 9| q {12: YYY} |
|10| x {12: YYY} |
|11| £ {12: YYY} |
{10:[Preview] }|
:%s/. .$/ YYY^ |
]])
feed("\\r KKK")
screen:expect([[
a {12: YYY} |
{11:[No Name] [+] }|
| 1| 1 {12: YYY} |
| 2|{12: KKK} |
| 3| A {12: YYY} |
| 4|{12: KKK} |
| 5| 4 {12: YYY} |
| 6|{12: KKK} |
| 7| X {12: YYY} |
| 8|{12: KKK} |
| 9| 7 {12: YYY} |
|10|{12: KKK} |
|11| K {12: YYY} |
{10:[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{12:one} |
everything |
someone |
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| some{12:one} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/\(some\)\@<=thing/one/^ |
]])
feed("<C-c>")
feed('gg')
poke_eventloop()
feed([[:%s/\(some\)\@<lt>!thing/one/]])
screen:expect([[
something |
every{12:one} |
someone |
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|2| every{12:one} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/\(some\)\@<!thing/one/^ |
]])
feed([[<C-c>]])
poke_eventloop()
feed([[:%s/some\(thing\)\@=/every/]])
screen:expect([[
{12:every}thing |
everything |
someone |
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| {12:every}thing |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/some\(thing\)\@=/every/^ |
]])
feed([[<C-c>]])
poke_eventloop()
feed([[:%s/some\(thing\)\@!/every/]])
screen:expect([[
something |
everything |
{12:every}one |
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|3| {12:every}one |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:%s/some\(thing\)\@!/every/^ |
]])
end)
it("doesn't prompt to swap cmd range", function()
screen = Screen.new(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([[
{12:MO}o lines |
{11:[No Name] [+] }|
|2| {12:MO}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
:2,1s/tw/MO/g^ |
]])
-- but should be prompted on hitting enter
feed('<CR>')
screen:expect([[
{12:MO}o lines |
{11:[No Name] [+] }|
|2| {12:MO}o lines |
{15:~ }|
{15:~ }|
{15:~ }|
{10:[Preview] }|
{13:Backwards range given, OK to swap (y/n)?}^ |
]])
feed('y')
screen:expect([[
Inc substitution on |
^MOo lines |
|
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
{13:Backwards range given, OK to swap (y/n)?}y |
]])
end)
end)
it(':substitute with inccommand during :terminal activity', function()
if helpers.skip_fragile(pending) then
return
end
retry(2, 40000, function()
local screen = Screen.new(30,15)
clear()
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()
local screen = Screen.new(30,12)
clear()
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([[
{12:ZZZ} bar baz |
bar baz fox |
bar {12:ZZZ} baz |
{15:~ }|
{15:~ }|
{15:~ }|
{11:[No Name] [+] }|
|1| {12:ZZZ} bar baz |
|3| bar {12:ZZZ} baz |
{15:~ }|
{10:[Preview] }|
:%s/foo/ZZZ^ |
]])
end)
it(':substitute with inccommand, allows :redraw before first separator is typed #18857', function()
local screen = Screen.new(30,6)
clear()
common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz')
command('hi! link NormalFloat CursorLine')
local float_buf = meths.create_buf(false, true)
meths.open_win(float_buf, false, {
relative = 'editor', height = 1, width = 5, row = 3, col = 0, focusable = false,
})
feed(':%s')
screen:expect([[
foo bar baz |
bar baz fox |
bar foo baz |
{16: }{15: }|
{15:~ }|
:%s^ |
]])
meths.buf_set_lines(float_buf, 0, -1, true, {'foo'})
command('redraw')
screen:expect([[
foo bar baz |
bar baz fox |
bar foo baz |
{16:foo }{15: }|
{15:~ }|
:%s^ |
]])
end)
it(':substitute with inccommand, does not crash if range contains invalid marks', function()
local screen = Screen.new(30, 6)
clear()
common_setup(screen, 'split', 'test')
feed([[:'a,'bs]])
screen:expect([[
test |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:'a,'bs^ |
]])
-- v:errmsg shouldn't be set either before the first separator is typed
eq('', eval('v:errmsg'))
feed('/')
screen:expect([[
test |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:'a,'bs/^ |
]])
end)
it(':substitute with inccommand, no unnecessary redraw if preview is not shown', function()
local screen = Screen.new(60, 6)
clear()
common_setup(screen, 'split', 'test')
feed(':ls<CR>')
screen:expect([[
test |
{15:~ }|
{11: }|
:ls |
1 %a + "[No Name]" line 1 |
{13:Press ENTER or type command to continue}^ |
]])
feed(':s')
-- no unnecessary redraw, so messages are still shown
screen:expect([[
test |
{15:~ }|
{11: }|
:ls |
1 %a + "[No Name]" line 1 |
:s^ |
]])
feed('o')
screen:expect([[
test |
{15:~ }|
{11: }|
:ls |
1 %a + "[No Name]" line 1 |
:so^ |
]])
feed('<BS>')
screen:expect([[
test |
{15:~ }|
{11: }|
:ls |
1 %a + "[No Name]" line 1 |
:s^ |
]])
feed('/test')
-- now inccommand is shown, so screen is redrawn
screen:expect([[
{12:test} |
{15:~ }|
{15:~ }|
{15:~ }|
{15:~ }|
:s/test^ |
]])
end)
it(":substitute doesn't crash with inccommand, if undo is empty #12932", function()
local screen = Screen.new(10,5)
clear()
command('set undolevels=-1')
common_setup(screen, 'split', 'test')
feed(':%s/test')
sleep(100)
feed('/')
sleep(100)
feed('f')
screen:expect([[
{12:f} |
{15:~ }|
{15:~ }|
{15:~ }|
:%s/test/f^ |
]])
assert_alive()
end)
it(':substitute with inccommand works properly if undo is not synced #20029', function()
local screen = Screen.new(30, 6)
clear()
common_setup(screen, 'nosplit', 'foo\nbar\nbaz')
meths.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 {12:hjkl} |
foo |
bar |
baz |
bbbbb {12:hjkl} |
:'<,'>s/asdf/hjkl^ |
]])
feed('<CR>')
expect([[
aaaaa hjkl
foo
bar
baz
bbbbb hjkl]])
feed('u')
expect([[
foo
bar
baz]])
end)
it('long :%s/ with inccommand does not collapse cmdline', function()
local screen = Screen.new(10,5)
clear()
common_setup(screen)
command('set inccommand=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([[
|
{11: }|
:%s/AAAAAAAA|
AAAAAAAAAAAA|
AAAAAAA^ |
]])
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)