diff options
author | Famiu Haque <famiuhaque@protonmail.com> | 2022-04-20 17:02:18 +0600 |
---|---|---|
committer | Famiu Haque <famiuhaque@protonmail.com> | 2022-05-31 20:55:05 +0600 |
commit | 46536f53e82967dcac8d030ee3394cdb156f9603 (patch) | |
tree | 4444067831639a6a1eb1916ca9cd5002261932ec /test/functional/ui/inccommand_user_spec.lua | |
parent | e9803e1de6497ee21f77f45cf2670c2fe4e8ab22 (diff) | |
download | rneovim-46536f53e82967dcac8d030ee3394cdb156f9603.tar.gz rneovim-46536f53e82967dcac8d030ee3394cdb156f9603.tar.bz2 rneovim-46536f53e82967dcac8d030ee3394cdb156f9603.zip |
feat: add preview functionality to user commands
Adds a Lua-only `preview` flag to user commands which allows the command to be incrementally previewed like `:substitute` when 'inccommand' is set.
Diffstat (limited to 'test/functional/ui/inccommand_user_spec.lua')
-rw-r--r-- | test/functional/ui/inccommand_user_spec.lua | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua new file mode 100644 index 0000000000..e2cd82943e --- /dev/null +++ b/test/functional/ui/inccommand_user_spec.lua @@ -0,0 +1,329 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local insert = helpers.insert +local feed = helpers.feed +local command = helpers.command + +-- Implements a :Replace command that works like :substitute. +local setup_replace_cmd = [[ + local function show_replace_preview(buf, 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 + + vim.g.prevns = preview_ns + vim.g.prevbuf = preview_buf + + for _, match in ipairs(matches) do + local lnum = match[1] + local line_matches = 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 + + 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] or '' + local pat2 = opts.fargs[2] or '' + local line1 = opts.line1 + local line2 = opts.line2 + + local buf = vim.api.nvim_get_current_buf() + local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0) + local 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 + matches[#matches+1] = { line1 + i - 1, line_matches } + end + end + + local new_lines = {} + + for _, match in ipairs(matches) do + local lnum = match[1] + local line_matches = 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 + + 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(buf, 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 occurences 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) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Yellow1}, + [2] = {foreground = Screen.colors.Blue1, bold = true}, + [3] = {reverse = true}, + [4] = {reverse = true, bold = true} + }) + screen:attach() + 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('works with inccommand=nosplit', function() + command('set inccommand=nosplit') + feed(':Replace text cats') + screen:expect([[ + {1:cats} on line 1 | + more {1:cats} on line 2 | + oh no, even more {1:cats} | + will the {1:cats} ever stop | + oh well | + did the {1:cats} stop | + why won't it stop | + make the {1:cats} stop | + | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :Replace text cats^ | + ]]) + end) + + it('works with inccommand=split', function() + command('set inccommand=split') + feed(':Replace text cats') + screen:expect([[ + {1:cats} on line 1 | + more {1:cats} on line 2 | + oh no, even more {1:cats} | + will the {1:cats} ever stop | + oh well | + did the {1:cats} stop | + why won't it stop | + make the {1:cats} stop | + | + {4:[No Name] [+] }| + |1| {1:cats} on line 1 | + |2| more {1:cats} on line 2 | + |3| oh no, even more {1:cats} | + |4| will the {1:cats} ever stop | + |6| did the {1:cats} stop | + {3:[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 | + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + 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 | + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :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([[ + {1: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 | + | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :.Replace text cats^ | + ]]) + end) +end) |