window_spec.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. local helpers = require('test.functional.helpers')(after_each)
  2. local Screen = require('test.functional.ui.screen')
  3. local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq,
  4. ok, feed, insert, eval, tabpage = helpers.clear, helpers.nvim, helpers.curbuf,
  5. helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq,
  6. helpers.neq, helpers.ok, helpers.feed, helpers.insert, helpers.eval,
  7. helpers.tabpage
  8. local poke_eventloop = helpers.poke_eventloop
  9. local curwinmeths = helpers.curwinmeths
  10. local exec = helpers.exec
  11. local funcs = helpers.funcs
  12. local request = helpers.request
  13. local NIL = helpers.NIL
  14. local meths = helpers.meths
  15. local command = helpers.command
  16. local pcall_err = helpers.pcall_err
  17. local assert_alive = helpers.assert_alive
  18. -- check if str is visible at the beginning of some line
  19. local function is_visible(str)
  20. local slen = string.len(str)
  21. local nlines = eval("&lines")
  22. for i = 1,nlines do
  23. local iseq = true
  24. for j = 1,slen do
  25. if string.byte(str,j) ~= eval("screenchar("..i..","..j..")") then
  26. iseq = false
  27. break
  28. end
  29. end
  30. if iseq then
  31. return true
  32. end
  33. end
  34. return false
  35. end
  36. describe('API/win', function()
  37. before_each(clear)
  38. describe('get_buf', function()
  39. it('works', function()
  40. eq(curbuf(), window('get_buf', nvim('list_wins')[1]))
  41. nvim('command', 'new')
  42. nvim('set_current_win', nvim('list_wins')[2])
  43. eq(curbuf(), window('get_buf', nvim('list_wins')[2]))
  44. neq(window('get_buf', nvim('list_wins')[1]),
  45. window('get_buf', nvim('list_wins')[2]))
  46. end)
  47. end)
  48. describe('set_buf', function()
  49. it('works', function()
  50. nvim('command', 'new')
  51. local windows = nvim('list_wins')
  52. neq(window('get_buf', windows[2]), window('get_buf', windows[1]))
  53. window('set_buf', windows[2], window('get_buf', windows[1]))
  54. eq(window('get_buf', windows[2]), window('get_buf', windows[1]))
  55. end)
  56. it('validates args', function()
  57. eq('Invalid buffer id: 23', pcall_err(window, 'set_buf', nvim('get_current_win'), 23))
  58. eq('Invalid window id: 23', pcall_err(window, 'set_buf', 23, nvim('get_current_buf')))
  59. end)
  60. end)
  61. describe('{get,set}_cursor', function()
  62. it('works', function()
  63. eq({1, 0}, curwin('get_cursor'))
  64. nvim('command', 'normal ityping\027o some text')
  65. eq('typing\n some text', curbuf_contents())
  66. eq({2, 10}, curwin('get_cursor'))
  67. curwin('set_cursor', {2, 6})
  68. nvim('command', 'normal i dumb')
  69. eq('typing\n some dumb text', curbuf_contents())
  70. end)
  71. it('does not leak memory when using invalid window ID with invalid pos', function()
  72. eq('Invalid window id: 1', pcall_err(meths.win_set_cursor, 1, {"b\na"}))
  73. end)
  74. it('updates the screen, and also when the window is unfocused', function()
  75. insert("prologue")
  76. feed('100o<esc>')
  77. insert("epilogue")
  78. local win = curwin()
  79. feed('gg')
  80. poke_eventloop() -- let nvim process the 'gg' command
  81. -- cursor position is at beginning
  82. eq({1, 0}, window('get_cursor', win))
  83. eq(true, is_visible("prologue"))
  84. eq(false, is_visible("epilogue"))
  85. -- move cursor to end
  86. window('set_cursor', win, {101, 0})
  87. eq(false, is_visible("prologue"))
  88. eq(true, is_visible("epilogue"))
  89. -- move cursor to the beginning again
  90. window('set_cursor', win, {1, 0})
  91. eq(true, is_visible("prologue"))
  92. eq(false, is_visible("epilogue"))
  93. -- move focus to new window
  94. nvim('command',"new")
  95. neq(win, curwin())
  96. -- sanity check, cursor position is kept
  97. eq({1, 0}, window('get_cursor', win))
  98. eq(true, is_visible("prologue"))
  99. eq(false, is_visible("epilogue"))
  100. -- move cursor to end
  101. window('set_cursor', win, {101, 0})
  102. eq(false, is_visible("prologue"))
  103. eq(true, is_visible("epilogue"))
  104. -- move cursor to the beginning again
  105. window('set_cursor', win, {1, 0})
  106. eq(true, is_visible("prologue"))
  107. eq(false, is_visible("epilogue"))
  108. -- curwin didn't change back
  109. neq(win, curwin())
  110. end)
  111. it('remembers what column it wants to be in', function()
  112. insert("first line")
  113. feed('o<esc>')
  114. insert("second line")
  115. feed('gg')
  116. poke_eventloop() -- let nvim process the 'gg' command
  117. -- cursor position is at beginning
  118. local win = curwin()
  119. eq({1, 0}, window('get_cursor', win))
  120. -- move cursor to column 5
  121. window('set_cursor', win, {1, 5})
  122. -- move down a line
  123. feed('j')
  124. poke_eventloop() -- let nvim process the 'j' command
  125. -- cursor is still in column 5
  126. eq({2, 5}, window('get_cursor', win))
  127. end)
  128. it('updates cursorline and statusline ruler in non-current window', function()
  129. local screen = Screen.new(60, 8)
  130. screen:set_default_attr_ids({
  131. [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
  132. [2] = {background = Screen.colors.Grey90}, -- CursorLine
  133. [3] = {bold = true, reverse = true}, -- StatusLine
  134. [4] = {reverse = true}, -- StatusLineNC
  135. })
  136. screen:attach()
  137. command('set ruler')
  138. command('set cursorline')
  139. insert([[
  140. aaa
  141. bbb
  142. ccc
  143. ddd]])
  144. local oldwin = curwin()
  145. command('vsplit')
  146. screen:expect([[
  147. aaa │aaa |
  148. bbb │bbb |
  149. ccc │ccc |
  150. {2:dd^d }│{2:ddd }|
  151. {1:~ }│{1:~ }|
  152. {1:~ }│{1:~ }|
  153. {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 4,3 All}|
  154. |
  155. ]])
  156. window('set_cursor', oldwin, {1, 0})
  157. screen:expect([[
  158. aaa │{2:aaa }|
  159. bbb │bbb |
  160. ccc │ccc |
  161. {2:dd^d }│ddd |
  162. {1:~ }│{1:~ }|
  163. {1:~ }│{1:~ }|
  164. {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 1,1 All}|
  165. |
  166. ]])
  167. end)
  168. end)
  169. describe('{get,set}_height', function()
  170. it('works', function()
  171. nvim('command', 'vsplit')
  172. eq(window('get_height', nvim('list_wins')[2]),
  173. window('get_height', nvim('list_wins')[1]))
  174. nvim('set_current_win', nvim('list_wins')[2])
  175. nvim('command', 'split')
  176. eq(window('get_height', nvim('list_wins')[2]),
  177. math.floor(window('get_height', nvim('list_wins')[1]) / 2))
  178. window('set_height', nvim('list_wins')[2], 2)
  179. eq(2, window('get_height', nvim('list_wins')[2]))
  180. end)
  181. it('do not cause ml_get errors with foldmethod=expr #19989', function()
  182. insert([[
  183. aaaaa
  184. bbbbb
  185. ccccc]])
  186. command('set foldmethod=expr')
  187. exec([[
  188. new
  189. let w = nvim_get_current_win()
  190. wincmd w
  191. call nvim_win_set_height(w, 5)
  192. ]])
  193. feed('l')
  194. eq('', meths.get_vvar('errmsg'))
  195. end)
  196. end)
  197. describe('{get,set}_width', function()
  198. it('works', function()
  199. nvim('command', 'split')
  200. eq(window('get_width', nvim('list_wins')[2]),
  201. window('get_width', nvim('list_wins')[1]))
  202. nvim('set_current_win', nvim('list_wins')[2])
  203. nvim('command', 'vsplit')
  204. eq(window('get_width', nvim('list_wins')[2]),
  205. math.floor(window('get_width', nvim('list_wins')[1]) / 2))
  206. window('set_width', nvim('list_wins')[2], 2)
  207. eq(2, window('get_width', nvim('list_wins')[2]))
  208. end)
  209. it('do not cause ml_get errors with foldmethod=expr #19989', function()
  210. insert([[
  211. aaaaa
  212. bbbbb
  213. ccccc]])
  214. command('set foldmethod=expr')
  215. exec([[
  216. vnew
  217. let w = nvim_get_current_win()
  218. wincmd w
  219. call nvim_win_set_width(w, 5)
  220. ]])
  221. feed('l')
  222. eq('', meths.get_vvar('errmsg'))
  223. end)
  224. end)
  225. describe('{get,set,del}_var', function()
  226. it('works', function()
  227. curwin('set_var', 'lua', {1, 2, {['3'] = 1}})
  228. eq({1, 2, {['3'] = 1}}, curwin('get_var', 'lua'))
  229. eq({1, 2, {['3'] = 1}}, nvim('eval', 'w:lua'))
  230. eq(1, funcs.exists('w:lua'))
  231. curwinmeths.del_var('lua')
  232. eq(0, funcs.exists('w:lua'))
  233. eq('Key not found: lua', pcall_err(curwinmeths.del_var, 'lua'))
  234. curwinmeths.set_var('lua', 1)
  235. command('lockvar w:lua')
  236. eq('Key is locked: lua', pcall_err(curwinmeths.del_var, 'lua'))
  237. eq('Key is locked: lua', pcall_err(curwinmeths.set_var, 'lua', 1))
  238. end)
  239. it('window_set_var returns the old value', function()
  240. local val1 = {1, 2, {['3'] = 1}}
  241. local val2 = {4, 7}
  242. eq(NIL, request('window_set_var', 0, 'lua', val1))
  243. eq(val1, request('window_set_var', 0, 'lua', val2))
  244. end)
  245. it('window_del_var returns the old value', function()
  246. local val1 = {1, 2, {['3'] = 1}}
  247. local val2 = {4, 7}
  248. eq(NIL, request('window_set_var', 0, 'lua', val1))
  249. eq(val1, request('window_set_var', 0, 'lua', val2))
  250. eq(val2, request('window_del_var', 0, 'lua'))
  251. end)
  252. end)
  253. describe('nvim_win_get_option, nvim_win_set_option', function()
  254. it('works', function()
  255. curwin('set_option', 'colorcolumn', '4,3')
  256. eq('4,3', curwin('get_option', 'colorcolumn'))
  257. command("set modified hidden")
  258. command("enew") -- edit new buffer, window option is preserved
  259. eq('4,3', curwin('get_option', 'colorcolumn'))
  260. -- global-local option
  261. curwin('set_option', 'statusline', 'window-status')
  262. eq('window-status', curwin('get_option', 'statusline'))
  263. eq('', nvim('get_option', 'statusline'))
  264. command("set modified")
  265. command("enew") -- global-local: not preserved in new buffer
  266. -- confirm local value was not copied
  267. eq('', curwin('get_option', 'statusline'))
  268. eq('', eval('&l:statusline'))
  269. end)
  270. it('after switching windows #15390', function()
  271. nvim('command', 'tabnew')
  272. local tab1 = unpack(nvim('list_tabpages'))
  273. local win1 = unpack(tabpage('list_wins', tab1))
  274. window('set_option', win1, 'statusline', 'window-status')
  275. nvim('command', 'split')
  276. nvim('command', 'wincmd J')
  277. nvim('command', 'wincmd j')
  278. eq('window-status', window('get_option', win1, 'statusline'))
  279. assert_alive()
  280. end)
  281. it('returns values for unset local options', function()
  282. eq(-1, curwin('get_option', 'scrolloff'))
  283. end)
  284. end)
  285. describe('get_position', function()
  286. it('works', function()
  287. local height = window('get_height', nvim('list_wins')[1])
  288. local width = window('get_width', nvim('list_wins')[1])
  289. nvim('command', 'split')
  290. nvim('command', 'vsplit')
  291. eq({0, 0}, window('get_position', nvim('list_wins')[1]))
  292. local vsplit_pos = math.floor(width / 2)
  293. local split_pos = math.floor(height / 2)
  294. local win2row, win2col =
  295. unpack(window('get_position', nvim('list_wins')[2]))
  296. local win3row, win3col =
  297. unpack(window('get_position', nvim('list_wins')[3]))
  298. eq(0, win2row)
  299. eq(0, win3col)
  300. ok(vsplit_pos - 1 <= win2col and win2col <= vsplit_pos + 1)
  301. ok(split_pos - 1 <= win3row and win3row <= split_pos + 1)
  302. end)
  303. end)
  304. describe('get_position', function()
  305. it('works', function()
  306. nvim('command', 'tabnew')
  307. nvim('command', 'vsplit')
  308. eq(window('get_tabpage',
  309. nvim('list_wins')[1]), nvim('list_tabpages')[1])
  310. eq(window('get_tabpage',
  311. nvim('list_wins')[2]), nvim('list_tabpages')[2])
  312. eq(window('get_tabpage',
  313. nvim('list_wins')[3]), nvim('list_tabpages')[2])
  314. end)
  315. end)
  316. describe('get_number', function()
  317. it('works', function()
  318. local wins = nvim('list_wins')
  319. eq(1, window('get_number', wins[1]))
  320. nvim('command', 'split')
  321. local win1, win2 = unpack(nvim('list_wins'))
  322. eq(1, window('get_number', win1))
  323. eq(2, window('get_number', win2))
  324. nvim('command', 'wincmd J')
  325. eq(2, window('get_number', win1))
  326. eq(1, window('get_number', win2))
  327. nvim('command', 'tabnew')
  328. local win3 = nvim('list_wins')[3]
  329. -- First tab page
  330. eq(2, window('get_number', win1))
  331. eq(1, window('get_number', win2))
  332. -- Second tab page
  333. eq(1, window('get_number', win3))
  334. end)
  335. end)
  336. describe('is_valid', function()
  337. it('works', function()
  338. nvim('command', 'split')
  339. local win = nvim('list_wins')[2]
  340. nvim('set_current_win', win)
  341. ok(window('is_valid', win))
  342. nvim('command', 'close')
  343. ok(not window('is_valid', win))
  344. end)
  345. end)
  346. describe('close', function()
  347. it('can close current window', function()
  348. local oldwin = meths.get_current_win()
  349. command('split')
  350. local newwin = meths.get_current_win()
  351. meths.win_close(newwin,false)
  352. eq({oldwin}, meths.list_wins())
  353. end)
  354. it('can close noncurrent window', function()
  355. local oldwin = meths.get_current_win()
  356. command('split')
  357. local newwin = meths.get_current_win()
  358. meths.win_close(oldwin,false)
  359. eq({newwin}, meths.list_wins())
  360. end)
  361. it("handles changed buffer when 'hidden' is unset", function()
  362. command('set nohidden')
  363. local oldwin = meths.get_current_win()
  364. insert('text')
  365. command('new')
  366. local newwin = meths.get_current_win()
  367. eq("Vim:E37: No write since last change (add ! to override)",
  368. pcall_err(meths.win_close, oldwin,false))
  369. eq({newwin,oldwin}, meths.list_wins())
  370. end)
  371. it('handles changed buffer with force', function()
  372. local oldwin = meths.get_current_win()
  373. insert('text')
  374. command('new')
  375. local newwin = meths.get_current_win()
  376. meths.win_close(oldwin,true)
  377. eq({newwin}, meths.list_wins())
  378. end)
  379. it('in cmdline-window #9767', function()
  380. command('split')
  381. eq(2, #meths.list_wins())
  382. local oldwin = meths.get_current_win()
  383. -- Open cmdline-window.
  384. feed('q:')
  385. eq(3, #meths.list_wins())
  386. eq(':', funcs.getcmdwintype())
  387. -- Vim: not allowed to close other windows from cmdline-window.
  388. eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  389. pcall_err(meths.win_close, oldwin, true))
  390. -- Close cmdline-window.
  391. meths.win_close(0,true)
  392. eq(2, #meths.list_wins())
  393. eq('', funcs.getcmdwintype())
  394. end)
  395. it('closing current (float) window of another tabpage #15313', function()
  396. command('tabedit')
  397. eq(2, eval('tabpagenr()'))
  398. local win = meths.open_win(0, true, {
  399. relative='editor', row=10, col=10, width=50, height=10
  400. })
  401. local tab = eval('tabpagenr()')
  402. command('tabprevious')
  403. eq(1, eval('tabpagenr()'))
  404. meths.win_close(win, false)
  405. eq(1001, meths.tabpage_get_win(tab).id)
  406. assert_alive()
  407. end)
  408. end)
  409. describe('hide', function()
  410. it('can hide current window', function()
  411. local oldwin = meths.get_current_win()
  412. command('split')
  413. local newwin = meths.get_current_win()
  414. meths.win_hide(newwin)
  415. eq({oldwin}, meths.list_wins())
  416. end)
  417. it('can hide noncurrent window', function()
  418. local oldwin = meths.get_current_win()
  419. command('split')
  420. local newwin = meths.get_current_win()
  421. meths.win_hide(oldwin)
  422. eq({newwin}, meths.list_wins())
  423. end)
  424. it('does not close the buffer', function()
  425. local oldwin = meths.get_current_win()
  426. local oldbuf = meths.get_current_buf()
  427. local buf = meths.create_buf(true, false)
  428. local newwin = meths.open_win(buf, true, {
  429. relative='win', row=3, col=3, width=12, height=3
  430. })
  431. meths.win_hide(newwin)
  432. eq({oldwin}, meths.list_wins())
  433. eq({oldbuf, buf}, meths.list_bufs())
  434. end)
  435. it('deletes the buffer when bufhidden=wipe', function()
  436. local oldwin = meths.get_current_win()
  437. local oldbuf = meths.get_current_buf()
  438. local buf = meths.create_buf(true, false)
  439. local newwin = meths.open_win(buf, true, {
  440. relative='win', row=3, col=3, width=12, height=3
  441. })
  442. meths.buf_set_option(buf, 'bufhidden', 'wipe')
  443. meths.win_hide(newwin)
  444. eq({oldwin}, meths.list_wins())
  445. eq({oldbuf}, meths.list_bufs())
  446. end)
  447. end)
  448. describe('open_win', function()
  449. it('noautocmd option works', function()
  450. command('autocmd BufEnter,BufLeave,BufWinEnter * let g:fired = 1')
  451. meths.open_win(meths.create_buf(true, true), true, {
  452. relative='win', row=3, col=3, width=12, height=3, noautocmd=true
  453. })
  454. eq(0, funcs.exists('g:fired'))
  455. meths.open_win(meths.create_buf(true, true), true, {
  456. relative='win', row=3, col=3, width=12, height=3
  457. })
  458. eq(1, funcs.exists('g:fired'))
  459. end)
  460. end)
  461. describe('get_config', function()
  462. it('includes border', function()
  463. local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }
  464. local win = meths.open_win(0, true, {
  465. relative='win', row=3, col=3, width=12, height=3,
  466. border = b,
  467. })
  468. local cfg = meths.win_get_config(win)
  469. eq(b, cfg.border)
  470. end)
  471. it('includes border with highlight group', function()
  472. local b = {
  473. {'a', 'Normal'},
  474. {'b', 'Special'},
  475. {'c', 'String'},
  476. {'d', 'Comment'},
  477. {'e', 'Visual'},
  478. {'f', 'Error'},
  479. {'g', 'Constant'},
  480. {'h', 'PreProc'},
  481. }
  482. local win = meths.open_win(0, true, {
  483. relative='win', row=3, col=3, width=12, height=3,
  484. border = b,
  485. })
  486. local cfg = meths.win_get_config(win)
  487. eq(b, cfg.border)
  488. end)
  489. end)
  490. end)