aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
authorJavier Lopez <graulopezjavier@gmail.com>2022-06-30 07:59:52 -0500
committerGitHub <noreply@github.com>2022-06-30 05:59:52 -0700
commit565f72b9689e0c440ff72c712a090224aaf7631b (patch)
tree425b6a00993aea16e9c3084711d3d6282fbc1f83 /test/functional
parentcb84f5ee530f0f32b92bed5b4ad41344e8b551aa (diff)
downloadrneovim-565f72b9689e0c440ff72c712a090224aaf7631b.tar.gz
rneovim-565f72b9689e0c440ff72c712a090224aaf7631b.tar.bz2
rneovim-565f72b9689e0c440ff72c712a090224aaf7631b.zip
feat(marks): restore viewport on jump #15831
** Refactor Previously most functions used to "get" a mark returned a position, changed the line number and sometimes changed even the current buffer. Now functions return a {x}fmark_T making calling context aware whether the mark is in another buffer without arcane casting. A new function is provided for switching to the mark buffer and returning a flag style Enum to convey what happen in the movement. If the cursor changed, line, columns, if it changed buffer, etc. The function to get named mark was split into multiple functions. - mark_get() -> fmark_T - mark_get_global() -> xfmark_T - mark_get_local() -> fmark_T - mark_get_motion() -> fmark_T - mark_get_visual() -> fmark_T Functions that manage the changelist and jumplist were also modified to return mark types. - get_jumplist -> fmark_T - get_changelist -> fmark_T The refactor is also seen mainly on normal.c, where all the mark movement has been siphoned through one function nv_gomark, while the other functions handle getting the mark and setting their movement flags. To handle whether context marks should be left, etc. ** Mark View While doing the refactor the concept of a mark view was also implemented: The view of a mark currently implemented as the number of lines between the mark position on creation and the window topline. This allows for moving not only back to the position of a mark but having the window look similar to when the mark was defined. This is done by carrying and extra element in the fmark_T struct, which can be extended later to also restore horizontal shift. *** User space features 1. There's a new option, jumpoptions+=view enables the mark view restoring automatically when using the jumplist, changelist, alternate-file and mark motions. <C-O> <C-I> g; g, <C-^> '[mark] `[mark] ** Limitations - The view information is not saved in shada. - Calls to get_mark should copy the value in the pointer since we are using pos_to_mark() to wrap and provide a homogeneous interfaces. This was also a limitation in the previous state of things.
Diffstat (limited to 'test/functional')
-rw-r--r--test/functional/editor/jump_spec.lua115
-rw-r--r--test/functional/editor/mark_spec.lua380
2 files changed, 495 insertions, 0 deletions
diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua
index d09c20f226..d3d3d7f79d 100644
--- a/test/functional/editor/jump_spec.lua
+++ b/test/functional/editor/jump_spec.lua
@@ -1,4 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
@@ -7,6 +8,7 @@ local funcs = helpers.funcs
local feed = helpers.feed
local exec_capture = helpers.exec_capture
local write_file = helpers.write_file
+local curbufmeths = helpers.curbufmeths
describe('jumplist', function()
local fname1 = 'Xtest-functional-normal-jump'
@@ -137,3 +139,116 @@ describe("jumpoptions=stack behaves like 'tagstack'", function()
exec_capture('jumps'))
end)
end)
+
+describe("jumpoptions=view", function()
+ local file1 = 'Xtestfile-functional-editor-jumps'
+ local file2 = 'Xtestfile-functional-editor-jumps-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('restores the view', function()
+ local screen = Screen.new(5, 8)
+ screen:attach()
+ command("edit " .. file1)
+ feed("12Gztj")
+ feed("gg<C-o>")
+ screen:expect([[
+ 12 line |
+ ^13 line |
+ 14 line |
+ 15 line |
+ 16 line |
+ 17 line |
+ 18 line |
+ |
+ ]])
+ end)
+
+ it('restores the view across files', function()
+ local screen = Screen.new(5, 5)
+ screen:attach()
+ command("args " .. file1 .. " " .. file2)
+ feed("12Gzt")
+ command("next")
+ feed("G")
+ screen:expect([[
+ 27 line |
+ 28 line |
+ 29 line |
+ ^30 line |
+ |
+ ]])
+ feed("<C-o><C-o>")
+ screen:expect([[
+ ^12 line |
+ 13 line |
+ 14 line |
+ 15 line |
+ |
+ ]])
+ end)
+
+ it('restores the view across files with <C-^>', function()
+ local screen = Screen.new(5, 5)
+ screen:attach()
+ command("args " .. file1 .. " " .. file2)
+ feed("12Gzt")
+ command("next")
+ feed("G")
+ screen:expect([[
+ 27 line |
+ 28 line |
+ 29 line |
+ ^30 line |
+ |
+ ]])
+ feed("<C-^>")
+ screen:expect([[
+ ^12 line |
+ 13 line |
+ 14 line |
+ 15 line |
+ |
+ ]])
+ end)
+
+ it('falls back to standard behavior when view can\'t be recovered', function()
+ local screen = Screen.new(5, 8)
+ screen:attach()
+ command("edit " .. file1)
+ feed("7GzbG")
+ curbufmeths.set_lines(0, 2, true, {})
+ -- Move to line 7, and set it as the last line visible on the view with zb, meaning to recover
+ -- the view it needs to put the cursor 7 lines from the top line. Then go to the end of the
+ -- file, delete 2 lines before line 7, meaning the jump/mark is moved 2 lines up to line 5.
+ -- Therefore when trying to jump back to it it's not possible to set a 7 line offset from the
+ -- mark position to the top line, since there's only 5 lines from the mark position to line 0.
+ -- Therefore falls back to standard behavior which is centering the view/line.
+ feed("<C-o>")
+ screen:expect([[
+ 4 line |
+ 5 line |
+ 6 line |
+ ^7 line |
+ 8 line |
+ 9 line |
+ 10 line |
+ |
+ ]])
+ end)
+end)
diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua
new file mode 100644
index 0000000000..8b469286ec
--- /dev/null
+++ b/test/functional/editor/mark_spec.lua
@@ -0,0 +1,380 @@
+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("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("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("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("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("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("Vim(normal):E20: Mark not set", err)
+ err = pcall_err(helpers.exec_capture, "normal! '.")
+ eq("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("Vim(normal):E20: Mark not set", err)
+ err = pcall_err(helpers.exec_capture, "normal! `>")
+ eq("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("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("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("Vim(normal):E92: Buffer 1 not found", err)
+ os.remove(file1)
+ 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)
+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', 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 |
+ |
+ ]])
+ 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:.*} |
+ |
+ ]])
+ end)
+end)