mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 03:48:18 +00:00

Add tests for undotree(). Helped-by: Björn Linse <bjorn.linse@gmail.com> When "curhead" points to a valid head, the value of "newhead" is meaningless (and really should be set to null). The undo state for a buffer is _logically_ the enum: enum UndoState { CurrentHead(head), NewHead(head), EmptyTree } nvim _represents_ this as: whenever `curbuf->b_u_curhead` is nonnull it should be used as the current head, and `curbuf->b_u_newhead` is ignored. If the there is a current head, then this will be redoed on the next redo, and its parent will be undone on next undo. Only if `b_u_curhead` is NULL, `b_u_newhead` will be used as the head to undo (and it is not possible to redo). Also both can be NULL, to indicate an empty undotree. (To be fair, this only strictly true when calling undo.c from the outside, in some places _within_ a function in undo.c both values might be meaningful) Apparently `undotree()` breaks this non-abstraction, this _cosmetic_ issue can easily be fixed by `ex_substitute` also saving and restoring `b_u_newhead`, but is doesn't reflect any error really how `u_undo_and_forget` manipulates the _actual_ state of the undo tree.
1399 lines
40 KiB
Lua
1399 lines
40 KiB
Lua
local helpers = require('test.functional.helpers')(after_each)
|
||
local Screen = require('test.functional.ui.screen')
|
||
local clear = helpers.clear
|
||
local curbufmeths = helpers.curbufmeths
|
||
local eq = helpers.eq
|
||
local eval = helpers.eval
|
||
local execute = helpers.execute
|
||
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 source = helpers.source
|
||
local wait = helpers.wait
|
||
|
||
local default_text = [[
|
||
Inc substitution on
|
||
two lines
|
||
]]
|
||
|
||
local function common_setup(screen, inccommand, text)
|
||
if screen then
|
||
execute("syntax on")
|
||
execute("set nohlsearch")
|
||
execute("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
|
||
})
|
||
end
|
||
|
||
execute("set inccommand=" .. (inccommand and inccommand or ""))
|
||
|
||
if text then
|
||
insert(text)
|
||
end
|
||
end
|
||
|
||
describe(":substitute, inccommand=split does not trigger preview", function()
|
||
before_each(function()
|
||
clear()
|
||
common_setup(nil, "split", default_text)
|
||
end)
|
||
|
||
it("if invoked by a script ", function()
|
||
source('%s/tw/MO/g')
|
||
wait()
|
||
eq(1, eval("bufnr('$')"))
|
||
|
||
-- sanity check: assert the buffer state
|
||
expect(default_text:gsub("tw", "MO"))
|
||
end)
|
||
|
||
it("if invoked by feedkeys()", function()
|
||
-- in a script...
|
||
source([[:call feedkeys(":%s/tw/MO/g\<CR>")]])
|
||
wait()
|
||
-- or interactively...
|
||
feed([[:call feedkeys(":%s/tw/MO/g\<CR>")<CR>]])
|
||
wait()
|
||
eq(1, eval("bufnr('$')"))
|
||
|
||
-- sanity check: assert the buffer state
|
||
expect(default_text:gsub("tw", "MO"))
|
||
end)
|
||
end)
|
||
|
||
describe(":substitute, 'inccommand' preserves", function()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
before_each(clear)
|
||
|
||
it('listed buffers (:ls)', function()
|
||
local screen = Screen.new(30,10)
|
||
common_setup(screen, "split", "ABC")
|
||
|
||
execute("%s/AB/BA/")
|
||
execute("ls")
|
||
|
||
screen:expect([[
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
: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)
|
||
execute("set inccommand=" .. case)
|
||
|
||
local delims = { '/', '#', ';', '%', ',', '@', '!', ''}
|
||
for _,delim in pairs(delims) do
|
||
execute("%s"..delim.."lines"..delim.."LINES"..delim.."g")
|
||
expect([[
|
||
Inc substitution on
|
||
two LINES
|
||
]])
|
||
execute("undo")
|
||
end
|
||
end)
|
||
end
|
||
|
||
for _, case in pairs{"", "split", "nosplit"} do
|
||
it("'undolevels' (inccommand="..case..")", function()
|
||
execute("set undolevels=139")
|
||
execute("setlocal undolevels=34")
|
||
execute("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()
|
||
execute("set undolevels=1000")
|
||
execute("set inccommand=" .. case)
|
||
local expected_undotree = eval("undotree()")
|
||
|
||
-- Start typing an incomplete :substitute command.
|
||
feed([[:%s/e/YYYY/g]])
|
||
wait()
|
||
-- 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()
|
||
execute("set undolevels=1000")
|
||
execute("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]])
|
||
wait()
|
||
-- 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()
|
||
execute("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/")
|
||
wait()
|
||
|
||
eq(expected_tick, eval("b:changedtick"))
|
||
end)
|
||
end
|
||
|
||
end)
|
||
|
||
describe(":substitute, 'inccommand' preserves undo", function()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
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)
|
||
clear()
|
||
execute("set inccommand=" .. split)
|
||
|
||
insert("1")
|
||
feed("o2<esc>")
|
||
execute("undo")
|
||
feed("o3<esc>")
|
||
if redoable then
|
||
feed("o4<esc>")
|
||
execute("undo")
|
||
end
|
||
feed(substring.. "<enter>")
|
||
execute("undo")
|
||
|
||
feed("g-")
|
||
expect([[
|
||
1
|
||
2]])
|
||
|
||
feed("g+")
|
||
expect([[
|
||
1
|
||
3]])
|
||
end
|
||
|
||
local function test_notsub(substring, split, redoable)
|
||
clear()
|
||
execute("set inccommand=" .. split)
|
||
|
||
insert("1")
|
||
feed("o2<esc>")
|
||
execute("undo")
|
||
feed("o3<esc>")
|
||
if redoable then
|
||
feed("o4<esc>")
|
||
execute("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)
|
||
clear()
|
||
execute("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>")
|
||
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
|
||
|
||
-- TODO(vim): This does not work, even in Vim.
|
||
-- Waiting for fix (perhaps from upstream).
|
||
pending("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)
|
||
execute("set undolevels=0")
|
||
|
||
feed("1G0")
|
||
insert("X")
|
||
feed(":%s/tw/MO/<esc>")
|
||
execute("undo")
|
||
expect(default_text)
|
||
execute("undo")
|
||
expect(default_text:gsub("Inc", "XInc"))
|
||
execute("undo")
|
||
|
||
execute("%s/tw/MO/g")
|
||
expect(default_text:gsub("tw", "MO"))
|
||
execute("undo")
|
||
expect(default_text)
|
||
execute("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)
|
||
execute("set undolevels=1")
|
||
|
||
feed("1G0")
|
||
insert("X")
|
||
feed("IY<esc>")
|
||
feed(":%s/tw/MO/<esc>")
|
||
-- using execute("undo") here will result in a "Press ENTER" prompt
|
||
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([[
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
else
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
end
|
||
end
|
||
screen:detach()
|
||
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)
|
||
execute("set undolevels=2")
|
||
|
||
feed("2GAx<esc>")
|
||
feed("Ay<esc>")
|
||
feed("Az<esc>")
|
||
feed(":%s/tw/AR<esc>")
|
||
-- using execute("undo") here will result in a "Press ENTER" prompt
|
||
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([[
|
||
two line^s |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
else
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
two line^s |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st 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([[
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
else
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
end
|
||
screen:detach()
|
||
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)
|
||
|
||
execute("set undolevels=-1")
|
||
feed(":%s/tw/MO/g<enter>")
|
||
-- using execute("undo") here will result in a "Press ENTER" prompt
|
||
feed("u")
|
||
if case == "split" then
|
||
screen:expect([[
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
else
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
^MOo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
end
|
||
|
||
-- repeat with an interrupted substitution
|
||
clear()
|
||
common_setup(screen, case, default_text)
|
||
|
||
execute("set undolevels=-1")
|
||
feed("1G")
|
||
feed("IL<esc>")
|
||
feed(":%s/tw/MO/g<esc>")
|
||
feed("u")
|
||
|
||
if case == "split" then
|
||
screen:expect([[
|
||
^two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
elseif case == "" then
|
||
screen:expect([[
|
||
^LInc substitution on|
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
else
|
||
screen:expect([[
|
||
LInc substitution on|
|
||
^two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
Already...st change |
|
||
]])
|
||
end
|
||
end
|
||
screen:detach()
|
||
end)
|
||
|
||
end)
|
||
|
||
describe(":substitute, inccommand=split", function()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
local screen = Screen.new(30,15)
|
||
|
||
before_each(function()
|
||
clear()
|
||
common_setup(screen, "split", default_text .. default_text)
|
||
end)
|
||
|
||
after_each(function()
|
||
screen:detach()
|
||
end)
|
||
|
||
it("preserves 'modified' buffer flag", function()
|
||
execute("set nomodified")
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{11:[No Name] }|
|
||
|2| two lines |
|
||
|4| two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw^ |
|
||
]])
|
||
feed([[<C-\><C-N>]]) -- Cancel the :substitute command.
|
||
eq(0, eval("&modified"))
|
||
end)
|
||
|
||
it('shows split window when typing the pattern', function()
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| two lines |
|
||
|4| two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw^ |
|
||
]])
|
||
end)
|
||
|
||
it('shows split window with empty replacement', function()
|
||
feed(":%s/tw/")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| o lines |
|
||
|4| o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw/^ |
|
||
]])
|
||
|
||
feed("x")
|
||
screen:expect([[
|
||
xo lines |
|
||
Inc substitution on |
|
||
xo lines |
|
||
|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| {12:x}o lines |
|
||
|4| {12:x}o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw/x^ |
|
||
]])
|
||
|
||
feed("<bs>")
|
||
screen:expect([[
|
||
o lines |
|
||
Inc substitution on |
|
||
o lines |
|
||
|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| o lines |
|
||
|4| o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw/^ |
|
||
]])
|
||
|
||
end)
|
||
|
||
it('shows split window when typing replacement', function()
|
||
feed(":%s/tw/XX")
|
||
screen:expect([[
|
||
XXo lines |
|
||
Inc substitution on |
|
||
XXo lines |
|
||
|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| {12:XX}o lines |
|
||
|4| {12:XX}o lines |
|
||
|
|
||
{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 |
|
||
two lines |
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
:s/tw^ |
|
||
]])
|
||
end)
|
||
|
||
it("'hlsearch' highlights the substitution, 'cursorline' does not", function()
|
||
execute("set hlsearch")
|
||
execute("set cursorline") -- Should NOT appear in the preview window.
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
{9:tw}{16:o lines }|
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| {9:tw}o lines |
|
||
|4| {9:tw}o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw^ |
|
||
]])
|
||
end)
|
||
|
||
it('highlights the replacement text correctly', function()
|
||
feed('ggO')
|
||
feed('M M M<esc>')
|
||
feed(':%s/M/123/g')
|
||
screen:expect([[
|
||
123 123 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:~ }|
|
||
{10:[Preview] }|
|
||
:%s/M/123/g^ |
|
||
]])
|
||
end)
|
||
|
||
it('actually replaces text', function()
|
||
feed(":%s/tw/XX/g<enter>")
|
||
|
||
screen:expect([[
|
||
XXo lines |
|
||
Inc substitution on |
|
||
^XXo lines |
|
||
|
|
||
{15:~ }|
|
||
{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")
|
||
execute("1,1000s/tw/BB/g")
|
||
|
||
feed(":%s/tw/X")
|
||
screen:expect([[
|
||
BBo lines |
|
||
Inc substitution on |
|
||
Xo lines |
|
||
Inc substitution on |
|
||
Xo 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>")
|
||
wait()
|
||
feed("g")
|
||
wait()
|
||
feed("<BS>")
|
||
wait()
|
||
feed("g")
|
||
wait()
|
||
feed("<CR>")
|
||
wait()
|
||
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([[
|
||
^two lines |
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
2 matches on 2 lines |
|
||
]])
|
||
end)
|
||
|
||
end)
|
||
|
||
describe(":substitute, inccommand=nosplit", function()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
local screen = Screen.new(20,10)
|
||
|
||
before_each(function()
|
||
clear()
|
||
common_setup(screen, "nosplit", default_text .. default_text)
|
||
end)
|
||
|
||
after_each(function()
|
||
if screen then screen:detach() end
|
||
end)
|
||
|
||
it('does not show a split window anytime', function()
|
||
execute("set hlsearch")
|
||
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
{9:tw}o lines |
|
||
Inc substitution on |
|
||
{9:tw}o lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
:%s/tw^ |
|
||
]])
|
||
|
||
feed("/BM")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
BMo lines |
|
||
Inc substitution on |
|
||
BMo lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
:%s/tw/BM^ |
|
||
]])
|
||
|
||
feed("/")
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
BMo lines |
|
||
Inc substitution on |
|
||
BMo 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)
|
||
|
||
end)
|
||
|
||
describe(":substitute, 'inccommand' with a failing expression", function()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
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)
|
||
execute("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
|
||
execute("set inccommand=" .. case)
|
||
feed(":silent! %s/tw/" .. repl .. "/<enter>")
|
||
expect(default_text:gsub("tw", ""))
|
||
execute("undo")
|
||
end
|
||
end
|
||
end)
|
||
|
||
end)
|
||
|
||
describe("'inccommand' and :cnoremap", function()
|
||
local cases = { "", "split", "nosplit" }
|
||
|
||
local function refresh(case)
|
||
clear()
|
||
common_setup(nil, case, default_text)
|
||
end
|
||
|
||
it('work with remapped characters', function()
|
||
for _, case in pairs(cases) do
|
||
refresh(case)
|
||
local command = "%s/lines/LINES/g"
|
||
|
||
for i = 1, string.len(command) do
|
||
local c = string.sub(command, i, i)
|
||
execute("cnoremap ".. c .. " " .. c)
|
||
end
|
||
|
||
execute(command)
|
||
expect([[
|
||
Inc substitution on
|
||
two LINES
|
||
]])
|
||
end
|
||
end)
|
||
|
||
it('work when mappings move the cursor', function()
|
||
for _, case in pairs(cases) do
|
||
refresh(case)
|
||
execute("cnoremap ,S LINES/<left><left><left><left><left><left>")
|
||
|
||
feed(":%s/lines/,Sor three <enter>")
|
||
expect([[
|
||
Inc substitution on
|
||
two or three LINES
|
||
]])
|
||
|
||
execute("cnoremap ;S /X/<left><left><left>")
|
||
feed(":%s/;SI<enter>")
|
||
expect([[
|
||
Xnc substitution on
|
||
two or three LXNES
|
||
]])
|
||
|
||
execute("cnoremap ,T //Y/<left><left><left>")
|
||
feed(":%s,TX<enter>")
|
||
expect([[
|
||
Ync substitution on
|
||
two or three LYNES
|
||
]])
|
||
|
||
execute("cnoremap ;T s//Z/<left><left><left>")
|
||
feed(":%;TY<enter>")
|
||
expect([[
|
||
Znc substitution on
|
||
two or three LZNES
|
||
]])
|
||
end
|
||
end)
|
||
|
||
it('does not work with a failing mapping', function()
|
||
for _, case in pairs(cases) do
|
||
refresh(case)
|
||
execute("cnoremap <expr> x execute('bwipeout!')[-1].'x'")
|
||
|
||
feed(":%s/tw/tox<enter>")
|
||
|
||
-- error thrown b/c of the mapping
|
||
neq(nil, eval('v:errmsg'):find('^E523:'))
|
||
expect(default_text)
|
||
end
|
||
end)
|
||
|
||
it('work when temporarily moving the cursor', function()
|
||
for _, case in pairs(cases) do
|
||
refresh(case)
|
||
execute("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)
|
||
execute("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", {})
|
||
execute("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()
|
||
if helpers.pending_win32(pending) then return end
|
||
|
||
local screen
|
||
local function refresh()
|
||
clear()
|
||
screen = Screen.new(40,30)
|
||
common_setup(screen, "split", default_text)
|
||
end
|
||
|
||
after_each(function()
|
||
screen:detach()
|
||
end)
|
||
|
||
it('work after more splits', function()
|
||
refresh()
|
||
|
||
execute("vsplit")
|
||
execute("split")
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
two lines {10:|}two lines |
|
||
{10:|} |
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{11:[No Name] [+] }{10:|}{15:~ }|
|
||
two lines {10:|}{15:~ }|
|
||
{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{10:[No Name] [+] [No Name] [+] }|
|
||
|2| two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw^ |
|
||
]])
|
||
|
||
feed("<esc>")
|
||
execute("only")
|
||
execute("split")
|
||
execute("vsplit")
|
||
|
||
feed(":%s/tw")
|
||
screen:expect([[
|
||
Inc substitution on {10:|}Inc substitution on|
|
||
two lines {10:|}two lines |
|
||
{10:|} |
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{15:~ }{10:|}{15:~ }|
|
||
{11:[No Name] [+] }{10:[No Name] [+] }|
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[No Name] [+] }|
|
||
|2| two lines |
|
||
|
|
||
{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()
|
||
execute("set " .. setting)
|
||
|
||
feed(":%s/tw")
|
||
|
||
screen:expect([[
|
||
Inc substitution on |
|
||
two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{11:[No Name] [+] }|
|
||
|2| two lines |
|
||
|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{15:~ }|
|
||
{10:[Preview] }|
|
||
:%s/tw^ |
|
||
]])
|
||
end
|
||
end)
|
||
|
||
end)
|