mirror of
https://github.com/neovim/neovim.git
synced 2024-12-26 14:11:15 -07:00
6ea6b3fee2
Extmarks can contain URLs which can then be drawn in any supporting UI. In the TUI, for example, URLs are "drawn" by emitting the OSC 8 control sequence to the TTY. On terminals which support the OSC 8 sequence this will create clickable hyperlinks. URLs are treated as inline highlights in the decoration subsystem, so are included in the `DecorSignHighlight` structure. However, unlike other inline highlights they use allocated memory which must be freed, so they set the `ext` flag in `DecorInline` so that their lifetimes are managed along with other allocated memory like virtual text. The decoration subsystem then adds the URLs as a new highlight attribute. The highlight subsystem maintains a set of unique URLs to avoid duplicating allocations for the same string. To attach a URL to an existing highlight attribute we call `hl_add_url` which finds the URL in the set (allocating and adding it if it does not exist) and sets the `url` highlight attribute to the index of the URL in the set (using an index helps keep the size of the `HlAttrs` struct small). This has the potential to lead to an increase in highlight attributes if a URL is used over a range that contains many different highlight attributes, because now each existing attribute must be combined with the URL. In practice, however, URLs typically span a range containing a single highlight (e.g. link text in Markdown), so this is likely just a pathological edge case. When a new highlight attribute is defined with a URL it is copied to all attached UIs with the `hl_attr_define` UI event. The TUI manages its own set of URLs (just like the highlight subsystem) to minimize allocations. The TUI keeps track of which URL is "active" for the cell it is printing. If no URL is active and a cell containing a URL is printed, the opening OSC 8 sequence is emitted and that URL becomes the actively tracked URL. If the cursor is moved while in the middle of a URL span, we emit the terminating OSC sequence to prevent the hyperlink from spanning multiple lines. This does not support nested hyperlinks, but that is a rare (and, frankly, bizarre) use case. If a valid use case for nested hyperlinks ever presents itself we can address that issue then.
3176 lines
114 KiB
Lua
3176 lines
114 KiB
Lua
-- TUI acceptance tests.
|
|
-- Uses :terminal as a way to send keys and assert screen state.
|
|
--
|
|
-- "bracketed paste" terminal feature:
|
|
-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
|
|
|
|
local helpers = require('test.functional.helpers')(after_each)
|
|
local thelpers = require('test.functional.terminal.helpers')
|
|
local Screen = require('test.functional.ui.screen')
|
|
local eq = helpers.eq
|
|
local feed_command = helpers.feed_command
|
|
local feed_data = thelpers.feed_data
|
|
local clear = helpers.clear
|
|
local command = helpers.command
|
|
local dedent = helpers.dedent
|
|
local exec = helpers.exec
|
|
local exec_lua = helpers.exec_lua
|
|
local testprg = helpers.testprg
|
|
local retry = helpers.retry
|
|
local nvim_prog = helpers.nvim_prog
|
|
local nvim_set = helpers.nvim_set
|
|
local ok = helpers.ok
|
|
local read_file = helpers.read_file
|
|
local fn = helpers.fn
|
|
local api = helpers.api
|
|
local is_ci = helpers.is_ci
|
|
local is_os = helpers.is_os
|
|
local new_pipename = helpers.new_pipename
|
|
local spawn_argv = helpers.spawn_argv
|
|
local set_session = helpers.set_session
|
|
local write_file = helpers.write_file
|
|
local eval = helpers.eval
|
|
|
|
if helpers.skip(is_os('win')) then
|
|
return
|
|
end
|
|
|
|
describe('TUI', function()
|
|
local screen
|
|
local child_session
|
|
local child_exec_lua
|
|
|
|
before_each(function()
|
|
clear()
|
|
local child_server = new_pipename()
|
|
screen = thelpers.setup_child_nvim({
|
|
'--listen',
|
|
child_server,
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors laststatus=2 background=dark',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
})
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
child_session = helpers.connect(child_server)
|
|
child_exec_lua = thelpers.make_lua_executor(child_session)
|
|
end)
|
|
|
|
-- Wait for mode in the child Nvim (avoid "typeahead race" #10826).
|
|
local function wait_for_mode(mode)
|
|
retry(nil, nil, function()
|
|
local _, m = child_session:request('nvim_get_mode')
|
|
eq(mode, m.mode)
|
|
end)
|
|
end
|
|
|
|
-- Assert buffer contents in the child Nvim.
|
|
local function expect_child_buf_lines(expected)
|
|
assert(type({}) == type(expected))
|
|
retry(nil, nil, function()
|
|
local _, buflines = child_session:request('nvim_buf_get_lines', 0, 0, -1, false)
|
|
eq(expected, buflines)
|
|
end)
|
|
end
|
|
|
|
it('rapid resize #7572 #7628', function()
|
|
helpers.skip(helpers.is_asan(), 'Test extra unstable with ASAN. See #23762')
|
|
-- Need buffer rows to provoke the behavior.
|
|
feed_data(':edit test/functional/fixtures/bigfile.txt\n')
|
|
screen:expect([[
|
|
{1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
|
|
0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; |
|
|
0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
|
|
0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
|
|
{5:test/functional/fixtures/bigfile.txt }|
|
|
:edit test/functional/fixtures/bigfile.txt |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
command('call jobresize(b:terminal_job_id, 58, 9)')
|
|
command('call jobresize(b:terminal_job_id, 62, 13)')
|
|
command('call jobresize(b:terminal_job_id, 100, 42)')
|
|
command('call jobresize(b:terminal_job_id, 37, 1000)')
|
|
-- Resize to <5 columns.
|
|
screen:try_resize(4, 44)
|
|
command('call jobresize(b:terminal_job_id, 4, 1000)')
|
|
-- Resize to 1 row, then to 1 column, then increase rows to 4.
|
|
screen:try_resize(44, 1)
|
|
command('call jobresize(b:terminal_job_id, 44, 1)')
|
|
screen:try_resize(1, 1)
|
|
command('call jobresize(b:terminal_job_id, 1, 1)')
|
|
screen:try_resize(1, 4)
|
|
command('call jobresize(b:terminal_job_id, 1, 4)')
|
|
screen:try_resize(57, 17)
|
|
command('call jobresize(b:terminal_job_id, 57, 17)')
|
|
retry(nil, nil, function()
|
|
eq({ true, 57 }, { child_session:request('nvim_win_get_width', 0) })
|
|
end)
|
|
end)
|
|
|
|
it('accepts resize while pager is active', function()
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
set more
|
|
func! ManyErr()
|
|
for i in range(20)
|
|
echoerr "FAIL ".i
|
|
endfor
|
|
endfunc
|
|
]],
|
|
{}
|
|
)
|
|
feed_data(':call ManyErr()\r')
|
|
screen:expect {
|
|
grid = [[
|
|
{8:Error detected while processing function ManyErr:} |
|
|
{11:line 2:} |
|
|
{8:FAIL 0} |
|
|
{8:FAIL 1} |
|
|
{8:FAIL 2} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
screen:try_resize(50, 10)
|
|
screen:expect {
|
|
grid = [[
|
|
:call ManyErr() |
|
|
{8:Error detected while processing function ManyErr:} |
|
|
{11:line 2:} |
|
|
{8:FAIL 0} |
|
|
{8:FAIL 1} |
|
|
{8:FAIL 2} |
|
|
|*2
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data('j')
|
|
screen:expect {
|
|
grid = [[
|
|
{8:Error detected while processing function ManyErr:} |
|
|
{11:line 2:} |
|
|
{8:FAIL 0} |
|
|
{8:FAIL 1} |
|
|
{8:FAIL 2} |
|
|
{8:FAIL 3} |
|
|
{8:FAIL 4} |
|
|
{8:FAIL 5} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
screen:try_resize(50, 7)
|
|
screen:expect {
|
|
grid = [[
|
|
{8:FAIL 1} |
|
|
{8:FAIL 2} |
|
|
{8:FAIL 3} |
|
|
{8:FAIL 4} |
|
|
{8:FAIL 5} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
screen:try_resize(50, 5)
|
|
screen:expect {
|
|
grid = [[
|
|
{8:FAIL 3} |
|
|
{8:FAIL 4} |
|
|
{8:FAIL 5} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data('g')
|
|
screen:expect {
|
|
grid = [[
|
|
:call ManyErr() |
|
|
{8:Error detected while processing function ManyErr:} |
|
|
{11:line 2:} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
screen:try_resize(50, 10)
|
|
screen:expect {
|
|
grid = [[
|
|
:call ManyErr() |
|
|
{8:Error detected while processing function ManyErr:} |
|
|
{11:line 2:} |
|
|
{8:FAIL 0} |
|
|
{8:FAIL 1} |
|
|
{8:FAIL 2} |
|
|
{8:FAIL 3} |
|
|
{8:FAIL 4} |
|
|
{10:-- More --}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data('\003')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*6
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('accepts basic utf-8 input', function()
|
|
feed_data('iabc\ntest1\ntest2')
|
|
screen:expect([[
|
|
abc |
|
|
test1 |
|
|
test2{1: } |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027')
|
|
screen:expect([[
|
|
abc |
|
|
test1 |
|
|
test{1:2} |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('interprets leading <Esc> byte as ALT modifier in normal-mode', function()
|
|
local keys = 'dfghjkl'
|
|
for c in keys:gmatch('.') do
|
|
feed_data(':nnoremap <a-' .. c .. '> ialt-' .. c .. '<cr><esc>\r')
|
|
feed_data('\027' .. c)
|
|
end
|
|
screen:expect([[
|
|
alt-j |
|
|
alt-k |
|
|
alt-l |
|
|
{1: } |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('gg')
|
|
screen:expect([[
|
|
{1:a}lt-d |
|
|
alt-f |
|
|
alt-g |
|
|
alt-h |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('interprets ESC+key as ALT chord in i_CTRL-V', function()
|
|
-- Vim represents ALT/META by setting the "high bit" of the modified key:
|
|
-- ALT+j inserts "ê". Nvim does not (#3982).
|
|
feed_data('i\022\027j')
|
|
screen:expect([[
|
|
<M-j>{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('interprets <Esc>[27u as <Esc>', function()
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
nnoremap <M-;> <Nop>
|
|
nnoremap <Esc> AESC<Esc>
|
|
nnoremap ; Asemicolon<Esc>
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('\027[27u;')
|
|
screen:expect([[
|
|
ESCsemicolo{1:n} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <Esc>; should be recognized as <M-;> when <M-;> is mapped
|
|
feed_data('\027;')
|
|
screen:expect_unchanged()
|
|
end)
|
|
|
|
it('interprets <Esc><Nul> as <M-C-Space> #17198', function()
|
|
feed_data('i\022\027\000')
|
|
screen:expect([[
|
|
<M-C-Space>{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('accepts ASCII control sequences', function()
|
|
feed_data('i')
|
|
feed_data('\022\007') -- ctrl+g
|
|
feed_data('\022\022') -- ctrl+v
|
|
feed_data('\022\013') -- ctrl+m
|
|
local attrs = screen:get_default_attr_ids()
|
|
attrs[11] = { foreground = 81 }
|
|
screen:expect(
|
|
[[
|
|
{11:^G^V^M}{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
attrs
|
|
)
|
|
end)
|
|
|
|
local function test_mouse_wheel(esc)
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
set number nostartofline nowrap mousescroll=hor:1,ver:1
|
|
call setline(1, repeat([join(range(10), '----')], 10))
|
|
vsplit
|
|
]],
|
|
{}
|
|
)
|
|
screen:expect([[
|
|
{11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----|
|
|
{11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----|
|
|
{11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----|
|
|
{11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelDown> in active window
|
|
if esc then
|
|
feed_data('\027[<65;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'down', '', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----|
|
|
{11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----|
|
|
{11: 4 }0----1----2----3----4│{11: 3 }0----1----2----3----|
|
|
{11: 5 }0----1----2----3----4│{11: 4 }0----1----2----3----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelDown> in inactive window
|
|
if esc then
|
|
feed_data('\027[<65;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'down', '', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----|
|
|
{11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----|
|
|
{11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----|
|
|
{11: 5 }0----1----2----3----4│{11: 5 }0----1----2----3----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelRight> in active window
|
|
if esc then
|
|
feed_data('\027[<67;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'right', '', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----|
|
|
{11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----|
|
|
{11: 4 }----1----2----3----4-│{11: 4 }0----1----2----3----|
|
|
{11: 5 }----1----2----3----4-│{11: 5 }0----1----2----3----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelRight> in inactive window
|
|
if esc then
|
|
feed_data('\027[<67;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'right', '', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4|
|
|
{11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4|
|
|
{11: 4 }----1----2----3----4-│{11: 4 }----1----2----3----4|
|
|
{11: 5 }----1----2----3----4-│{11: 5 }----1----2----3----4|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelDown> in active window
|
|
if esc then
|
|
feed_data('\027[<69;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4|
|
|
{11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4|
|
|
{11: 7 }----1----2----3----4-│{11: 4 }----1----2----3----4|
|
|
{11: 8 }----1----2----3----4-│{11: 5 }----1----2----3----4|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelDown> in inactive window
|
|
if esc then
|
|
feed_data('\027[<69;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4|
|
|
{11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4|
|
|
{11: 7 }----1----2----3----4-│{11: 7 }----1----2----3----4|
|
|
{11: 8 }----1----2----3----4-│{11: 8 }----1----2----3----4|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelRight> in active window
|
|
if esc then
|
|
feed_data('\027[<71;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4|
|
|
{11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4|
|
|
{11: 7 }----6----7----8----9 │{11: 7 }----1----2----3----4|
|
|
{11: 8 }----6----7----8----9 │{11: 8 }----1----2----3----4|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelRight> in inactive window
|
|
if esc then
|
|
feed_data('\027[<71;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----|
|
|
{11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----|
|
|
{11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----|
|
|
{11: 8 }----6----7----8----9 │{11: 8 }5----6----7----8----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelUp> in active window
|
|
if esc then
|
|
feed_data('\027[<64;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'up', '', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----|
|
|
{11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----|
|
|
{11: 6 }----6----7----8----9 │{11: 7 }5----6----7----8----|
|
|
{11: 7 }----6----7----8----9 │{11: 8 }5----6----7----8----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelUp> in inactive window
|
|
if esc then
|
|
feed_data('\027[<64;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'up', '', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----|
|
|
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----|
|
|
{11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----|
|
|
{11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelLeft> in active window
|
|
if esc then
|
|
feed_data('\027[<66;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'left', '', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----|
|
|
{11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----|
|
|
{11: 6 }5----6----7----8----9│{11: 6 }5----6----7----8----|
|
|
{11: 7 }5----6----7----8----9│{11: 7 }5----6----7----8----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <ScrollWheelLeft> in inactive window
|
|
if esc then
|
|
feed_data('\027[<66;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'left', '', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---|
|
|
{11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---|
|
|
{11: 6 }5----6----7----8----9│{11: 6 }-5----6----7----8---|
|
|
{11: 7 }5----6----7----8----9│{11: 7 }-5----6----7----8---|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelUp> in active window
|
|
if esc then
|
|
feed_data('\027[<68;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'up', 'S', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---|
|
|
{11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---|
|
|
{11: 3 }5----6----7----8----9│{11: 6 }-5----6----7----8---|
|
|
{11: 4 }5{1:-}---6----7----8----9│{11: 7 }-5----6----7----8---|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelUp> in inactive window
|
|
if esc then
|
|
feed_data('\027[<68;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'up', 'S', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---|
|
|
{11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---|
|
|
{11: 3 }5----6----7----8----9│{11: 3 }-5----6----7----8---|
|
|
{11: 4 }5{1:-}---6----7----8----9│{11: 4 }-5----6----7----8---|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelLeft> in active window
|
|
if esc then
|
|
feed_data('\027[<70;8;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'left', 'S', 0, 0, 7)
|
|
end
|
|
screen:expect([[
|
|
{11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---|
|
|
{11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---|
|
|
{11: 3 }0----1----2----3----4│{11: 3 }-5----6----7----8---|
|
|
{11: 4 }0----1----2----3----{1:4}│{11: 4 }-5----6----7----8---|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- <S-ScrollWheelLeft> in inactive window
|
|
if esc then
|
|
feed_data('\027[<70;48;1M')
|
|
else
|
|
api.nvim_input_mouse('wheel', 'left', 'S', 0, 0, 47)
|
|
end
|
|
screen:expect([[
|
|
{11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----|
|
|
{11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----|
|
|
{11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----|
|
|
{11: 4 }0----1----2----3----{1:4}│{11: 4 }0----1----2----3----|
|
|
{5:[No Name] [+] }{1:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end
|
|
|
|
describe('accepts mouse wheel events', function()
|
|
it('(mouse events sent to host)', function()
|
|
test_mouse_wheel(false)
|
|
end)
|
|
|
|
it('(escape sequences sent to child)', function()
|
|
test_mouse_wheel(true)
|
|
end)
|
|
end)
|
|
|
|
local function test_mouse_popup(esc)
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
call setline(1, 'popup menu test')
|
|
set mouse=a mousemodel=popup
|
|
|
|
aunmenu PopUp
|
|
menu PopUp.foo :let g:menustr = 'foo'<CR>
|
|
menu PopUp.bar :let g:menustr = 'bar'<CR>
|
|
menu PopUp.baz :let g:menustr = 'baz'<CR>
|
|
highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse
|
|
highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold
|
|
]],
|
|
{}
|
|
)
|
|
if esc then
|
|
feed_data('\027[<2;5;1M')
|
|
else
|
|
api.nvim_input_mouse('right', 'press', '', 0, 0, 4)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }{13: foo }{4: }|
|
|
{4:~ }{13: bar }{4: }|
|
|
{4:~ }{13: baz }{4: }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
if esc then
|
|
feed_data('\027[<2;5;1m')
|
|
else
|
|
api.nvim_input_mouse('right', 'release', '', 0, 0, 4)
|
|
end
|
|
screen:expect_unchanged()
|
|
if esc then
|
|
feed_data('\027[<35;7;4M')
|
|
else
|
|
api.nvim_input_mouse('move', '', '', 0, 3, 6)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }{13: foo }{4: }|
|
|
{4:~ }{13: bar }{4: }|
|
|
{4:~ }{14: baz }{4: }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
if esc then
|
|
feed_data('\027[<0;7;3M')
|
|
else
|
|
api.nvim_input_mouse('left', 'press', '', 0, 2, 6)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
:let g:menustr = 'bar' |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
if esc then
|
|
feed_data('\027[<0;7;3m')
|
|
else
|
|
api.nvim_input_mouse('left', 'release', '', 0, 2, 6)
|
|
end
|
|
screen:expect_unchanged()
|
|
if esc then
|
|
feed_data('\027[<2;45;3M')
|
|
else
|
|
api.nvim_input_mouse('right', 'press', '', 0, 2, 44)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }|*2
|
|
{4:~ }{13: foo }{4: }|
|
|
{5:[No Name] [+] }{13: bar }{5: }|
|
|
:let g:menustr = 'bar' {13: baz } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
if esc then
|
|
feed_data('\027[<34;48;6M')
|
|
else
|
|
api.nvim_input_mouse('right', 'drag', '', 0, 5, 47)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }|*2
|
|
{4:~ }{13: foo }{4: }|
|
|
{5:[No Name] [+] }{13: bar }{5: }|
|
|
:let g:menustr = 'bar' {14: baz } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
if esc then
|
|
feed_data('\027[<2;48;6m')
|
|
else
|
|
api.nvim_input_mouse('right', 'release', '', 0, 5, 47)
|
|
end
|
|
screen:expect([[
|
|
{1:p}opup menu test |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
:let g:menustr = 'baz' |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end
|
|
|
|
describe('mouse events work with right-click menu', function()
|
|
it('(mouse events sent to host)', function()
|
|
test_mouse_popup(false)
|
|
end)
|
|
|
|
it('(escape sequences sent to child)', function()
|
|
test_mouse_popup(true)
|
|
end)
|
|
end)
|
|
|
|
it('accepts keypad keys from kitty keyboard protocol #19180', function()
|
|
feed_data('i')
|
|
feed_data(fn.nr2char(57399)) -- KP_0
|
|
feed_data(fn.nr2char(57400)) -- KP_1
|
|
feed_data(fn.nr2char(57401)) -- KP_2
|
|
feed_data(fn.nr2char(57402)) -- KP_3
|
|
feed_data(fn.nr2char(57403)) -- KP_4
|
|
feed_data(fn.nr2char(57404)) -- KP_5
|
|
feed_data(fn.nr2char(57405)) -- KP_6
|
|
feed_data(fn.nr2char(57406)) -- KP_7
|
|
feed_data(fn.nr2char(57407)) -- KP_8
|
|
feed_data(fn.nr2char(57408)) -- KP_9
|
|
feed_data(fn.nr2char(57409)) -- KP_DECIMAL
|
|
feed_data(fn.nr2char(57410)) -- KP_DIVIDE
|
|
feed_data(fn.nr2char(57411)) -- KP_MULTIPLY
|
|
feed_data(fn.nr2char(57412)) -- KP_SUBTRACT
|
|
feed_data(fn.nr2char(57413)) -- KP_ADD
|
|
feed_data(fn.nr2char(57414)) -- KP_ENTER
|
|
feed_data(fn.nr2char(57415)) -- KP_EQUAL
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
={1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57417)) -- KP_LEFT
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
{1:=} |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57418)) -- KP_RIGHT
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
={1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57419)) -- KP_UP
|
|
screen:expect([[
|
|
0{1:1}23456789./*-+ |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57420)) -- KP_DOWN
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
={1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57425)) -- KP_INSERT
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
={1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- REPLACE --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[27u') -- ESC
|
|
screen:expect([[
|
|
0123456789./*-+ |
|
|
{1:=} |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[57417;5u') -- CTRL + KP_LEFT
|
|
screen:expect([[
|
|
{1:0}123456789./*-+ |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT
|
|
screen:expect([[
|
|
0123456789{1:.}/*-+ |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57426)) -- KP_DELETE
|
|
screen:expect([[
|
|
0123456789{1:/}*-+ |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57423)) -- KP_HOME
|
|
screen:expect([[
|
|
{1:0}123456789/*-+ |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(fn.nr2char(57424)) -- KP_END
|
|
screen:expect([[
|
|
0123456789/*-{1:+} |
|
|
= |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
tab split
|
|
tabnew
|
|
highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline
|
|
]],
|
|
{}
|
|
)
|
|
screen:expect([[
|
|
{12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}|
|
|
{1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP
|
|
screen:expect([[
|
|
{12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}|
|
|
0123456789/*-{1:+} |
|
|
= |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN
|
|
screen:expect([[
|
|
{12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}|
|
|
{1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('supports Super and Meta modifiers', function()
|
|
feed_data('i')
|
|
feed_data('\022\027[106;9u') -- Super + j
|
|
feed_data('\022\027[107;33u') -- Meta + k
|
|
feed_data('\022\027[13;41u') -- Super + Meta + Enter
|
|
feed_data('\022\027[127;48u') -- Shift + Alt + Ctrl + Super + Meta + Backspace
|
|
feed_data('\n')
|
|
feed_data('\022\027[57376;9u') -- Super + F13
|
|
feed_data('\022\027[57377;33u') -- Meta + F14
|
|
feed_data('\022\027[57378;41u') -- Super + Meta + F15
|
|
feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16
|
|
screen:expect([[
|
|
<D-j><T-k><T-D-CR><M-T-C-S-D-BS> |
|
|
<D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>{1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('paste: Insert mode', function()
|
|
-- "bracketed paste"
|
|
feed_data('i""\027i\027[200~')
|
|
screen:expect([[
|
|
"{1:"} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('pasted from terminal')
|
|
expect_child_buf_lines({ '"pasted from terminal"' })
|
|
screen:expect([[
|
|
"pasted from terminal{1:"} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[201~') -- End paste.
|
|
feed_data('\027[27u') -- ESC: go to Normal mode.
|
|
wait_for_mode('n')
|
|
screen:expect([[
|
|
"pasted from termina{1:l}" |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- Dot-repeat/redo.
|
|
feed_data('2.')
|
|
expect_child_buf_lines({ '"pasted from terminapasted from terminalpasted from terminall"' })
|
|
screen:expect([[
|
|
"pasted from terminapasted from terminalpasted fro|
|
|
m termina{1:l}l" |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- Undo.
|
|
feed_data('u')
|
|
expect_child_buf_lines({ '"pasted from terminal"' })
|
|
feed_data('u')
|
|
expect_child_buf_lines({ '""' })
|
|
feed_data('u')
|
|
expect_child_buf_lines({ '' })
|
|
end)
|
|
|
|
it('paste: select-mode', function()
|
|
feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027')
|
|
wait_for_mode('n')
|
|
screen:expect {
|
|
grid = [[
|
|
this is line 1 |
|
|
this is line 2 |
|
|
line 3 is here |
|
|
{1: } |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Select-mode. Use <C-n> to move down.
|
|
feed_data('gg04lgh\14\14')
|
|
wait_for_mode('s')
|
|
feed_data('\027[200~')
|
|
feed_data('just paste it™')
|
|
feed_data('\027[201~')
|
|
screen:expect {
|
|
grid = [[
|
|
thisjust paste it{1:™}3 is here |
|
|
|
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Undo.
|
|
feed_data('u')
|
|
expect_child_buf_lines {
|
|
'this is line 1',
|
|
'this is line 2',
|
|
'line 3 is here',
|
|
'',
|
|
}
|
|
-- Redo.
|
|
feed_data('\18') -- <C-r>
|
|
expect_child_buf_lines {
|
|
'thisjust paste it™3 is here',
|
|
'',
|
|
}
|
|
end)
|
|
|
|
it('paste: terminal mode', function()
|
|
if is_ci('github') then
|
|
pending('tty-test complains about not owning the terminal -- actions/runner#241')
|
|
end
|
|
child_exec_lua('vim.o.statusline="^^^^^^^"')
|
|
child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test'))
|
|
feed_data('i')
|
|
screen:expect {
|
|
grid = [[
|
|
tty ready |
|
|
{1: } |
|
|
|*2
|
|
{5:^^^^^^^ }|
|
|
{3:-- TERMINAL --} |*2
|
|
]],
|
|
}
|
|
feed_data('\027[200~')
|
|
feed_data('hallo')
|
|
feed_data('\027[201~')
|
|
screen:expect {
|
|
grid = [[
|
|
tty ready |
|
|
hallo{1: } |
|
|
|*2
|
|
{5:^^^^^^^ }|
|
|
{3:-- TERMINAL --} |*2
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('paste: normal-mode (+CRLF #10872)', function()
|
|
feed_data(':set ruler | echo')
|
|
wait_for_mode('c')
|
|
feed_data('\n')
|
|
wait_for_mode('n')
|
|
local expected_lf = { 'line 1', 'ESC:\027 / CR: \rx' }
|
|
local expected_crlf = { 'line 1', 'ESC:\027 / CR: ', 'x' }
|
|
local expected_grid1 = [[
|
|
line 1 |
|
|
ESC:{11:^[} / CR: |
|
|
{1:x} |
|
|
{4:~ }|
|
|
{5:[No Name] [+] 3,1 All}|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]]
|
|
local expected_attr = {
|
|
[1] = { reverse = true },
|
|
[3] = { bold = true },
|
|
[4] = { foreground = tonumber('0x00000c') },
|
|
[5] = { bold = true, reverse = true },
|
|
[11] = { foreground = tonumber('0x000051') },
|
|
[12] = { reverse = true, foreground = tonumber('0x000051') },
|
|
}
|
|
-- "bracketed paste"
|
|
feed_data('\027[200~' .. table.concat(expected_lf, '\n') .. '\027[201~')
|
|
screen:expect { grid = expected_grid1, attr_ids = expected_attr }
|
|
-- Dot-repeat/redo.
|
|
feed_data('.')
|
|
screen:expect {
|
|
grid = [[
|
|
ESC:{11:^[} / CR: |
|
|
xline 1 |
|
|
ESC:{11:^[} / CR: |
|
|
{1:x} |
|
|
{5:[No Name] [+] 5,1 Bot}|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
attr_ids = expected_attr,
|
|
}
|
|
-- Undo.
|
|
feed_data('u')
|
|
expect_child_buf_lines(expected_crlf)
|
|
feed_data('u')
|
|
expect_child_buf_lines({ '' })
|
|
feed_data(':echo')
|
|
wait_for_mode('c')
|
|
feed_data('\n')
|
|
wait_for_mode('n')
|
|
-- CRLF input
|
|
feed_data('\027[200~' .. table.concat(expected_lf, '\r\n') .. '\027[201~')
|
|
screen:expect { grid = expected_grid1, attr_ids = expected_attr }
|
|
expect_child_buf_lines(expected_crlf)
|
|
end)
|
|
|
|
it('paste: cmdline-mode inserts 1 line', function()
|
|
feed_data('ifoo\n') -- Insert some text (for dot-repeat later).
|
|
feed_data('\027:""') -- Enter Cmdline-mode.
|
|
feed_data('\027[D') -- <Left> to place cursor between quotes.
|
|
wait_for_mode('c')
|
|
-- "bracketed paste"
|
|
feed_data('\027[200~line 1\nline 2\n')
|
|
wait_for_mode('c')
|
|
feed_data('line 3\nline 4\n\027[201~')
|
|
wait_for_mode('c')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |
|
|
|
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
:"line 1{1:"} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Dot-repeat/redo.
|
|
feed_data('\027[27u')
|
|
wait_for_mode('n')
|
|
feed_data('.')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |*2
|
|
{1: } |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('paste: cmdline-mode collects chunks of unfinished line', function()
|
|
local function expect_cmdline(expected)
|
|
retry(nil, nil, function()
|
|
local _, cmdline = child_session:request('nvim_call_function', 'getcmdline', {})
|
|
eq(expected, cmdline)
|
|
end)
|
|
end
|
|
feed_data('\027:""') -- Enter Cmdline-mode.
|
|
feed_data('\027[D') -- <Left> to place cursor between quotes.
|
|
wait_for_mode('c')
|
|
feed_data('\027[200~stuff 1 ')
|
|
expect_cmdline('"stuff 1 "')
|
|
-- Discards everything after the first line.
|
|
feed_data('more\nstuff 2\nstuff 3\n')
|
|
expect_cmdline('"stuff 1 more"')
|
|
feed_data('stuff 3')
|
|
expect_cmdline('"stuff 1 more"')
|
|
-- End the paste sequence.
|
|
feed_data('\027[201~')
|
|
feed_data(' typed')
|
|
expect_cmdline('"stuff 1 more typed"')
|
|
end)
|
|
|
|
it('paste: recovers from vim.paste() failure', function()
|
|
child_session:request(
|
|
'nvim_exec_lua',
|
|
[[
|
|
_G.save_paste_fn = vim.paste
|
|
-- Stack traces for this test are non-deterministic, so disable them
|
|
_G.debug.traceback = function(msg) return msg end
|
|
vim.paste = function(lines, phase) error("fake fail") end
|
|
]],
|
|
{}
|
|
)
|
|
-- Prepare something for dot-repeat/redo.
|
|
feed_data('ifoo\n\027[27u')
|
|
wait_for_mode('n')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |
|
|
{1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Start pasting...
|
|
feed_data('\027[200~line 1\nline 2\n')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |
|
|
|
|
|
{5: }|
|
|
{8:paste: Error executing lua: [string "<nvim>"]:4: f}|
|
|
{8:ake fail} |
|
|
{10:Press ENTER or type command to continue}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Remaining chunks are discarded after vim.paste() failure.
|
|
feed_data('line 3\nline 4\n')
|
|
feed_data('line 5\nline 6\n')
|
|
feed_data('line 7\nline 8\n')
|
|
-- Stop paste.
|
|
feed_data('\027[201~')
|
|
feed_data('\n') -- <CR>
|
|
expect_child_buf_lines({ 'foo', '' })
|
|
--Dot-repeat/redo is not modified by failed paste.
|
|
feed_data('.')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |*2
|
|
{1: } |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Editor should still work after failed/drained paste.
|
|
feed_data('ityped input...\027[27u')
|
|
screen:expect {
|
|
grid = [[
|
|
foo |*2
|
|
typed input..{1:.} |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Paste works if vim.paste() succeeds.
|
|
child_session:request(
|
|
'nvim_exec_lua',
|
|
[[
|
|
vim.paste = _G.save_paste_fn
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('\027[200~line A\nline B\n\027[201~')
|
|
feed_data('\n') -- <CR>
|
|
screen:expect {
|
|
grid = [[
|
|
foo |
|
|
typed input...line A |
|
|
line B |
|
|
{1: } |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('paste: vim.paste() cancel (retval=false) #10865', function()
|
|
-- This test only exercises the "cancel" case. Use-case would be "dangling
|
|
-- paste", but that is not implemented yet. #10865
|
|
child_session:request(
|
|
'nvim_exec_lua',
|
|
[[
|
|
vim.paste = function(lines, phase) return false end
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('\027[200~line A\nline B\n\027[201~')
|
|
feed_data('ifoo\n\027[27u')
|
|
expect_child_buf_lines({ 'foo', '' })
|
|
end)
|
|
|
|
it("paste: 'nomodifiable' buffer", function()
|
|
child_session:request('nvim_command', 'set nomodifiable')
|
|
child_session:request(
|
|
'nvim_exec_lua',
|
|
[[
|
|
-- Truncate the error message to hide the line number
|
|
_G.debug.traceback = function(msg) return msg:sub(-49) end
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('\027[200~fail 1\nfail 2\n\027[201~')
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
{4:~ }|
|
|
{5: }|
|
|
{8:paste: Error executing lua: Vim:E21: Cannot make c}|
|
|
{8:hanges, 'modifiable' is off} |
|
|
{10:Press ENTER or type command to continue}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('\n') -- <Enter>
|
|
child_session:request('nvim_command', 'set modifiable')
|
|
feed_data('\027[200~success 1\nsuccess 2\n\027[201~')
|
|
screen:expect {
|
|
grid = [[
|
|
success 1 |
|
|
success 2 |
|
|
{1: } |
|
|
{4:~ }|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('paste: exactly 64 bytes #10311', function()
|
|
local expected = string.rep('z', 64)
|
|
feed_data('i')
|
|
wait_for_mode('i')
|
|
-- "bracketed paste"
|
|
feed_data('\027[200~' .. expected .. '\027[201~')
|
|
expect_child_buf_lines({ expected })
|
|
feed_data(' end')
|
|
expected = expected .. ' end'
|
|
screen:expect([[
|
|
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
|
|
zzzzzzzzzzzzzz end{1: } |
|
|
{4:~ }|*2
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
expect_child_buf_lines({ expected })
|
|
end)
|
|
|
|
it('paste: less-than sign in cmdline #11088', function()
|
|
local expected = '<'
|
|
feed_data(':')
|
|
wait_for_mode('c')
|
|
-- "bracketed paste"
|
|
feed_data('\027[200~' .. expected .. '\027[201~')
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:<{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('paste: big burst of input', function()
|
|
feed_data(':set ruler\n')
|
|
local t = {}
|
|
for i = 1, 3000 do
|
|
t[i] = 'item ' .. tostring(i)
|
|
end
|
|
feed_data('i')
|
|
wait_for_mode('i')
|
|
-- "bracketed paste"
|
|
feed_data('\027[200~' .. table.concat(t, '\n') .. '\027[201~')
|
|
expect_child_buf_lines(t)
|
|
feed_data(' end')
|
|
screen:expect([[
|
|
item 2997 |
|
|
item 2998 |
|
|
item 2999 |
|
|
item 3000 end{1: } |
|
|
{5:[No Name] [+] 3000,14 Bot}|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[27u') -- ESC: go to Normal mode.
|
|
wait_for_mode('n')
|
|
-- Dot-repeat/redo.
|
|
feed_data('.')
|
|
screen:expect([[
|
|
item 2997 |
|
|
item 2998 |
|
|
item 2999 |
|
|
item 3000 en{1:d}d |
|
|
{5:[No Name] [+] 5999,13 Bot}|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('paste: forwards spurious "start paste" code', function()
|
|
-- If multiple "start paste" sequences are sent without a corresponding
|
|
-- "stop paste" sequence, only the first occurrence should be consumed.
|
|
|
|
-- Send the "start paste" sequence.
|
|
feed_data('i\027[200~')
|
|
feed_data('\npasted from terminal (1)\n')
|
|
-- Send spurious "start paste" sequence.
|
|
feed_data('\027[200~')
|
|
feed_data('\n')
|
|
-- Send the "stop paste" sequence.
|
|
feed_data('\027[201~')
|
|
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
pasted from terminal (1) |
|
|
{6:^[}[200~ |
|
|
{1: } |
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
attr_ids = {
|
|
[1] = { reverse = true },
|
|
[2] = { background = tonumber('0x00000b') },
|
|
[3] = { bold = true },
|
|
[4] = { foreground = tonumber('0x00000c') },
|
|
[5] = { bold = true, reverse = true },
|
|
[6] = { foreground = tonumber('0x000051') },
|
|
},
|
|
}
|
|
end)
|
|
|
|
it('paste: ignores spurious "stop paste" code', function()
|
|
-- If "stop paste" sequence is received without a preceding "start paste"
|
|
-- sequence, it should be ignored.
|
|
feed_data('i')
|
|
-- Send "stop paste" sequence.
|
|
feed_data('\027[201~')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('paste: split "start paste" code', function()
|
|
feed_data('i')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Send split "start paste" sequence.
|
|
feed_data('\027[2')
|
|
feed_data('00~pasted from terminal\027[201~')
|
|
screen:expect([[
|
|
pasted from terminal{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('paste: split "stop paste" code', function()
|
|
feed_data('i')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- Send split "stop paste" sequence.
|
|
feed_data('\027[200~pasted from terminal\027[20')
|
|
feed_data('1~')
|
|
screen:expect([[
|
|
pasted from terminal{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('paste: streamed paste with isolated "stop paste" code', function()
|
|
child_session:request(
|
|
'nvim_exec_lua',
|
|
[[
|
|
_G.paste_phases = {}
|
|
vim.paste = (function(overridden)
|
|
return function(lines, phase)
|
|
table.insert(_G.paste_phases, phase)
|
|
overridden(lines, phase)
|
|
end
|
|
end)(vim.paste)
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('i')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('\027[200~pasted') -- phase 1
|
|
screen:expect([[
|
|
pasted{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data(' from terminal') -- phase 2
|
|
screen:expect([[
|
|
pasted from terminal{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
-- Send isolated "stop paste" sequence.
|
|
feed_data('\027[201~') -- phase 3
|
|
screen:expect_unchanged()
|
|
local _, rv = child_session:request('nvim_exec_lua', [[return _G.paste_phases]], {})
|
|
eq({ 1, 2, 3 }, rv)
|
|
end)
|
|
|
|
it('allows termguicolors to be set at runtime', function()
|
|
screen:set_option('rgb', true)
|
|
screen:set_default_attr_ids({
|
|
[1] = { reverse = true },
|
|
[2] = { foreground = tonumber('0x4040ff'), fg_indexed = true },
|
|
[3] = { bold = true, reverse = true },
|
|
[4] = { bold = true },
|
|
[5] = { reverse = true, foreground = tonumber('0xe0e000'), fg_indexed = true },
|
|
[6] = { foreground = tonumber('0xe0e000'), fg_indexed = true },
|
|
[7] = { reverse = true, foreground = Screen.colors.SeaGreen4 },
|
|
[8] = { foreground = Screen.colors.SeaGreen4 },
|
|
[9] = { bold = true, foreground = Screen.colors.Blue1 },
|
|
[10] = { foreground = Screen.colors.Blue },
|
|
})
|
|
|
|
feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n')
|
|
feed_data('i')
|
|
feed_data('\022\007') -- ctrl+g
|
|
feed_data('\028\014') -- crtl+\ ctrl+N
|
|
feed_data(':set termguicolors?\n')
|
|
screen:expect([[
|
|
{5:^}{6:G} |
|
|
{2:~ }|*3
|
|
{3:[No Name] [+] }|
|
|
notermguicolors |
|
|
{4:-- TERMINAL --} |
|
|
]])
|
|
|
|
feed_data(':set termguicolors\n')
|
|
screen:expect([[
|
|
{7:^}{8:G} |
|
|
{9:~}{10: }|*3
|
|
{3:[No Name] [+] }|
|
|
:set termguicolors |
|
|
{4:-- TERMINAL --} |
|
|
]])
|
|
|
|
feed_data(':set notermguicolors\n')
|
|
screen:expect([[
|
|
{5:^}{6:G} |
|
|
{2:~ }|*3
|
|
{3:[No Name] [+] }|
|
|
:set notermguicolors |
|
|
{4:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('forwards :term palette colors with termguicolors', function()
|
|
if is_ci('github') then
|
|
pending('tty-test complains about not owning the terminal -- actions/runner#241')
|
|
end
|
|
screen:set_rgb_cterm(true)
|
|
screen:set_default_attr_ids({
|
|
[1] = { { reverse = true }, { reverse = true } },
|
|
[2] = { { bold = true, reverse = true }, { bold = true, reverse = true } },
|
|
[3] = { { bold = true }, { bold = true } },
|
|
[4] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } },
|
|
[5] = { { foreground = tonumber('0xff8000') }, {} },
|
|
})
|
|
|
|
child_exec_lua('vim.o.statusline="^^^^^^^"')
|
|
child_exec_lua('vim.o.termguicolors=true')
|
|
child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test'))
|
|
screen:expect {
|
|
grid = [[
|
|
{1:t}ty ready |
|
|
|*3
|
|
{2:^^^^^^^ }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data(
|
|
':call chansend(&channel, "\\033[38;5;3mtext\\033[38:2:255:128:0mcolor\\033[0;10mtext")\n'
|
|
)
|
|
screen:expect {
|
|
grid = [[
|
|
{1:t}ty ready |
|
|
{4:text}{5:color}text |
|
|
|*2
|
|
{2:^^^^^^^ }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data(':set notermguicolors\n')
|
|
screen:expect {
|
|
grid = [[
|
|
{1:t}ty ready |
|
|
{4:text}colortext |
|
|
|*2
|
|
{2:^^^^^^^ }|
|
|
:set notermguicolors |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('in nvim_list_uis()', function()
|
|
-- $TERM in :terminal.
|
|
local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color'
|
|
local expected = {
|
|
{
|
|
chan = 1,
|
|
ext_cmdline = false,
|
|
ext_hlstate = false,
|
|
ext_linegrid = true,
|
|
ext_messages = false,
|
|
ext_multigrid = false,
|
|
ext_popupmenu = false,
|
|
ext_tabline = false,
|
|
ext_termcolors = true,
|
|
ext_wildmenu = false,
|
|
height = 6,
|
|
override = false,
|
|
rgb = false,
|
|
stdin_tty = true,
|
|
stdout_tty = true,
|
|
term_background = '',
|
|
term_colors = 256,
|
|
term_name = exp_term,
|
|
width = 50,
|
|
},
|
|
}
|
|
local _, rv = child_session:request('nvim_list_uis')
|
|
eq(expected, rv)
|
|
end)
|
|
|
|
it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function()
|
|
child_session:request(
|
|
'nvim_buf_set_lines',
|
|
0,
|
|
0,
|
|
-1,
|
|
true,
|
|
{ ('℃'):rep(60), ('℃'):rep(60) }
|
|
)
|
|
child_session:request('nvim_set_option_value', 'cursorline', true, {})
|
|
child_session:request('nvim_set_option_value', 'list', true, {})
|
|
child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 })
|
|
feed_data('gg')
|
|
local singlewidth_screen = [[
|
|
{13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}|
|
|
{12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }|
|
|
℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃|
|
|
℃℃℃℃℃℃℃℃℃℃{4:$} |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]]
|
|
-- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width,
|
|
-- the second cell of "℃" is a space and the attributes of the "℃" are applied to it.
|
|
local doublewidth_screen = [[
|
|
{13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
|
|
{12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
|
|
{12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }|
|
|
℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {4:@@@@}|
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]]
|
|
screen:expect(singlewidth_screen)
|
|
child_session:request('nvim_set_option_value', 'ambiwidth', 'double', {})
|
|
screen:expect(doublewidth_screen)
|
|
child_session:request('nvim_set_option_value', 'ambiwidth', 'single', {})
|
|
screen:expect(singlewidth_screen)
|
|
child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2103, 0x2103, 2 } } })
|
|
screen:expect(doublewidth_screen)
|
|
child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2103, 0x2103, 1 } } })
|
|
screen:expect(singlewidth_screen)
|
|
end)
|
|
|
|
it('draws correctly when cursor_address overflows #21643', function()
|
|
helpers.skip(is_os('mac'), 'FIXME: crashes/errors on macOS')
|
|
screen:try_resize(77, 855)
|
|
retry(nil, nil, function()
|
|
eq({ true, 852 }, { child_session:request('nvim_win_get_height', 0) })
|
|
end)
|
|
-- Use full screen message so that redrawing afterwards is more deterministic.
|
|
child_session:notify('nvim_command', 'intro')
|
|
screen:expect({ any = 'Nvim' })
|
|
-- Going to top-left corner needs 3 bytes.
|
|
-- Setting underline attribute needs 9 bytes.
|
|
-- The whole line needs 3 + 9 + 65513 + 3 = 65528 bytes.
|
|
-- The cursor_address that comes after will overflow the 65535-byte buffer.
|
|
local line = ('a'):rep(65513) .. '℃'
|
|
child_session:notify(
|
|
'nvim_exec_lua',
|
|
[[
|
|
vim.api.nvim_buf_set_lines(0, 0, -1, true, {...})
|
|
vim.o.cursorline = true
|
|
]],
|
|
{ line, 'b' }
|
|
)
|
|
-- Close the :intro message and redraw the lines.
|
|
feed_data('\n')
|
|
screen:expect(
|
|
'{13:a}{12:'
|
|
.. ('a'):rep(76)
|
|
.. '}|\n'
|
|
.. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(849)
|
|
.. '{12:'
|
|
.. ('a'):rep(63)
|
|
.. '℃'
|
|
.. (' '):rep(13)
|
|
.. '}|\n'
|
|
.. dedent([[
|
|
b |
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |]])
|
|
)
|
|
end)
|
|
|
|
it('visual bell (padding) does not crash #21610', function()
|
|
feed_data ':set visualbell\n'
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:set visualbell |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
-- move left is enough to invoke the bell
|
|
feed_data 'h'
|
|
-- visual change to show we process events after this
|
|
feed_data 'i'
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('no assert failure on deadly signal #21896', function()
|
|
exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
|
|
screen:expect {
|
|
grid = [[
|
|
Vim: Caught deadly signal 'SIGTERM' |
|
|
|*2
|
|
[Process exited 1]{1: } |
|
|
|*2
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('no stack-use-after-scope with cursor color #22432', function()
|
|
screen:set_option('rgb', true)
|
|
command('set termguicolors')
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
set tgc
|
|
hi Cursor guifg=Red guibg=Green
|
|
set guicursor=n:block-Cursor/lCursor
|
|
]],
|
|
{}
|
|
)
|
|
screen:set_default_attr_ids({
|
|
[1] = { reverse = true },
|
|
[2] = { bold = true, foreground = Screen.colors.Blue },
|
|
[3] = { foreground = Screen.colors.Blue },
|
|
[4] = { reverse = true, bold = true },
|
|
[5] = { bold = true },
|
|
})
|
|
screen:expect([[
|
|
{1: } |
|
|
{2:~}{3: }|*3
|
|
{4:[No Name] }|
|
|
|
|
|
{5:-- TERMINAL --} |
|
|
]])
|
|
feed_data('i')
|
|
screen:expect([[
|
|
{1: } |
|
|
{2:~}{3: }|*3
|
|
{4:[No Name] }|
|
|
{5:-- INSERT --} |
|
|
{5:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('redraws on SIGWINCH even if terminal size is unchanged #23411', function()
|
|
child_session:request('nvim_echo', { { 'foo' } }, false, {})
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
foo |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]])
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('supports hiding cursor', function()
|
|
child_session:request(
|
|
'nvim_command',
|
|
"let g:id = jobstart([v:progpath, '--clean', '--headless'])"
|
|
)
|
|
feed_data(':call jobwait([g:id])\n')
|
|
screen:expect([[
|
|
|
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:call jobwait([g:id]) |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\003')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
Type :qa and press <Enter> to exit Nvim |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('cursor is not hidden on incsearch with no match', function()
|
|
feed_data('ifoo\027')
|
|
feed_data('/foo')
|
|
screen:expect([[
|
|
{1:foo} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
/foo{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
screen:sleep(10)
|
|
feed_data('b')
|
|
screen:expect([[
|
|
foo |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
/foob{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
screen:sleep(10)
|
|
feed_data('a')
|
|
screen:expect([[
|
|
foo |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
/fooba{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('emits hyperlinks with OSC 8', function()
|
|
exec_lua([[
|
|
local buf = vim.api.nvim_get_current_buf()
|
|
_G.urls = {}
|
|
vim.api.nvim_create_autocmd('TermRequest', {
|
|
buffer = buf,
|
|
callback = function(args)
|
|
local req = args.data
|
|
if not req then
|
|
return
|
|
end
|
|
local url = req:match('\027]8;;(.*)$')
|
|
if url ~= nil then
|
|
table.insert(_G.urls, url)
|
|
end
|
|
end,
|
|
})
|
|
]])
|
|
child_exec_lua([[
|
|
vim.api.nvim_buf_set_lines(0, 0, 0, true, {'Hello'})
|
|
local ns = vim.api.nvim_create_namespace('test')
|
|
vim.api.nvim_buf_set_extmark(0, ns, 0, 1, {
|
|
end_col = 3,
|
|
url = 'https://example.com',
|
|
})
|
|
]])
|
|
retry(nil, 1000, function()
|
|
eq({ 'https://example.com', '' }, exec_lua([[return _G.urls]]))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('TUI', function()
|
|
before_each(clear)
|
|
|
|
it('resize at startup #17285 #15044 #11330', function()
|
|
local screen = Screen.new(50, 10)
|
|
screen:set_default_attr_ids({
|
|
[1] = { reverse = true },
|
|
[2] = { bold = true, foreground = Screen.colors.Blue },
|
|
[3] = { bold = true },
|
|
[4] = { foreground = tonumber('0x4040ff'), fg_indexed = true },
|
|
[5] = { bold = true, reverse = true },
|
|
})
|
|
screen:attach()
|
|
fn.termopen({
|
|
nvim_prog,
|
|
'--clean',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set notermguicolors',
|
|
'--cmd',
|
|
'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile',
|
|
}, {
|
|
env = {
|
|
VIMRUNTIME = os.getenv('VIMRUNTIME'),
|
|
},
|
|
})
|
|
exec([[
|
|
sleep 500m
|
|
vs new
|
|
]])
|
|
screen:expect([[
|
|
^ │ |
|
|
{2:~ }│{4:~ }|*5
|
|
{2:~ }│{5:[No Name] 0,0-1 All}|
|
|
{2:~ }│ |
|
|
{5:new }{1:{MATCH:<.*[/\]nvim }}|
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('argv[0] can be overridden #23953', function()
|
|
if not exec_lua('return pcall(require, "ffi")') then
|
|
pending('missing LuaJIT FFI')
|
|
end
|
|
local script_file = 'Xargv0.lua'
|
|
write_file(
|
|
script_file,
|
|
[=[
|
|
local ffi = require('ffi')
|
|
ffi.cdef([[int execl(const char *, const char *, ...);]])
|
|
ffi.C.execl(vim.v.progpath, 'Xargv0nvim', '--clean', nil)
|
|
]=]
|
|
)
|
|
finally(function()
|
|
os.remove(script_file)
|
|
end)
|
|
local screen = thelpers.setup_child_nvim({ '--clean', '-l', script_file })
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
~ |*3
|
|
[No Name] 0,0-1 All|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data(':put =v:argv + [v:progname]\n')
|
|
screen:expect {
|
|
grid = [[
|
|
Xargv0nvim |
|
|
--embed |
|
|
--clean |
|
|
{1:X}argv0nvim |
|
|
[No Name] [+] 5,1 Bot|
|
|
4 more lines |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('with non-tty (pipe) stdout/stderr', function()
|
|
finally(function()
|
|
os.remove('testF')
|
|
end)
|
|
local screen = thelpers.screen_setup(
|
|
0,
|
|
('"%s" -u NONE -i NONE --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format(
|
|
nvim_prog
|
|
),
|
|
nil,
|
|
{ VIMRUNTIME = os.getenv('VIMRUNTIME') }
|
|
)
|
|
feed_data(':w testF\n:q\n')
|
|
screen:expect([[
|
|
:w testF |
|
|
:q |
|
|
abc |
|
|
|
|
|
[Process exited 0]{1: } |
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('<C-h> #10134', function()
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noruler notermguicolors',
|
|
'--cmd',
|
|
':nnoremap <C-h> :echomsg "\\<C-h\\>"<CR>',
|
|
})
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
command([[call chansend(b:terminal_job_id, "\<C-h>")]])
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
<C-h> |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('draws line with many trailing spaces correctly #24955', function()
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'set notermguicolors',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'call setline(1, ["1st line" .. repeat(" ", 153), "2nd line"])',
|
|
}, { cols = 80 })
|
|
screen:expect {
|
|
grid = [[
|
|
{1:1}st line |
|
|
|*2
|
|
2nd line |
|
|
{5:[No Name] [+] 1,1 All}|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('$')
|
|
screen:expect {
|
|
grid = [[
|
|
1st line |
|
|
|
|
|
{1: } |
|
|
2nd line |
|
|
{5:[No Name] [+] 1,161 All}|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
end)
|
|
|
|
describe('TUI UIEnter/UILeave', function()
|
|
it('fires exactly once, after VimEnter', function()
|
|
clear()
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile noshowcmd noruler notermguicolors',
|
|
'--cmd',
|
|
'let g:evs = []',
|
|
'--cmd',
|
|
'autocmd UIEnter * :call add(g:evs, "UIEnter")',
|
|
'--cmd',
|
|
'autocmd UILeave * :call add(g:evs, "UILeave")',
|
|
'--cmd',
|
|
'autocmd VimEnter * :call add(g:evs, "VimEnter")',
|
|
})
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data(':echo g:evs\n')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
['VimEnter', 'UIEnter'] |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
end)
|
|
end)
|
|
|
|
describe('TUI FocusGained/FocusLost', function()
|
|
local screen
|
|
local child_session
|
|
|
|
before_each(function()
|
|
clear()
|
|
local child_server = new_pipename()
|
|
screen = thelpers.setup_child_nvim({
|
|
'--listen',
|
|
child_server,
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile noshowcmd noruler notermguicolors background=dark',
|
|
})
|
|
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
child_session = helpers.connect(child_server)
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
autocmd FocusGained * echo 'gained'
|
|
autocmd FocusLost * echo 'lost'
|
|
]],
|
|
{}
|
|
)
|
|
feed_data('\034\016') -- CTRL-\ CTRL-N
|
|
end)
|
|
|
|
it('in normal-mode', function()
|
|
retry(2, 3 * screen.timeout, function()
|
|
feed_data('\027[I')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
gained |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
|
|
feed_data('\027[O')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
lost |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
it('in insert-mode', function()
|
|
feed_data(':set noshowmode\r')
|
|
feed_data('i')
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:set noshowmode |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
retry(2, 3 * screen.timeout, function()
|
|
feed_data('\027[I')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
gained |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[O')
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
lost |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
-- During cmdline-mode we ignore :echo invoked by timers/events.
|
|
-- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419.
|
|
it('in cmdline-mode does NOT :echo', function()
|
|
feed_data(':')
|
|
feed_data('\027[I')
|
|
screen:expect([[
|
|
|
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
feed_data('\027[O')
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
:{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
unchanged = true,
|
|
}
|
|
end)
|
|
|
|
it('in cmdline-mode', function()
|
|
-- Set up autocmds that modify the buffer, instead of just calling :echo.
|
|
-- This is how we can test handling of focus gained/lost during cmdline-mode.
|
|
-- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419.
|
|
child_session:request(
|
|
'nvim_exec2',
|
|
[[
|
|
autocmd!
|
|
autocmd FocusLost * call append(line('$'), 'lost')
|
|
autocmd FocusGained * call append(line('$'), 'gained')
|
|
]],
|
|
{}
|
|
)
|
|
retry(2, 3 * screen.timeout, function()
|
|
-- Enter cmdline-mode.
|
|
feed_data(':')
|
|
screen:sleep(1)
|
|
-- Send focus lost/gained termcodes.
|
|
feed_data('\027[O')
|
|
feed_data('\027[I')
|
|
screen:sleep(1)
|
|
-- Exit cmdline-mode. Redraws from timers/events are blocked during
|
|
-- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode.
|
|
feed_data('\n')
|
|
screen:expect { any = 'lost' .. (' '):rep(46) .. '|\ngained' }
|
|
end)
|
|
end)
|
|
|
|
it('in terminal-mode', function()
|
|
feed_data(':set shell=' .. testprg('shell-test') .. ' shellcmdflag=EXE\n')
|
|
feed_data(':set noshowmode laststatus=0\n')
|
|
|
|
feed_data(':terminal zia\n')
|
|
-- Wait for terminal to be ready.
|
|
screen:expect {
|
|
grid = [[
|
|
{1:r}eady $ zia |
|
|
|
|
|
[Process exited 0] |
|
|
|*2
|
|
:terminal zia |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data('\027[I')
|
|
screen:expect {
|
|
grid = [[
|
|
{1:r}eady $ zia |
|
|
|
|
|
[Process exited 0] |
|
|
|*2
|
|
gained |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
timeout = (4 * screen.timeout),
|
|
}
|
|
|
|
feed_data('\027[O')
|
|
screen:expect([[
|
|
{1:r}eady $ zia |
|
|
|
|
|
[Process exited 0] |
|
|
|*2
|
|
lost |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
it('in press-enter prompt', function()
|
|
feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n")
|
|
-- Execute :messages to provoke the press-enter prompt.
|
|
feed_data(':messages\n')
|
|
screen:expect {
|
|
grid = [[
|
|
msg1 |
|
|
msg2 |
|
|
msg3 |
|
|
msg4 |
|
|
msg5 |
|
|
{10:Press ENTER or type command to continue}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('\027[I')
|
|
feed_data('\027[I')
|
|
screen:expect {
|
|
grid = [[
|
|
msg1 |
|
|
msg2 |
|
|
msg3 |
|
|
msg4 |
|
|
msg5 |
|
|
{10:Press ENTER or type command to continue}{1: } |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
unchanged = true,
|
|
}
|
|
end)
|
|
end)
|
|
|
|
-- These tests require `thelpers` because --headless/--embed
|
|
-- does not initialize the TUI.
|
|
describe("TUI 't_Co' (terminal colors)", function()
|
|
local screen
|
|
|
|
local function assert_term_colors(term, colorterm, maxcolors)
|
|
clear({ env = { TERM = term }, args = {} })
|
|
screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors',
|
|
}, {
|
|
env = {
|
|
LANG = 'C',
|
|
TERM = term or '',
|
|
COLORTERM = colorterm or '',
|
|
},
|
|
})
|
|
|
|
local tline
|
|
if maxcolors == 8 then
|
|
tline = '{9:~ }'
|
|
elseif maxcolors == 16 then
|
|
tline = '~ '
|
|
else
|
|
tline = '{4:~ }'
|
|
end
|
|
|
|
screen:expect(string.format(
|
|
[[
|
|
{1: } |
|
|
%s|*4
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
tline
|
|
))
|
|
|
|
feed_data(':echo &t_Co\n')
|
|
screen:expect(string.format(
|
|
[[
|
|
{1: } |
|
|
%s|*4
|
|
%-3s |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
tline,
|
|
tostring(maxcolors and maxcolors or '')
|
|
))
|
|
end
|
|
|
|
-- ansi and no terminal type at all:
|
|
|
|
it('no TERM uses 8 colors', function()
|
|
assert_term_colors(nil, nil, 8)
|
|
end)
|
|
|
|
it('TERM=ansi no COLORTERM uses 8 colors', function()
|
|
assert_term_colors('ansi', nil, 8)
|
|
end)
|
|
|
|
it('TERM=ansi with COLORTERM=anything-no-number uses 16 colors', function()
|
|
assert_term_colors('ansi', 'yet-another-term', 16)
|
|
end)
|
|
|
|
it('unknown TERM COLORTERM with 256 in name uses 256 colors', function()
|
|
assert_term_colors('ansi', 'yet-another-term-256color', 256)
|
|
end)
|
|
|
|
it('TERM=ansi-256color sets 256 colours', function()
|
|
assert_term_colors('ansi-256color', nil, 256)
|
|
end)
|
|
|
|
-- Unknown terminal types:
|
|
|
|
it('unknown TERM no COLORTERM sets 8 colours', function()
|
|
assert_term_colors('yet-another-term', nil, 8)
|
|
end)
|
|
|
|
it('unknown TERM with COLORTERM=anything-no-number uses 16 colors', function()
|
|
assert_term_colors('yet-another-term', 'yet-another-term', 16)
|
|
end)
|
|
|
|
it('unknown TERM with 256 in name sets 256 colours', function()
|
|
assert_term_colors('yet-another-term-256color', nil, 256)
|
|
end)
|
|
|
|
it('unknown TERM COLORTERM with 256 in name uses 256 colors', function()
|
|
assert_term_colors('yet-another-term', 'yet-another-term-256color', 256)
|
|
end)
|
|
|
|
-- Linux kernel terminal emulator:
|
|
|
|
it('TERM=linux uses 256 colors', function()
|
|
assert_term_colors('linux', nil, 256)
|
|
end)
|
|
|
|
it('TERM=linux-16color uses 256 colors', function()
|
|
assert_term_colors('linux-16color', nil, 256)
|
|
end)
|
|
|
|
it('TERM=linux-256color uses 256 colors', function()
|
|
assert_term_colors('linux-256color', nil, 256)
|
|
end)
|
|
|
|
-- screen:
|
|
--
|
|
-- FreeBSD falls back to the built-in screen-256colour entry.
|
|
-- Linux and MacOS have a screen entry in external terminfo with 8 colours,
|
|
-- which is raised to 16 by COLORTERM.
|
|
|
|
it('TERM=screen no COLORTERM uses 8/256 colors', function()
|
|
if is_os('freebsd') then
|
|
assert_term_colors('screen', nil, 256)
|
|
else
|
|
assert_term_colors('screen', nil, 8)
|
|
end
|
|
end)
|
|
|
|
it('TERM=screen COLORTERM=screen uses 16/256 colors', function()
|
|
if is_os('freebsd') then
|
|
assert_term_colors('screen', 'screen', 256)
|
|
else
|
|
assert_term_colors('screen', 'screen', 16)
|
|
end
|
|
end)
|
|
|
|
it('TERM=screen COLORTERM=screen-256color uses 256 colors', function()
|
|
assert_term_colors('screen', 'screen-256color', 256)
|
|
end)
|
|
|
|
it('TERM=screen-256color no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('screen-256color', nil, 256)
|
|
end)
|
|
|
|
-- tmux:
|
|
--
|
|
-- FreeBSD and MacOS fall back to the built-in tmux-256colour entry.
|
|
-- Linux has a tmux entry in external terminfo with 8 colours,
|
|
-- which is raised to 256.
|
|
|
|
it('TERM=tmux no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('tmux', nil, 256)
|
|
end)
|
|
|
|
it('TERM=tmux COLORTERM=tmux uses 256 colors', function()
|
|
assert_term_colors('tmux', 'tmux', 256)
|
|
end)
|
|
|
|
it('TERM=tmux COLORTERM=tmux-256color uses 256 colors', function()
|
|
assert_term_colors('tmux', 'tmux-256color', 256)
|
|
end)
|
|
|
|
it('TERM=tmux-256color no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('tmux-256color', nil, 256)
|
|
end)
|
|
|
|
-- xterm and imitators:
|
|
|
|
it('TERM=xterm uses 256 colors', function()
|
|
assert_term_colors('xterm', nil, 256)
|
|
end)
|
|
|
|
it('TERM=xterm COLORTERM=gnome-terminal uses 256 colors', function()
|
|
assert_term_colors('xterm', 'gnome-terminal', 256)
|
|
end)
|
|
|
|
it('TERM=xterm COLORTERM=mate-terminal uses 256 colors', function()
|
|
assert_term_colors('xterm', 'mate-terminal', 256)
|
|
end)
|
|
|
|
it('TERM=xterm-256color uses 256 colors', function()
|
|
assert_term_colors('xterm-256color', nil, 256)
|
|
end)
|
|
|
|
-- rxvt and stterm:
|
|
--
|
|
-- FreeBSD and MacOS fall back to the built-in rxvt-256color and
|
|
-- st-256colour entries.
|
|
-- Linux has an rxvt, an st, and an st-16color entry in external terminfo
|
|
-- with 8, 8, and 16 colours respectively, which are raised to 256.
|
|
|
|
it('TERM=rxvt no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('rxvt', nil, 256)
|
|
end)
|
|
|
|
it('TERM=rxvt COLORTERM=rxvt uses 256 colors', function()
|
|
assert_term_colors('rxvt', 'rxvt', 256)
|
|
end)
|
|
|
|
it('TERM=rxvt-256color uses 256 colors', function()
|
|
assert_term_colors('rxvt-256color', nil, 256)
|
|
end)
|
|
|
|
it('TERM=st no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('st', nil, 256)
|
|
end)
|
|
|
|
it('TERM=st COLORTERM=st uses 256 colors', function()
|
|
assert_term_colors('st', 'st', 256)
|
|
end)
|
|
|
|
it('TERM=st COLORTERM=st-256color uses 256 colors', function()
|
|
assert_term_colors('st', 'st-256color', 256)
|
|
end)
|
|
|
|
it('TERM=st-16color no COLORTERM uses 8/256 colors', function()
|
|
assert_term_colors('st', nil, 256)
|
|
end)
|
|
|
|
it('TERM=st-16color COLORTERM=st uses 16/256 colors', function()
|
|
assert_term_colors('st', 'st', 256)
|
|
end)
|
|
|
|
it('TERM=st-16color COLORTERM=st-256color uses 256 colors', function()
|
|
assert_term_colors('st', 'st-256color', 256)
|
|
end)
|
|
|
|
it('TERM=st-256color uses 256 colors', function()
|
|
assert_term_colors('st-256color', nil, 256)
|
|
end)
|
|
|
|
-- gnome and vte:
|
|
--
|
|
-- FreeBSD and MacOS fall back to the built-in vte-256color entry.
|
|
-- Linux has a gnome, a vte, a gnome-256color, and a vte-256color entry in
|
|
-- external terminfo with 8, 8, 256, and 256 colours respectively, which are
|
|
-- raised to 256.
|
|
|
|
it('TERM=gnome no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('gnome', nil, 256)
|
|
end)
|
|
|
|
it('TERM=gnome COLORTERM=gnome uses 256 colors', function()
|
|
assert_term_colors('gnome', 'gnome', 256)
|
|
end)
|
|
|
|
it('TERM=gnome COLORTERM=gnome-256color uses 256 colors', function()
|
|
assert_term_colors('gnome', 'gnome-256color', 256)
|
|
end)
|
|
|
|
it('TERM=gnome-256color uses 256 colors', function()
|
|
assert_term_colors('gnome-256color', nil, 256)
|
|
end)
|
|
|
|
it('TERM=vte no COLORTERM uses 256 colors', function()
|
|
assert_term_colors('vte', nil, 256)
|
|
end)
|
|
|
|
it('TERM=vte COLORTERM=vte uses 256 colors', function()
|
|
assert_term_colors('vte', 'vte', 256)
|
|
end)
|
|
|
|
it('TERM=vte COLORTERM=vte-256color uses 256 colors', function()
|
|
assert_term_colors('vte', 'vte-256color', 256)
|
|
end)
|
|
|
|
it('TERM=vte-256color uses 256 colors', function()
|
|
assert_term_colors('vte-256color', nil, 256)
|
|
end)
|
|
|
|
-- others:
|
|
|
|
-- TODO(blueyed): this is made pending, since it causes failure + later hang
|
|
-- when using non-compatible libvterm (#9494/#10179).
|
|
pending('TERM=interix uses 8 colors', function()
|
|
assert_term_colors('interix', nil, 8)
|
|
end)
|
|
|
|
it('TERM=iTerm.app uses 256 colors', function()
|
|
assert_term_colors('iTerm.app', nil, 256)
|
|
end)
|
|
|
|
it('TERM=iterm uses 256 colors', function()
|
|
assert_term_colors('iterm', nil, 256)
|
|
end)
|
|
end)
|
|
|
|
-- These tests require `thelpers` because --headless/--embed
|
|
-- does not initialize the TUI.
|
|
describe("TUI 'term' option", function()
|
|
local screen
|
|
|
|
local function assert_term(term_envvar, term_expected)
|
|
clear()
|
|
screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors',
|
|
}, {
|
|
env = {
|
|
LANG = 'C',
|
|
TERM = term_envvar or '',
|
|
},
|
|
})
|
|
|
|
local full_timeout = screen.timeout
|
|
screen.timeout = 250 -- We want screen:expect() to fail quickly.
|
|
retry(nil, 2 * full_timeout, function() -- Wait for TUI thread to set 'term'.
|
|
feed_data(":echo 'term='.(&term)\n")
|
|
screen:expect { any = 'term=' .. term_expected }
|
|
end)
|
|
end
|
|
|
|
it('gets builtin term if $TERM is invalid', function()
|
|
assert_term('foo', 'builtin_ansi')
|
|
end)
|
|
|
|
it('gets system-provided term if $TERM is valid', function()
|
|
if is_os('openbsd') then
|
|
assert_term('xterm', 'xterm')
|
|
elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used.
|
|
assert_term('xterm', 'builtin_xterm')
|
|
elseif is_os('mac') then
|
|
local status, _ = pcall(assert_term, 'xterm', 'xterm')
|
|
if not status then
|
|
pending('macOS: unibilium could not find terminfo')
|
|
end
|
|
else
|
|
assert_term('xterm', 'xterm')
|
|
end
|
|
end)
|
|
|
|
it('builtin terms', function()
|
|
-- These non-standard terminfos are always builtin.
|
|
assert_term('win32con', 'builtin_win32con')
|
|
assert_term('conemu', 'builtin_conemu')
|
|
assert_term('vtpcon', 'builtin_vtpcon')
|
|
end)
|
|
end)
|
|
|
|
-- These tests require `thelpers` because --headless/--embed
|
|
-- does not initialize the TUI.
|
|
describe('TUI', function()
|
|
local screen
|
|
local logfile = 'Xtest_tui_verbose_log'
|
|
after_each(function()
|
|
os.remove(logfile)
|
|
end)
|
|
|
|
-- Runs (child) `nvim` in a TTY (:terminal), to start the builtin TUI.
|
|
local function nvim_tui(extra_args)
|
|
clear()
|
|
screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors',
|
|
extra_args,
|
|
}, {
|
|
env = {
|
|
LANG = 'C',
|
|
},
|
|
})
|
|
end
|
|
|
|
it('-V3log logs terminfo values', function()
|
|
nvim_tui('-V3' .. logfile)
|
|
|
|
-- Wait for TUI to start.
|
|
feed_data('Gitext')
|
|
screen:expect([[
|
|
text{1: } |
|
|
{4:~ }|*4
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
|
|
retry(nil, 3000, function() -- Wait for log file to be flushed.
|
|
local log = read_file('Xtest_tui_verbose_log') or ''
|
|
eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}}
|
|
ok(#log > 50)
|
|
end)
|
|
end)
|
|
|
|
it('does not crash on large inputs #26099', function()
|
|
nvim_tui()
|
|
|
|
screen:expect([[
|
|
{1: } |
|
|
{4:~ }|*4
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
|
|
feed_data(string.format('\027]52;c;%s\027\\', string.rep('A', 8192)))
|
|
|
|
screen:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*4
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
unchanged = true,
|
|
}
|
|
end)
|
|
|
|
it('queries the terminal for truecolor support', function()
|
|
clear()
|
|
exec_lua([[
|
|
vim.api.nvim_create_autocmd('TermRequest', {
|
|
callback = function(args)
|
|
local req = args.data
|
|
local payload = req:match('^\027P%+q([%x;]+)$')
|
|
if payload then
|
|
vim.g.xtgettcap = true
|
|
return true
|
|
end
|
|
end,
|
|
})
|
|
]])
|
|
screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
}, {
|
|
env = {
|
|
VIMRUNTIME = os.getenv('VIMRUNTIME'),
|
|
|
|
-- Force COLORTERM to be unset and use a TERM that does not contain Tc or RGB in terminfo.
|
|
-- This will force the nested nvim instance to query with XTGETTCAP
|
|
COLORTERM = '',
|
|
TERM = 'xterm-256colors',
|
|
},
|
|
})
|
|
|
|
retry(nil, 1000, function()
|
|
eq(true, eval("get(g:, 'xtgettcap', v:false)"))
|
|
eq(1, eval('&termguicolors'))
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('TUI bg color', function()
|
|
before_each(clear)
|
|
|
|
local attr_ids = {
|
|
[1] = { reverse = true },
|
|
[2] = { bold = true },
|
|
[3] = { reverse = true, bold = true },
|
|
[4] = { foreground = tonumber('0x00000a') },
|
|
}
|
|
|
|
it('is properly set in a nested Nvim instance when background=dark', function()
|
|
command('highlight clear Normal')
|
|
command('set background=dark') -- set outer Nvim background
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile',
|
|
})
|
|
screen:set_default_attr_ids(attr_ids)
|
|
retry(nil, 30000, function() -- wait for automatic background processing
|
|
screen:sleep(20)
|
|
feed_command('set background?') -- check nested Nvim background
|
|
screen:expect([[
|
|
{1: } |
|
|
{2:~} |
|
|
{2:~} |
|
|
{2:~} |
|
|
{3:[No Name] 0,0-1 All}|
|
|
background=dark |
|
|
{4:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
it('is properly set in a nested Nvim instance when background=light', function()
|
|
command('highlight clear Normal')
|
|
command('set background=light') -- set outer Nvim background
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile',
|
|
})
|
|
retry(nil, 30000, function() -- wait for automatic background processing
|
|
screen:sleep(20)
|
|
feed_command('set background?') -- check nested Nvim background
|
|
screen:expect([[
|
|
{1: } |
|
|
{3:~} |
|
|
{3:~} |
|
|
{3:~} |
|
|
{5:[No Name] 0,0-1 All}|
|
|
background=light |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
it('queries the terminal for background color', function()
|
|
exec_lua([[
|
|
vim.api.nvim_create_autocmd('TermRequest', {
|
|
callback = function(args)
|
|
local req = args.data
|
|
if req == '\027]11;?' then
|
|
vim.g.oscrequest = true
|
|
return true
|
|
end
|
|
end,
|
|
})
|
|
]])
|
|
thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile',
|
|
})
|
|
retry(nil, 1000, function()
|
|
eq(true, eval("get(g:, 'oscrequest', v:false)"))
|
|
end)
|
|
end)
|
|
|
|
it('triggers OptionSet from automatic background processing', function()
|
|
local screen = thelpers.setup_child_nvim({
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
'set noswapfile',
|
|
'-c',
|
|
'autocmd OptionSet background echo "did OptionSet, yay!"',
|
|
})
|
|
retry(nil, 30000, function() -- wait for automatic background processing
|
|
screen:sleep(20)
|
|
screen:expect([[
|
|
{1: } |
|
|
{3:~} |
|
|
{3:~} |
|
|
{3:~} |
|
|
{5:[No Name] 0,0-1 All}|
|
|
did OptionSet, yay! |
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
-- These tests require `thelpers` because --headless/--embed
|
|
-- does not initialize the TUI.
|
|
describe('TUI as a client', function()
|
|
it('connects to remote instance (with its own TUI)', function()
|
|
local server_super = spawn_argv(false) -- equivalent to clear()
|
|
local client_super = spawn_argv(true)
|
|
|
|
set_session(server_super)
|
|
local server_pipe = new_pipename()
|
|
local screen_server = thelpers.setup_child_nvim({
|
|
'--listen',
|
|
server_pipe,
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors laststatus=2 background=dark',
|
|
})
|
|
|
|
feed_data('iHello, World')
|
|
screen_server:expect {
|
|
grid = [[
|
|
Hello, World{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('\027')
|
|
screen_server:expect {
|
|
grid = [[
|
|
Hello, Worl{1:d} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
set_session(client_super)
|
|
local screen_client = thelpers.setup_child_nvim({
|
|
'--server',
|
|
server_pipe,
|
|
'--remote-ui',
|
|
})
|
|
|
|
screen_client:expect {
|
|
grid = [[
|
|
Hello, Worl{1:d} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
-- grid smaller than containing terminal window is cleared properly
|
|
feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n")
|
|
feed_data('0:set lines=3\n')
|
|
screen_server:expect {
|
|
grid = [[
|
|
{1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
|
|
{5:[No Name] [+] }|
|
|
|*4
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data(':q!\n')
|
|
|
|
server_super:close()
|
|
client_super:close()
|
|
end)
|
|
|
|
it('connects to remote instance (--headless)', function()
|
|
local server = spawn_argv(false) -- equivalent to clear()
|
|
local client_super = spawn_argv(true)
|
|
|
|
set_session(server)
|
|
local server_pipe = api.nvim_get_vvar('servername')
|
|
server:request('nvim_input', 'iHalloj!<Esc>')
|
|
server:request('nvim_command', 'set notermguicolors')
|
|
|
|
set_session(client_super)
|
|
local screen_client = thelpers.setup_child_nvim({
|
|
'--server',
|
|
server_pipe,
|
|
'--remote-ui',
|
|
})
|
|
|
|
screen_client:expect {
|
|
grid = [[
|
|
Halloj{1:!} |
|
|
{4:~ }|*4
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
-- No heap-use-after-free when receiving UI events after deadly signal #22184
|
|
server:request('nvim_input', ('a'):rep(1000))
|
|
exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
|
|
screen_client:expect {
|
|
grid = [[
|
|
Vim: Caught deadly signal 'SIGTERM' |
|
|
|*2
|
|
[Process exited 1]{1: } |
|
|
|*2
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
eq(0, api.nvim_get_vvar('shell_error'))
|
|
-- exits on input eof #22244
|
|
fn.system({ nvim_prog, '--server', server_pipe, '--remote-ui' })
|
|
eq(1, api.nvim_get_vvar('shell_error'))
|
|
|
|
client_super:close()
|
|
server:close()
|
|
end)
|
|
|
|
it('throws error when no server exists', function()
|
|
clear()
|
|
local screen = thelpers.setup_child_nvim({
|
|
'--server',
|
|
'127.0.0.1:2436546',
|
|
'--remote-ui',
|
|
}, { cols = 60 })
|
|
|
|
screen:expect([[
|
|
Remote ui failed to start: {MATCH:.*}|
|
|
|
|
|
[Process exited 1]{1: } |
|
|
|*3
|
|
{3:-- TERMINAL --} |
|
|
]])
|
|
end)
|
|
|
|
local function test_remote_tui_quit(status)
|
|
local server_super = spawn_argv(false) -- equivalent to clear()
|
|
local client_super = spawn_argv(true)
|
|
|
|
set_session(server_super)
|
|
local server_pipe = new_pipename()
|
|
local screen_server = thelpers.setup_child_nvim({
|
|
'--listen',
|
|
server_pipe,
|
|
'-u',
|
|
'NONE',
|
|
'-i',
|
|
'NONE',
|
|
'--cmd',
|
|
'colorscheme vim',
|
|
'--cmd',
|
|
nvim_set .. ' notermguicolors laststatus=2 background=dark',
|
|
})
|
|
screen_server:expect {
|
|
grid = [[
|
|
{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
feed_data('iHello, World')
|
|
screen_server:expect {
|
|
grid = [[
|
|
Hello, World{1: } |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
{3:-- INSERT --} |
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
feed_data('\027')
|
|
screen_server:expect {
|
|
grid = [[
|
|
Hello, Worl{1:d} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
set_session(client_super)
|
|
local screen_client = thelpers.setup_child_nvim({
|
|
'--server',
|
|
server_pipe,
|
|
'--remote-ui',
|
|
})
|
|
|
|
screen_client:expect {
|
|
grid = [[
|
|
Hello, Worl{1:d} |
|
|
{4:~ }|*3
|
|
{5:[No Name] [+] }|
|
|
|
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
-- quitting the server
|
|
set_session(server_super)
|
|
feed_data(status and ':' .. status .. 'cquit!\n' or ':quit!\n')
|
|
status = status and status or 0
|
|
screen_server:expect {
|
|
grid = [[
|
|
|
|
|
[Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
|
|
|*4
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
-- assert that client has exited
|
|
screen_client:expect {
|
|
grid = [[
|
|
|
|
|
[Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
|
|
|*4
|
|
{3:-- TERMINAL --} |
|
|
]],
|
|
}
|
|
|
|
server_super:close()
|
|
client_super:close()
|
|
end
|
|
|
|
describe('exits when server quits', function()
|
|
it('with :quit', function()
|
|
test_remote_tui_quit()
|
|
end)
|
|
|
|
it('with :cquit', function()
|
|
test_remote_tui_quit(42)
|
|
end)
|
|
end)
|
|
end)
|