ui_event_spec.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local eq = t.eq
  5. local exec_lua = n.exec_lua
  6. local clear = n.clear
  7. local feed = n.feed
  8. local fn = n.fn
  9. local assert_log = t.assert_log
  10. local check_close = n.check_close
  11. local testlog = 'Xtest_lua_ui_event_log'
  12. describe('vim.ui_attach', function()
  13. local screen
  14. before_each(function()
  15. clear()
  16. exec_lua [[
  17. ns = vim.api.nvim_create_namespace 'testspace'
  18. events = {}
  19. function on_event(event, ...)
  20. events[#events+1] = {event, ...}
  21. return true
  22. end
  23. function get_events()
  24. local ret_events = events
  25. events = {}
  26. return ret_events
  27. end
  28. ]]
  29. screen = Screen.new(40, 5)
  30. end)
  31. local function expect_events(expected)
  32. local evs = exec_lua 'return get_events(...)'
  33. eq(expected, evs, vim.inspect(evs))
  34. end
  35. it('can receive popupmenu events', function()
  36. exec_lua [[ vim.ui_attach(ns, {ext_popupmenu=true}, on_event) ]]
  37. feed('ifo')
  38. screen:expect {
  39. grid = [[
  40. fo^ |
  41. {1:~ }|*3
  42. {5:-- INSERT --} |
  43. ]],
  44. }
  45. fn.complete(1, { 'food', 'foobar', 'foo' })
  46. screen:expect {
  47. grid = [[
  48. food^ |
  49. {1:~ }|*3
  50. {5:-- INSERT --} |
  51. ]],
  52. }
  53. expect_events {
  54. {
  55. 'popupmenu_show',
  56. { { 'food', '', '', '' }, { 'foobar', '', '', '' }, { 'foo', '', '', '' } },
  57. 0,
  58. 0,
  59. 0,
  60. 1,
  61. },
  62. }
  63. feed '<c-n>'
  64. screen:expect {
  65. grid = [[
  66. foobar^ |
  67. {1:~ }|*3
  68. {5:-- INSERT --} |
  69. ]],
  70. }
  71. expect_events {
  72. { 'popupmenu_select', 1 },
  73. }
  74. feed '<c-y>'
  75. screen:expect_unchanged()
  76. expect_events {
  77. { 'popupmenu_hide' },
  78. }
  79. -- vim.ui_detach() stops events, and reenables builtin pum immediately
  80. exec_lua [[
  81. vim.ui_detach(ns)
  82. vim.fn.complete(1, {'food', 'foobar', 'foo'})
  83. ]]
  84. screen:expect {
  85. grid = [[
  86. food^ |
  87. {12:food }{1: }|
  88. {4:foobar }{1: }|
  89. {4:foo }{1: }|
  90. {5:-- INSERT --} |
  91. ]],
  92. }
  93. expect_events {}
  94. end)
  95. it('does not crash on exit', function()
  96. fn.system({
  97. n.nvim_prog,
  98. '-u',
  99. 'NONE',
  100. '-i',
  101. 'NONE',
  102. '--cmd',
  103. [[ lua ns = vim.api.nvim_create_namespace 'testspace' ]],
  104. '--cmd',
  105. [[ lua vim.ui_attach(ns, {ext_popupmenu=true}, function() end) ]],
  106. '--cmd',
  107. 'quitall!',
  108. })
  109. eq(0, n.eval('v:shell_error'))
  110. end)
  111. it('can receive accurate message kinds even if they are history', function()
  112. exec_lua([[
  113. vim.cmd.echomsg("'message1'")
  114. print('message2')
  115. vim.ui_attach(ns, { ext_messages = true }, on_event)
  116. vim.cmd.echomsg("'message3'")
  117. ]])
  118. feed(':messages<cr>')
  119. feed('<cr>')
  120. local actual = exec_lua([[
  121. return vim.tbl_filter(function (event)
  122. return event[1] == "msg_history_show"
  123. end, events)
  124. ]])
  125. eq({
  126. {
  127. 'msg_history_show',
  128. {
  129. { 'echomsg', { { 0, 'message1', 0 } } },
  130. { 'lua_print', { { 0, 'message2', 0 } } },
  131. { 'echomsg', { { 0, 'message3', 0 } } },
  132. },
  133. },
  134. }, actual, vim.inspect(actual))
  135. end)
  136. it('ui_refresh() activates correct capabilities without remote UI', function()
  137. screen:detach()
  138. exec_lua('vim.ui_attach(ns, { ext_cmdline = true }, on_event)')
  139. eq(1, n.api.nvim_get_option_value('cmdheight', {}))
  140. exec_lua('vim.ui_detach(ns)')
  141. exec_lua('vim.ui_attach(ns, { ext_messages = true }, on_event)')
  142. n.api.nvim_set_option_value('cmdheight', 1, {})
  143. screen:attach()
  144. eq(1, n.api.nvim_get_option_value('cmdheight', {}))
  145. end)
  146. it("ui_refresh() sets 'cmdheight' for all open tabpages with ext_messages", function()
  147. exec_lua('vim.cmd.tabnew()')
  148. exec_lua('vim.ui_attach(ns, { ext_messages = true }, on_event)')
  149. exec_lua('vim.cmd.tabnext()')
  150. eq(0, n.api.nvim_get_option_value('cmdheight', {}))
  151. end)
  152. it('avoids recursive flushing and invalid memory access with :redraw', function()
  153. exec_lua([[
  154. _G.cmdline = 0
  155. vim.ui_attach(ns, { ext_messages = true }, function(ev)
  156. if ev == 'msg_show' then
  157. vim.schedule(function() vim.cmd.redraw() end)
  158. else
  159. vim.cmd.redraw()
  160. end
  161. _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0)
  162. end
  163. )]])
  164. feed(':')
  165. n.assert_alive()
  166. eq(2, exec_lua('return _G.cmdline'))
  167. n.assert_alive()
  168. feed('version<CR><CR>v<Esc>')
  169. n.assert_alive()
  170. end)
  171. it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function()
  172. exec_lua([[
  173. vim.cmd.norm('ifoobar')
  174. vim.cmd('1split cmdline')
  175. local buf = vim.api.nvim_get_current_buf()
  176. vim.cmd.wincmd('p')
  177. vim.ui_attach(ns, { ext_cmdline = true }, function(event, ...)
  178. if event == 'cmdline_show' then
  179. local content = select(1, ...)
  180. vim.api.nvim_buf_set_lines(buf, -2, -1, false, {content[1][2]})
  181. vim.cmd('redraw')
  182. end
  183. return true
  184. end)
  185. ]])
  186. -- Updates a cmdline window
  187. feed(':cmdline')
  188. screen:expect({
  189. grid = [[
  190. cmdline |
  191. {2:cmdline [+] }|
  192. fooba^r |
  193. {3:[No Name] [+] }|
  194. |
  195. ]],
  196. })
  197. -- Does not clear 'incsearch' highlighting
  198. feed('<Esc>/foo')
  199. screen:expect({
  200. grid = [[
  201. foo |
  202. {2:cmdline [+] }|
  203. {2:foo}ba^r |
  204. {3:[No Name] [+] }|
  205. |
  206. ]],
  207. })
  208. -- Shows new cmdline state during 'inccommand'
  209. feed('<Esc>:%s/bar/baz')
  210. screen:expect({
  211. grid = [[
  212. %s/bar/baz |
  213. {2:cmdline [+] }|
  214. foo{10:ba^z} |
  215. {3:[No Name] [+] }|
  216. |
  217. ]],
  218. })
  219. end)
  220. it('msg_show in fast context', function()
  221. exec_lua([[
  222. vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
  223. if event == "msg_show" then
  224. vim.api.nvim_get_runtime_file("foo", false)
  225. -- non-"fast-api" is not allowed in msg_show callback and should be scheduled
  226. local _, err = pcall(vim.api.nvim_buf_set_lines, 0, -2, -1, false, { content[1][2] })
  227. pcall(vim.api.nvim__redraw, { flush = true })
  228. vim.schedule(function()
  229. vim.api.nvim_buf_set_lines(0, -2, -1, false, { content[1][2], err })
  230. end)
  231. end
  232. end)
  233. ]])
  234. -- "fast-api" does not prevent aborting :function
  235. feed(':func Foo()<cr>bar<cr>endf<cr>:func Foo()<cr>')
  236. screen:expect({
  237. grid = [[
  238. ^E122: Function Foo already exists, add !|
  239. to replace it |
  240. E5560: nvim_buf_set_lines must not be ca|
  241. lled in a fast event context |
  242. {1:~ }|
  243. ]],
  244. messages = {
  245. {
  246. content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } },
  247. kind = 'emsg',
  248. },
  249. },
  250. })
  251. -- No fast context for prompt message kinds
  252. feed(':%s/Function/Replacement/c<cr>')
  253. screen:expect({
  254. grid = [[
  255. ^E122: {10:Function} Foo already exists, add !|
  256. to replace it |
  257. replace with Replacement (y/n/a/q/l/^E/^|
  258. Y)? |
  259. {1:~ }|
  260. ]],
  261. messages = {
  262. {
  263. content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 19 } },
  264. kind = 'confirm_sub',
  265. },
  266. },
  267. })
  268. feed('<esc>:call inputlist(["Select:", "One", "Two"])<cr>')
  269. screen:expect({
  270. grid = [[
  271. E122: {10:Function} Foo already exists, add !|
  272. to replace it |
  273. Type number and <Enter> or click with th|
  274. e mouse (q or empty cancels): |
  275. {1:^~ }|
  276. ]],
  277. messages = {
  278. {
  279. content = { { 'Select:\nOne\nTwo\n' } },
  280. kind = 'list_cmd',
  281. },
  282. {
  283. content = { { 'Type number and <Enter> or click with the mouse (q or empty cancels): ' } },
  284. kind = 'number_prompt',
  285. },
  286. },
  287. })
  288. end)
  289. end)
  290. describe('vim.ui_attach', function()
  291. local screen
  292. before_each(function()
  293. clear({ env = { NVIM_LOG_FILE = testlog } })
  294. screen = Screen.new(40, 5)
  295. end)
  296. after_each(function()
  297. check_close()
  298. os.remove(testlog)
  299. end)
  300. it('error in callback is logged', function()
  301. exec_lua([[
  302. local ns = vim.api.nvim_create_namespace('testspace')
  303. vim.ui_attach(ns, { ext_popupmenu = true }, function() error(42) end)
  304. ]])
  305. feed('ifoo<CR>foobar<CR>fo<C-X><C-N>')
  306. assert_log('Error executing UI event callback: Error executing lua: .*: 42', testlog, 100)
  307. end)
  308. it('detaches after excessive errors', function()
  309. screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.SeaGreen } })
  310. exec_lua([[
  311. vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function()
  312. vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] })
  313. end)
  314. ]])
  315. screen:expect({
  316. grid = [[
  317. ^ |
  318. {1:~ }|*4
  319. ]],
  320. })
  321. feed('ifoo')
  322. screen:expect({
  323. grid = [[
  324. foo^ |
  325. {1:~ }|*4
  326. ]],
  327. showmode = { { '-- INSERT --', 5, 12 } },
  328. })
  329. feed('<esc>:1mes clear<cr>:mes<cr>')
  330. screen:expect({
  331. grid = [[
  332. foo |
  333. {3: }|
  334. {9:Excessive errors in vim.ui_attach() call}|
  335. {9:back from ns: 1.} |
  336. {100:Press ENTER or type command to continue}^ |
  337. ]],
  338. })
  339. feed('<cr>')
  340. -- Also when scheduled
  341. exec_lua([[
  342. vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function()
  343. vim.schedule(function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end)
  344. end)
  345. ]])
  346. screen:expect({
  347. any = 'fo^o',
  348. messages = {
  349. {
  350. content = {
  351. {
  352. 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>',
  353. 9,
  354. 7,
  355. },
  356. },
  357. kind = 'lua_error',
  358. },
  359. {
  360. content = {
  361. {
  362. 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>',
  363. 9,
  364. 7,
  365. },
  366. },
  367. kind = 'lua_error',
  368. },
  369. {
  370. content = { { 'Press ENTER or type command to continue', 100, 19 } },
  371. kind = 'return_prompt',
  372. },
  373. },
  374. })
  375. feed('<esc>:1mes clear<cr>:mes<cr>')
  376. screen:expect({
  377. grid = [[
  378. foo |
  379. {3: }|
  380. {9:Excessive errors in vim.ui_attach() call}|
  381. {9:back from ns: 2.} |
  382. {100:Press ENTER or type command to continue}^ |
  383. ]],
  384. })
  385. end)
  386. end)