Files
neovim/test/functional/ui/inccommand_spec.lua
Justin M. Keyes aa0e09d251 'inccommand': Preserve curbuf->b_u_newhead.
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.
2016-11-09 01:03:47 +01:00

1399 lines
40 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)