commands_spec.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. -- Test suite for checking :lua* commands
  2. local t = require('test.testutil')
  3. local n = require('test.functional.testnvim')()
  4. local Screen = require('test.functional.ui.screen')
  5. local eq = t.eq
  6. local NIL = vim.NIL
  7. local eval = n.eval
  8. local feed = n.feed
  9. local clear = n.clear
  10. local matches = t.matches
  11. local api = n.api
  12. local exec_lua = n.exec_lua
  13. local exec_capture = n.exec_capture
  14. local fn = n.fn
  15. local source = n.source
  16. local dedent = t.dedent
  17. local command = n.command
  18. local exc_exec = n.exc_exec
  19. local pcall_err = t.pcall_err
  20. local write_file = t.write_file
  21. local remove_trace = t.remove_trace
  22. before_each(clear)
  23. describe(':lua', function()
  24. it('works', function()
  25. eq('', exec_capture('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TEST"})'))
  26. eq({ '', 'TEST' }, api.nvim_buf_get_lines(0, 0, 100, false))
  27. source([[
  28. lua << EOF
  29. vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TSET"})
  30. EOF]])
  31. eq({ '', 'TSET' }, api.nvim_buf_get_lines(0, 0, 100, false))
  32. source([[
  33. lua << EOF
  34. vim.api.nvim_buf_set_lines(1, 1, 2, false, {"SETT"})]])
  35. eq({ '', 'SETT' }, api.nvim_buf_get_lines(0, 0, 100, false))
  36. source([[
  37. lua << EOF
  38. vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
  39. vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
  40. vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
  41. EOF]])
  42. eq({ '', 'ETTS', 'TTSE', 'STTE' }, api.nvim_buf_get_lines(0, 0, 100, false))
  43. matches(
  44. '.*Vim%(lua%):E15: Invalid expression: .*',
  45. pcall_err(
  46. source,
  47. [[
  48. lua << eval EOF
  49. {}
  50. EOF
  51. ]]
  52. )
  53. )
  54. end)
  55. it('throws catchable errors', function()
  56. eq('Vim(lua):E471: Argument required', pcall_err(command, 'lua'))
  57. eq(
  58. [[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']],
  59. pcall_err(command, 'lua ()')
  60. )
  61. eq(
  62. [[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]],
  63. remove_trace(exc_exec('lua error("TEST")'))
  64. )
  65. eq(
  66. [[Vim(lua):E5108: Error executing lua [string ":lua"]:1: Invalid buffer id: -10]],
  67. remove_trace(exc_exec('lua vim.api.nvim_buf_set_lines(-10, 1, 1, false, {"TEST"})'))
  68. )
  69. eq({ '' }, api.nvim_buf_get_lines(0, 0, 100, false))
  70. end)
  71. it('works with NULL errors', function()
  72. eq([=[Vim(lua):E5108: Error executing lua [NULL]]=], exc_exec('lua error(nil)'))
  73. end)
  74. it('accepts embedded NLs without heredoc', function()
  75. -- Such code is usually used for `:execute 'lua' {generated_string}`:
  76. -- heredocs do not work in this case.
  77. command([[
  78. lua
  79. vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
  80. vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
  81. vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
  82. ]])
  83. eq({ '', 'ETTS', 'TTSE', 'STTE' }, api.nvim_buf_get_lines(0, 0, 100, false))
  84. end)
  85. it('preserves global and not preserves local variables', function()
  86. eq('', exec_capture('lua gvar = 42'))
  87. eq('', exec_capture('lua local lvar = 100500'))
  88. eq(NIL, fn.luaeval('lvar'))
  89. eq(42, fn.luaeval('gvar'))
  90. end)
  91. it('works with long strings', function()
  92. local s = ('x'):rep(100500)
  93. eq(
  94. 'Vim(lua):E5107: Error loading lua [string ":lua"]:0: unfinished string near \'<eof>\'',
  95. pcall_err(command, ('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s))
  96. )
  97. eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, false))
  98. eq('', exec_capture(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s)))
  99. eq({ '', s }, api.nvim_buf_get_lines(0, 0, -1, false))
  100. end)
  101. it('can show multiline error messages', function()
  102. local screen = Screen.new(40, 10)
  103. screen:set_default_attr_ids({
  104. [1] = { bold = true, foreground = Screen.colors.Blue1 },
  105. [2] = { bold = true, reverse = true },
  106. [3] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
  107. [4] = { bold = true, foreground = Screen.colors.SeaGreen4 },
  108. })
  109. feed(':lua error("fail\\nmuch error\\nsuch details")<cr>')
  110. screen:expect {
  111. grid = [[
  112. {2: }|
  113. {3:E5108: Error executing lua [string ":lua}|
  114. {3:"]:1: fail} |
  115. {3:much error} |
  116. {3:such details} |
  117. {3:stack traceback:} |
  118. {3: [C]: in function 'error'} |
  119. {3: [string ":lua"]:1: in main chunk}|
  120. |
  121. {4:Press ENTER or type command to continue}^ |
  122. ]],
  123. }
  124. feed('<cr>')
  125. screen:expect {
  126. grid = [[
  127. ^ |
  128. {1:~ }|*8
  129. |
  130. ]],
  131. }
  132. eq(
  133. 'E5108: Error executing lua [string ":lua"]:1: fail\nmuch error\nsuch details',
  134. remove_trace(eval('v:errmsg'))
  135. )
  136. local status, err = pcall(command, 'lua error("some error\\nin a\\nAPI command")')
  137. local expected =
  138. 'Vim(lua):E5108: Error executing lua [string ":lua"]:1: some error\nin a\nAPI command'
  139. eq(false, status)
  140. eq(expected, string.sub(remove_trace(err), -string.len(expected)))
  141. feed(':messages<cr>')
  142. screen:expect {
  143. grid = [[
  144. {2: }|
  145. {3:E5108: Error executing lua [string ":lua}|
  146. {3:"]:1: fail} |
  147. {3:much error} |
  148. {3:such details} |
  149. {3:stack traceback:} |
  150. {3: [C]: in function 'error'} |
  151. {3: [string ":lua"]:1: in main chunk}|
  152. |
  153. {4:Press ENTER or type command to continue}^ |
  154. ]],
  155. }
  156. end)
  157. it('prints result of =expr', function()
  158. exec_lua('x = 5')
  159. eq('5', exec_capture(':lua =x'))
  160. eq('5', exec_capture('=x'))
  161. exec_lua("function x() return 'hello' end")
  162. eq('hello', exec_capture(':lua = x()'))
  163. exec_lua('x = {a = 1, b = 2}')
  164. eq('{\n a = 1,\n b = 2\n}', exec_capture(':lua =x'))
  165. exec_lua(function()
  166. function _G.x(success)
  167. if success then
  168. return true, 'Return value'
  169. else
  170. return false, nil, 'Error message'
  171. end
  172. end
  173. end)
  174. eq(
  175. dedent [[
  176. true
  177. Return value]],
  178. exec_capture(':lua =x(true)')
  179. )
  180. eq(
  181. dedent [[
  182. false
  183. nil
  184. Error message]],
  185. exec_capture('=x(false)')
  186. )
  187. end)
  188. it('with range', function()
  189. local screen = Screen.new(40, 10)
  190. api.nvim_buf_set_lines(0, 0, 0, 0, { 'nonsense', 'function x() print "hello" end', 'x()' })
  191. -- ":{range}lua" fails on invalid Lua code.
  192. eq(
  193. [[:{range}lua: Vim(lua):E5107: Error loading lua [string ":{range}lua"]:0: '=' expected near '<eof>']],
  194. pcall_err(command, '1lua')
  195. )
  196. -- ":{range}lua" executes valid Lua code.
  197. feed(':2,3lua<CR>')
  198. screen:expect {
  199. grid = [[
  200. nonsense |
  201. function x() print "hello" end |
  202. x() |
  203. ^ |
  204. {1:~ }|*5
  205. hello |
  206. ]],
  207. attr_ids = {
  208. [1] = { foreground = Screen.colors.Blue, bold = true },
  209. },
  210. }
  211. -- ":{range}lua {code}" executes {code}, ignoring {range}
  212. eq('', exec_capture('1lua gvar = 42'))
  213. eq(42, fn.luaeval('gvar'))
  214. end)
  215. end)
  216. describe(':luado command', function()
  217. it('works', function()
  218. api.nvim_buf_set_lines(0, 0, 1, false, { 'ABC', 'def', 'gHi' })
  219. eq('', exec_capture('luado lines = (lines or {}) lines[#lines + 1] = {linenr, line}'))
  220. eq({ 'ABC', 'def', 'gHi' }, api.nvim_buf_get_lines(0, 0, -1, false))
  221. eq({ { 1, 'ABC' }, { 2, 'def' }, { 3, 'gHi' } }, fn.luaeval('lines'))
  222. -- Automatic transformation of numbers
  223. eq('', exec_capture('luado return linenr'))
  224. eq({ '1', '2', '3' }, api.nvim_buf_get_lines(0, 0, -1, false))
  225. eq('', exec_capture('luado return ("<%02x>"):format(line:byte())'))
  226. eq({ '<31>', '<32>', '<33>' }, api.nvim_buf_get_lines(0, 0, -1, false))
  227. end)
  228. it('stops processing lines when suddenly out of lines', function()
  229. api.nvim_buf_set_lines(0, 0, 1, false, { 'ABC', 'def', 'gHi' })
  230. eq('', exec_capture('2,$luado runs = ((runs or 0) + 1) vim.api.nvim_command("%d")'))
  231. eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, false))
  232. eq(1, fn.luaeval('runs'))
  233. api.nvim_buf_set_lines(0, 0, -1, false, { 'one', 'two', 'three' })
  234. eq('', exec_capture('luado vim.api.nvim_command("%d")'))
  235. eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, false))
  236. api.nvim_buf_set_lines(0, 0, -1, false, { 'one', 'two', 'three' })
  237. eq('', exec_capture('luado vim.api.nvim_command("1,2d")'))
  238. eq({ 'three' }, api.nvim_buf_get_lines(0, 0, -1, false))
  239. api.nvim_buf_set_lines(0, 0, -1, false, { 'one', 'two', 'three' })
  240. eq('', exec_capture('luado vim.api.nvim_command("2,3d"); return "REPLACED"'))
  241. eq({ 'REPLACED' }, api.nvim_buf_get_lines(0, 0, -1, false))
  242. api.nvim_buf_set_lines(0, 0, -1, false, { 'one', 'two', 'three' })
  243. eq('', exec_capture('2,3luado vim.api.nvim_command("1,2d"); return "REPLACED"'))
  244. eq({ 'three' }, api.nvim_buf_get_lines(0, 0, -1, false))
  245. end)
  246. it('fails on errors', function()
  247. eq(
  248. [[Vim(luado):E5109: Error loading lua: [string ":luado"]:0: unexpected symbol near ')']],
  249. pcall_err(command, 'luado ()')
  250. )
  251. eq(
  252. [[Vim(luado):E5111: Error calling lua: [string ":luado"]:0: attempt to perform arithmetic on global 'liness' (a nil value)]],
  253. pcall_err(command, 'luado return liness + 1')
  254. )
  255. end)
  256. it('works with NULL errors', function()
  257. eq([=[Vim(luado):E5111: Error calling lua: [NULL]]=], exc_exec('luado error(nil)'))
  258. end)
  259. it('fails in sandbox when needed', function()
  260. api.nvim_buf_set_lines(0, 0, 1, false, { 'ABC', 'def', 'gHi' })
  261. eq(
  262. 'Vim(luado):E48: Not allowed in sandbox: sandbox luado runs = (runs or 0) + 1',
  263. pcall_err(command, 'sandbox luado runs = (runs or 0) + 1')
  264. )
  265. eq(NIL, fn.luaeval('runs'))
  266. end)
  267. it('works with long strings', function()
  268. local s = ('x'):rep(100500)
  269. eq(
  270. 'Vim(luado):E5109: Error loading lua: [string ":luado"]:0: unfinished string near \'<eof>\'',
  271. pcall_err(command, ('luado return "%s'):format(s))
  272. )
  273. eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, false))
  274. eq('', exec_capture(('luado return "%s"'):format(s)))
  275. eq({ s }, api.nvim_buf_get_lines(0, 0, -1, false))
  276. end)
  277. end)
  278. describe(':luafile', function()
  279. local fname = 'Xtest-functional-lua-commands-luafile'
  280. after_each(function()
  281. os.remove(fname)
  282. end)
  283. it('works', function()
  284. write_file(
  285. fname,
  286. [[
  287. vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
  288. vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
  289. vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
  290. ]]
  291. )
  292. eq('', exec_capture('luafile ' .. fname))
  293. eq({ '', 'ETTS', 'TTSE', 'STTE' }, api.nvim_buf_get_lines(0, 0, 100, false))
  294. end)
  295. it('correctly errors out', function()
  296. write_file(fname, '()')
  297. eq(
  298. ("Vim(luafile):E5112: Error while creating lua chunk: %s:1: unexpected symbol near ')'"):format(
  299. fname
  300. ),
  301. exc_exec('luafile ' .. fname)
  302. )
  303. write_file(fname, 'vimm.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})')
  304. eq(
  305. ("Vim(luafile):E5113: Error while calling lua chunk: %s:1: attempt to index global 'vimm' (a nil value)"):format(
  306. fname
  307. ),
  308. remove_trace(exc_exec('luafile ' .. fname))
  309. )
  310. end)
  311. it('works with NULL errors', function()
  312. write_file(fname, 'error(nil)')
  313. eq(
  314. [=[Vim(luafile):E5113: Error while calling lua chunk: [NULL]]=],
  315. exc_exec('luafile ' .. fname)
  316. )
  317. end)
  318. end)