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