mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 11:15:14 -07:00
a741c7fd04
Problem: nvim_parse_cmd() in pcall() may show an error message (side-effect): :lua pcall(vim.api.nvim_parse_cmd, vim.fn.getcmdline(), {}) E16: Invalid range Solution: Avoid emsg() in the nvim_parse_cmd() codepath. - refactor(api): add error message output parameter to get_address() - fix: null check emsg() parameter - refactor: remove emsg_off workaround from do_incsearch_highlighting() - refactor: remove emsg_off workaround from cmdpreview_may_show() - refactor: remove remaining calls to emsg() from parse_cmd_address() and get_address() - (refactor): lint set_cmd_dflall_range() - refactor: addr_error() - move output parameter to return value Fix #20339 TODO: These are the functions called by `get_address()`: ``` nvim_parse_cmd() -> parse_cmdline() -> parse_cmd_address() -> get_address() skipwhite() addr_error() qf_get_cur_idx() qf_get_cur_valid_idx() qf_get_size() qf_get_valid_size() mark_get() mark_check() assert() skip_regexp() magic_isset() > do_search() > searchit() ascii_isdigit() getdigits() getdigits_int32() compute_buffer_local_count() hasFolding() ``` From these functions, I found at least two that call emsg directly: - do_search() - seems to be simple to refactor - searchit() - will be more challenging because it may generate multiple error messages, which can't be handled by the current `errormsg` out-parameter. For example, it makes multiple calls to `vim_regexec_multi()` in a loop that possibly generate error messages, and later `searchit()` itself may generate another one: -c194acbfc4/src/nvim/search.c (L631-L647)
-c194acbfc4/src/nvim/search.c (L939-L954)
--------- Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
465 lines
12 KiB
Lua
465 lines
12 KiB
Lua
local helpers = require('test.functional.helpers')(after_each)
|
|
local Screen = require('test.functional.ui.screen')
|
|
local meths = helpers.meths
|
|
local curbufmeths = helpers.curbufmeths
|
|
local clear = helpers.clear
|
|
local command = helpers.command
|
|
local funcs = helpers.funcs
|
|
local eq = helpers.eq
|
|
local feed = helpers.feed
|
|
local write_file = helpers.write_file
|
|
local pcall_err = helpers.pcall_err
|
|
local cursor = function() return helpers.meths.win_get_cursor(0) end
|
|
|
|
describe('named marks', function()
|
|
local file1 = 'Xtestfile-functional-editor-marks'
|
|
local file2 = 'Xtestfile-functional-editor-marks-2'
|
|
before_each(function()
|
|
clear()
|
|
write_file(file1, '1test1\n1test2\n1test3\n1test4', false, false)
|
|
write_file(file2, '2test1\n2test2\n2test3\n2test4', false, false)
|
|
end)
|
|
after_each(function()
|
|
os.remove(file1)
|
|
os.remove(file2)
|
|
end)
|
|
|
|
|
|
it("can be set", function()
|
|
command("edit " .. file1)
|
|
command("mark a")
|
|
eq({1, 0}, curbufmeths.get_mark("a"))
|
|
feed("jmb")
|
|
eq({2, 0}, curbufmeths.get_mark("b"))
|
|
feed("jmB")
|
|
eq({3, 0}, curbufmeths.get_mark("B"))
|
|
command("4kc")
|
|
eq({4, 0}, curbufmeths.get_mark("c"))
|
|
end)
|
|
|
|
it("errors when set out of range with :mark", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "1000mark x")
|
|
eq("nvim_exec2(): Vim(mark):E16: Invalid range: 1000mark x", err)
|
|
end)
|
|
|
|
it("errors when set out of range with :k", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "1000kx")
|
|
eq("nvim_exec2(): Vim(k):E16: Invalid range: 1000kx", err)
|
|
end)
|
|
|
|
it("errors on unknown mark name with :mark", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "mark #")
|
|
eq("nvim_exec2(): Vim(mark):E191: Argument must be a letter or forward/backward quote", err)
|
|
end)
|
|
|
|
it("errors on unknown mark name with '", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! '#")
|
|
eq("nvim_exec2(): Vim(normal):E78: Unknown mark", err)
|
|
end)
|
|
|
|
it("errors on unknown mark name with `", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! `#")
|
|
eq("nvim_exec2(): Vim(normal):E78: Unknown mark", err)
|
|
end)
|
|
|
|
it("errors when moving to a mark that is not set with '", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! 'z")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
err = pcall_err(helpers.exec_capture, "normal! '.")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
end)
|
|
|
|
it("errors when moving to a mark that is not set with `", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! `z")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
err = pcall_err(helpers.exec_capture, "normal! `>")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
end)
|
|
|
|
it("errors when moving to a global mark that is not set with '", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! 'Z")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
end)
|
|
|
|
it("errors when moving to a global mark that is not set with `", function()
|
|
command("edit " .. file1)
|
|
local err = pcall_err(helpers.exec_capture, "normal! `Z")
|
|
eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
|
|
end)
|
|
|
|
it("can move to them using '", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("j")
|
|
feed("ma")
|
|
feed("G'a")
|
|
eq({2, 0}, cursor())
|
|
feed("mA")
|
|
command("next")
|
|
feed("'A")
|
|
eq(1, meths.get_current_buf().id)
|
|
eq({2, 0}, cursor())
|
|
end)
|
|
|
|
it("can move to them using `", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("jll")
|
|
feed("ma")
|
|
feed("G`a")
|
|
eq({2, 2}, cursor())
|
|
feed("mA")
|
|
command("next")
|
|
feed("`A")
|
|
eq(1, meths.get_current_buf().id)
|
|
eq({2, 2}, cursor())
|
|
end)
|
|
|
|
it("can move to them using g'", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("jll")
|
|
feed("ma")
|
|
feed("Gg'a")
|
|
eq({2, 0}, cursor())
|
|
feed("mA")
|
|
command("next")
|
|
feed("g'A")
|
|
eq(1, meths.get_current_buf().id)
|
|
eq({2, 0}, cursor())
|
|
end)
|
|
|
|
it("can move to them using g`", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("jll")
|
|
feed("ma")
|
|
feed("Gg`a")
|
|
eq({2, 2}, cursor())
|
|
feed("mA")
|
|
command("next")
|
|
feed("g`A")
|
|
eq(1, meths.get_current_buf().id)
|
|
eq({2, 2}, cursor())
|
|
end)
|
|
|
|
it("errors when it can't find the buffer", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("mA")
|
|
command("next")
|
|
command("bw! " .. file1 )
|
|
local err = pcall_err(helpers.exec_capture, "normal! 'A")
|
|
eq("nvim_exec2(): Vim(normal):E92: Buffer 1 not found", err)
|
|
os.remove(file1)
|
|
end)
|
|
|
|
it("errors when using a mark in another buffer in command range", function()
|
|
feed('ifoo<Esc>mA')
|
|
command('enew')
|
|
feed('ibar<Esc>')
|
|
eq("Vim(print):E20: Mark not set: 'Aprint", pcall_err(command, [['Aprint]]))
|
|
end)
|
|
|
|
it("leave a context mark when moving with '", function()
|
|
command("edit " .. file1)
|
|
feed("llmamA")
|
|
feed("10j0") -- first col, last line
|
|
local pos = cursor()
|
|
feed("'a")
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
feed("'A")
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("leave a context mark when moving with `", function()
|
|
command("edit " .. file1)
|
|
feed("llmamA")
|
|
feed("10j0") -- first col, last line
|
|
local pos = cursor()
|
|
feed("`a")
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
feed("`A")
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("leave a context mark when the mark changes buffer with g'", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
local pos
|
|
feed("GmA")
|
|
command("next")
|
|
pos = cursor()
|
|
command("clearjumps")
|
|
feed("g'A") -- since the mark is in another buffer, it leaves a context mark
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("leave a context mark when the mark changes buffer with g`", function()
|
|
command("args " .. file1 .. " " .. file2)
|
|
local pos
|
|
feed("GmA")
|
|
command("next")
|
|
pos = cursor()
|
|
command("clearjumps")
|
|
feed("g`A") -- since the mark is in another buffer, it leaves a context mark
|
|
feed("<C-o>")
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("do not leave a context mark when moving with g'", function()
|
|
command("edit " .. file1)
|
|
local pos
|
|
feed("ma")
|
|
pos = cursor() -- Mark pos
|
|
feed("10j0") -- first col, last line
|
|
feed("g'a")
|
|
feed("<C-o>") -- should do nothing
|
|
eq(pos, cursor())
|
|
feed("mA")
|
|
pos = cursor() -- Mark pos
|
|
feed("10j0") -- first col, last line
|
|
feed("g'a")
|
|
feed("<C-o>") -- should do nothing
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("do not leave a context mark when moving with g`", function()
|
|
command("edit " .. file1)
|
|
local pos
|
|
feed("ma")
|
|
pos = cursor() -- Mark pos
|
|
feed("10j0") -- first col, last line
|
|
feed("g`a")
|
|
feed("<C-o>") -- should do nothing
|
|
eq(pos, cursor())
|
|
feed("mA")
|
|
pos = cursor() -- Mark pos
|
|
feed("10j0") -- first col, last line
|
|
feed("g'a")
|
|
feed("<C-o>") -- should do nothing
|
|
eq(pos, cursor())
|
|
end)
|
|
|
|
it("open folds when moving to them", function()
|
|
command("edit " .. file1)
|
|
feed("jzfG") -- Fold from the second line to the end
|
|
command("3mark a")
|
|
feed("G") -- On top of the fold
|
|
assert(funcs.foldclosed('.') ~= -1) -- folded
|
|
feed("'a")
|
|
eq(-1, funcs.foldclosed('.'))
|
|
|
|
feed("zc")
|
|
assert(funcs.foldclosed('.') ~= -1) -- folded
|
|
-- TODO: remove this workaround after fixing #15873
|
|
feed("k`a")
|
|
eq(-1, funcs.foldclosed('.'))
|
|
|
|
feed("zc")
|
|
assert(funcs.foldclosed('.') ~= -1) -- folded
|
|
feed("kg'a")
|
|
eq(-1, funcs.foldclosed('.'))
|
|
|
|
feed("zc")
|
|
assert(funcs.foldclosed('.') ~= -1) -- folded
|
|
feed("kg`a")
|
|
eq(-1, funcs.foldclosed('.'))
|
|
end)
|
|
|
|
it("do not open folds when moving to them doesn't move the cursor", function()
|
|
command("edit " .. file1)
|
|
feed("jzfG") -- Fold from the second line to the end
|
|
assert(funcs.foldclosed('.') == 2) -- folded
|
|
feed("ma")
|
|
feed("'a")
|
|
feed("`a")
|
|
feed("g'a")
|
|
feed("g`a")
|
|
-- should still be folded
|
|
eq(2, funcs.foldclosed('.'))
|
|
end)
|
|
|
|
it("getting '{ '} '( ') does not move cursor", function()
|
|
meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'})
|
|
meths.win_set_cursor(0, {2, 0})
|
|
funcs.getpos("'{")
|
|
eq({2, 0}, meths.win_get_cursor(0))
|
|
funcs.getpos("'}")
|
|
eq({2, 0}, meths.win_get_cursor(0))
|
|
funcs.getpos("'(")
|
|
eq({2, 0}, meths.win_get_cursor(0))
|
|
funcs.getpos("')")
|
|
eq({2, 0}, meths.win_get_cursor(0))
|
|
end)
|
|
|
|
it('in command range does not move cursor #19248', function()
|
|
meths.create_user_command('Test', ':', {range = true})
|
|
meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'})
|
|
meths.win_set_cursor(0, {2, 0})
|
|
command([['{,'}Test]])
|
|
eq({2, 0}, meths.win_get_cursor(0))
|
|
end)
|
|
end)
|
|
|
|
describe('named marks view', function()
|
|
local file1 = 'Xtestfile-functional-editor-marks'
|
|
local file2 = 'Xtestfile-functional-editor-marks-2'
|
|
local function content()
|
|
local c = {}
|
|
for i=1,30 do
|
|
c[i] = i .. " line"
|
|
end
|
|
return table.concat(c, "\n")
|
|
end
|
|
before_each(function()
|
|
clear()
|
|
write_file(file1, content(), false, false)
|
|
write_file(file2, content(), false, false)
|
|
command("set jumpoptions+=view")
|
|
end)
|
|
after_each(function()
|
|
os.remove(file1)
|
|
os.remove(file2)
|
|
end)
|
|
|
|
it('is restored in normal mode but not op-pending mode', function()
|
|
local screen = Screen.new(5, 8)
|
|
screen:attach()
|
|
command("edit " .. file1)
|
|
feed("<C-e>jWma")
|
|
feed("G'a")
|
|
local expected = [[
|
|
2 line |
|
|
^3 line |
|
|
4 line |
|
|
5 line |
|
|
6 line |
|
|
7 line |
|
|
8 line |
|
|
|
|
|
]]
|
|
screen:expect({grid=expected})
|
|
feed("G`a")
|
|
screen:expect([[
|
|
2 line |
|
|
3 ^line |
|
|
4 line |
|
|
5 line |
|
|
6 line |
|
|
7 line |
|
|
8 line |
|
|
|
|
|
]])
|
|
-- not in op-pending mode #20886
|
|
feed("ggj=`a")
|
|
screen:expect([[
|
|
1 line |
|
|
^2 line |
|
|
3 line |
|
|
4 line |
|
|
5 line |
|
|
6 line |
|
|
7 line |
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('is restored across files', function()
|
|
local screen = Screen.new(5, 5)
|
|
screen:attach()
|
|
command("args " .. file1 .. " " .. file2)
|
|
feed("<C-e>mA")
|
|
local mark_view = [[
|
|
^2 line |
|
|
3 line |
|
|
4 line |
|
|
5 line |
|
|
|
|
|
]]
|
|
screen:expect(mark_view)
|
|
command("next")
|
|
screen:expect([[
|
|
^1 line |
|
|
2 line |
|
|
3 line |
|
|
4 line |
|
|
|
|
|
]])
|
|
feed("'A")
|
|
screen:expect(mark_view)
|
|
end)
|
|
|
|
it('fallback to standard behavior when view can\'t be recovered', function()
|
|
local screen = Screen.new(10, 10)
|
|
screen:attach()
|
|
command("edit " .. file1)
|
|
feed("7GzbmaG") -- Seven lines from the top
|
|
command("new") -- Screen size for window is now half the height can't be restored
|
|
feed("<C-w>p'a")
|
|
screen:expect([[
|
|
|
|
|
~ |
|
|
~ |
|
|
~ |
|
|
[No Name] |
|
|
6 line |
|
|
^7 line |
|
|
8 line |
|
|
{MATCH:.*marks} |
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('fallback to standard behavior when mark is loaded from shada', function()
|
|
local screen = Screen.new(10, 6)
|
|
screen:attach()
|
|
command('edit ' .. file1)
|
|
feed('G')
|
|
feed('mA')
|
|
screen:expect([[
|
|
26 line |
|
|
27 line |
|
|
28 line |
|
|
29 line |
|
|
^30 line |
|
|
|
|
|
]])
|
|
command('set shadafile=Xtestfile-functional-editor-marks-shada')
|
|
finally(function()
|
|
command('set shadafile=NONE')
|
|
os.remove('Xtestfile-functional-editor-marks-shada')
|
|
end)
|
|
command('wshada!')
|
|
command('bwipe!')
|
|
screen:expect([[
|
|
^ |
|
|
~ |
|
|
~ |
|
|
~ |
|
|
~ |
|
|
|
|
|
]])
|
|
command('rshada!')
|
|
command('edit ' .. file1)
|
|
feed('`"')
|
|
screen:expect([[
|
|
26 line |
|
|
27 line |
|
|
28 line |
|
|
29 line |
|
|
^30 line |
|
|
|
|
|
]])
|
|
feed('`A')
|
|
screen:expect_unchanged()
|
|
end)
|
|
end)
|