api_spec.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. -- Test suite for testing interactions with API bindings
  2. local t = require('test.testutil')
  3. local n = require('test.functional.testnvim')()
  4. local exc_exec = n.exc_exec
  5. local remove_trace = t.remove_trace
  6. local fn = n.fn
  7. local clear = n.clear
  8. local eval = n.eval
  9. local NIL = vim.NIL
  10. local eq = t.eq
  11. local exec_lua = n.exec_lua
  12. local pcall_err = t.pcall_err
  13. before_each(clear)
  14. describe('luaeval(vim.api.…)', function()
  15. describe('with channel_id and buffer handle', function()
  16. describe('nvim_buf_get_lines', function()
  17. it('works', function()
  18. fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' })
  19. eq({ 'a\000b' }, fn.luaeval('vim.api.nvim_buf_get_lines(1, 2, 3, false)'))
  20. end)
  21. end)
  22. describe('nvim_buf_set_lines', function()
  23. it('works', function()
  24. fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' })
  25. eq(NIL, fn.luaeval('vim.api.nvim_buf_set_lines(1, 1, 2, false, {"b\\0a"})'))
  26. eq(
  27. { 'abc', 'b\000a', 'a\000b', 'ttt' },
  28. fn.luaeval('vim.api.nvim_buf_get_lines(1, 0, 4, false)')
  29. )
  30. end)
  31. end)
  32. end)
  33. describe('with errors', function()
  34. it('transforms API error from nvim_buf_set_lines into lua error', function()
  35. fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' })
  36. eq(
  37. { false, "'replacement string' item contains newlines" },
  38. fn.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}')
  39. )
  40. end)
  41. it('transforms API error from nvim_win_set_cursor into lua error', function()
  42. eq(
  43. { false, 'Argument "pos" must be a [row, col] array' },
  44. fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}')
  45. )
  46. -- Used to produce a memory leak due to a bug in nvim_win_set_cursor
  47. eq(
  48. { false, 'Invalid window id: -1' },
  49. fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}')
  50. )
  51. end)
  52. it(
  53. 'transforms API error from nvim_win_set_cursor + same array as in first test into lua error',
  54. function()
  55. eq(
  56. { false, 'Argument "pos" must be a [row, col] array' },
  57. fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {"b\\na"})}')
  58. )
  59. end
  60. )
  61. end)
  62. it('correctly evaluates API code which calls luaeval', function()
  63. local str = (
  64. ([===[vim.api.nvim_eval([==[
  65. luaeval('vim.api.nvim_eval([=[
  66. luaeval("vim.api.nvim_eval([[
  67. luaeval(1)
  68. ]])")
  69. ]=])')
  70. ]==])]===]):gsub('\n', ' ')
  71. )
  72. eq(1, fn.luaeval(str))
  73. end)
  74. it('correctly converts from API objects', function()
  75. eq(1, fn.luaeval('vim.api.nvim_eval("1")'))
  76. eq('1', fn.luaeval([[vim.api.nvim_eval('"1"')]]))
  77. eq('Blobby', fn.luaeval('vim.api.nvim_eval("0z426c6f626279")'))
  78. eq({}, fn.luaeval('vim.api.nvim_eval("[]")'))
  79. eq({}, fn.luaeval('vim.api.nvim_eval("{}")'))
  80. eq(1, fn.luaeval('vim.api.nvim_eval("1.0")'))
  81. eq('\000', fn.luaeval('vim.api.nvim_eval("0z00")'))
  82. eq(true, fn.luaeval('vim.api.nvim_eval("v:true")'))
  83. eq(false, fn.luaeval('vim.api.nvim_eval("v:false")'))
  84. eq(NIL, fn.luaeval('vim.api.nvim_eval("v:null")'))
  85. eq(0, eval([[type(luaeval('vim.api.nvim_eval("1")'))]]))
  86. eq(1, eval([[type(luaeval('vim.api.nvim_eval("''1''")'))]]))
  87. eq(1, eval([[type(luaeval('vim.api.nvim_eval("0zbeef")'))]]))
  88. eq(3, eval([[type(luaeval('vim.api.nvim_eval("[]")'))]]))
  89. eq(4, eval([[type(luaeval('vim.api.nvim_eval("{}")'))]]))
  90. eq(5, eval([[type(luaeval('vim.api.nvim_eval("1.0")'))]]))
  91. eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:true")'))]]))
  92. eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:false")'))]]))
  93. eq(7, eval([[type(luaeval('vim.api.nvim_eval("v:null")'))]]))
  94. eq({ foo = 42 }, fn.luaeval([[vim.api.nvim_eval('{"foo": 42}')]]))
  95. eq({ 42 }, fn.luaeval([[vim.api.nvim_eval('[42]')]]))
  96. eq(
  97. { foo = { bar = 42 }, baz = 50 },
  98. fn.luaeval([[vim.api.nvim_eval('{"foo": {"bar": 42}, "baz": 50}')]])
  99. )
  100. eq({ { 42 }, {} }, fn.luaeval([=[vim.api.nvim_eval('[[42], []]')]=]))
  101. end)
  102. it('correctly converts to API objects', function()
  103. eq(1, fn.luaeval('vim.api.nvim__id(1)'))
  104. eq('1', fn.luaeval('vim.api.nvim__id("1")'))
  105. eq({ 1 }, fn.luaeval('vim.api.nvim__id({1})'))
  106. eq({ foo = 1 }, fn.luaeval('vim.api.nvim__id({foo=1})'))
  107. eq(1.5, fn.luaeval('vim.api.nvim__id(1.5)'))
  108. eq(true, fn.luaeval('vim.api.nvim__id(true)'))
  109. eq(false, fn.luaeval('vim.api.nvim__id(false)'))
  110. eq(NIL, fn.luaeval('vim.api.nvim__id(nil)'))
  111. -- API strings from Blobs can work as NUL-terminated C strings
  112. eq(
  113. 'Vim(call):E5555: API call: Vim:E15: Invalid expression: ""',
  114. exc_exec('call nvim_eval(v:_null_blob)')
  115. )
  116. eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ""', exc_exec('call nvim_eval(0z)'))
  117. eq(1, eval('nvim_eval(0z31)'))
  118. eq(0, eval([[type(luaeval('vim.api.nvim__id(1)'))]]))
  119. eq(1, eval([[type(luaeval('vim.api.nvim__id("1")'))]]))
  120. eq(3, eval([[type(luaeval('vim.api.nvim__id({1})'))]]))
  121. eq(4, eval([[type(luaeval('vim.api.nvim__id({foo=1})'))]]))
  122. eq(5, eval([[type(luaeval('vim.api.nvim__id(1.5)'))]]))
  123. eq(6, eval([[type(luaeval('vim.api.nvim__id(true)'))]]))
  124. eq(6, eval([[type(luaeval('vim.api.nvim__id(false)'))]]))
  125. eq(7, eval([[type(luaeval('vim.api.nvim__id(nil)'))]]))
  126. eq(
  127. { foo = 1, bar = { 42, { { baz = true }, 5 } } },
  128. fn.luaeval('vim.api.nvim__id({foo=1, bar={42, {{baz=true}, 5}}})')
  129. )
  130. eq(true, fn.luaeval('vim.api.nvim__id(vim.api.nvim__id)(true)'))
  131. eq(
  132. 42,
  133. exec_lua(function()
  134. local f = vim.api.nvim__id({ 42, vim.api.nvim__id })
  135. return f[2](f[1])
  136. end)
  137. )
  138. end)
  139. it('correctly converts container objects with type_idx to API objects', function()
  140. eq(
  141. 5,
  142. eval('type(luaeval("vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=0})"))')
  143. )
  144. eq(4, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary})'))]]))
  145. eq(3, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})'))]]))
  146. eq({}, fn.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})'))
  147. -- Presence of type_idx makes Vim ignore some keys
  148. eq(
  149. { 42 },
  150. fn.luaeval(
  151. 'vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'
  152. )
  153. )
  154. eq(
  155. { foo = 2 },
  156. fn.luaeval(
  157. 'vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'
  158. )
  159. )
  160. eq(
  161. 10,
  162. fn.luaeval(
  163. 'vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'
  164. )
  165. )
  166. eq(
  167. {},
  168. fn.luaeval(
  169. 'vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})'
  170. )
  171. )
  172. end)
  173. it('correctly converts arrays with type_idx to API objects', function()
  174. eq(3, eval([[type(luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})'))]]))
  175. eq({}, fn.luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})'))
  176. eq(
  177. { 42 },
  178. fn.luaeval(
  179. 'vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'
  180. )
  181. )
  182. eq(
  183. { { foo = 2 } },
  184. fn.luaeval(
  185. 'vim.api.nvim__id_array({{[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'
  186. )
  187. )
  188. eq(
  189. { 10 },
  190. fn.luaeval(
  191. 'vim.api.nvim__id_array({{[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'
  192. )
  193. )
  194. eq(
  195. {},
  196. fn.luaeval(
  197. 'vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})'
  198. )
  199. )
  200. eq({}, fn.luaeval('vim.api.nvim__id_array({})'))
  201. eq(3, eval([[type(luaeval('vim.api.nvim__id_array({})'))]]))
  202. end)
  203. it('correctly converts dictionaries with type_idx to API objects', function()
  204. eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})'))]]))
  205. eq({}, fn.luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})'))
  206. eq(
  207. { v = { 42 } },
  208. fn.luaeval(
  209. 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'
  210. )
  211. )
  212. eq(
  213. { foo = 2 },
  214. fn.luaeval(
  215. 'vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})'
  216. )
  217. )
  218. eq(
  219. { v = 10 },
  220. fn.luaeval(
  221. 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})'
  222. )
  223. )
  224. eq(
  225. { v = {} },
  226. fn.luaeval(
  227. 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2}})'
  228. )
  229. )
  230. -- If API requests dict, then empty table will be the one. This is not
  231. -- the case normally because empty table is an empty array.
  232. eq({}, fn.luaeval('vim.api.nvim__id_dict({})'))
  233. eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({})'))]]))
  234. end)
  235. it('converts booleans in positional args', function()
  236. eq({ '' }, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, false) ]])
  237. eq({ '' }, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, nil) ]])
  238. eq(
  239. 'Index out of bounds',
  240. pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, true) ]])
  241. )
  242. eq(
  243. 'Index out of bounds',
  244. pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 1) ]])
  245. )
  246. -- this follows lua conventions for bools (not api convention for Boolean)
  247. eq(
  248. 'Index out of bounds',
  249. pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 0) ]])
  250. )
  251. eq(
  252. 'Index out of bounds',
  253. pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, {}) ]])
  254. )
  255. end)
  256. it('converts booleans in optional args', function()
  257. eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=false}) ]])
  258. eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {}) ]]) -- same as {output=nil}
  259. -- API conventions (not lua conventions): zero is falsy
  260. eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=0}) ]])
  261. eq(
  262. { output = 'foobar' },
  263. exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=true}) ]]
  264. )
  265. eq({ output = 'foobar' }, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=1}) ]])
  266. eq(
  267. [[Invalid 'output': not a boolean]],
  268. pcall_err(exec_lua, [[ return vim.api.nvim_exec2("echo 'foobar'", {output={}}) ]])
  269. )
  270. end)
  271. it('errors out correctly when working with API', function()
  272. -- Conversion errors
  273. eq(
  274. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'obj': Cannot convert given Lua table]],
  275. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]]))
  276. )
  277. -- Errors in number of arguments
  278. eq(
  279. 'Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
  280. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id()")]]))
  281. )
  282. eq(
  283. 'Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
  284. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]]))
  285. )
  286. eq(
  287. 'Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 2 arguments',
  288. remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]]))
  289. )
  290. -- Error in argument types
  291. eq(
  292. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'name': Expected Lua string]],
  293. remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]]))
  294. )
  295. eq(
  296. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'start': Expected Lua number]],
  297. remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]]))
  298. )
  299. eq(
  300. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'start': Number is not integral]],
  301. remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]]))
  302. )
  303. eq(
  304. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'window': Expected Lua number]],
  305. remove_trace(exc_exec([[call luaeval("vim.api.nvim_win_is_valid(nil)")]]))
  306. )
  307. eq(
  308. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'flt': Expected Lua number]],
  309. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]]))
  310. )
  311. eq(
  312. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'flt': Expected Float-like Lua table]],
  313. remove_trace(
  314. exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]])
  315. )
  316. )
  317. eq(
  318. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'arr': Expected Lua table]],
  319. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]]))
  320. )
  321. eq(
  322. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'arr': Expected Array-like Lua table]],
  323. remove_trace(
  324. exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]])
  325. )
  326. )
  327. eq(
  328. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Lua table]],
  329. remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dict(1)")]]))
  330. )
  331. eq(
  332. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Dict-like Lua table]],
  333. remove_trace(
  334. exc_exec([[call luaeval("vim.api.nvim__id_dict({[vim.type_idx]=vim.types.array})")]])
  335. )
  336. )
  337. eq(
  338. [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected Lua table]],
  339. remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_keymap('', '', '', '')")]]))
  340. )
  341. -- TODO: check for errors with Tabpage argument
  342. -- TODO: check for errors with Window argument
  343. -- TODO: check for errors with Buffer argument
  344. end)
  345. it('accepts any value as API Boolean', function()
  346. eq('', fn.luaeval('vim.api.nvim_replace_termcodes("", vim, false, nil)'))
  347. eq('', fn.luaeval('vim.api.nvim_replace_termcodes("", 0, 1.5, "test")'))
  348. eq(
  349. '',
  350. fn.luaeval('vim.api.nvim_replace_termcodes("", true, {}, {[vim.type_idx]=vim.types.array})')
  351. )
  352. end)
  353. it('serializes sparse arrays in Lua', function()
  354. eq({ [1] = vim.NIL, [2] = 2 }, exec_lua [[ return { [2] = 2 } ]])
  355. end)
  356. end)