ex_terminal_spec.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. local helpers = require('test.functional.helpers')(after_each)
  2. local Screen = require('test.functional.ui.screen')
  3. local assert_alive = helpers.assert_alive
  4. local clear, poke_eventloop, nvim = helpers.clear, helpers.poke_eventloop, helpers.nvim
  5. local testprg, source, eq = helpers.testprg, helpers.source, helpers.eq
  6. local feed = helpers.feed
  7. local feed_command, eval = helpers.feed_command, helpers.eval
  8. local funcs = helpers.funcs
  9. local retry = helpers.retry
  10. local ok = helpers.ok
  11. local iswin = helpers.iswin
  12. local command = helpers.command
  13. describe(':terminal', function()
  14. local screen
  15. before_each(function()
  16. clear()
  17. screen = Screen.new(50, 4)
  18. screen:attach({rgb=false})
  19. end)
  20. it("does not interrupt Press-ENTER prompt #2748", function()
  21. -- Ensure that :messages shows Press-ENTER.
  22. source([[
  23. echomsg "msg1"
  24. echomsg "msg2"
  25. echomsg "msg3"
  26. ]])
  27. -- Invoke a command that emits frequent terminal activity.
  28. feed([[:terminal "]]..testprg('shell-test')..[[" REP 9999 !terminal_output!<cr>]])
  29. feed([[<C-\><C-N>]])
  30. poke_eventloop()
  31. -- Wait for some terminal activity.
  32. retry(nil, 4000, function()
  33. ok(funcs.line('$') > 6)
  34. end)
  35. feed_command("messages")
  36. screen:expect([[
  37. msg1 |
  38. msg2 |
  39. msg3 |
  40. Press ENTER or type command to continue^ |
  41. ]])
  42. end)
  43. it("reads output buffer on terminal reporting #4151", function()
  44. if helpers.pending_win32(pending) then return end
  45. if iswin() then
  46. feed_command([[terminal powershell -NoProfile -NoLogo -Command Write-Host -NoNewline "\"$([char]27)[6n\""; Start-Sleep -Milliseconds 500 ]])
  47. else
  48. feed_command([[terminal printf '\e[6n'; sleep 0.5 ]])
  49. end
  50. screen:expect{any='%^%[%[1;1R'}
  51. end)
  52. it("in normal-mode :split does not move cursor", function()
  53. if iswin() then
  54. feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]])
  55. else
  56. feed_command([[terminal while true; do echo foo; sleep .1; done]])
  57. end
  58. feed([[<C-\><C-N>M]]) -- move cursor away from last line
  59. poke_eventloop()
  60. eq(3, eval("line('$')")) -- window height
  61. eq(2, eval("line('.')")) -- cursor is in the middle
  62. feed_command('vsplit')
  63. eq(2, eval("line('.')")) -- cursor stays where we put it
  64. feed_command('split')
  65. eq(2, eval("line('.')")) -- cursor stays where we put it
  66. end)
  67. it('Enter/Leave does not increment jumplist #3723', function()
  68. feed_command('terminal')
  69. local function enter_and_leave()
  70. local lines_before = funcs.line('$')
  71. -- Create a new line (in the shell). For a normal buffer this
  72. -- increments the jumplist; for a terminal-buffer it should not. #3723
  73. feed('i')
  74. poke_eventloop()
  75. feed('<CR><CR><CR><CR>')
  76. poke_eventloop()
  77. feed([[<C-\><C-N>]])
  78. poke_eventloop()
  79. -- Wait for >=1 lines to be created.
  80. retry(nil, 4000, function()
  81. ok(funcs.line('$') > lines_before)
  82. end)
  83. end
  84. enter_and_leave()
  85. enter_and_leave()
  86. enter_and_leave()
  87. ok(funcs.line('$') > 6) -- Verify assumption.
  88. local jumps = funcs.split(funcs.execute('jumps'), '\n')
  89. eq(' jump line col file/text', jumps[1])
  90. eq(3, #jumps)
  91. end)
  92. it('nvim_get_mode() in :terminal', function()
  93. command(':terminal')
  94. eq({ blocking=false, mode='nt' }, nvim('get_mode'))
  95. feed('i')
  96. eq({ blocking=false, mode='t' }, nvim('get_mode'))
  97. feed([[<C-\><C-N>]])
  98. eq({ blocking=false, mode='nt' }, nvim('get_mode'))
  99. end)
  100. it(':stopinsert RPC request exits terminal-mode #7807', function()
  101. command(':terminal')
  102. feed('i[tui] insert-mode')
  103. eq({ blocking=false, mode='t' }, nvim('get_mode'))
  104. command('stopinsert')
  105. eq({ blocking=false, mode='nt' }, nvim('get_mode'))
  106. end)
  107. it(':stopinsert in normal mode doesn\'t break insert mode #9889', function()
  108. command(':terminal')
  109. eq({ blocking=false, mode='nt' }, nvim('get_mode'))
  110. command(':stopinsert')
  111. eq({ blocking=false, mode='nt' }, nvim('get_mode'))
  112. feed('a')
  113. eq({ blocking=false, mode='t' }, nvim('get_mode'))
  114. end)
  115. end)
  116. describe(':terminal (with fake shell)', function()
  117. local screen
  118. before_each(function()
  119. clear()
  120. screen = Screen.new(50, 4)
  121. screen:attach({rgb=false})
  122. -- shell-test.c is a fake shell that prints its arguments and exits.
  123. nvim('set_option', 'shell', testprg('shell-test'))
  124. nvim('set_option', 'shellcmdflag', 'EXE')
  125. end)
  126. -- Invokes `:terminal {cmd}` using a fake shell (shell-test.c) which prints
  127. -- the {cmd} and exits immediately .
  128. local function terminal_with_fake_shell(cmd)
  129. feed_command("terminal "..(cmd and cmd or ""))
  130. end
  131. it('with no argument, acts like termopen()', function()
  132. if helpers.pending_win32(pending) then return end
  133. terminal_with_fake_shell()
  134. retry(nil, 4 * screen.timeout, function()
  135. screen:expect([[
  136. ^ready $ |
  137. [Process exited 0] |
  138. |
  139. :terminal |
  140. ]])
  141. end)
  142. end)
  143. it("with no argument, and 'shell' is set to empty string", function()
  144. nvim('set_option', 'shell', '')
  145. terminal_with_fake_shell()
  146. screen:expect([[
  147. ^ |
  148. ~ |
  149. ~ |
  150. E91: 'shell' option is empty |
  151. ]])
  152. end)
  153. it("with no argument, but 'shell' has arguments, acts like termopen()", function()
  154. if helpers.pending_win32(pending) then return end
  155. nvim('set_option', 'shell', testprg('shell-test')..' -t jeff')
  156. terminal_with_fake_shell()
  157. screen:expect([[
  158. ^jeff $ |
  159. [Process exited 0] |
  160. |
  161. :terminal |
  162. ]])
  163. end)
  164. it('executes a given command through the shell', function()
  165. if helpers.pending_win32(pending) then return end
  166. command('set shellxquote=') -- win: avoid extra quotes
  167. terminal_with_fake_shell('echo hi')
  168. screen:expect([[
  169. ^ready $ echo hi |
  170. |
  171. [Process exited 0] |
  172. :terminal echo hi |
  173. ]])
  174. end)
  175. it("executes a given command through the shell, when 'shell' has arguments", function()
  176. if helpers.pending_win32(pending) then return end
  177. nvim('set_option', 'shell', testprg('shell-test')..' -t jeff')
  178. command('set shellxquote=') -- win: avoid extra quotes
  179. terminal_with_fake_shell('echo hi')
  180. screen:expect([[
  181. ^jeff $ echo hi |
  182. |
  183. [Process exited 0] |
  184. :terminal echo hi |
  185. ]])
  186. end)
  187. it('allows quotes and slashes', function()
  188. if helpers.pending_win32(pending) then return end
  189. command('set shellxquote=') -- win: avoid extra quotes
  190. terminal_with_fake_shell([[echo 'hello' \ "world"]])
  191. screen:expect([[
  192. ^ready $ echo 'hello' \ "world" |
  193. |
  194. [Process exited 0] |
  195. :terminal echo 'hello' \ "world" |
  196. ]])
  197. end)
  198. it('ex_terminal() double-free #4554', function()
  199. source([[
  200. autocmd BufNew * set shell=foo
  201. terminal]])
  202. -- Verify that BufNew actually fired (else the test is invalid).
  203. eq('foo', eval('&shell'))
  204. end)
  205. it('ignores writes if the backing stream closes', function()
  206. terminal_with_fake_shell()
  207. feed('iiXXXXXXX')
  208. poke_eventloop()
  209. -- Race: Though the shell exited (and streams were closed by SIGCHLD
  210. -- handler), :terminal cleanup is pending on the main-loop.
  211. -- This write should be ignored (not crash, #5445).
  212. feed('iiYYYYYYY')
  213. assert_alive()
  214. end)
  215. it('works with findfile()', function()
  216. feed_command('terminal')
  217. eq('term://', string.match(eval('bufname("%")'), "^term://"))
  218. eq('scripts/shadacat.py', eval('findfile("scripts/shadacat.py", ".")'))
  219. end)
  220. it('works with :find', function()
  221. if helpers.pending_win32(pending) then return end
  222. terminal_with_fake_shell()
  223. screen:expect([[
  224. ^ready $ |
  225. [Process exited 0] |
  226. |
  227. :terminal |
  228. ]])
  229. eq('term://', string.match(eval('bufname("%")'), "^term://"))
  230. feed([[<C-\><C-N>]])
  231. feed_command([[find */shadacat.py]])
  232. if iswin() then
  233. eq('scripts\\shadacat.py', eval('bufname("%")'))
  234. else
  235. eq('scripts/shadacat.py', eval('bufname("%")'))
  236. end
  237. end)
  238. it('works with gf', function()
  239. if helpers.pending_win32(pending) then return end
  240. command('set shellxquote=') -- win: avoid extra quotes
  241. terminal_with_fake_shell([[echo "scripts/shadacat.py"]])
  242. retry(nil, 4 * screen.timeout, function()
  243. screen:expect([[
  244. ^ready $ echo "scripts/shadacat.py" |
  245. |
  246. [Process exited 0] |
  247. :terminal echo "scripts/shadacat.py" |
  248. ]])
  249. end)
  250. feed([[<C-\><C-N>]])
  251. eq('term://', string.match(eval('bufname("%")'), "^term://"))
  252. feed([[ggf"lgf]])
  253. eq('scripts/shadacat.py', eval('bufname("%")'))
  254. end)
  255. it('with bufhidden=delete #3958', function()
  256. command('set hidden')
  257. eq(1, eval('&hidden'))
  258. command('autocmd BufNew * setlocal bufhidden=delete')
  259. for _ = 1, 5 do
  260. source([[
  261. execute 'edit '.reltimestr(reltime())
  262. terminal]])
  263. end
  264. end)
  265. end)