source_spec.lua (9444B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 4 local command = n.command 5 local insert = n.insert 6 local eq = t.eq 7 local clear = n.clear 8 local api = n.api 9 local fn = n.fn 10 local feed = n.feed 11 local feed_command = n.feed_command 12 local write_file = t.write_file 13 local tmpname = t.tmpname 14 local exec = n.exec 15 local exc_exec = n.exc_exec 16 local exec_lua = n.exec_lua 17 local eval = n.eval 18 local exec_capture = n.exec_capture 19 local neq = t.neq 20 local matches = t.matches 21 local mkdir = t.mkdir 22 local rmdir = n.rmdir 23 local is_os = t.is_os 24 25 describe(':source', function() 26 before_each(function() 27 clear() 28 end) 29 30 it('sourcing a file that is deleted and recreated is consistent vim-patch:8.1.0151', function() 31 local test_file = 'Xfile.vim' 32 local other_file = 'Xfoobar' 33 local script = [[ 34 func Func() 35 endfunc 36 ]] 37 write_file(test_file, script) 38 command('source ' .. test_file) 39 os.remove(test_file) 40 write_file(test_file, script) 41 command('source ' .. test_file) 42 os.remove(test_file) 43 write_file(other_file, '') 44 write_file(test_file, script) 45 command('source ' .. test_file) 46 os.remove(other_file) 47 os.remove(test_file) 48 end) 49 50 it("changing 'shellslash' changes the result of expand()", function() 51 t.skip(not is_os('win'), "N/A: 'shellslash' only works on Windows") 52 53 api.nvim_set_option_value('shellslash', false, {}) 54 mkdir('Xshellslash') 55 56 write_file( 57 [[Xshellslash/Xstack.vim]], 58 [[ 59 let g:stack1 = expand('<stack>') 60 set shellslash 61 let g:stack2 = expand('<stack>') 62 set noshellslash 63 let g:stack3 = expand('<stack>') 64 ]] 65 ) 66 67 for _ = 1, 2 do 68 command([[source Xshellslash/Xstack.vim]]) 69 matches([[Xshellslash\Xstack%.vim]], api.nvim_get_var('stack1')) 70 matches([[Xshellslash/Xstack%.vim]], api.nvim_get_var('stack2')) 71 matches([[Xshellslash\Xstack%.vim]], api.nvim_get_var('stack3')) 72 end 73 74 write_file( 75 [[Xshellslash/Xstack.lua]], 76 [[ 77 vim.g.stack1 = vim.fn.expand('<stack>') 78 vim.o.shellslash = true 79 vim.g.stack2 = vim.fn.expand('<stack>') 80 vim.o.shellslash = false 81 vim.g.stack3 = vim.fn.expand('<stack>') 82 ]] 83 ) 84 85 for _ = 1, 2 do 86 command([[source Xshellslash/Xstack.lua]]) 87 matches([[Xshellslash\Xstack%.lua]], api.nvim_get_var('stack1')) 88 matches([[Xshellslash/Xstack%.lua]], api.nvim_get_var('stack2')) 89 matches([[Xshellslash\Xstack%.lua]], api.nvim_get_var('stack3')) 90 end 91 92 rmdir('Xshellslash') 93 end) 94 95 it('current buffer', function() 96 insert([[ 97 let a = 2 98 let b = #{ 99 \ k: "v" 100 "\ (o_o) 101 \ } 102 let c = expand("<SID>") 103 let s:s = 0zbeef.cafe 104 let d = s:s]]) 105 106 command('source') 107 eq('2', exec_capture('echo a')) 108 eq("{'k': 'v'}", exec_capture('echo b')) 109 eq('<SNR>1_', exec_capture('echo c')) 110 eq('0zBEEFCAFE', exec_capture('echo d')) 111 112 exec('set cpoptions+=C') 113 eq("Vim(let):E723: Missing end of Dictionary '}': ", exc_exec('source')) 114 end) 115 116 it('selection in current buffer', function() 117 insert([[ 118 let a = 2 119 let a = 3 120 let a = 4 121 let b = #{ 122 "\ (>_<) 123 \ K: "V" 124 \ } 125 function! s:C() abort 126 return expand("<SID>") .. "C()" 127 endfunction 128 let D = {-> s:C()}]]) 129 130 -- Source the 2nd line only 131 feed('ggjV') 132 feed_command(':source') 133 eq('3', exec_capture('echo a')) 134 135 -- Source last line only 136 feed_command(':$source') 137 eq('Vim(echo):E117: Unknown function: s:C', exc_exec('echo D()')) 138 139 -- Source from 2nd line to end of file 140 feed('ggjVG') 141 feed_command(':source') 142 eq('4', exec_capture('echo a')) 143 eq("{'K': 'V'}", exec_capture('echo b')) 144 eq('<SNR>1_C()', exec_capture('echo D()')) 145 146 -- Source last line after the lines that define s:C() have been sourced 147 feed_command(':$source') 148 eq('<SNR>1_C()', exec_capture('echo D()')) 149 150 exec('set cpoptions+=C') 151 eq("Vim(let):E723: Missing end of Dictionary '}': ", exc_exec("'<,'>source")) 152 end) 153 154 it('does not break if current buffer is modified while sourced', function() 155 insert [[ 156 bwipeout! 157 let a = 123 158 ]] 159 command('source') 160 eq('123', exec_capture('echo a')) 161 end) 162 163 it('multiline heredoc command', function() 164 insert([[ 165 lua << EOF 166 y = 4 167 EOF]]) 168 169 command('source') 170 eq('4', exec_capture('echo luaeval("y")')) 171 end) 172 173 --- @param verbose boolean 174 local function test_source_lua_file(verbose) 175 local test_file = 'Xtest.lua' 176 write_file( 177 test_file, 178 [[ 179 vim.g.sourced_lua = 1 180 vim.g.sfile_value = vim.fn.expand('<sfile>') 181 vim.g.stack_value = vim.fn.expand('<stack>') 182 vim.g.script_value = vim.fn.expand('<script>') 183 vim.g.script_id = tonumber(vim.fn.expand('<SID>'):match('<SNR>(%d+)_')) 184 vim.o.mouse = 'nv' 185 ]] 186 ) 187 188 command('set shellslash') 189 command(('%ssource %s'):format(verbose and 'verbose ' or '', test_file)) 190 eq(1, eval('g:sourced_lua')) 191 matches([[/Xtest%.lua$]], api.nvim_get_var('sfile_value')) 192 matches([[/Xtest%.lua$]], api.nvim_get_var('stack_value')) 193 matches([[/Xtest%.lua$]], api.nvim_get_var('script_value')) 194 195 local expected_sid = fn.getscriptinfo({ name = test_file })[1].sid 196 local sid = api.nvim_get_var('script_id') 197 eq(expected_sid, sid) 198 eq(sid, api.nvim_get_option_info2('mouse', {}).last_set_sid) 199 200 os.remove(test_file) 201 end 202 203 it('can source lua files', function() 204 test_source_lua_file(false) 205 end) 206 207 it('with :verbose modifier can source lua files', function() 208 test_source_lua_file(true) 209 end) 210 211 describe('can source current buffer', function() 212 local function test_source_lua_curbuf() 213 it('selected region', function() 214 insert([[ 215 vim.g.b = 5 216 vim.g.b = 6 217 vim.g.b = 7 218 a = [=[ 219 "\ a 220 \ b]=] 221 ]]) 222 feed('dd') 223 224 feed('ggjV') 225 feed_command(':source') 226 eq(6, eval('g:b')) 227 228 feed('GVkk') 229 feed_command(':source') 230 eq(' "\\ a\n \\ b', exec_lua('return _G.a')) 231 end) 232 233 it('whole buffer', function() 234 insert([[ 235 vim.g.c = 10 236 vim.g.c = 11 237 vim.g.c = 12 238 a = [=[ 239 \ 1 240 "\ 2]=] 241 vim.g.sfile_value = vim.fn.expand('<sfile>') 242 vim.g.stack_value = vim.fn.expand('<stack>') 243 vim.g.script_value = vim.fn.expand('<script>') 244 ]]) 245 feed('dd') 246 247 feed_command(':source') 248 local filepath = fn.expand('%:p') 249 if filepath == '' then 250 filepath = ':source buffer=1' 251 end 252 eq(12, eval('g:c')) 253 eq(' \\ 1\n "\\ 2', exec_lua('return _G.a')) 254 eq(filepath, api.nvim_get_var('sfile_value')) 255 eq(filepath, api.nvim_get_var('stack_value')) 256 eq(filepath, api.nvim_get_var('script_value')) 257 end) 258 end 259 260 describe('with ft=lua', function() 261 before_each(function() 262 command('setlocal ft=lua') 263 end) 264 test_source_lua_curbuf() 265 end) 266 267 describe('with .lua extension', function() 268 before_each(function() 269 command('edit ' .. tmpname() .. '.lua') 270 end) 271 test_source_lua_curbuf() 272 end) 273 end) 274 275 it("doesn't throw E484 for lua parsing/runtime errors", function() 276 local test_file = 'Xtest.lua' 277 278 -- Does throw E484 for unreadable files 279 local ok, result = pcall(exec_capture, ':source ' .. test_file .. 'noexisting') 280 eq(false, ok) 281 neq(nil, result:find('E484')) 282 283 -- Doesn't throw for parsing error 284 write_file(test_file, 'vim.g.c = ') 285 ok, result = pcall(exec_capture, ':source ' .. test_file) 286 eq(false, ok) 287 eq(nil, result:find('E484')) 288 os.remove(test_file) 289 290 -- Doesn't throw for runtime error 291 write_file(test_file, "error('Cause error anyway :D')") 292 ok, result = pcall(exec_capture, ':source ' .. test_file) 293 eq(false, ok) 294 eq(nil, result:find('E484')) 295 os.remove(test_file) 296 end) 297 298 it('sources Lua/Vimscript codeblocks based on treesitter injection', function() 299 insert([[ 300 *test.txt* Test help file 301 302 Lua example: >lua 303 vim.g.test_lua = 42 304 < 305 306 Vim example: >vim 307 let g:test_vim = 99 308 <]]) 309 command('setlocal filetype=help') 310 311 -- Source Lua codeblock (line 4 contains the Lua code) 312 command(':4source') 313 eq(42, eval('g:test_lua')) 314 315 -- Source Vimscript codeblock (line 8 contains the Vim code) 316 command(':8source') 317 eq(99, eval('g:test_vim')) 318 319 -- Test fallback without treesitter 320 command('enew') 321 insert([[let g:test_no_ts = 123]]) 322 command('setlocal filetype=') 323 command('source') 324 eq(123, eval('g:test_no_ts')) 325 end) 326 end) 327 328 it('$HOME is not shortened in filepath in v:stacktrace from sourced file', function() 329 local sep = n.get_pathsep() 330 local xhome = table.concat({ vim.uv.cwd(), 'Xhome' }, sep) 331 mkdir(xhome) 332 clear({ env = { HOME = xhome } }) 333 finally(function() 334 rmdir(xhome) 335 end) 336 local filepath = table.concat({ xhome, 'Xstacktrace.vim' }, sep) 337 local script = [[ 338 func Xfunc() 339 throw 'Exception from Xfunc' 340 endfunc 341 ]] 342 write_file(filepath, script) 343 exec('source ' .. filepath) 344 exec([[ 345 try 346 call Xfunc() 347 catch 348 let g:stacktrace = v:stacktrace 349 endtry 350 ]]) 351 eq(filepath, n.eval('g:stacktrace[-1].filepath')) 352 end)