buffer_spec.lua 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local tt = require('test.functional.testterm')
  5. local assert_alive = n.assert_alive
  6. local feed, clear = n.feed, n.clear
  7. local poke_eventloop = n.poke_eventloop
  8. local nvim_prog = n.nvim_prog
  9. local eval, feed_command, source = n.eval, n.feed_command, n.source
  10. local pcall_err = t.pcall_err
  11. local eq, neq = t.eq, t.neq
  12. local api = n.api
  13. local retry = t.retry
  14. local testprg = n.testprg
  15. local write_file = t.write_file
  16. local command = n.command
  17. local exc_exec = n.exc_exec
  18. local matches = t.matches
  19. local exec_lua = n.exec_lua
  20. local sleep = vim.uv.sleep
  21. local fn = n.fn
  22. local is_os = t.is_os
  23. local skip = t.skip
  24. describe(':terminal buffer', function()
  25. local screen
  26. before_each(function()
  27. clear()
  28. command('set modifiable swapfile undolevels=20')
  29. screen = tt.setup_screen()
  30. end)
  31. it('terminal-mode forces various options', function()
  32. feed([[<C-\><C-N>]])
  33. command('setlocal cursorline cursorlineopt=both cursorcolumn scrolloff=4 sidescrolloff=7')
  34. eq(
  35. { 'both', 1, 1, 4, 7 },
  36. eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')
  37. )
  38. eq('nt', eval('mode(1)'))
  39. -- Enter terminal-mode ("insert" mode in :terminal).
  40. feed('i')
  41. eq('t', eval('mode(1)'))
  42. eq(
  43. { 'number', 1, 0, 0, 0 },
  44. eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')
  45. )
  46. end)
  47. it('terminal-mode does not change cursorlineopt if cursorline is disabled', function()
  48. feed([[<C-\><C-N>]])
  49. command('setlocal nocursorline cursorlineopt=both')
  50. feed('i')
  51. eq({ 0, 'both' }, eval('[&l:cursorline, &l:cursorlineopt]'))
  52. end)
  53. it('terminal-mode disables cursorline when cursorlineopt is only set to "line"', function()
  54. feed([[<C-\><C-N>]])
  55. command('setlocal cursorline cursorlineopt=line')
  56. feed('i')
  57. eq({ 0, 'line' }, eval('[&l:cursorline, &l:cursorlineopt]'))
  58. end)
  59. describe('when a new file is edited', function()
  60. before_each(function()
  61. feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>')
  62. screen:expect([[
  63. ^ |
  64. {4:~ }|*5
  65. :enew |
  66. ]])
  67. end)
  68. it('will hide the buffer, ignoring the bufhidden option', function()
  69. feed(':bnext:l<esc>')
  70. screen:expect([[
  71. ^ |
  72. {4:~ }|*5
  73. |
  74. ]])
  75. end)
  76. end)
  77. describe('swap and undo', function()
  78. before_each(function()
  79. feed('<c-\\><c-n>')
  80. screen:expect([[
  81. tty ready |
  82. ^ |
  83. |*5
  84. ]])
  85. end)
  86. it('does not create swap files', function()
  87. local swapfile = api.nvim_exec('swapname', true):gsub('\n', '')
  88. eq(nil, io.open(swapfile))
  89. end)
  90. it('does not create undofiles files', function()
  91. local undofile = api.nvim_eval('undofile(bufname("%"))')
  92. eq(nil, io.open(undofile))
  93. end)
  94. end)
  95. it('cannot be modified directly', function()
  96. feed('<c-\\><c-n>dd')
  97. screen:expect([[
  98. tty ready |
  99. ^ |
  100. |*4
  101. {8:E21: Cannot make changes, 'modifiable' is off} |
  102. ]])
  103. end)
  104. it('sends data to the terminal when the "put" operator is used', function()
  105. feed('<c-\\><c-n>gg"ayj')
  106. feed_command('let @a = "appended " . @a')
  107. feed('"ap"ap')
  108. screen:expect([[
  109. ^tty ready |
  110. appended tty ready |*2
  111. |
  112. |*2
  113. :let @a = "appended " . @a |
  114. ]])
  115. -- operator count is also taken into consideration
  116. feed('3"ap')
  117. screen:expect([[
  118. ^tty ready |
  119. appended tty ready |*5
  120. :let @a = "appended " . @a |
  121. ]])
  122. end)
  123. it('sends data to the terminal when the ":put" command is used', function()
  124. feed('<c-\\><c-n>gg"ayj')
  125. feed_command('let @a = "appended " . @a')
  126. feed_command('put a')
  127. screen:expect([[
  128. ^tty ready |
  129. appended tty ready |
  130. |
  131. |*3
  132. :put a |
  133. ]])
  134. -- line argument is only used to move the cursor
  135. feed_command('6put a')
  136. screen:expect([[
  137. tty ready |
  138. appended tty ready |*2
  139. |
  140. |
  141. ^ |
  142. :6put a |
  143. ]])
  144. end)
  145. it('can be deleted', function()
  146. feed('<c-\\><c-n>:bd!<cr>')
  147. screen:expect([[
  148. ^ |
  149. {4:~ }|*5
  150. :bd! |
  151. ]])
  152. feed_command('bnext')
  153. screen:expect([[
  154. ^ |
  155. {4:~ }|*5
  156. :bnext |
  157. ]])
  158. end)
  159. it('handles loss of focus gracefully', function()
  160. -- Change the statusline to avoid printing the file name, which varies.
  161. api.nvim_set_option_value('statusline', '==========', {})
  162. -- Save the buffer number of the terminal for later testing.
  163. local tbuf = eval('bufnr("%")')
  164. local exitcmd = is_os('win') and "['cmd', '/c', 'exit']" or "['sh', '-c', 'exit']"
  165. source([[
  166. function! SplitWindow(id, data, event)
  167. new
  168. call feedkeys("iabc\<Esc>")
  169. endfunction
  170. startinsert
  171. call jobstart(]] .. exitcmd .. [[, {'on_exit': function("SplitWindow")})
  172. call feedkeys("\<C-\>", 't') " vim will expect <C-n>, but be exited out of
  173. " the terminal before it can be entered.
  174. ]])
  175. -- We should be in a new buffer now.
  176. screen:expect([[
  177. ab^c |
  178. {4:~ }|
  179. {5:========== }|
  180. rows: 2, cols: 50 |
  181. |
  182. {18:========== }|
  183. |
  184. ]])
  185. neq(tbuf, eval('bufnr("%")'))
  186. feed_command('quit!') -- Should exit the new window, not the terminal.
  187. eq(tbuf, eval('bufnr("%")'))
  188. end)
  189. describe('handles confirmations', function()
  190. it('with :confirm', function()
  191. feed('<c-\\><c-n>')
  192. feed_command('confirm bdelete')
  193. screen:expect { any = 'Close "term://' }
  194. end)
  195. it('with &confirm', function()
  196. feed('<c-\\><c-n>')
  197. feed_command('bdelete')
  198. screen:expect { any = 'E89' }
  199. feed('<cr>')
  200. eq('terminal', eval('&buftype'))
  201. feed_command('set confirm | bdelete')
  202. screen:expect { any = 'Close "term://' }
  203. feed('y')
  204. neq('terminal', eval('&buftype'))
  205. end)
  206. end)
  207. it('it works with set rightleft #11438', function()
  208. local columns = eval('&columns')
  209. feed(string.rep('a', columns))
  210. command('set rightleft')
  211. screen:expect([[
  212. ydaer ytt|
  213. ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
  214. |*4
  215. {3:-- TERMINAL --} |
  216. ]])
  217. command('bdelete!')
  218. end)
  219. it('requires bang (!) to close a running job #15402', function()
  220. skip(is_os('win'), 'Test freezes the CI and makes it time out')
  221. eq('Vim(wqall):E948: Job still running', exc_exec('wqall'))
  222. for _, cmd in ipairs({ 'bdelete', '%bdelete', 'bwipeout', 'bunload' }) do
  223. matches(
  224. '^Vim%('
  225. .. cmd:gsub('%%', '')
  226. .. '%):E89: term://.*tty%-test.* will be killed %(add %! to override%)$',
  227. exc_exec(cmd)
  228. )
  229. end
  230. command('call jobstop(&channel)')
  231. assert(0 >= eval('jobwait([&channel], 1000)[0]'))
  232. command('bdelete')
  233. end)
  234. it('stops running jobs with :quit', function()
  235. -- Open in a new window to avoid terminating the nvim instance
  236. command('split')
  237. command('terminal')
  238. command('set nohidden')
  239. command('quit')
  240. end)
  241. it('does not segfault when pasting empty register #13955', function()
  242. feed('<c-\\><c-n>')
  243. feed_command('put a') -- register a is empty
  244. n.assert_alive()
  245. end)
  246. it([[can use temporary normal mode <c-\><c-o>]], function()
  247. eq('t', fn.mode(1))
  248. feed [[<c-\><c-o>]]
  249. screen:expect {
  250. grid = [[
  251. tty ready |
  252. ^ |
  253. |*4
  254. {3:-- (terminal) --} |
  255. ]],
  256. }
  257. eq('ntT', fn.mode(1))
  258. feed [[:let g:x = 17]]
  259. screen:expect {
  260. grid = [[
  261. tty ready |
  262. |
  263. |*4
  264. :let g:x = 17^ |
  265. ]],
  266. }
  267. feed [[<cr>]]
  268. screen:expect {
  269. grid = [[
  270. tty ready |
  271. ^ |
  272. |*4
  273. {3:-- TERMINAL --} |
  274. ]],
  275. }
  276. eq('t', fn.mode(1))
  277. end)
  278. it('writing to an existing file with :w fails #13549', function()
  279. eq(
  280. 'Vim(write):E13: File exists (add ! to override)',
  281. pcall_err(command, 'write test/functional/fixtures/tty-test.c')
  282. )
  283. end)
  284. it('external interrupt (got_int) does not hang #20726', function()
  285. eq({ mode = 't', blocking = false }, api.nvim_get_mode())
  286. command('call timer_start(0, {-> interrupt()})')
  287. feed('<Ignore>') -- Add input to separate two RPC requests
  288. eq({ mode = 't', blocking = false }, api.nvim_get_mode())
  289. feed([[<C-\><C-N>]])
  290. eq({ mode = 'nt', blocking = false }, api.nvim_get_mode())
  291. command('bd!')
  292. end)
  293. it('correct size when switching buffers', function()
  294. local term_buf = api.nvim_get_current_buf()
  295. command('file foo | enew | vsplit')
  296. api.nvim_set_current_buf(term_buf)
  297. screen:expect([[
  298. tty ready │ |
  299. ^rows: 5, cols: 25 │{4:~ }|
  300. │{4:~ }|*3
  301. {17:foo [-] }{1:[No Name] }|
  302. |
  303. ]])
  304. feed('<C-^><C-W><C-O><C-^>')
  305. screen:expect([[
  306. tty ready |
  307. ^rows: 5, cols: 25 |
  308. rows: 6, cols: 50 |
  309. |*4
  310. ]])
  311. end)
  312. end)
  313. describe(':terminal buffer', function()
  314. before_each(clear)
  315. it('term_close() use-after-free #4393', function()
  316. command('terminal yes')
  317. feed('<Ignore>') -- Add input to separate two RPC requests
  318. command('bdelete!')
  319. end)
  320. describe('TermRequest', function()
  321. it('emits events #26972', function()
  322. local term = api.nvim_open_term(0, {})
  323. local termbuf = api.nvim_get_current_buf()
  324. -- Test that <abuf> is the terminal buffer, not the current buffer
  325. command('au TermRequest * let g:termbuf = +expand("<abuf>")')
  326. command('wincmd p')
  327. -- cwd will be inserted in a file URI, which cannot contain backs
  328. local cwd = t.fix_slashes(fn.getcwd())
  329. local parent = cwd:match('^(.+/)')
  330. local expected = '\027]7;file://host' .. parent
  331. api.nvim_chan_send(term, string.format('%s\027\\', expected))
  332. eq(expected, eval('v:termrequest'))
  333. eq(termbuf, eval('g:termbuf'))
  334. end)
  335. it('emits events for APC', function()
  336. local term = api.nvim_open_term(0, {})
  337. -- cwd will be inserted in a file URI, which cannot contain backs
  338. local cwd = t.fix_slashes(fn.getcwd())
  339. local parent = cwd:match('^(.+/)')
  340. local expected = '\027_Gfile://host' .. parent
  341. api.nvim_chan_send(term, string.format('%s\027\\', expected))
  342. eq(expected, eval('v:termrequest'))
  343. end)
  344. it('synchronization #27572', function()
  345. command('autocmd! nvim.terminal TermRequest')
  346. local term = exec_lua([[
  347. _G.input = {}
  348. local term = vim.api.nvim_open_term(0, {
  349. on_input = function(_, _, _, data)
  350. table.insert(_G.input, data)
  351. end,
  352. force_crlf = false,
  353. })
  354. vim.api.nvim_create_autocmd('TermRequest', {
  355. callback = function(args)
  356. if args.data.sequence == '\027]11;?' then
  357. table.insert(_G.input, '\027]11;rgb:0000/0000/0000\027\\')
  358. end
  359. end
  360. })
  361. return term
  362. ]])
  363. api.nvim_chan_send(term, '\027]11;?\007\027[5n\027]11;?\007\027[5n')
  364. eq({
  365. '\027]11;rgb:0000/0000/0000\027\\',
  366. '\027[0n',
  367. '\027]11;rgb:0000/0000/0000\027\\',
  368. '\027[0n',
  369. }, exec_lua('return _G.input'))
  370. end)
  371. it('works with vim.wait() from another autocommand #32706', function()
  372. command('autocmd! nvim.terminal TermRequest')
  373. exec_lua([[
  374. local term = vim.api.nvim_open_term(0, {})
  375. vim.api.nvim_create_autocmd('TermRequest', {
  376. buffer = 0,
  377. callback = function(ev)
  378. _G.sequence = ev.data.sequence
  379. _G.v_termrequest = vim.v.termrequest
  380. end,
  381. })
  382. vim.api.nvim_create_autocmd('TermEnter', {
  383. buffer = 0,
  384. callback = function()
  385. vim.api.nvim_chan_send(term, '\027]11;?\027\\')
  386. _G.result = vim.wait(3000, function()
  387. local expected = '\027]11;?'
  388. return _G.sequence == expected and _G.v_termrequest == expected
  389. end)
  390. end,
  391. })
  392. ]])
  393. feed('i')
  394. retry(nil, 4000, function()
  395. eq(true, exec_lua('return _G.result'))
  396. end)
  397. end)
  398. it('includes cursor position #31609', function()
  399. command('autocmd! nvim.terminal TermRequest')
  400. local screen = Screen.new(50, 10)
  401. local term = exec_lua([[
  402. _G.cursor = {}
  403. local term = vim.api.nvim_open_term(0, {})
  404. vim.api.nvim_create_autocmd('TermRequest', {
  405. callback = function(args)
  406. _G.cursor = args.data.cursor
  407. end
  408. })
  409. return term
  410. ]])
  411. -- Enter terminal mode so that the cursor follows the output
  412. feed('a')
  413. -- Put some lines into the scrollback. This tests the conversion from terminal line to buffer
  414. -- line.
  415. api.nvim_chan_send(term, string.rep('>\n', 20))
  416. screen:expect([[
  417. > |*8
  418. ^ |
  419. {5:-- TERMINAL --} |
  420. ]])
  421. -- Emit an OSC escape sequence
  422. api.nvim_chan_send(term, 'Hello\nworld!\027]133;D\027\\')
  423. screen:expect([[
  424. > |*7
  425. Hello |
  426. world!^ |
  427. {5:-- TERMINAL --} |
  428. ]])
  429. eq({ 22, 6 }, exec_lua('return _G.cursor'))
  430. end)
  431. it('does not cause hang in vim.wait() #32753', function()
  432. local screen = Screen.new(50, 10)
  433. exec_lua(function()
  434. local term = vim.api.nvim_open_term(0, {})
  435. -- Write OSC sequence with pending scrollback. TermRequest will
  436. -- reschedule itself onto an event queue until the pending scrollback is
  437. -- processed (i.e. the terminal is refreshed).
  438. vim.api.nvim_chan_send(term, string.format('%s\027]133;;\007', string.rep('a\n', 100)))
  439. -- vim.wait() drains the event queue. The terminal won't be refreshed
  440. -- until the event queue is empty. This test ensures that TermRequest
  441. -- does not continuously reschedule itself onto the same event queue,
  442. -- causing an infinite loop.
  443. vim.wait(100)
  444. end)
  445. screen:expect([[
  446. ^a |
  447. a |*8
  448. |
  449. ]])
  450. end)
  451. end)
  452. it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function()
  453. local testfilename = 'Xtestfile-functional-terminal-buffers_spec'
  454. write_file(testfilename, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa')
  455. finally(function()
  456. os.remove(testfilename)
  457. end)
  458. feed_command('edit ' .. testfilename)
  459. -- Move cursor away from the beginning of the line
  460. feed('$')
  461. -- Let jobstart(…,{term=true}) modify the buffer
  462. feed_command([[call jobstart("echo", {'term':v:true})]])
  463. assert_alive()
  464. feed_command('bdelete!')
  465. end)
  466. it('no heap-buffer-overflow when sending long line with nowrap #11548', function()
  467. feed_command('set nowrap')
  468. feed_command('autocmd TermOpen * startinsert')
  469. feed_command('call feedkeys("4000ai\\<esc>:terminal!\\<cr>")')
  470. assert_alive()
  471. end)
  472. it('truncates the size of grapheme clusters', function()
  473. local chan = api.nvim_open_term(0, {})
  474. local composing = ('a̳'):sub(2)
  475. api.nvim_chan_send(chan, 'a' .. composing:rep(20))
  476. retry(nil, nil, function()
  477. eq('a' .. composing:rep(14), api.nvim_get_current_line())
  478. end)
  479. end)
  480. it('handles extended grapheme clusters', function()
  481. local screen = Screen.new(50, 7)
  482. feed 'i'
  483. local chan = api.nvim_open_term(0, {})
  484. api.nvim_chan_send(chan, '🏴‍☠️ yarrr')
  485. screen:expect([[
  486. 🏴‍☠️ yarrr^ |
  487. |*5
  488. {5:-- TERMINAL --} |
  489. ]])
  490. eq('🏴‍☠️ yarrr', api.nvim_get_current_line())
  491. end)
  492. it('handles split UTF-8 sequences #16245', function()
  493. local screen = Screen.new(50, 7)
  494. fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
  495. screen:expect([[
  496. ^å |
  497. ref: å̲ |
  498. 1: å̲ |
  499. 2: å̲ |
  500. 3: å̲ |
  501. |*2
  502. ]])
  503. end)
  504. it('handles unprintable chars', function()
  505. local screen = Screen.new(50, 7)
  506. feed 'i'
  507. local chan = api.nvim_open_term(0, {})
  508. api.nvim_chan_send(chan, '\239\187\191') -- '\xef\xbb\xbf'
  509. screen:expect([[
  510. {18:<feff>}^ |
  511. |*5
  512. {5:-- TERMINAL --} |
  513. ]])
  514. eq('\239\187\191', api.nvim_get_current_line())
  515. end)
  516. it("handles bell respecting 'belloff' and 'visualbell'", function()
  517. local screen = Screen.new(50, 7)
  518. local chan = api.nvim_open_term(0, {})
  519. command('set belloff=')
  520. api.nvim_chan_send(chan, '\a')
  521. screen:expect(function()
  522. eq({ true, false }, { screen.bell, screen.visual_bell })
  523. end)
  524. screen.bell = false
  525. command('set visualbell')
  526. api.nvim_chan_send(chan, '\a')
  527. screen:expect(function()
  528. eq({ false, true }, { screen.bell, screen.visual_bell })
  529. end)
  530. screen.visual_bell = false
  531. command('set belloff=term')
  532. api.nvim_chan_send(chan, '\a')
  533. screen:expect({
  534. condition = function()
  535. eq({ false, false }, { screen.bell, screen.visual_bell })
  536. end,
  537. unchanged = true,
  538. })
  539. command('set belloff=all')
  540. api.nvim_chan_send(chan, '\a')
  541. screen:expect({
  542. condition = function()
  543. eq({ false, false }, { screen.bell, screen.visual_bell })
  544. end,
  545. unchanged = true,
  546. })
  547. end)
  548. end)
  549. describe('on_lines does not emit out-of-bounds line indexes when', function()
  550. before_each(function()
  551. clear()
  552. exec_lua([[
  553. function _G.register_callback(bufnr)
  554. _G.cb_error = ''
  555. vim.api.nvim_buf_attach(bufnr, false, {
  556. on_lines = function(_, bufnr, _, firstline, _, _)
  557. local status, msg = pcall(vim.api.nvim_buf_get_offset, bufnr, firstline)
  558. if not status then
  559. _G.cb_error = msg
  560. end
  561. end
  562. })
  563. end
  564. ]])
  565. end)
  566. it('creating a terminal buffer #16394', function()
  567. feed_command('autocmd TermOpen * ++once call v:lua.register_callback(str2nr(expand("<abuf>")))')
  568. feed_command('terminal')
  569. sleep(500)
  570. eq('', exec_lua([[return _G.cb_error]]))
  571. end)
  572. it('deleting a terminal buffer #16394', function()
  573. feed_command('terminal')
  574. sleep(500)
  575. feed_command('lua _G.register_callback(0)')
  576. feed_command('bdelete!')
  577. eq('', exec_lua([[return _G.cb_error]]))
  578. end)
  579. end)
  580. describe('terminal input', function()
  581. before_each(function()
  582. clear()
  583. exec_lua([[
  584. _G.input_data = ''
  585. vim.api.nvim_open_term(0, { on_input = function(_, _, _, data)
  586. _G.input_data = _G.input_data .. data
  587. end })
  588. ]])
  589. feed('i')
  590. poke_eventloop()
  591. end)
  592. it('<C-Space> is sent as NUL byte', function()
  593. feed('aaa<C-Space>bbb')
  594. eq('aaa\0bbb', exec_lua([[return _G.input_data]]))
  595. end)
  596. it('unknown special keys are not sent', function()
  597. feed('aaa<Help>bbb')
  598. eq('aaabbb', exec_lua([[return _G.input_data]]))
  599. end)
  600. end)
  601. describe('terminal input', function()
  602. it('sends various special keys with modifiers', function()
  603. clear()
  604. local screen = tt.setup_child_nvim({
  605. '-u',
  606. 'NONE',
  607. '-i',
  608. 'NONE',
  609. '--cmd',
  610. 'colorscheme vim',
  611. '--cmd',
  612. 'set notermguicolors',
  613. '-c',
  614. 'while 1 | redraw | echo keytrans(getcharstr(-1, #{simplify: 0})) | endwhile',
  615. })
  616. screen:expect([[
  617. ^ |
  618. {4:~ }|*3
  619. {5:[No Name] 0,0-1 All}|
  620. |
  621. {3:-- TERMINAL --} |
  622. ]])
  623. local keys = {
  624. '<Tab>',
  625. '<CR>',
  626. '<Esc>',
  627. '<M-Tab>',
  628. '<M-CR>',
  629. '<M-Esc>',
  630. '<BS>',
  631. '<S-Tab>',
  632. '<Insert>',
  633. '<Del>',
  634. '<PageUp>',
  635. '<PageDown>',
  636. '<S-Up>',
  637. '<C-Up>',
  638. '<Up>',
  639. '<S-Down>',
  640. '<C-Down>',
  641. '<Down>',
  642. '<S-Left>',
  643. '<C-Left>',
  644. '<Left>',
  645. '<S-Right>',
  646. '<C-Right>',
  647. '<Right>',
  648. '<S-Home>',
  649. '<C-Home>',
  650. '<Home>',
  651. '<S-End>',
  652. '<C-End>',
  653. '<End>',
  654. '<C-LeftMouse><0,0>',
  655. '<C-LeftDrag><0,1>',
  656. '<C-LeftRelease><0,1>',
  657. '<2-LeftMouse><0,1>',
  658. '<2-LeftDrag><0,0>',
  659. '<2-LeftRelease><0,0>',
  660. '<M-MiddleMouse><0,0>',
  661. '<M-MiddleDrag><0,1>',
  662. '<M-MiddleRelease><0,1>',
  663. '<2-MiddleMouse><0,1>',
  664. '<2-MiddleDrag><0,0>',
  665. '<2-MiddleRelease><0,0>',
  666. '<S-RightMouse><0,0>',
  667. '<S-RightDrag><0,1>',
  668. '<S-RightRelease><0,1>',
  669. '<2-RightMouse><0,1>',
  670. '<2-RightDrag><0,0>',
  671. '<2-RightRelease><0,0>',
  672. '<S-X1Mouse><0,0>',
  673. '<S-X1Drag><0,1>',
  674. '<S-X1Release><0,1>',
  675. '<2-X1Mouse><0,1>',
  676. '<2-X1Drag><0,0>',
  677. '<2-X1Release><0,0>',
  678. '<S-X2Mouse><0,0>',
  679. '<S-X2Drag><0,1>',
  680. '<S-X2Release><0,1>',
  681. '<2-X2Mouse><0,1>',
  682. '<2-X2Drag><0,0>',
  683. '<2-X2Release><0,0>',
  684. '<S-ScrollWheelUp>',
  685. '<S-ScrollWheelDown>',
  686. '<ScrollWheelUp>',
  687. '<ScrollWheelDown>',
  688. '<S-ScrollWheelLeft>',
  689. '<S-ScrollWheelRight>',
  690. '<ScrollWheelLeft>',
  691. '<ScrollWheelRight>',
  692. }
  693. -- FIXME: The escape sequence to enable kitty keyboard mode doesn't work on Windows
  694. if not is_os('win') then
  695. table.insert(keys, '<C-I>')
  696. table.insert(keys, '<C-M>')
  697. table.insert(keys, '<C-[>')
  698. end
  699. for _, key in ipairs(keys) do
  700. feed(key)
  701. screen:expect(([[
  702. |
  703. {4:~ }|*3
  704. {5:[No Name] 0,0-1 All}|
  705. %s^ {MATCH: *}|
  706. {3:-- TERMINAL --} |
  707. ]]):format(key:gsub('<%d+,%d+>$', '')))
  708. end
  709. end)
  710. end)
  711. if is_os('win') then
  712. describe(':terminal in Windows', function()
  713. local screen
  714. before_each(function()
  715. clear()
  716. feed_command('set modifiable swapfile undolevels=20')
  717. poke_eventloop()
  718. local cmd = { 'cmd.exe', '/K', 'PROMPT=$g$s' }
  719. screen = tt.setup_screen(nil, cmd)
  720. end)
  721. it('"put" operator sends data normally', function()
  722. feed('<c-\\><c-n>G')
  723. feed_command('let @a = ":: tty ready"')
  724. feed_command('let @a = @a . "\\n:: appended " . @a . "\\n\\n"')
  725. feed('"ap"ap')
  726. screen:expect([[
  727. |
  728. > :: tty ready |
  729. > :: appended :: tty ready |
  730. > :: tty ready |
  731. > :: appended :: tty ready |
  732. ^> |
  733. :let @a = @a . "\n:: appended " . @a . "\n\n" |
  734. ]])
  735. -- operator count is also taken into consideration
  736. feed('3"ap')
  737. screen:expect([[
  738. > :: appended :: tty ready |
  739. > :: tty ready |
  740. > :: appended :: tty ready |
  741. > :: tty ready |
  742. > :: appended :: tty ready |
  743. ^> |
  744. :let @a = @a . "\n:: appended " . @a . "\n\n" |
  745. ]])
  746. end)
  747. it('":put" command sends data normally', function()
  748. feed('<c-\\><c-n>G')
  749. feed_command('let @a = ":: tty ready"')
  750. feed_command('let @a = @a . "\\n:: appended " . @a . "\\n\\n"')
  751. feed_command('put a')
  752. screen:expect([[
  753. |
  754. > :: tty ready |
  755. > :: appended :: tty ready |
  756. > |
  757. |
  758. ^ |
  759. :put a |
  760. ]])
  761. -- line argument is only used to move the cursor
  762. feed_command('6put a')
  763. screen:expect([[
  764. |
  765. > :: tty ready |
  766. > :: appended :: tty ready |
  767. > :: tty ready |
  768. > :: appended :: tty ready |
  769. ^> |
  770. :6put a |
  771. ]])
  772. end)
  773. end)
  774. end
  775. describe('termopen() (deprecated alias to `jobstart(…,{term=true})`)', function()
  776. before_each(clear)
  777. it('disallowed when textlocked and in cmdwin buffer', function()
  778. command("autocmd TextYankPost <buffer> ++once call termopen('foo')")
  779. matches(
  780. 'Vim%(call%):E565: Not allowed to change text or change window$',
  781. pcall_err(command, 'normal! yy')
  782. )
  783. feed('q:')
  784. eq(
  785. 'Vim:E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
  786. pcall_err(fn.termopen, 'bar')
  787. )
  788. end)
  789. describe('$COLORTERM value', function()
  790. if skip(is_os('win'), 'Not applicable for Windows') then
  791. return
  792. end
  793. before_each(function()
  794. -- Outer value should never be propagated to :terminal
  795. fn.setenv('COLORTERM', 'wrongvalue')
  796. end)
  797. local function test_term_colorterm(expected, opts)
  798. local screen = Screen.new(50, 4)
  799. fn.termopen({
  800. nvim_prog,
  801. '-u',
  802. 'NONE',
  803. '-i',
  804. 'NONE',
  805. '--headless',
  806. '-c',
  807. 'echo $COLORTERM | quit',
  808. }, opts)
  809. screen:expect(([[
  810. ^%s{MATCH:%%s+}|
  811. [Process exited 0] |
  812. |*2
  813. ]]):format(expected))
  814. end
  815. describe("with 'notermguicolors'", function()
  816. before_each(function()
  817. command('set notermguicolors')
  818. end)
  819. it('is empty by default', function()
  820. test_term_colorterm('')
  821. end)
  822. it('can be overridden', function()
  823. test_term_colorterm('expectedvalue', { env = { COLORTERM = 'expectedvalue' } })
  824. end)
  825. end)
  826. describe("with 'termguicolors'", function()
  827. before_each(function()
  828. command('set termguicolors')
  829. end)
  830. it('is "truecolor" by default', function()
  831. test_term_colorterm('truecolor')
  832. end)
  833. it('can be overridden', function()
  834. test_term_colorterm('expectedvalue', { env = { COLORTERM = 'expectedvalue' } })
  835. end)
  836. end)
  837. end)
  838. end)