123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- local t = require('test.testutil')
- local n = require('test.functional.testnvim')()
- local Screen = require('test.functional.ui.screen')
- local api = n.api
- local clear = n.clear
- local eq = t.eq
- local exec_lua = n.exec_lua
- local insert = n.insert
- local feed = n.feed
- local command = n.command
- local assert_alive = n.assert_alive
- -- Implements a :Replace command that works like :substitute and has multibuffer support.
- local setup_replace_cmd = [[
- local function show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
- -- Find the width taken by the largest line number, used for padding the line numbers
- local highest_lnum = math.max(matches[#matches][1], 1)
- local highest_lnum_width = math.floor(math.log10(highest_lnum))
- local preview_buf_line = 0
- local multibuffer = #matches > 1
- for _, match in ipairs(matches) do
- local buf = match[1]
- local buf_matches = match[2]
- if multibuffer and #buf_matches > 0 and use_preview_win then
- local bufname = vim.api.nvim_buf_get_name(buf)
- if bufname == "" then
- bufname = string.format("Buffer #%d", buf)
- end
- vim.api.nvim_buf_set_lines(
- preview_buf,
- preview_buf_line,
- preview_buf_line,
- 0,
- { bufname .. ':' }
- )
- preview_buf_line = preview_buf_line + 1
- end
- for _, buf_match in ipairs(buf_matches) do
- local lnum = buf_match[1]
- local line_matches = buf_match[2]
- local prefix
- if use_preview_win then
- prefix = string.format(
- '|%s%d| ',
- string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
- lnum
- )
- vim.api.nvim_buf_set_lines(
- preview_buf,
- preview_buf_line,
- preview_buf_line,
- 0,
- { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
- )
- end
- for _, line_match in ipairs(line_matches) do
- vim.api.nvim_buf_add_highlight(
- buf,
- preview_ns,
- 'Substitute',
- lnum - 1,
- line_match[1],
- line_match[2]
- )
- if use_preview_win then
- vim.api.nvim_buf_add_highlight(
- preview_buf,
- preview_ns,
- 'Substitute',
- preview_buf_line,
- #prefix + line_match[1],
- #prefix + line_match[2]
- )
- end
- end
- preview_buf_line = preview_buf_line + 1
- end
- end
- if use_preview_win then
- return 2
- else
- return 1
- end
- end
- local function do_replace(opts, preview, preview_ns, preview_buf)
- local pat1 = opts.fargs[1]
- if not pat1 then return end
- local pat2 = opts.fargs[2] or ''
- local line1 = opts.line1
- local line2 = opts.line2
- local matches = {}
- -- Get list of valid and listed buffers
- local buffers = vim.tbl_filter(
- function(buf)
- if not (vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buflisted and buf ~= preview_buf)
- then
- return false
- end
- -- Check if there's at least one window using the buffer
- for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
- if vim.api.nvim_win_get_buf(win) == buf then
- return true
- end
- end
- return false
- end,
- vim.api.nvim_list_bufs()
- )
- for _, buf in ipairs(buffers) do
- local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, false)
- local buf_matches = {}
- for i, line in ipairs(lines) do
- local startidx, endidx = 0, 0
- local line_matches = {}
- local num = 1
- while startidx ~= -1 do
- local match = vim.fn.matchstrpos(line, pat1, 0, num)
- startidx, endidx = match[2], match[3]
- if startidx ~= -1 then
- line_matches[#line_matches+1] = { startidx, endidx }
- end
- num = num + 1
- end
- if #line_matches > 0 then
- buf_matches[#buf_matches+1] = { line1 + i - 1, line_matches }
- end
- end
- local new_lines = {}
- for _, buf_match in ipairs(buf_matches) do
- local lnum = buf_match[1]
- local line_matches = buf_match[2]
- local line = lines[lnum - line1 + 1]
- local pat_width_differences = {}
- -- If previewing, only replace the text in current buffer if pat2 isn't empty
- -- Otherwise, always replace the text
- if pat2 ~= '' or not preview then
- if preview then
- for _, line_match in ipairs(line_matches) do
- local startidx, endidx = unpack(line_match)
- local pat_match = line:sub(startidx + 1, endidx)
- pat_width_differences[#pat_width_differences+1] =
- #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
- end
- end
- new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
- end
- -- Highlight the matches if previewing
- if preview then
- local idx_offset = 0
- for i, line_match in ipairs(line_matches) do
- local startidx, endidx = unpack(line_match)
- -- Starting index of replacement text
- local repl_startidx = startidx + idx_offset
- -- Ending index of the replacement text (if pat2 isn't empty)
- local repl_endidx
- if pat2 ~= '' then
- repl_endidx = endidx + idx_offset + pat_width_differences[i]
- else
- repl_endidx = endidx + idx_offset
- end
- if pat2 ~= '' then
- idx_offset = idx_offset + pat_width_differences[i]
- end
- line_matches[i] = { repl_startidx, repl_endidx }
- end
- end
- end
- for lnum, line in pairs(new_lines) do
- vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
- end
- matches[#matches+1] = { buf, buf_matches }
- end
- if preview then
- local lnum = vim.api.nvim_win_get_cursor(0)[1]
- -- Use preview window only if preview buffer is provided and range isn't just the current line
- local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum)
- return show_replace_preview(use_preview_win, preview_ns, preview_buf, matches)
- end
- end
- local function replace(opts)
- do_replace(opts, false)
- end
- local function replace_preview(opts, preview_ns, preview_buf)
- return do_replace(opts, true, preview_ns, preview_buf)
- end
- -- ":<range>Replace <pat1> <pat2>"
- -- Replaces all occurrences of <pat1> in <range> with <pat2>
- vim.api.nvim_create_user_command(
- 'Replace',
- replace,
- { nargs = '*', range = '%', addr = 'lines',
- preview = replace_preview }
- )
- ]]
- describe("'inccommand' for user commands", function()
- local screen
- before_each(function()
- clear()
- screen = Screen.new(40, 17)
- exec_lua(setup_replace_cmd)
- command('set cmdwinheight=5')
- insert [[
- text on line 1
- more text on line 2
- oh no, even more text
- will the text ever stop
- oh well
- did the text stop
- why won't it stop
- make the text stop
- ]]
- end)
- it("can preview 'nomodifiable' buffer", function()
- exec_lua([[
- vim.api.nvim_create_user_command("PreviewTest", function() end, {
- preview = function(ev)
- vim.bo.modifiable = true
- vim.api.nvim_buf_set_lines(0, 0, -1, false, {"cats"})
- return 2
- end,
- })
- ]])
- command('set inccommand=split')
- command('set nomodifiable')
- eq(false, api.nvim_get_option_value('modifiable', { buf = 0 }))
- feed(':PreviewTest')
- screen:expect([[
- cats |
- {1:~ }|*8
- {3:[No Name] [+] }|
- |
- {1:~ }|*4
- {2:[Preview] }|
- :PreviewTest^ |
- ]])
- feed('<Esc>')
- screen:expect([[
- text on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- ^ |
- {1:~ }|*7
- |
- ]])
- eq(false, api.nvim_get_option_value('modifiable', { buf = 0 }))
- end)
- it('works with inccommand=nosplit', function()
- command('set inccommand=nosplit')
- feed(':Replace text cats')
- screen:expect([[
- {10:cats} on line 1 |
- more {10:cats} on line 2 |
- oh no, even more {10:cats} |
- will the {10:cats} ever stop |
- oh well |
- did the {10:cats} stop |
- why won't it stop |
- make the {10:cats} stop |
- |
- {1:~ }|*7
- :Replace text cats^ |
- ]])
- end)
- it('works with inccommand=split', function()
- command('set inccommand=split')
- feed(':Replace text cats')
- screen:expect([[
- {10:cats} on line 1 |
- more {10:cats} on line 2 |
- oh no, even more {10:cats} |
- will the {10:cats} ever stop |
- oh well |
- did the {10:cats} stop |
- why won't it stop |
- make the {10:cats} stop |
- |
- {3:[No Name] [+] }|
- |1| {10:cats} on line 1 |
- |2| more {10:cats} on line 2 |
- |3| oh no, even more {10:cats} |
- |4| will the {10:cats} ever stop |
- |6| did the {10:cats} stop |
- {2:[Preview] }|
- :Replace text cats^ |
- ]])
- end)
- it('properly closes preview when inccommand=split', function()
- command('set inccommand=split')
- feed(':Replace text cats<Esc>')
- screen:expect([[
- text on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- ^ |
- {1:~ }|*7
- |
- ]])
- end)
- it('properly executes command when inccommand=split', function()
- command('set inccommand=split')
- feed(':Replace text cats<CR>')
- screen:expect([[
- cats on line 1 |
- more cats on line 2 |
- oh no, even more cats |
- will the cats ever stop |
- oh well |
- did the cats stop |
- why won't it stop |
- make the cats stop |
- ^ |
- {1:~ }|*7
- :Replace text cats |
- ]])
- end)
- it('shows preview window only when range is not current line', function()
- command('set inccommand=split')
- feed('gg:.Replace text cats')
- screen:expect([[
- {10:cats} on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- |
- {1:~ }|*7
- :.Replace text cats^ |
- ]])
- end)
- it('does not crash on ambiguous command #18825', function()
- command('set inccommand=split')
- command('command Reply echo 1')
- feed(':R')
- assert_alive()
- feed('e')
- assert_alive()
- end)
- it('no crash if preview callback changes inccommand option', function()
- command('set inccommand=nosplit')
- exec_lua([[
- vim.api.nvim_create_user_command('Replace', function() end, {
- nargs = '*',
- preview = function()
- vim.api.nvim_set_option_value('inccommand', 'split', {})
- return 2
- end,
- })
- ]])
- feed(':R')
- assert_alive()
- feed('e')
- assert_alive()
- end)
- it('no crash when adding highlight after :substitute #21495', function()
- command('set inccommand=nosplit')
- exec_lua([[
- vim.api.nvim_create_user_command("Crash", function() end, {
- preview = function(_, preview_ns, _)
- vim.cmd("%s/text/cats/g")
- vim.api.nvim_buf_add_highlight(0, preview_ns, "Search", 0, 0, -1)
- return 1
- end,
- })
- ]])
- feed(':C')
- screen:expect([[
- {10: cats on line 1} |
- more cats on line 2 |
- oh no, even more cats |
- will the cats ever stop |
- oh well |
- did the cats stop |
- why won't it stop |
- make the cats stop |
- |
- {1:~ }|*7
- :C^ |
- ]])
- assert_alive()
- end)
- it('no crash if preview callback executes undo #20036', function()
- command('set inccommand=nosplit')
- exec_lua([[
- vim.api.nvim_create_user_command('Foo', function() end, {
- nargs = '?',
- preview = function(_, _, _)
- vim.cmd.undo()
- end,
- })
- ]])
- -- Clear undo history
- command('set undolevels=-1')
- feed('ggyyp')
- command('set undolevels=1000')
- feed('yypp:Fo')
- assert_alive()
- feed('<Esc>:Fo')
- assert_alive()
- end)
- local function test_preview_break_undo()
- command('set inccommand=nosplit')
- exec_lua([[
- vim.api.nvim_create_user_command('Test', function() end, {
- nargs = 1,
- preview = function(opts, _, _)
- vim.cmd('norm i' .. opts.args)
- return 1
- end
- })
- ]])
- feed(':Test a.a.a.a.')
- screen:expect([[
- text on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- a.a.a.a. |
- {1:~ }|*7
- :Test a.a.a.a.^ |
- ]])
- feed('<C-V><Esc>u')
- screen:expect([[
- text on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- a.a.a. |
- {1:~ }|*7
- :Test a.a.a.a.{18:^[}u^ |
- ]])
- feed('<Esc>')
- screen:expect([[
- text on line 1 |
- more text on line 2 |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- ^ |
- {1:~ }|*7
- |
- ]])
- end
- describe('breaking undo chain in Insert mode works properly', function()
- it('when using i_CTRL-G_u #20248', function()
- command('inoremap . .<C-G>u')
- test_preview_break_undo()
- end)
- it('when setting &l:undolevels to itself #24575', function()
- command('inoremap . .<Cmd>let &l:undolevels = &l:undolevels<CR>')
- test_preview_break_undo()
- end)
- end)
- it('disables preview if preview buffer cannot be created #27086', function()
- command('set inccommand=split')
- api.nvim_buf_set_name(0, '[Preview]')
- exec_lua([[
- vim.api.nvim_create_user_command('Test', function() end, {
- nargs = '*',
- preview = function(_, _, _)
- return 2
- end
- })
- ]])
- eq('split', api.nvim_get_option_value('inccommand', {}))
- feed(':Test')
- eq('nosplit', api.nvim_get_option_value('inccommand', {}))
- end)
- it('does not flush intermediate cursor position at end of message grid', function()
- exec_lua([[
- vim.api.nvim_create_user_command('Test', function() end, {
- nargs = '*',
- preview = function(_, _, _)
- vim.api.nvim_buf_set_text(0, 0, 0, 1, -1, { "Preview" })
- vim.cmd.sleep("1m")
- return 1
- end
- })
- ]])
- local cursor_goto = screen._handle_grid_cursor_goto
- screen._handle_grid_cursor_goto = function(...)
- cursor_goto(...)
- assert(screen._cursor.col < 12)
- end
- feed(':Test baz<Left><Left>arb')
- screen:expect({
- grid = [[
- Preview |
- oh no, even more text |
- will the text ever stop |
- oh well |
- did the text stop |
- why won't it stop |
- make the text stop |
- |
- {1:~ }|*8
- :Test barb^az |
- ]],
- })
- end)
- end)
- describe("'inccommand' with multiple buffers", function()
- local screen
- before_each(function()
- clear()
- screen = Screen.new(40, 17)
- exec_lua(setup_replace_cmd)
- command('set cmdwinheight=10')
- insert [[
- foo bar baz
- bar baz foo
- baz foo bar
- ]]
- command('vsplit | enew')
- insert [[
- bar baz foo
- baz foo bar
- foo bar baz
- ]]
- end)
- it('works', function()
- command('set inccommand=nosplit')
- feed(':Replace foo bar')
- screen:expect([[
- bar baz {10:bar} │ {10:bar} bar baz |
- baz {10:bar} bar │ bar baz {10:bar} |
- {10:bar} bar baz │ baz {10:bar} bar |
- │ |
- {1:~ }│{1:~ }|*11
- {3:[No Name] [+] }{2:[No Name] [+] }|
- :Replace foo bar^ |
- ]])
- feed('<CR>')
- screen:expect([[
- bar baz bar │ bar bar baz |
- baz bar bar │ bar baz bar |
- bar bar baz │ baz bar bar |
- ^ │ |
- {1:~ }│{1:~ }|*11
- {3:[No Name] [+] }{2:[No Name] [+] }|
- :Replace foo bar |
- ]])
- end)
- it('works with inccommand=split', function()
- command('set inccommand=split')
- feed(':Replace foo bar')
- screen:expect([[
- bar baz {10:bar} │ {10:bar} bar baz |
- baz {10:bar} bar │ bar baz {10:bar} |
- {10:bar} bar baz │ baz {10:bar} bar |
- │ |
- {3:[No Name] [+] }{2:[No Name] [+] }|
- Buffer #1: |
- |1| {10:bar} bar baz |
- |2| bar baz {10:bar} |
- |3| baz {10:bar} bar |
- Buffer #2: |
- |1| bar baz {10:bar} |
- |2| baz {10:bar} bar |
- |3| {10:bar} bar baz |
- |
- {1:~ }|
- {2:[Preview] }|
- :Replace foo bar^ |
- ]])
- feed('<CR>')
- screen:expect([[
- bar baz bar │ bar bar baz |
- baz bar bar │ bar baz bar |
- bar bar baz │ baz bar bar |
- ^ │ |
- {1:~ }│{1:~ }|*11
- {3:[No Name] [+] }{2:[No Name] [+] }|
- :Replace foo bar |
- ]])
- end)
- end)
|