diff options
Diffstat (limited to 'test/functional/ui/inccommand_spec.lua')
-rw-r--r-- | test/functional/ui/inccommand_spec.lua | 1398 |
1 files changed, 1398 insertions, 0 deletions
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua new file mode 100644 index 0000000000..f2282e3fb8 --- /dev/null +++ b/test/functional/ui/inccommand_spec.lua @@ -0,0 +1,1398 @@ +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) |