mark_spec.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local api = n.api
  5. local clear = n.clear
  6. local command = n.command
  7. local fn = n.fn
  8. local eq = t.eq
  9. local feed = n.feed
  10. local write_file = t.write_file
  11. local pcall_err = t.pcall_err
  12. local cursor = function()
  13. return n.api.nvim_win_get_cursor(0)
  14. end
  15. describe('named marks', function()
  16. local file1 = 'Xtestfile-functional-editor-marks'
  17. local file2 = 'Xtestfile-functional-editor-marks-2'
  18. before_each(function()
  19. clear()
  20. write_file(file1, '1test1\n1test2\n1test3\n1test4', false, false)
  21. write_file(file2, '2test1\n2test2\n2test3\n2test4', false, false)
  22. end)
  23. after_each(function()
  24. os.remove(file1)
  25. os.remove(file2)
  26. end)
  27. it('can be set', function()
  28. command('edit ' .. file1)
  29. command('mark a')
  30. eq({ 1, 0 }, api.nvim_buf_get_mark(0, 'a'))
  31. feed('jmb')
  32. eq({ 2, 0 }, api.nvim_buf_get_mark(0, 'b'))
  33. feed('jmB')
  34. eq({ 3, 0 }, api.nvim_buf_get_mark(0, 'B'))
  35. command('4kc')
  36. eq({ 4, 0 }, api.nvim_buf_get_mark(0, 'c'))
  37. end)
  38. it('errors when set out of range with :mark', function()
  39. command('edit ' .. file1)
  40. local err = pcall_err(n.exec_capture, '1000mark x')
  41. eq('nvim_exec2(): Vim(mark):E16: Invalid range: 1000mark x', err)
  42. end)
  43. it('errors when set out of range with :k', function()
  44. command('edit ' .. file1)
  45. local err = pcall_err(n.exec_capture, '1000kx')
  46. eq('nvim_exec2(): Vim(k):E16: Invalid range: 1000kx', err)
  47. end)
  48. it('errors on unknown mark name with :mark', function()
  49. command('edit ' .. file1)
  50. local err = pcall_err(n.exec_capture, 'mark #')
  51. eq('nvim_exec2(): Vim(mark):E191: Argument must be a letter or forward/backward quote', err)
  52. end)
  53. it("errors on unknown mark name with '", function()
  54. command('edit ' .. file1)
  55. local err = pcall_err(n.exec_capture, "normal! '#")
  56. eq('nvim_exec2(): Vim(normal):E78: Unknown mark', err)
  57. end)
  58. it('errors on unknown mark name with `', function()
  59. command('edit ' .. file1)
  60. local err = pcall_err(n.exec_capture, 'normal! `#')
  61. eq('nvim_exec2(): Vim(normal):E78: Unknown mark', err)
  62. end)
  63. it("errors when moving to a mark that is not set with '", function()
  64. command('edit ' .. file1)
  65. local err = pcall_err(n.exec_capture, "normal! 'z")
  66. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  67. err = pcall_err(n.exec_capture, "normal! '.")
  68. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  69. end)
  70. it('errors when moving to a mark that is not set with `', function()
  71. command('edit ' .. file1)
  72. local err = pcall_err(n.exec_capture, 'normal! `z')
  73. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  74. err = pcall_err(n.exec_capture, 'normal! `>')
  75. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  76. end)
  77. it("errors when moving to a global mark that is not set with '", function()
  78. command('edit ' .. file1)
  79. local err = pcall_err(n.exec_capture, "normal! 'Z")
  80. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  81. end)
  82. it('errors when moving to a global mark that is not set with `', function()
  83. command('edit ' .. file1)
  84. local err = pcall_err(n.exec_capture, 'normal! `Z')
  85. eq('nvim_exec2(): Vim(normal):E20: Mark not set', err)
  86. end)
  87. it("can move to them using '", function()
  88. command('args ' .. file1 .. ' ' .. file2)
  89. feed('j')
  90. feed('ma')
  91. feed("G'a")
  92. eq({ 2, 0 }, cursor())
  93. feed('mA')
  94. command('next')
  95. feed("'A")
  96. eq(1, api.nvim_get_current_buf())
  97. eq({ 2, 0 }, cursor())
  98. end)
  99. it('can move to them using `', function()
  100. command('args ' .. file1 .. ' ' .. file2)
  101. feed('jll')
  102. feed('ma')
  103. feed('G`a')
  104. eq({ 2, 2 }, cursor())
  105. feed('mA')
  106. command('next')
  107. feed('`A')
  108. eq(1, api.nvim_get_current_buf())
  109. eq({ 2, 2 }, cursor())
  110. end)
  111. it("can move to them using g'", function()
  112. command('args ' .. file1 .. ' ' .. file2)
  113. feed('jll')
  114. feed('ma')
  115. feed("Gg'a")
  116. eq({ 2, 0 }, cursor())
  117. feed('mA')
  118. command('next')
  119. feed("g'A")
  120. eq(1, api.nvim_get_current_buf())
  121. eq({ 2, 0 }, cursor())
  122. end)
  123. it('can move to them using g`', function()
  124. command('args ' .. file1 .. ' ' .. file2)
  125. feed('jll')
  126. feed('ma')
  127. feed('Gg`a')
  128. eq({ 2, 2 }, cursor())
  129. feed('mA')
  130. command('next')
  131. feed('g`A')
  132. eq(1, api.nvim_get_current_buf())
  133. eq({ 2, 2 }, cursor())
  134. end)
  135. it("can move to them using :'", function()
  136. command('args ' .. file1 .. ' ' .. file2)
  137. feed('j')
  138. feed('ma')
  139. feed('G')
  140. command("'a")
  141. eq({ 2, 0 }, cursor())
  142. feed('mA')
  143. command('next')
  144. command("'A")
  145. eq(1, api.nvim_get_current_buf())
  146. eq({ 2, 0 }, cursor())
  147. end)
  148. it("errors when it can't find the buffer", function()
  149. command('args ' .. file1 .. ' ' .. file2)
  150. feed('mA')
  151. command('next')
  152. command('bw! ' .. file1)
  153. local err = pcall_err(n.exec_capture, "normal! 'A")
  154. eq('nvim_exec2(): Vim(normal):E92: Buffer 1 not found', err)
  155. os.remove(file1)
  156. end)
  157. it('errors when using a mark in another buffer in command range', function()
  158. feed('ifoo<Esc>mA')
  159. command('enew')
  160. feed('ibar<Esc>')
  161. eq("Vim(print):E20: Mark not set: 'Aprint", pcall_err(command, [['Aprint]]))
  162. end)
  163. it("leave a context mark when moving with '", function()
  164. command('edit ' .. file1)
  165. feed('llmamA')
  166. feed('10j0') -- first col, last line
  167. local pos = cursor()
  168. feed("'a")
  169. feed('<C-o>')
  170. eq(pos, cursor())
  171. feed("'A")
  172. feed('<C-o>')
  173. eq(pos, cursor())
  174. end)
  175. it('leave a context mark when moving with `', function()
  176. command('edit ' .. file1)
  177. feed('llmamA')
  178. feed('10j0') -- first col, last line
  179. local pos = cursor()
  180. feed('`a')
  181. feed('<C-o>')
  182. eq(pos, cursor())
  183. feed('`A')
  184. feed('<C-o>')
  185. eq(pos, cursor())
  186. end)
  187. it("leave a context mark when the mark changes buffer with g'", function()
  188. command('args ' .. file1 .. ' ' .. file2)
  189. local pos
  190. feed('GmA')
  191. command('next')
  192. pos = cursor()
  193. command('clearjumps')
  194. feed("g'A") -- since the mark is in another buffer, it leaves a context mark
  195. feed('<C-o>')
  196. eq(pos, cursor())
  197. end)
  198. it('leave a context mark when the mark changes buffer with g`', function()
  199. command('args ' .. file1 .. ' ' .. file2)
  200. local pos
  201. feed('GmA')
  202. command('next')
  203. pos = cursor()
  204. command('clearjumps')
  205. feed('g`A') -- since the mark is in another buffer, it leaves a context mark
  206. feed('<C-o>')
  207. eq(pos, cursor())
  208. end)
  209. it("do not leave a context mark when moving with g'", function()
  210. command('edit ' .. file1)
  211. local pos
  212. feed('ma')
  213. pos = cursor() -- Mark pos
  214. feed('10j0') -- first col, last line
  215. feed("g'a")
  216. feed('<C-o>') -- should do nothing
  217. eq(pos, cursor())
  218. feed('mA')
  219. pos = cursor() -- Mark pos
  220. feed('10j0') -- first col, last line
  221. feed("g'a")
  222. feed('<C-o>') -- should do nothing
  223. eq(pos, cursor())
  224. end)
  225. it('do not leave a context mark when moving with g`', function()
  226. command('edit ' .. file1)
  227. local pos
  228. feed('ma')
  229. pos = cursor() -- Mark pos
  230. feed('10j0') -- first col, last line
  231. feed('g`a')
  232. feed('<C-o>') -- should do nothing
  233. eq(pos, cursor())
  234. feed('mA')
  235. pos = cursor() -- Mark pos
  236. feed('10j0') -- first col, last line
  237. feed("g'a")
  238. feed('<C-o>') -- should do nothing
  239. eq(pos, cursor())
  240. end)
  241. it('open folds when moving to them', function()
  242. command('edit ' .. file1)
  243. feed('jzfG') -- Fold from the second line to the end
  244. command('3mark a')
  245. feed('G') -- On top of the fold
  246. assert(fn.foldclosed('.') ~= -1) -- folded
  247. feed("'a")
  248. eq(-1, fn.foldclosed('.'))
  249. feed('zc')
  250. assert(fn.foldclosed('.') ~= -1) -- folded
  251. -- TODO: remove this workaround after fixing #15873
  252. feed('k`a')
  253. eq(-1, fn.foldclosed('.'))
  254. feed('zc')
  255. assert(fn.foldclosed('.') ~= -1) -- folded
  256. feed("kg'a")
  257. eq(-1, fn.foldclosed('.'))
  258. feed('zc')
  259. assert(fn.foldclosed('.') ~= -1) -- folded
  260. feed('kg`a')
  261. eq(-1, fn.foldclosed('.'))
  262. end)
  263. it("do not open folds when moving to them doesn't move the cursor", function()
  264. command('edit ' .. file1)
  265. feed('jzfG') -- Fold from the second line to the end
  266. assert(fn.foldclosed('.') == 2) -- folded
  267. feed('ma')
  268. feed("'a")
  269. feed('`a')
  270. feed("g'a")
  271. feed('g`a')
  272. -- should still be folded
  273. eq(2, fn.foldclosed('.'))
  274. end)
  275. it("getting '{ '} '( ') does not move cursor", function()
  276. api.nvim_buf_set_lines(0, 0, 0, true, { 'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee' })
  277. api.nvim_win_set_cursor(0, { 2, 0 })
  278. fn.getpos("'{")
  279. eq({ 2, 0 }, api.nvim_win_get_cursor(0))
  280. fn.getpos("'}")
  281. eq({ 2, 0 }, api.nvim_win_get_cursor(0))
  282. fn.getpos("'(")
  283. eq({ 2, 0 }, api.nvim_win_get_cursor(0))
  284. fn.getpos("')")
  285. eq({ 2, 0 }, api.nvim_win_get_cursor(0))
  286. end)
  287. it('in command range does not move cursor #19248', function()
  288. api.nvim_create_user_command('Test', ':', { range = true })
  289. api.nvim_buf_set_lines(0, 0, 0, true, { 'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee' })
  290. api.nvim_win_set_cursor(0, { 2, 0 })
  291. command([['{,'}Test]])
  292. eq({ 2, 0 }, api.nvim_win_get_cursor(0))
  293. end)
  294. end)
  295. describe('named marks view', function()
  296. local file1 = 'Xtestfile-functional-editor-marks'
  297. local file2 = 'Xtestfile-functional-editor-marks-2'
  298. local function content()
  299. local c = {}
  300. for i = 1, 30 do
  301. c[i] = i .. ' line'
  302. end
  303. return table.concat(c, '\n')
  304. end
  305. before_each(function()
  306. clear()
  307. write_file(file1, content(), false, false)
  308. write_file(file2, content(), false, false)
  309. command('set jumpoptions+=view')
  310. end)
  311. after_each(function()
  312. os.remove(file1)
  313. os.remove(file2)
  314. end)
  315. it('is restored in normal mode but not op-pending mode', function()
  316. local screen = Screen.new(5, 8)
  317. command('edit ' .. file1)
  318. feed('<C-e>jWma')
  319. feed("G'a")
  320. local expected = [[
  321. 2 line |
  322. ^3 line |
  323. 4 line |
  324. 5 line |
  325. 6 line |
  326. 7 line |
  327. 8 line |
  328. |
  329. ]]
  330. screen:expect({ grid = expected })
  331. feed('G`a')
  332. screen:expect([[
  333. 2 line |
  334. 3 ^line |
  335. 4 line |
  336. 5 line |
  337. 6 line |
  338. 7 line |
  339. 8 line |
  340. |
  341. ]])
  342. -- not in op-pending mode #20886
  343. feed('ggj=`a')
  344. screen:expect([[
  345. 1 line |
  346. ^2 line |
  347. 3 line |
  348. 4 line |
  349. 5 line |
  350. 6 line |
  351. 7 line |
  352. |
  353. ]])
  354. end)
  355. it('is restored across files', function()
  356. local screen = Screen.new(5, 5)
  357. command('args ' .. file1 .. ' ' .. file2)
  358. feed('<C-e>mA')
  359. local mark_view = [[
  360. ^2 line |
  361. 3 line |
  362. 4 line |
  363. 5 line |
  364. |
  365. ]]
  366. screen:expect(mark_view)
  367. command('next')
  368. screen:expect([[
  369. ^1 line |
  370. 2 line |
  371. 3 line |
  372. 4 line |
  373. |
  374. ]])
  375. feed("'A")
  376. screen:expect(mark_view)
  377. end)
  378. it("fallback to standard behavior when view can't be recovered", function()
  379. local screen = Screen.new(10, 10)
  380. command('edit ' .. file1)
  381. feed('7GzbmaG') -- Seven lines from the top
  382. command('new') -- Screen size for window is now half the height can't be restored
  383. feed("<C-w>p'a")
  384. screen:expect([[
  385. |
  386. {1:~ }|*3
  387. {2:[No Name] }|
  388. 6 line |
  389. ^7 line |
  390. 8 line |
  391. {3:<itor-marks }|
  392. |
  393. ]])
  394. end)
  395. it('fallback to standard behavior when mark is loaded from shada', function()
  396. local screen = Screen.new(10, 6)
  397. command('edit ' .. file1)
  398. feed('G')
  399. feed('mA')
  400. screen:expect([[
  401. 26 line |
  402. 27 line |
  403. 28 line |
  404. 29 line |
  405. ^30 line |
  406. |
  407. ]])
  408. command('set shadafile=Xtestfile-functional-editor-marks-shada')
  409. finally(function()
  410. command('set shadafile=NONE')
  411. os.remove('Xtestfile-functional-editor-marks-shada')
  412. end)
  413. command('wshada!')
  414. command('bwipe!')
  415. screen:expect([[
  416. ^ |
  417. {1:~ }|*4
  418. |
  419. ]])
  420. command('rshada!')
  421. command('edit ' .. file1)
  422. feed('`"')
  423. screen:expect([[
  424. 26 line |
  425. 27 line |
  426. 28 line |
  427. 29 line |
  428. ^30 line |
  429. |
  430. ]])
  431. feed('`A')
  432. screen:expect_unchanged()
  433. end)
  434. end)