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\")]]) -- or interactively... feed([[:call feedkeys(":%s/bs/BUU/g\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/") 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([[]]) -- 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]]) feed([[osome text 2]]) -- Add an undo branch. feed([[u]]) -- More changes, more undo branches. feed([[osome text 3]]) feed([[AX]]) feed([[...]]) feed([[uu]]) feed([[osome text 4]]) feed([[uu]]) feed([[osome text 5]]) 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([[]]) -- 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]]) feed([[osome text 2]]) 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') expect([[ SUB substitution on two lines ]]) feed(':%s/line/') poke_eventloop() feed('~') poke_eventloop() feed('') expect([[ SUB substitution on two SUBs ]]) feed(':%s/sti/') poke_eventloop() feed('~') poke_eventloop() feed('B') poke_eventloop() feed('') expect([[ SUB subSUBBtution on two SUBs ]]) feed(':%s/ion/NEW') expect([[ SUB subSUBBtutNEW on two SUBs ]]) feed(':%s/two/') poke_eventloop() feed('N') poke_eventloop() feed('~') poke_eventloop() feed('') expect([[ SUB subSUBBtutNEW on NNEW SUBs ]]) feed(':%s/bS/') poke_eventloop() feed('~') poke_eventloop() feed('W') poke_eventloop() feed('') 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/", ":%s/1/a", ":%s/1/a", ":%s/1/ax", ":%s/1/ax", ":%s/1/ax", ":%s/1/ax", ":%s/1/ax/", ":%s/1/ax/", ":%s/1/ax//", ":%s/1/ax/g", ":%s/1/ax/g", ":%s/1/ax/g" } local function test_sub(substring, split, redoable) command('bwipe!') feed_command("set inccommand=" .. split) insert("1") feed("o2") feed_command("undo") feed("o3") if redoable then feed("o4") feed_command("undo") end feed(substring.. "") 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") feed_command("undo") feed("o3") if redoable then feed("o4") feed_command("undo") end feed(substring .. "") feed("g-") expect([[ 1 2]]) feed("g+") expect([[ 1 3]]) if redoable then feed("") expect([[ 1 3 4]]) end end local function test_threetree(substring, split) command('bwipe!') feed_command("set inccommand=" .. split) insert("1") feed("o2") feed("o3") feed("uu") feed("oa") feed("ob") feed("uu") feed("oA") feed("oB") -- 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 .. "") expect([[ 1]]) feed("g-") expect([[ ]]) feed("g+") expect([[ 1]]) feed("") expect([[ 1 A]]) feed("g-") -- go to b feed("2u") feed(substring .. "") feed("") expect([[ 1 a]]) feed("g-") -- go to 3 feed("2u") feed(substring .. "") feed("") 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/") 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") feed(":%s/tw/MO/") -- 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") feed(":%s/MO/GO/g") feed(":%s/GO/NO/g") 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") feed("Ay") feed("Az") feed(":%s/tw/AR") -- 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") feed(":%s/MO/GO/g") feed(":%s/GO/NO/g") feed(":%s/NO/LO/g") 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") -- 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") feed(":%s/tw/MO/g") 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([[]]) -- 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('') screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') screen:expect{any=[[{12:to}o lines]]} feed('') 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('') -- leading colons feed(':::%s/tw/to') screen:expect{any=[[{12:to}o lines]]} feed('') 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('') 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('') 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("") 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') 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") 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("") poke_eventloop() feed("g") poke_eventloop() feed("") poke_eventloop() feed("g") poke_eventloop() feed("") poke_eventloop() feed(":vs tmp") eq(3, helpers.call('bufnr', '$')) end) it('works with the n flag', function() feed(":%s/tw/Mix/n") 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;;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([[]]) 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([[]]) 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("") -- 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([[='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('') -- 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([[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('') -- 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([[]]) -- 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('') screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') screen:expect{any=[[{12:to}o lines]]} feed('') 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("") command("set icm=split") feed(":%s/tw/OKOK") feed("") 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("") 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("") -- 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/") 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 .. "/") 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('') 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/") feed(":%s/lines/,Sor three ") expect([[ Inc substitution on two or three LINES ]]) feed_command("cnoremap ;S /X/") feed(":%s/;SI") expect([[ Xnc substitution on two or three LXNES ]]) feed_command("cnoremap ,T //Y/") feed(":%s,TX") expect([[ Ync substitution on two or three LYNES ]]) feed_command("cnoremap ;T s//Z/") feed(":%;TY") 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 x execute('bwipeout!')[-1].'x'") feed(":%s/tw/tox") screen:expect{any=[[{14:^E565:]]} feed('') -- 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 x cursor(1, 1)[-1].'x'") feed(":%s/tw/tox/g") 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 x execute('set inccommand=')[-1]") feed(":%s/tw/toxa/g") 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 eextend(g:, {'fo': getcmdline()}) \.fo:new:bw!:=remove(g:, 'fo')x]]) feed(":%s/tw/tox") feed("/") 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(''))") 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("/") 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("") 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") 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") 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("") 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("") 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//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//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\)\@=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("") feed('gg') poke_eventloop() feed([[:%s/\(some\)\@!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\)\@]]) 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([[]]) 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('') 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]]):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. helpers.poke_eventloop() 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') 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('') 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', '', '``>obbbbb asdfV`lljj') 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('') 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)