neovim/test/unit/vterm_spec.lua
James McCoy 7a367c6967
test(vterm): move test functions into vterm_test fixture
In order to run unittests with a release build, we need the test
functions to be accessible when NDEBUG is defined. Moving the functions
into the test fixture ensures they are available and only available for
use by the unit tests.
2024-12-02 21:49:14 -05:00

3596 lines
98 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local t = require('test.unit.testutil')
local itp = t.gen_itp(it)
local bit = require('bit')
--- @class vterm
--- @field ENC_UTF8 integer
--- @field VTERM_ATTR_BLINK integer
--- @field VTERM_ATTR_BOLD integer
--- @field VTERM_ATTR_FONT integer
--- @field VTERM_ATTR_ITALIC integer
--- @field VTERM_ATTR_REVERSE integer
--- @field VTERM_ATTR_UNDERLINE integer
--- @field VTERM_BASELINE_RAISE integer
--- @field VTERM_KEY_ENTER integer
--- @field VTERM_KEY_FUNCTION_0 integer
--- @field VTERM_KEY_KP_0 integer
--- @field VTERM_KEY_NONE integer
--- @field VTERM_KEY_TAB integer
--- @field VTERM_KEY_UP integer
--- @field VTERM_MAX_CHARS_PER_CELL integer
--- @field VTERM_MOD_ALT integer
--- @field VTERM_MOD_CTRL integer
--- @field VTERM_MOD_SHIFT integer
--- @field parser_apc function
--- @field parser_csi function
--- @field parser_dcs function
--- @field parser_osc function
--- @field parser_pm function
--- @field parser_sos function
--- @field parser_text function
--- @field print_color function
--- @field screen_sb_clear function
--- @field screen_sb_popline function
--- @field screen_sb_pushline function
--- @field selection_query function
--- @field selection_set function
--- @field state_erase function
--- @field state_movecursor function
--- @field state_moverect function
--- @field state_pos function
--- @field state_putglyph function
--- @field state_sb_clear function
--- @field state_scrollrect function
--- @field state_setpenattr function
--- @field state_settermprop function
--- @field term_output function
--- @field vterm_input_write function
--- @field vterm_keyboard_end_paste function
--- @field vterm_keyboard_key function
--- @field vterm_keyboard_start_paste function
--- @field vterm_keyboard_unichar function
--- @field vterm_lookup_encoding fun(any, any):any
--- @field vterm_mouse_button function
--- @field vterm_mouse_move function
--- @field vterm_new fun(any, any):any
--- @field vterm_obtain_screen fun(any):any
--- @field vterm_obtain_state fun(any): any
--- @field vterm_output_set_callback function
--- @field vterm_parser_set_callbacks fun(any, any, any):any
--- @field vterm_screen_convert_color_to_rgb function
--- @field vterm_screen_enable_altscreen function
--- @field vterm_screen_enable_reflow function
--- @field vterm_screen_get_attrs_extent function
--- @field vterm_screen_get_cell function
--- @field vterm_screen_get_chars fun(any, any, any, any):any
--- @field vterm_screen_get_text fun(any, any, any, any):any
--- @field vterm_screen_is_eol fun(any, any):any
--- @field vterm_screen_reset function
--- @field vterm_screen_set_callbacks function
--- @field vterm_set_size function
--- @field vterm_set_utf8 fun(any, any, any):any
--- @field vterm_state_focus_in function
--- @field vterm_state_focus_out function
--- @field vterm_state_get_cursorpos fun(any, any)
--- @field vterm_state_get_lineinfo fun(any, any):any
--- @field vterm_state_get_penattr function
--- @field vterm_state_reset function
--- @field vterm_state_set_bold_highbright function
--- @field vterm_state_set_callbacks function
--- @field vterm_state_set_selection_callbacks function
--- @field vterm_state_set_unrecognised_fallbacks function
local vterm = t.cimport(
'./src/vterm/vterm.h',
'./src/vterm/vterm_internal.h',
'./test/unit/fixtures/vterm_test.h'
)
--- @return string
local function read_rm()
local f = assert(io.open(t.paths.vterm_test_file, 'rb'))
local text = f:read('*a')
f:close()
vim.fs.rm(t.paths.vterm_test_file, { force = true })
return text
end
local function append(str)
local f = assert(io.open(t.paths.vterm_test_file, 'a'))
f:write(str)
f:close()
return 1
end
local function parser_control(control)
return append(string.format('control %02x\n', control))
end
local function parser_escape(bytes)
return append(string.format('escape %s\n', t.ffi.string(bytes)))
end
local function wantparser(vt)
assert(vt)
local parser_cbs = t.ffi.new('VTermParserCallbacks')
parser_cbs['text'] = vterm.parser_text
parser_cbs['control'] = parser_control
parser_cbs['escape'] = parser_escape
parser_cbs['csi'] = vterm.parser_csi
parser_cbs['osc'] = vterm.parser_osc
parser_cbs['dcs'] = vterm.parser_dcs
parser_cbs['apc'] = vterm.parser_apc
parser_cbs['pm'] = vterm.parser_pm
parser_cbs['sos'] = vterm.parser_sos
vterm.vterm_parser_set_callbacks(vt, parser_cbs, nil)
end
--- @return any
local function init()
local vt = vterm.vterm_new(25, 80)
vterm.vterm_output_set_callback(vt, vterm.term_output, nil)
vterm.vterm_set_utf8(vt, true)
return vt
end
local function state_setlineinfo()
return 1
end
--- @return any
local function wantstate(vt, opts)
opts = opts or {}
assert(vt)
local state = vterm.vterm_obtain_state(vt)
local state_cbs = t.ffi.new('VTermStateCallbacks')
state_cbs['putglyph'] = vterm.state_putglyph
state_cbs['movecursor'] = vterm.state_movecursor
state_cbs['scrollrect'] = vterm.state_scrollrect
state_cbs['moverect'] = vterm.state_moverect
state_cbs['erase'] = vterm.state_erase
state_cbs['setpenattr'] = vterm.state_setpenattr
state_cbs['settermprop'] = vterm.state_settermprop
state_cbs['setlineinfo'] = state_setlineinfo
state_cbs['sb_clear'] = vterm.state_sb_clear
local selection_cbs = t.ffi.new('VTermSelectionCallbacks')
selection_cbs['set'] = vterm.selection_set
selection_cbs['query'] = vterm.selection_query
vterm.vterm_state_set_callbacks(state, state_cbs, nil)
-- In some tests we want to check the behaviour of overflowing the buffer, so make it nicely small
vterm.vterm_state_set_selection_callbacks(state, selection_cbs, nil, nil, 16)
vterm.vterm_state_set_bold_highbright(state, 1)
vterm.vterm_state_reset(state, 1)
local fallbacks = t.ffi.new('VTermStateFallbacks')
fallbacks['control'] = parser_control
fallbacks['csi'] = vterm.parser_csi
fallbacks['osc'] = vterm.parser_osc
fallbacks['dcs'] = vterm.parser_dcs
fallbacks['apc'] = vterm.parser_apc
fallbacks['pm'] = vterm.parser_pm
fallbacks['sos'] = vterm.parser_sos
vterm.want_state_scrollback = opts.b or false
vterm.want_state_erase = opts.e or false
vterm.vterm_state_set_unrecognised_fallbacks(state, opts.f and fallbacks or nil, nil)
vterm.want_state_putglyph = opts.g or false
vterm.want_state_moverect = opts.m or false
vterm.want_state_settermprop = opts.p or false
vterm.want_state_scrollrect = opts.s or false
return state
end
--- @return any
local function wantscreen(vt, opts)
opts = opts or {}
local screen = vterm.vterm_obtain_screen(vt)
local screen_cbs = t.ffi.new('VTermScreenCallbacks')
-- TODO(dundargoc): fix
-- screen_cbs['damage'] = vterm.screen_damage
screen_cbs['moverect'] = vterm.state_moverect
screen_cbs['movecursor'] = vterm.state_movecursor
screen_cbs['settermprop'] = vterm.state_settermprop
screen_cbs['sb_pushline'] = vterm.screen_sb_pushline
screen_cbs['sb_popline'] = vterm.screen_sb_popline
screen_cbs['sb_clear'] = vterm.screen_sb_clear
vterm.vterm_screen_set_callbacks(screen, screen_cbs, nil)
if opts.a then
vterm.vterm_screen_enable_altscreen(screen, 1)
end
vterm.want_screen_scrollback = opts.b or false
vterm.want_state_movecursor = opts.c or false
-- TODO(dundargoc): fix
-- vterm.want_screen_damage = opts.d or opts.D or false
-- vterm.want_screen_cells = opts.D or false
vterm.want_state_moverect = opts.m or false
vterm.want_state_settermprop = opts.p or false
if opts.r then
vterm.vterm_screen_enable_reflow(screen, true)
end
return screen
end
local function reset(state, screen)
if state then
vterm.vterm_state_reset(state, 1)
vterm.vterm_state_get_cursorpos(state, vterm.state_pos)
end
if screen then
vterm.vterm_screen_reset(screen, 1)
end
end
local function push(input, vt)
vterm.vterm_input_write(vt, input, string.len(input))
end
local function expect(expected)
local actual = read_rm()
t.eq(expected .. '\n', actual)
end
local function expect_output(expected_preformat)
local actual = read_rm()
local expected = 'output '
for c in string.gmatch(expected_preformat, '.') do
if expected ~= 'output ' then
expected = expected .. ','
end
expected = string.format('%s%x', expected, string.byte(c))
end
t.eq(expected .. '\n', actual)
end
local function cursor(row, col, state)
local pos = t.ffi.new('VTermPos') --- @type {row: integer, col: integer}
vterm.vterm_state_get_cursorpos(state, pos)
t.eq(row, pos.row)
t.eq(col, pos.col)
end
local function lineinfo(row, expected, state)
local info = vterm.vterm_state_get_lineinfo(state, row)
local dwl = info.doublewidth == 1
local dhl = info.doubleheight == 1
local cont = info.continuation == 1
t.eq(dwl, expected.dwl or false)
t.eq(dhl, expected.dhl or false)
t.eq(cont, expected.cont or false)
end
local function pen(attribute, expected, state)
local is_bool = { bold = true, italic = true, blink = true, reverse = true }
local vterm_attribute = {
bold = vterm.VTERM_ATTR_BOLD,
underline = vterm.VTERM_ATTR_UNDERLINE,
italic = vterm.VTERM_ATTR_ITALIC,
blink = vterm.VTERM_ATTR_BLINK,
reverse = vterm.VTERM_ATTR_REVERSE,
font = vterm.VTERM_ATTR_FONT,
}
local val = t.ffi.new('VTermValue') --- @type {boolean: integer}
vterm.vterm_state_get_penattr(state, vterm_attribute[attribute], val)
local actual = val.boolean --- @type integer|boolean
if is_bool[attribute] then
actual = val.boolean == 1
end
t.eq(expected, actual)
end
local function resize(rows, cols, vt)
vterm.vterm_set_size(vt, rows, cols)
end
local function screen_chars(start_row, start_col, end_row, end_col, expected, screen)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = start_row
rect['start_col'] = start_col
rect['end_row'] = end_row
rect['end_col'] = end_col
local len = vterm.vterm_screen_get_chars(screen, nil, 0, rect)
local chars = t.ffi.new('uint32_t[?]', len)
vterm.vterm_screen_get_chars(screen, chars, len, rect)
local actual = ''
for i = 0, tonumber(len) - 1 do
actual = actual .. string.char(chars[i])
end
t.eq(expected, actual)
end
local function screen_text(start_row, start_col, end_row, end_col, expected, screen)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = start_row
rect['start_col'] = start_col
rect['end_row'] = end_row
rect['end_col'] = end_col
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
local text = t.ffi.new('unsigned char[?]', len)
vterm.vterm_screen_get_text(screen, text, len, rect)
local actual = ''
for i = 0, tonumber(len) - 1 do
actual = string.format('%s%02x,', actual, text[i])
end
actual = actual:sub(1, -2)
t.eq(expected, actual)
end
--- @param row integer
local function screen_row(row, expected, screen, end_col)
local rect = t.ffi.new('VTermRect')
rect['start_row'] = row
rect['start_col'] = 0
rect['end_row'] = row + 1
rect['end_col'] = end_col or 80
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
local text = t.ffi.new('unsigned char[?]', len)
vterm.vterm_screen_get_text(screen, text, len, rect)
t.eq(expected, t.ffi.string(text))
end
local function screen_cell(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local cell = t.ffi.new('VTermScreenCell')
vterm.vterm_screen_get_cell(screen, pos, cell)
local actual = '{'
for i = 0, vterm.VTERM_MAX_CHARS_PER_CELL - 1 do
if cell['chars'][i] ~= 0 then
if i > 0 then
actual = actual .. ','
end
actual = string.format('%s%02x', actual, cell['chars'][i])
end
end
actual = string.format('%s} width=%d attrs={', actual, cell['width'])
actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
actual = actual
.. (cell['attrs'].underline ~= 0 and string.format('U%d', cell['attrs'].underline) or '')
actual = actual .. (cell['attrs'].italic ~= 0 and 'I' or '')
actual = actual .. (cell['attrs'].blink ~= 0 and 'K' or '')
actual = actual .. (cell['attrs'].reverse ~= 0 and 'R' or '')
actual = actual .. (cell['attrs'].font ~= 0 and string.format('F%d', cell['attrs'].font) or '')
actual = actual .. (cell['attrs'].small ~= 0 and 'S' or '')
if cell['attrs'].baseline ~= 0 then
actual = actual .. (cell['attrs'].baseline == vterm.VTERM_BASELINE_RAISE and '^' or '_')
end
actual = actual .. '} '
actual = actual .. (cell['attrs'].dwl ~= 0 and 'dwl ' or '')
if cell['attrs'].dhl ~= 0 then
actual = actual .. string.format('dhl-%s ', cell['attrs'].dhl == 2 and 'bottom' or 'top')
end
actual = string.format('%sfg=', actual)
vterm.vterm_screen_convert_color_to_rgb(screen, cell['fg'])
vterm.print_color(cell['fg'])
actual = actual .. read_rm()
actual = actual .. ' bg='
vterm.vterm_screen_convert_color_to_rgb(screen, cell['bg'])
vterm.print_color(cell['bg'])
actual = actual .. read_rm()
t.eq(expected, actual)
end
local function screen_eol(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local is_eol = vterm.vterm_screen_is_eol(screen, pos)
t.eq(expected, is_eol)
end
local function screen_attrs_extent(row, col, expected, screen)
local pos = t.ffi.new('VTermPos')
pos['row'] = row
pos['col'] = col
local rect = t.ffi.new('VTermRect')
rect['start_col'] = 0
rect['end_col'] = -1
vterm.vterm_screen_get_attrs_extent(screen, rect, pos, 1)
local actual = string.format(
'%d,%d-%d,%d',
rect['start_row'],
rect['start_col'],
rect['end_row'],
rect['end_col']
)
t.eq(expected, actual)
end
local function wantencoding()
local encoding = t.ffi.new('VTermEncodingInstance')
encoding['enc'] = vterm.vterm_lookup_encoding(vterm.ENC_UTF8, string.byte('u'))
if encoding.enc.init then
encoding.enc.init(encoding.enc, encoding['data'])
end
return encoding
end
local function encin(input, encoding)
local len = string.len(input)
local cp = t.ffi.new('uint32_t[?]', len)
local cpi = t.ffi.new('int[1]')
local pos = t.ffi.new('size_t[1]', 0)
encoding.enc.decode(encoding.enc, encoding.data, cp, cpi, len, input, pos, len)
local f = assert(io.open(t.paths.vterm_test_file, 'w'))
if tonumber(cpi[0]) > 0 then
f:write('encout ')
for i = 0, cpi[0] - 1 do
if i == 0 then
f:write(string.format('%x', cp[i]))
else
f:write(string.format(',%x', cp[i]))
end
end
f:write('\n')
end
f:close()
end
local function strpe_modifiers(input_mod)
local mod = t.ffi.new('VTermModifier') ---@type any
if input_mod.C then
mod = bit.bor(mod, vterm.VTERM_MOD_CTRL)
end
if input_mod.S then
mod = bit.bor(mod, vterm.VTERM_MOD_SHIFT)
end
if input_mod.A then
mod = bit.bor(mod, vterm.VTERM_MOD_ALT)
end
return mod
end
local function strp_key(input_key)
if input_key == 'up' then
return vterm.VTERM_KEY_UP
end
if input_key == 'tab' then
return vterm.VTERM_KEY_TAB
end
if input_key == 'enter' then
return vterm.VTERM_KEY_ENTER
end
if input_key == 'f1' then
return vterm.VTERM_KEY_FUNCTION_0 + 1
end
if input_key == 'kp0' then
return vterm.VTERM_KEY_KP_0
end
return vterm.VTERM_KEY_NONE
end
local function mousemove(row, col, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
vterm.vterm_mouse_move(vt, row, col, mod)
end
local function mousebtn(press, button, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
local flag = press == 'd' or press == 'D'
vterm.vterm_mouse_button(vt, button, flag, mod)
end
local function inchar(c, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
vterm.vterm_keyboard_unichar(vt, c, mod)
end
local function inkey(input_key, vt, input_mod)
input_mod = input_mod or {}
local mod = strpe_modifiers(input_mod)
local key = strp_key(input_key)
vterm.vterm_keyboard_key(vt, key, mod)
end
before_each(function()
vim.fs.rm(t.paths.vterm_test_file, { force = true })
end)
describe('vterm', function()
itp('02parser', function()
local vt = init()
vterm.vterm_set_utf8(vt, false)
wantparser(vt)
-- Basic text
push('hello', vt)
expect('text 68,65,6c,6c,6f')
-- C0
push('\x03', vt)
expect('control 03')
push('\x1f', vt)
expect('control 1f')
-- C1 8bit
push('\x83', vt)
expect('control 83')
push('\x99', vt)
expect('control 99')
-- C1 7bit
push('\x1b\x43', vt)
expect('control 83')
push('\x1b\x59', vt)
expect('control 99')
-- High bytes
push('\xa0\xcc\xfe', vt)
expect('text a0,cc,fe')
-- Mixed
push('1\n2', vt)
expect('text 31\ncontrol 0a\ntext 32')
-- Escape
push('\x1b=', vt)
expect('escape =')
-- Escape 2-byte
push('\x1b(X', vt)
expect('escape (X')
-- Split write Escape
push('\x1b(', vt)
push('Y', vt)
expect('escape (Y')
-- Escape cancels Escape, starts another
push('\x1b(\x1b)Z', vt)
expect('escape )Z')
-- CAN cancels Escape, returns to normal mode
push('\x1b(\x18AB', vt)
expect('text 41,42')
-- C0 in Escape interrupts and continues
push('\x1b(\nX', vt)
expect('control 0a\nescape (X')
-- CSI 0 args
push('\x1b[a', vt)
expect('csi 61 *')
-- CSI 1 arg
push('\x1b[9b', vt)
expect('csi 62 9')
-- CSI 2 args
push('\x1b[3;4c', vt)
expect('csi 63 3,4')
-- CSI 1 arg 1 sub
push('\x1b[1:2c', vt)
expect('csi 63 1+,2')
-- CSI many digits
push('\x1b[678d', vt)
expect('csi 64 678')
-- CSI leading zero
push('\x1b[007e', vt)
expect('csi 65 7')
-- CSI qmark
push('\x1b[?2;7f', vt)
expect('csi 66 L=3f 2,7')
-- CSI greater
push('\x1b[>c', vt)
expect('csi 63 L=3e *')
-- CSI SP
push('\x1b[12 q', vt)
expect('csi 71 12 I=20')
-- Mixed CSI
push('A\x1b[8mB', vt)
expect('text 41\ncsi 6d 8\ntext 42')
-- Split write
push('\x1b', vt)
push('[a', vt)
expect('csi 61 *')
push('foo\x1b[', vt)
expect('text 66,6f,6f')
push('4b', vt)
expect('csi 62 4')
push('\x1b[12;', vt)
push('3c', vt)
expect('csi 63 12,3')
-- Escape cancels CSI, starts Escape
push('\x1b[123\x1b9', vt)
expect('escape 9')
-- CAN cancels CSI, returns to normal mode
push('\x1b[12\x18AB', vt)
expect('text 41,42')
-- C0 in Escape interrupts and continues
push('\x1b(\nX', vt)
expect('control 0a\nescape (X')
-- OSC BEL
push('\x1b]1;Hello\x07', vt)
expect('osc [1;Hello]')
-- OSC ST (7bit)
push('\x1b]1;Hello\x1b\\', vt)
expect('osc [1;Hello]')
-- OSC ST (8bit)
push('\x9d1;Hello\x9c', vt)
expect('osc [1;Hello]')
-- OSC in parts
push('\x1b]52;abc', vt)
expect('osc [52;abc')
push('def', vt)
expect('osc def')
push('ghi\x1b\\', vt)
expect('osc ghi]')
-- OSC BEL without semicolon
push('\x1b]1234\x07', vt)
expect('osc [1234;]')
-- OSC ST without semicolon
push('\x1b]1234\x1b\\', vt)
expect('osc [1234;]')
-- Escape cancels OSC, starts Escape
push('\x1b]Something\x1b9', vt)
expect('escape 9')
-- CAN cancels OSC, returns to normal mode
push('\x1b]12\x18AB', vt)
expect('text 41,42')
-- C0 in OSC interrupts and continues
push('\x1b]2;\nBye\x07', vt)
expect('osc [2;\ncontrol 0a\nosc Bye]')
-- DCS BEL
push('\x1bPHello\x07', vt)
expect('dcs [Hello]')
-- DCS ST (7bit)
push('\x1bPHello\x1b\\', vt)
expect('dcs [Hello]')
-- DCS ST (8bit)
push('\x90Hello\x9c', vt)
expect('dcs [Hello]')
-- Split write of 7bit ST
push('\x1bPABC\x1b', vt)
expect('dcs [ABC')
push('\\', vt)
expect('dcs ]')
-- Escape cancels DCS, starts Escape
push('\x1bPSomething\x1b9', vt)
expect('escape 9')
-- CAN cancels DCS, returns to normal mode
push('\x1bP12\x18AB', vt)
expect('text 41,42')
-- C0 in OSC interrupts and continues
push('\x1bPBy\ne\x07', vt)
expect('dcs [By\ncontrol 0a\ndcs e]')
-- APC BEL
push('\x1b_Hello\x07', vt)
expect('apc [Hello]')
-- APC ST (7bit)
push('\x1b_Hello\x1b\\', vt)
expect('apc [Hello]')
-- APC ST (8bit)
push('\x9fHello\x9c', vt)
expect('apc [Hello]')
-- PM BEL
push('\x1b^Hello\x07', vt)
expect('pm [Hello]')
-- PM ST (7bit)
push('\x1b^Hello\x1b\\', vt)
expect('pm [Hello]')
-- PM ST (8bit)
push('\x9eHello\x9c', vt)
expect('pm [Hello]')
-- SOS BEL
push('\x1bXHello\x07', vt)
expect('sos [Hello]')
-- SOS ST (7bit)
push('\x1bXHello\x1b\\', vt)
expect('sos [Hello]')
-- SOS ST (8bit)
push('\x98Hello\x9c', vt)
expect('sos [Hello]')
push('\x1bXABC\x01DEF\x1b\\', vt)
expect('sos [ABC\x01DEF]')
push('\x1bXABC\x99DEF\x1b\\', vt)
expect('sos [ABC\x99DEF]')
-- NUL ignored
push('\x00', vt)
-- NUL ignored within CSI
push('\x1b[12\x003m', vt)
expect('csi 6d 123')
-- DEL ignored
push('\x7f', vt)
-- DEL ignored within CSI
push('\x1b[12\x7f3m', vt)
expect('csi 6d 123')
-- DEL inside text"
push('AB\x7fC', vt)
expect('text 41,42\ntext 43')
end)
itp('03encoding_utf8', function()
local encoding = wantencoding()
-- Low
encin('123', encoding)
expect('encout 31,32,33')
-- We want to prove the UTF-8 parser correctly handles all the sequences.
-- Easy way to do this is to check it does low/high boundary cases, as that
-- leaves only two for each sequence length
--
-- These ranges are therefore:
--
-- Two bytes:
-- U+0080 = 000 10000000 => 00010 000000
-- => 11000010 10000000 = C2 80
-- U+07FF = 111 11111111 => 11111 111111
-- => 11011111 10111111 = DF BF
--
-- Three bytes:
-- U+0800 = 00001000 00000000 => 0000 100000 000000
-- => 11100000 10100000 10000000 = E0 A0 80
-- U+FFFD = 11111111 11111101 => 1111 111111 111101
-- => 11101111 10111111 10111101 = EF BF BD
-- (We avoid U+FFFE and U+FFFF as they're invalid codepoints)
--
-- Four bytes:
-- U+10000 = 00001 00000000 00000000 => 000 010000 000000 000000
-- => 11110000 10010000 10000000 10000000 = F0 90 80 80
-- U+1FFFFF = 11111 11111111 11111111 => 111 111111 111111 111111
-- => 11110111 10111111 10111111 10111111 = F7 BF BF BF
-- 2 byte
encin('\xC2\x80\xDF\xBF', encoding)
expect('encout 80,7ff')
-- 3 byte
encin('\xE0\xA0\x80\xEF\xBF\xBD', encoding)
expect('encout 800,fffd')
-- 4 byte
encin('\xF0\x90\x80\x80\xF7\xBF\xBF\xBF', encoding)
expect('encout 10000,1fffff')
-- Next up, we check some invalid sequences
-- + Early termination (back to low bytes too soon)
-- + Early restart (another sequence introduction before the previous one was finished)
-- Early termination
encin('\xC2!', encoding)
expect('encout fffd,21')
encin('\xE0!\xE0\xA0!', encoding)
expect('encout fffd,21,fffd,21')
encin('\xF0!\xF0\x90!\xF0\x90\x80!', encoding)
expect('encout fffd,21,fffd,21,fffd,21')
-- Early restart
encin('\xC2\xC2\x90', encoding)
expect('encout fffd,90')
encin('\xE0\xC2\x90\xE0\xA0\xC2\x90', encoding)
expect('encout fffd,90,fffd,90')
encin('\xF0\xC2\x90\xF0\x90\xC2\x90\xF0\x90\x80\xC2\x90', encoding)
expect('encout fffd,90,fffd,90,fffd,90')
-- Test the overlong sequences by giving an overlong encoding of U+0000 and
-- an encoding of the highest codepoint still too short
--
-- Two bytes:
-- U+0000 = C0 80
-- U+007F = 000 01111111 => 00001 111111 =>
-- => 11000001 10111111 => C1 BF
--
-- Three bytes:
-- U+0000 = E0 80 80
-- U+07FF = 00000111 11111111 => 0000 011111 111111
-- => 11100000 10011111 10111111 = E0 9F BF
--
-- Four bytes:
-- U+0000 = F0 80 80 80
-- U+FFFF = 11111111 11111111 => 000 001111 111111 111111
-- => 11110000 10001111 10111111 10111111 = F0 8F BF BF
-- Overlong
encin('\xC0\x80\xC1\xBF', encoding)
expect('encout fffd,fffd')
encin('\xE0\x80\x80\xE0\x9F\xBF', encoding)
expect('encout fffd,fffd')
encin('\xF0\x80\x80\x80\xF0\x8F\xBF\xBF', encoding)
expect('encout fffd,fffd')
-- UTF-16 surrogates U+D800 and U+DFFF
-- UTF-16 Surrogates
encin('\xED\xA0\x80\xED\xBF\xBF', encoding)
expect('encout fffd,fffd')
-- Split write
encin('\xC2', encoding)
encin('\xA0', encoding)
expect('encout a0')
encin('\xE0', encoding)
encin('\xA0\x80', encoding)
expect('encout 800')
encin('\xE0\xA0', encoding)
encin('\x80', encoding)
expect('encout 800')
encin('\xF0', encoding)
encin('\x90\x80\x80', encoding)
expect('encout 10000')
encin('\xF0\x90', encoding)
encin('\x80\x80', encoding)
expect('encout 10000')
encin('\xF0\x90\x80', encoding)
encin('\x80', encoding)
expect('encout 10000')
end)
itp('10state_putglyph', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- Low
reset(state, nil)
push('ABC', vt)
expect('putglyph 41 1 0,0\nputglyph 42 1 0,1\nputglyph 43 1 0,2')
-- UTF-8 1 char
-- U+00C1 = 0xC3 0x81 name: LATIN CAPITAL LETTER A WITH ACUTE
-- U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
reset(state, nil)
push('\xC3\x81\xC3\xA9', vt)
expect('putglyph c1 1 0,0\nputglyph e9 1 0,1')
-- UTF-8 split writes
reset(state, nil)
push('\xC3', vt)
push('\x81', vt)
expect('putglyph c1 1 0,0')
-- UTF-8 wide char
-- U+FF10 = EF BC 90 name: FULLWIDTH DIGIT ZERO
reset(state, nil)
push('\xEF\xBC\x90 ', vt)
expect('putglyph ff10 2 0,0\nputglyph 20 1 0,2')
-- UTF-8 emoji wide char
-- U+1F600 = F0 9F 98 80 name: GRINNING FACE
reset(state, nil)
push('\xF0\x9F\x98\x80 ', vt)
expect('putglyph 1f600 2 0,0\nputglyph 20 1 0,2')
-- UTF-8 combining chars
-- U+0301 = CC 81 name: COMBINING ACUTE
reset(state, nil)
push('e\xCC\x81Z', vt)
expect('putglyph 65,301 1 0,0\nputglyph 5a 1 0,1')
-- Combining across buffers
reset(state, nil)
push('e', vt)
expect('putglyph 65 1 0,0')
push('\xCC\x81Z', vt)
expect('putglyph 65,301 1 0,0\nputglyph 5a 1 0,1')
-- Spare combining chars get truncated
reset(state, nil)
push('e' .. string.rep('\xCC\x81', 10), vt)
expect('putglyph 65,301,301,301,301,301 1 0,0') -- and nothing more
reset(state, nil)
push('e', vt)
expect('putglyph 65 1 0,0')
push('\xCC\x81', vt)
expect('putglyph 65,301 1 0,0')
push('\xCC\x82', vt)
expect('putglyph 65,301,302 1 0,0')
-- DECSCA protected
reset(state, nil)
push('A\x1b[1"qB\x1b[2"qC', vt)
expect('putglyph 41 1 0,0\nputglyph 42 1 0,1 prot\nputglyph 43 1 0,2')
end)
itp('11state_movecursor', function()
local vt = init()
local state = wantstate(vt)
-- Implicit
push('ABC', vt)
cursor(0, 3, state)
-- Backspace
push('\b', vt)
cursor(0, 2, state)
-- Horizontal Tab
push('\t', vt)
cursor(0, 8, state)
-- Carriage Return
push('\r', vt)
cursor(0, 0, state)
-- Linefeed
push('\n', vt)
cursor(1, 0, state)
-- Backspace bounded by lefthand edge
push('\x1b[4;2H', vt)
cursor(3, 1, state)
push('\b', vt)
cursor(3, 0, state)
push('\b', vt)
cursor(3, 0, state)
-- Backspace cancels phantom
push('\x1b[4;80H', vt)
cursor(3, 79, state)
push('X', vt)
cursor(3, 79, state)
push('\b', vt)
cursor(3, 78, state)
-- HT bounded by righthand edge
push('\x1b[1;78H', vt)
cursor(0, 77, state)
push('\t', vt)
cursor(0, 79, state)
push('\t', vt)
cursor(0, 79, state)
reset(state, nil)
-- Index
push('ABC\x1bD', vt)
cursor(1, 3, state)
-- Reverse Index
push('\x1bM', vt)
cursor(0, 3, state)
-- Newline
push('\x1bE', vt)
cursor(1, 0, state)
reset(state, nil)
-- Cursor Forward
push('\x1b[B', vt)
cursor(1, 0, state)
push('\x1b[3B', vt)
cursor(4, 0, state)
push('\x1b[0B', vt)
cursor(5, 0, state)
-- Cursor Down
push('\x1b[C', vt)
cursor(5, 1, state)
push('\x1b[3C', vt)
cursor(5, 4, state)
push('\x1b[0C', vt)
cursor(5, 5, state)
-- Cursor Up
push('\x1b[A', vt)
cursor(4, 5, state)
push('\x1b[3A', vt)
cursor(1, 5, state)
push('\x1b[0A', vt)
cursor(0, 5, state)
-- Cursor Backward
push('\x1b[D', vt)
cursor(0, 4, state)
push('\x1b[3D', vt)
cursor(0, 1, state)
push('\x1b[0D', vt)
cursor(0, 0, state)
-- Cursor Next Line
push(' ', vt)
cursor(0, 3, state)
push('\x1b[E', vt)
cursor(1, 0, state)
push(' ', vt)
cursor(1, 3, state)
push('\x1b[2E', vt)
cursor(3, 0, state)
push('\x1b[0E', vt)
cursor(4, 0, state)
-- Cursor Previous Line
push(' ', vt)
cursor(4, 3, state)
push('\x1b[F', vt)
cursor(3, 0, state)
push(' ', vt)
cursor(3, 3, state)
push('\x1b[2F', vt)
cursor(1, 0, state)
push('\x1b[0F', vt)
cursor(0, 0, state)
-- Cursor Horizonal Absolute
push('\n', vt)
cursor(1, 0, state)
push('\x1b[20G', vt)
cursor(1, 19, state)
push('\x1b[G', vt)
cursor(1, 0, state)
-- Cursor Position
push('\x1b[10;5H', vt)
cursor(9, 4, state)
push('\x1b[8H', vt)
cursor(7, 0, state)
push('\x1b[H', vt)
cursor(0, 0, state)
-- Cursor Position cancels phantom
push('\x1b[10;78H', vt)
cursor(9, 77, state)
push('ABC', vt)
cursor(9, 79, state)
push('\x1b[10;80H', vt)
push('C', vt)
cursor(9, 79, state)
push('X', vt)
cursor(10, 1, state)
reset(state, nil)
-- Bounds Checking
push('\x1b[A', vt)
cursor(0, 0, state)
push('\x1b[D', vt)
cursor(0, 0, state)
push('\x1b[25;80H', vt)
cursor(24, 79, state)
push('\x1b[B', vt)
cursor(24, 79, state)
push('\x1b[C', vt)
cursor(24, 79, state)
push('\x1b[E', vt)
cursor(24, 0, state)
push('\x1b[H', vt)
cursor(0, 0, state)
push('\x1b[F', vt)
cursor(0, 0, state)
push('\x1b[999G', vt)
cursor(0, 79, state)
push('\x1b[99;99H', vt)
cursor(24, 79, state)
reset(state, nil)
-- Horizontal Position Absolute
push('\x1b[5`', vt)
cursor(0, 4, state)
-- Horizontal Position Relative
push('\x1b[3a', vt)
cursor(0, 7, state)
-- Horizontal Position Backward
push('\x1b[3j', vt)
cursor(0, 4, state)
-- Horizontal and Vertical Position
push('\x1b[3;3f', vt)
cursor(2, 2, state)
-- Vertical Position Absolute
push('\x1b[5d', vt)
cursor(4, 2, state)
-- Vertical Position Relative
push('\x1b[2e', vt)
cursor(6, 2, state)
-- Vertical Position Backward
push('\x1b[2k', vt)
cursor(4, 2, state)
reset(state, nil)
-- Horizontal Tab
push('\t', vt)
cursor(0, 8, state)
push(' ', vt)
cursor(0, 11, state)
push('\t', vt)
cursor(0, 16, state)
push(' ', vt)
cursor(0, 23, state)
push('\t', vt)
cursor(0, 24, state)
push(' ', vt)
cursor(0, 32, state)
push('\t', vt)
cursor(0, 40, state)
-- Cursor Horizontal Tab
push('\x1b[I', vt)
cursor(0, 48, state)
push('\x1b[2I', vt)
cursor(0, 64, state)
-- Cursor Backward Tab
push('\x1b[Z', vt)
cursor(0, 56, state)
push('\x1b[2Z', vt)
cursor(0, 40, state)
end)
itp('12state_scroll', function()
local vt = init()
local state = wantstate(vt, { s = true })
-- Linefeed
push(string.rep('\n', 24), vt)
cursor(24, 0, state)
push('\n', vt)
expect('scrollrect 0..25,0..80 => +1,+0')
cursor(24, 0, state)
reset(state, nil)
-- Index
push('\x1b[25H', vt)
push('\x1bD', vt)
expect('scrollrect 0..25,0..80 => +1,+0')
reset(state, nil)
-- Reverse Index
push('\x1bM', vt)
expect('scrollrect 0..25,0..80 => -1,+0')
reset(state, nil)
-- Linefeed in DECSTBM
push('\x1b[1;10r', vt)
cursor(0, 0, state)
push(string.rep('\n', 9), vt)
cursor(9, 0, state)
push('\n', vt)
expect('scrollrect 0..10,0..80 => +1,+0')
cursor(9, 0, state)
-- Linefeed outside DECSTBM
push('\x1b[20H', vt)
cursor(19, 0, state)
push('\n', vt)
cursor(20, 0, state)
-- Index in DECSTBM
push('\x1b[9;10r', vt)
push('\x1b[10H', vt)
push('\x1bM', vt)
cursor(8, 0, state)
push('\x1bM', vt)
expect('scrollrect 8..10,0..80 => -1,+0')
-- Reverse Index in DECSTBM
push('\x1b[25H', vt)
cursor(24, 0, state)
push('\n', vt)
-- no scrollrect
cursor(24, 0, state)
-- Linefeed in DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[3;10r\x1b[10;40s', vt)
push('\x1b[10;10H\n', vt)
expect('scrollrect 2..10,9..40 => +1,+0')
-- IND/RI in DECSTBM+DECSLRM
push('\x1bD', vt)
expect('scrollrect 2..10,9..40 => +1,+0')
push('\x1b[3;10H\x1bM', vt)
expect('scrollrect 2..10,9..40 => -1,+0')
-- DECRQSS on DECSTBM
push('\x1bP$qr\x1b\\', vt)
expect_output('\x1bP1$r3;10r\x1b\\')
-- DECRQSS on DECSLRM
push('\x1bP$qs\x1b\\', vt)
expect_output('\x1bP1$r10;40s\x1b\\')
-- Setting invalid DECSLRM with !DECVSSM is still rejected
push('\x1b[?69l\x1b[;0s\x1b[?69h', vt)
reset(state, nil)
-- Scroll Down
push('\x1b[S', vt)
expect('scrollrect 0..25,0..80 => +1,+0')
cursor(0, 0, state)
push('\x1b[2S', vt)
expect('scrollrect 0..25,0..80 => +2,+0')
cursor(0, 0, state)
push('\x1b[100S', vt)
expect('scrollrect 0..25,0..80 => +25,+0')
-- Scroll Up
push('\x1b[T', vt)
expect('scrollrect 0..25,0..80 => -1,+0')
cursor(0, 0, state)
push('\x1b[2T', vt)
expect('scrollrect 0..25,0..80 => -2,+0')
cursor(0, 0, state)
push('\x1b[100T', vt)
expect('scrollrect 0..25,0..80 => -25,+0')
-- SD/SU in DECSTBM
push('\x1b[5;20r', vt)
push('\x1b[S', vt)
expect('scrollrect 4..20,0..80 => +1,+0')
push('\x1b[T', vt)
expect('scrollrect 4..20,0..80 => -1,+0')
reset(state, nil)
-- SD/SU in DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[3;10r\x1b[10;40s', vt)
cursor(0, 0, state)
push('\x1b[3;10H', vt)
cursor(2, 9, state)
push('\x1b[S', vt)
expect('scrollrect 2..10,9..40 => +1,+0')
push('\x1b[?69l', vt)
push('\x1b[S', vt)
expect('scrollrect 2..10,0..80 => +1,+0')
-- Invalid boundaries
reset(state, nil)
push('\x1b[100;105r\x1bD', vt)
push('\x1b[5;2r\x1bD', vt)
reset(state, nil)
state = wantstate(vt, { m = true, e = true })
-- Scroll Down move+erase emulation
push('\x1b[S', vt)
expect('moverect 1..25,0..80 -> 0..24,0..80\nerase 24..25,0..80')
cursor(0, 0, state)
push('\x1b[2S', vt)
expect('moverect 2..25,0..80 -> 0..23,0..80\nerase 23..25,0..80')
cursor(0, 0, state)
-- Scroll Up move+erase emulation
push('\x1b[T', vt)
expect('moverect 0..24,0..80 -> 1..25,0..80\nerase 0..1,0..80')
cursor(0, 0, state)
push('\x1b[2T', vt)
expect('moverect 0..23,0..80 -> 2..25,0..80\nerase 0..2,0..80')
cursor(0, 0, state)
-- DECSTBM resets cursor position
push('\x1b[5;5H', vt)
cursor(4, 4, state)
push('\x1b[r', vt)
cursor(0, 0, state)
end)
itp('13state_edit', function()
local vt = init()
local state = wantstate(vt, { s = true, e = true, b = true })
-- ICH
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ACD', vt)
push('\x1b[2D', vt)
cursor(0, 1, state)
push('\x1b[@', vt)
expect('scrollrect 0..1,1..80 => +0,-1')
cursor(0, 1, state)
push('B', vt)
cursor(0, 2, state)
push('\x1b[3@', vt)
expect('scrollrect 0..1,2..80 => +0,-3')
-- ICH with DECSLRM
push('\x1b[?69h', vt)
push('\x1b[;50s', vt)
push('\x1b[20G\x1b[@', vt)
expect('scrollrect 0..1,19..50 => +0,-1')
-- ICH outside DECSLRM
push('\x1b[70G\x1b[@', vt)
-- nothing happens
-- DCH
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABBC', vt)
push('\x1b[3D', vt)
cursor(0, 1, state)
push('\x1b[P', vt)
expect('scrollrect 0..1,1..80 => +0,+1')
cursor(0, 1, state)
push('\x1b[3P', vt)
expect('scrollrect 0..1,1..80 => +0,+3')
cursor(0, 1, state)
-- DCH with DECSLRM
push('\x1b[?69h', vt)
push('\x1b[;50s', vt)
push('\x1b[20G\x1b[P', vt)
expect('scrollrect 0..1,19..50 => +0,+1')
-- DCH outside DECSLRM
push('\x1b[70G\x1b[P', vt)
-- nothing happens
-- ECH
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABC', vt)
push('\x1b[2D', vt)
cursor(0, 1, state)
push('\x1b[X', vt)
expect('erase 0..1,1..2')
cursor(0, 1, state)
push('\x1b[3X', vt)
expect('erase 0..1,1..4')
cursor(0, 1, state)
-- ECH more columns than there are should be bounded
push('\x1b[100X', vt)
expect('erase 0..1,1..80')
-- IL
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('A\r\nC', vt)
cursor(1, 1, state)
push('\x1b[L', vt)
expect('scrollrect 1..25,0..80 => -1,+0')
-- TODO(libvterm): ECMA-48 says we should move to line home, but neither xterm nor xfce4-terminal do this
cursor(1, 1, state)
push('\rB', vt)
cursor(1, 1, state)
push('\x1b[3L', vt)
expect('scrollrect 1..25,0..80 => -3,+0')
-- IL with DECSTBM
push('\x1b[5;15r', vt)
push('\x1b[5H\x1b[L', vt)
expect('scrollrect 4..15,0..80 => -1,+0')
-- IL outside DECSTBM
push('\x1b[20H\x1b[L', vt)
-- nothing happens
-- IL with DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[10;50s', vt)
push('\x1b[5;10H\x1b[L', vt)
expect('scrollrect 4..15,9..50 => -1,+0')
-- DL
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('A\r\nB\r\nB\r\nC', vt)
cursor(3, 1, state)
push('\x1b[2H', vt)
cursor(1, 0, state)
push('\x1b[M', vt)
expect('scrollrect 1..25,0..80 => +1,+0')
cursor(1, 0, state)
push('\x1b[3M', vt)
expect('scrollrect 1..25,0..80 => +3,+0')
cursor(1, 0, state)
-- DL with DECSTBM
push('\x1b[5;15r', vt)
push('\x1b[5H\x1b[M', vt)
expect('scrollrect 4..15,0..80 => +1,+0')
-- DL outside DECSTBM
push('\x1b[20H\x1b[M', vt)
-- nothing happens
-- DL with DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[10;50s', vt)
push('\x1b[5;10H\x1b[M', vt)
expect('scrollrect 4..15,9..50 => +1,+0')
-- DECIC
reset(state, nil)
expect('erase 0..25,0..80')
push("\x1b[20G\x1b[5'}", vt)
expect('scrollrect 0..25,19..80 => +0,-5')
-- DECIC with DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[4;20r\x1b[20;60s', vt)
push("\x1b[4;20H\x1b[3'}", vt)
expect('scrollrect 3..20,19..60 => +0,-3')
-- DECIC outside DECSLRM
push("\x1b[70G\x1b['}", vt)
-- nothing happens
-- DECDC
reset(state, nil)
expect('erase 0..25,0..80')
push("\x1b[20G\x1b[5'~", vt)
expect('scrollrect 0..25,19..80 => +0,+5')
-- DECDC with DECSTBM+DECSLRM
push('\x1b[?69h', vt)
push('\x1b[4;20r\x1b[20;60s', vt)
push("\x1b[4;20H\x1b[3'~", vt)
expect('scrollrect 3..20,19..60 => +0,+3')
-- DECDC outside DECSLRM
push("\x1b[70G\x1b['~", vt)
-- nothing happens
-- EL 0
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABCDE', vt)
push('\x1b[3D', vt)
cursor(0, 2, state)
push('\x1b[0K', vt)
expect('erase 0..1,2..80')
cursor(0, 2, state)
-- EL 1
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABCDE', vt)
push('\x1b[3D', vt)
cursor(0, 2, state)
push('\x1b[1K', vt)
expect('erase 0..1,0..3')
cursor(0, 2, state)
-- EL 2
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABCDE', vt)
push('\x1b[3D', vt)
cursor(0, 2, state)
push('\x1b[2K', vt)
expect('erase 0..1,0..80')
cursor(0, 2, state)
-- SEL
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[11G', vt)
cursor(0, 10, state)
push('\x1b[?0K', vt)
expect('erase 0..1,10..80 selective')
cursor(0, 10, state)
push('\x1b[?1K', vt)
expect('erase 0..1,0..11 selective')
cursor(0, 10, state)
push('\x1b[?2K', vt)
expect('erase 0..1,0..80 selective')
cursor(0, 10, state)
-- ED 0
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[2;2H', vt)
cursor(1, 1, state)
push('\x1b[0J', vt)
expect('erase 1..2,1..80\nerase 2..25,0..80')
cursor(1, 1, state)
-- ED 1
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[2;2H', vt)
cursor(1, 1, state)
push('\x1b[1J', vt)
expect('erase 0..1,0..80\nerase 1..2,0..2')
cursor(1, 1, state)
-- ED 2
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[2;2H', vt)
cursor(1, 1, state)
push('\x1b[2J', vt)
expect('erase 0..25,0..80')
cursor(1, 1, state)
-- ED 3
push('\x1b[3J', vt)
expect('sb_clear')
-- SED
reset(state, nil)
expect('erase 0..25,0..80')
push('\x1b[5;5H', vt)
cursor(4, 4, state)
push('\x1b[?0J', vt)
expect('erase 4..5,4..80 selective\nerase 5..25,0..80 selective')
cursor(4, 4, state)
push('\x1b[?1J', vt)
expect('erase 0..4,0..80 selective\nerase 4..5,0..5 selective')
cursor(4, 4, state)
push('\x1b[?2J', vt)
expect('erase 0..25,0..80 selective')
cursor(4, 4, state)
-- DECRQSS on DECSCA
push('\x1b[2"q', vt)
push('\x1bP$q"q\x1b\\', vt)
expect_output('\x1bP1$r2"q\x1b\\')
state = wantstate(vt, { m = true, e = true, b = true })
expect('erase 0..25,0..80') -- TODO(dundargoc): strange, this should not be needed according to the original code
-- ICH move+erase emuation
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ACD', vt)
push('\x1b[2D', vt)
cursor(0, 1, state)
push('\x1b[@', vt)
expect('moverect 0..1,1..79 -> 0..1,2..80\nerase 0..1,1..2')
cursor(0, 1, state)
push('B', vt)
cursor(0, 2, state)
push('\x1b[3@', vt)
expect('moverect 0..1,2..77 -> 0..1,5..80\nerase 0..1,2..5')
-- DCH move+erase emulation
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('ABBC', vt)
push('\x1b[3D', vt)
cursor(0, 1, state)
push('\x1b[P', vt)
expect('moverect 0..1,2..80 -> 0..1,1..79\nerase 0..1,79..80')
cursor(0, 1, state)
push('\x1b[3P', vt)
expect('moverect 0..1,4..80 -> 0..1,1..77\nerase 0..1,77..80')
cursor(0, 1, state)
end)
itp('14state_encoding', function()
local vt = init()
vterm.vterm_set_utf8(vt, false)
local state = wantstate(vt, { g = true })
-- Default
reset(state, nil)
push('#', vt)
expect('putglyph 23 1 0,0')
-- Designate G0=UK
reset(state, nil)
push('\x1b(A', vt)
push('#', vt)
expect('putglyph a3 1 0,0')
-- Designate G0=DEC drawing
reset(state, nil)
push('\x1b(0', vt)
push('a', vt)
expect('putglyph 2592 1 0,0')
-- Designate G1 + LS1
reset(state, nil)
push('\x1b)0', vt)
push('a', vt)
expect('putglyph 61 1 0,0')
push('\x0e', vt)
push('a', vt)
expect('putglyph 2592 1 0,1')
-- LS0
push('\x0f', vt)
push('a', vt)
expect('putglyph 61 1 0,2')
-- Designate G2 + LS2
push('\x1b*0', vt)
push('a', vt)
expect('putglyph 61 1 0,3')
push('\x1bn', vt)
push('a', vt)
expect('putglyph 2592 1 0,4')
push('\x0f', vt)
push('a', vt)
expect('putglyph 61 1 0,5')
-- Designate G3 + LS3
push('\x1b+0', vt)
push('a', vt)
expect('putglyph 61 1 0,6')
push('\x1bo', vt)
push('a', vt)
expect('putglyph 2592 1 0,7')
push('\x0f', vt)
push('a', vt)
expect('putglyph 61 1 0,8')
-- SS2
push('a\x8eaa', vt)
expect('putglyph 61 1 0,9\nputglyph 2592 1 0,10\nputglyph 61 1 0,11')
-- SS3
push('a\x8faa', vt)
expect('putglyph 61 1 0,12\nputglyph 2592 1 0,13\nputglyph 61 1 0,14')
-- LS1R
reset(state, nil)
push('\x1b~', vt)
push('\xe1', vt)
expect('putglyph 61 1 0,0')
push('\x1b)0', vt)
push('\xe1', vt)
expect('putglyph 2592 1 0,1')
-- LS2R
reset(state, nil)
push('\x1b}', vt)
push('\xe1', vt)
expect('putglyph 61 1 0,0')
push('\x1b*0', vt)
push('\xe1', vt)
expect('putglyph 2592 1 0,1')
-- LS3R
reset(state, nil)
push('\x1b|', vt)
push('\xe1', vt)
expect('putglyph 61 1 0,0')
push('\x1b+0', vt)
push('\xe1', vt)
expect('putglyph 2592 1 0,1')
vterm.vterm_set_utf8(vt, true)
-- U+0108 == c4 88
reset(state, nil)
push('\x1b(B', vt)
push('AB\xc4\x88D', vt)
expect('putglyph 41 1 0,0\nputglyph 42 1 0,1\nputglyph 108 1 0,2\nputglyph 44 1 0,3')
end)
itp('15state_mode', function()
local vt = init()
local state = wantstate(vt, { g = true, m = true, e = true })
-- Insert/Replace Mode
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('AC\x1b[DB', vt)
expect('putglyph 41 1 0,0\nputglyph 43 1 0,1\nputglyph 42 1 0,1')
push('\x1b[4h', vt)
push('\x1b[G', vt)
push('AC\x1b[DB', vt)
expect(
'moverect 0..1,0..79 -> 0..1,1..80\nerase 0..1,0..1\nputglyph 41 1 0,0\nmoverect 0..1,1..79 -> 0..1,2..80\nerase 0..1,1..2\nputglyph 43 1 0,1\nmoverect 0..1,1..79 -> 0..1,2..80\nerase 0..1,1..2\nputglyph 42 1 0,1'
)
-- Insert mode only happens once for UTF-8 combining
push('e', vt)
expect('moverect 0..1,2..79 -> 0..1,3..80\nerase 0..1,2..3\nputglyph 65 1 0,2')
push('\xCC\x81', vt)
expect('putglyph 65,301 1 0,2')
-- Newline/Linefeed mode
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[5G\n', vt)
cursor(1, 4, state)
push('\x1b[20h', vt)
push('\x1b[5G\n', vt)
cursor(2, 0, state)
-- DEC origin mode
reset(state, nil)
expect('erase 0..25,0..80')
cursor(0, 0, state)
push('\x1b[5;15r', vt)
push('\x1b[H', vt)
cursor(0, 0, state)
push('\x1b[3;3H', vt)
cursor(2, 2, state)
push('\x1b[?6h', vt)
push('\x1b[H', vt)
cursor(4, 0, state)
push('\x1b[3;3H', vt)
cursor(6, 2, state)
-- DECRQM on DECOM
push('\x1b[?6h', vt)
push('\x1b[?6$p', vt)
expect_output('\x1b[?6;1$y')
push('\x1b[?6l', vt)
push('\x1b[?6$p', vt)
expect_output('\x1b[?6;2$y')
-- Origin mode with DECSLRM
push('\x1b[?6h', vt)
push('\x1b[?69h', vt)
push('\x1b[20;60s', vt)
push('\x1b[H', vt)
cursor(4, 19, state)
push('\x1b[?69l', vt)
-- Origin mode bounds cursor to scrolling region
push('\x1b[H', vt)
push('\x1b[10A', vt)
cursor(4, 0, state)
push('\x1b[20B', vt)
cursor(14, 0, state)
-- Origin mode without scroll region
push('\x1b[?6l', vt)
push('\x1b[r\x1b[?6h', vt)
cursor(0, 0, state)
end)
itp('16state_resize', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- Placement
reset(state, nil)
push('AB\x1b[79GCDE', vt)
expect(
'putglyph 41 1 0,0\nputglyph 42 1 0,1\nputglyph 43 1 0,78\nputglyph 44 1 0,79\nputglyph 45 1 1,0'
)
-- Resize
reset(state, nil)
resize(27, 85, vt)
push('AB\x1b[79GCDE', vt)
expect(
'putglyph 41 1 0,0\nputglyph 42 1 0,1\nputglyph 43 1 0,78\nputglyph 44 1 0,79\nputglyph 45 1 0,80'
)
cursor(0, 81, state)
-- Resize without reset
resize(28, 90, vt)
cursor(0, 81, state)
push('FGHI', vt)
expect('putglyph 46 1 0,81\nputglyph 47 1 0,82\nputglyph 48 1 0,83\nputglyph 49 1 0,84')
cursor(0, 85, state)
-- Resize shrink moves cursor
resize(25, 80, vt)
cursor(0, 79, state)
-- Resize grow doesn't cancel phantom
reset(state, nil)
push('\x1b[79GAB', vt)
expect('putglyph 41 1 0,78\nputglyph 42 1 0,79')
cursor(0, 79, state)
resize(30, 100, vt)
cursor(0, 80, state)
push('C', vt)
expect('putglyph 43 1 0,80')
cursor(0, 81, state)
end)
itp('17state_mouse', function()
local vt = init()
local state = wantstate(vt, { p = true })
-- DECRQM on with mouse off
push('\x1b[?1000$p', vt)
expect_output('\x1b[?1000;2$y')
push('\x1b[?1002$p', vt)
expect_output('\x1b[?1002;2$y')
push('\x1b[?1003$p', vt)
expect_output('\x1b[?1003;2$y')
-- Mouse in simple button report mode
reset(state, nil)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
push('\x1b[?1000h', vt)
expect('settermprop 8 1')
-- Press 1
mousemove(0, 0, vt)
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\x21\x21')
-- Release 1
mousebtn('u', 1, vt)
expect_output('\x1b[M\x23\x21\x21')
-- Ctrl-Press 1
mousebtn('d', 1, vt, { C = true })
expect_output('\x1b[M\x30\x21\x21')
mousebtn('u', 1, vt, { C = true })
expect_output('\x1b[M\x33\x21\x21')
-- Button 2
mousebtn('d', 2, vt)
expect_output('\x1b[M\x21\x21\x21')
mousebtn('u', 2, vt)
expect_output('\x1b[M\x23\x21\x21')
-- Position
mousemove(10, 20, vt)
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\x35\x2b')
mousebtn('u', 1, vt)
expect_output('\x1b[M\x23\x35\x2b')
mousemove(10, 21, vt)
-- no output
-- Wheel events
mousebtn('d', 4, vt)
expect_output('\x1b[M\x60\x36\x2b')
mousebtn('d', 4, vt)
expect_output('\x1b[M\x60\x36\x2b')
mousebtn('d', 5, vt)
expect_output('\x1b[M\x61\x36\x2b')
mousebtn('d', 6, vt)
expect_output('\x1b[M\x62\x36\x2b')
mousebtn('d', 7, vt)
expect_output('\x1b[M\x63\x36\x2b')
-- DECRQM on mouse button mode
push('\x1b[?1000$p', vt)
expect_output('\x1b[?1000;1$y')
push('\x1b[?1002$p', vt)
expect_output('\x1b[?1002;2$y')
push('\x1b[?1003$p', vt)
expect_output('\x1b[?1003;2$y')
-- Drag events
reset(state, nil)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
push('\x1b[?1002h', vt)
expect('settermprop 8 2')
mousemove(5, 5, vt)
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\x26\x26')
mousemove(5, 6, vt)
expect_output('\x1b[M\x40\x27\x26')
mousemove(6, 6, vt)
expect_output('\x1b[M\x40\x27\x27')
mousemove(6, 6, vt)
-- no output
mousebtn('u', 1, vt)
expect_output('\x1b[M\x23\x27\x27')
mousemove(6, 7, vt)
-- no output
-- DECRQM on mouse drag mode
push('\x1b[?1000$p', vt)
expect_output('\x1b[?1000;2$y')
push('\x1b[?1002$p', vt)
expect_output('\x1b[?1002;1$y')
push('\x1b[?1003$p', vt)
expect_output('\x1b[?1003;2$y')
-- Non-drag motion events
push('\x1b[?1003h', vt)
expect('settermprop 8 3')
mousemove(6, 8, vt)
expect_output('\x1b[M\x43\x29\x27')
-- DECRQM on mouse motion mode
push('\x1b[?1000$p', vt)
expect_output('\x1b[?1000;2$y')
push('\x1b[?1002$p', vt)
expect_output('\x1b[?1002;2$y')
push('\x1b[?1003$p', vt)
expect_output('\x1b[?1003;1$y')
-- Bounds checking
mousemove(300, 300, vt)
expect_output('\x1b[M\x43\xff\xff')
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\xff\xff')
mousebtn('u', 1, vt)
expect_output('\x1b[M\x23\xff\xff')
-- DECRQM on standard encoding mode
push('\x1b[?1005$p', vt)
expect_output('\x1b[?1005;2$y')
push('\x1b[?1006$p', vt)
expect_output('\x1b[?1006;2$y')
push('\x1b[?1015$p', vt)
expect_output('\x1b[?1015;2$y')
-- UTF-8 extended encoding mode
-- 300 + 32 + 1 = 333 = U+014d = \xc5\x8d
push('\x1b[?1005h', vt)
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\xc5\x8d\xc5\x8d')
mousebtn('u', 1, vt)
expect_output('\x1b[M\x23\xc5\x8d\xc5\x8d')
-- DECRQM on UTF-8 extended encoding mode
push('\x1b[?1005$p', vt)
expect_output('\x1b[?1005;1$y')
push('\x1b[?1006$p', vt)
expect_output('\x1b[?1006;2$y')
push('\x1b[?1015$p', vt)
expect_output('\x1b[?1015;2$y')
-- SGR extended encoding mode
push('\x1b[?1006h', vt)
mousebtn('d', 1, vt)
expect_output('\x1b[<0;301;301M')
mousebtn('u', 1, vt)
expect_output('\x1b[<0;301;301m')
-- DECRQM on SGR extended encoding mode
push('\x1b[?1005$p', vt)
expect_output('\x1b[?1005;2$y')
push('\x1b[?1006$p', vt)
expect_output('\x1b[?1006;1$y')
push('\x1b[?1015$p', vt)
expect_output('\x1b[?1015;2$y')
-- rxvt extended encoding mode
push('\x1b[?1015h', vt)
mousebtn('d', 1, vt)
expect_output('\x1b[0;301;301M')
mousebtn('u', 1, vt)
expect_output('\x1b[3;301;301M')
-- DECRQM on rxvt extended encoding mode
push('\x1b[?1005$p', vt)
expect_output('\x1b[?1005;2$y')
push('\x1b[?1006$p', vt)
expect_output('\x1b[?1006;2$y')
push('\x1b[?1015$p', vt)
expect_output('\x1b[?1015;1$y')
-- Mouse disabled reports nothing
reset(state, nil)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
mousemove(0, 0, vt)
mousebtn('d', 1, vt)
mousebtn('u', 1, vt)
-- DECSM can set multiple modes at once
push('\x1b[?1002;1006h', vt)
expect('settermprop 8 2')
mousebtn('d', 1, vt)
expect_output('\x1b[<0;1;1M')
end)
itp('18state_termprops', function()
local vt = init()
local state = wantstate(vt, { p = true })
reset(state, nil)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
-- Cursor visibility
push('\x1b[?25h', vt)
expect('settermprop 1 true')
push('\x1b[?25$p', vt)
expect_output('\x1b[?25;1$y')
push('\x1b[?25l', vt)
expect('settermprop 1 false')
push('\x1b[?25$p', vt)
expect_output('\x1b[?25;2$y')
-- Cursor blink
push('\x1b[?12h', vt)
expect('settermprop 2 true')
push('\x1b[?12$p', vt)
expect_output('\x1b[?12;1$y')
push('\x1b[?12l', vt)
expect('settermprop 2 false')
push('\x1b[?12$p', vt)
expect_output('\x1b[?12;2$y')
-- Cursor shape
push('\x1b[3 q', vt)
expect('settermprop 2 true\nsettermprop 7 2')
-- Title
push('\x1b]2;Here is my title\a', vt)
expect('settermprop 4 ["Here is my title"]')
-- Title split write
push('\x1b]2;Here is', vt)
expect('settermprop 4 ["Here is"')
push(' another title\a', vt)
expect('settermprop 4 " another title"]')
end)
itp('20state_wrapping', function()
local vt = init()
local state = wantstate(vt, { g = true, m = true })
-- 79th Column
push('\x1b[75G', vt)
push(string.rep('A', 5), vt)
expect(
'putglyph 41 1 0,74\nputglyph 41 1 0,75\nputglyph 41 1 0,76\nputglyph 41 1 0,77\nputglyph 41 1 0,78'
)
cursor(0, 79, state)
-- 80th Column Phantom
push('A', vt)
expect('putglyph 41 1 0,79')
cursor(0, 79, state)
-- Line Wraparound
push('B', vt)
expect('putglyph 42 1 1,0')
cursor(1, 1, state)
-- Line Wraparound during combined write
push('\x1b[78G', vt)
push('BBBCC', vt)
expect(
'putglyph 42 1 1,77\nputglyph 42 1 1,78\nputglyph 42 1 1,79\nputglyph 43 1 2,0\nputglyph 43 1 2,1'
)
cursor(2, 2, state)
-- DEC Auto Wrap Mode
reset(state, nil)
push('\x1b[?7l', vt)
push('\x1b[75G', vt)
push(string.rep('D', 6), vt)
expect(
'putglyph 44 1 0,74\nputglyph 44 1 0,75\nputglyph 44 1 0,76\nputglyph 44 1 0,77\nputglyph 44 1 0,78\nputglyph 44 1 0,79'
)
cursor(0, 79, state)
push('D', vt)
expect('putglyph 44 1 0,79')
cursor(0, 79, state)
push('\x1b[?7h', vt)
-- 80th column causes linefeed on wraparound
push('\x1b[25;78HABC', vt)
expect('putglyph 41 1 24,77\nputglyph 42 1 24,78\nputglyph 43 1 24,79')
cursor(24, 79, state)
push('D', vt)
expect('moverect 1..25,0..80 -> 0..24,0..80\nputglyph 44 1 24,0')
-- 80th column phantom linefeed phantom cancelled by explicit cursor move
push('\x1b[25;78HABC', vt)
expect('putglyph 41 1 24,77\nputglyph 42 1 24,78\nputglyph 43 1 24,79')
cursor(24, 79, state)
push('\x1b[25;1HD', vt)
expect('putglyph 44 1 24,0')
end)
itp('21state_tabstops', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- Initial
reset(state, nil)
push('\tX', vt)
expect('putglyph 58 1 0,8')
push('\tX', vt)
expect('putglyph 58 1 0,16')
cursor(0, 17, state)
-- HTS
push('\x1b[5G\x1bH', vt)
push('\x1b[G\tX', vt)
expect('putglyph 58 1 0,4')
cursor(0, 5, state)
-- TBC 0
push('\x1b[9G\x1b[g', vt)
push('\x1b[G\tX\tX', vt)
expect('putglyph 58 1 0,4\nputglyph 58 1 0,16')
cursor(0, 17, state)
-- TBC 3
push('\x1b[3g\x1b[50G\x1bH\x1b[G', vt)
cursor(0, 0, state)
push('\tX', vt)
expect('putglyph 58 1 0,49')
cursor(0, 50, state)
-- Tabstops after resize
reset(state, nil)
resize(30, 100, vt)
-- Should be 100/8 = 12 tabstops
push('\tX', vt)
expect('putglyph 58 1 0,8')
push('\tX', vt)
expect('putglyph 58 1 0,16')
push('\tX', vt)
expect('putglyph 58 1 0,24')
push('\tX', vt)
expect('putglyph 58 1 0,32')
push('\tX', vt)
expect('putglyph 58 1 0,40')
push('\tX', vt)
expect('putglyph 58 1 0,48')
push('\tX', vt)
expect('putglyph 58 1 0,56')
push('\tX', vt)
expect('putglyph 58 1 0,64')
push('\tX', vt)
expect('putglyph 58 1 0,72')
push('\tX', vt)
expect('putglyph 58 1 0,80')
push('\tX', vt)
expect('putglyph 58 1 0,88')
push('\tX', vt)
expect('putglyph 58 1 0,96')
cursor(0, 97, state)
end)
itp('22state_save', function()
local vt = init()
local state = wantstate(vt, { p = true })
reset(state, nil)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
-- Set up state
push('\x1b[2;2H', vt)
cursor(1, 1, state)
push('\x1b[1m', vt)
pen('bold', true, state)
-- Save
push('\x1b[?1048h', vt)
-- Change state
push('\x1b[5;5H', vt)
cursor(4, 4, state)
push('\x1b[4 q', vt)
expect('settermprop 2 false\nsettermprop 7 2')
push('\x1b[22;4m', vt)
pen('bold', false, state)
pen('underline', 1, state)
-- Restore
push('\x1b[?1048l', vt)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
cursor(1, 1, state)
pen('bold', true, state)
pen('underline', 0, state)
-- Save/restore using DECSC/DECRC
push('\x1b[2;2H\x1b7', vt)
cursor(1, 1, state)
push('\x1b[5;5H', vt)
cursor(4, 4, state)
push('\x1b8', vt)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
cursor(1, 1, state)
-- Save twice, restore twice happens on both edge transitions
push('\x1b[2;10H\x1b[?1048h\x1b[6;10H\x1b[?1048h', vt)
push('\x1b[H', vt)
cursor(0, 0, state)
push('\x1b[?1048l', vt)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
cursor(5, 9, state)
push('\x1b[H', vt)
cursor(0, 0, state)
push('\x1b[?1048l', vt)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
cursor(5, 9, state)
end)
itp('25state_input', function()
local vt = init()
local state = wantstate(vt)
-- Unmodified ASCII
inchar(41, vt)
expect('output 29')
inchar(61, vt)
expect('output 3d')
-- Ctrl modifier on ASCII letters
inchar(41, vt, { C = true })
expect('output 1b,5b,34,31,3b,35,75')
inchar(61, vt, { C = true })
expect('output 1b,5b,36,31,3b,35,75')
-- Alt modifier on ASCII letters
inchar(41, vt, { A = true })
expect('output 1b,29')
inchar(61, vt, { A = true })
expect('output 1b,3d')
-- Ctrl-Alt modifier on ASCII letters
inchar(41, vt, { C = true, A = true })
expect('output 1b,5b,34,31,3b,37,75')
inchar(61, vt, { C = true, A = true })
expect('output 1b,5b,36,31,3b,37,75')
-- Special handling of Ctrl-I
inchar(49, vt)
expect('output 31')
inchar(69, vt)
expect('output 45')
inchar(49, vt, { C = true })
expect('output 1b,5b,34,39,3b,35,75')
inchar(69, vt, { C = true })
expect('output 1b,5b,36,39,3b,35,75')
inchar(49, vt, { A = true })
expect('output 1b,31')
inchar(69, vt, { A = true })
expect('output 1b,45')
inchar(49, vt, { A = true, C = true })
expect('output 1b,5b,34,39,3b,37,75')
inchar(69, vt, { A = true, C = true })
expect('output 1b,5b,36,39,3b,37,75')
-- Special handling of Space
inchar(20, vt)
expect('output 14')
inchar(20, vt, { S = true })
expect('output 14')
inchar(20, vt, { C = true })
expect('output 1b,5b,32,30,3b,35,75')
inchar(20, vt, { C = true, S = true })
expect('output 1b,5b,32,30,3b,35,75')
inchar(20, vt, { A = true })
expect('output 1b,14')
inchar(20, vt, { S = true, A = true })
expect('output 1b,14')
inchar(20, vt, { C = true, A = true })
expect('output 1b,5b,32,30,3b,37,75')
inchar(20, vt, { S = true, C = true, A = true })
expect('output 1b,5b,32,30,3b,37,75')
-- Cursor keys in reset (cursor) mode
inkey('up', vt)
expect_output('\x1b[A')
inkey('up', vt, { S = true })
expect_output('\x1b[1;2A')
inkey('up', vt, { C = true })
expect_output('\x1b[1;5A')
inkey('up', vt, { S = true, C = true })
expect_output('\x1b[1;6A')
inkey('up', vt, { A = true })
expect_output('\x1b[1;3A')
inkey('up', vt, { S = true, A = true })
expect_output('\x1b[1;4A')
inkey('up', vt, { C = true, A = true })
expect_output('\x1b[1;7A')
inkey('up', vt, { S = true, C = true, A = true })
expect_output('\x1b[1;8A')
-- Cursor keys in application mode
push('\x1b[?1h', vt)
-- Plain "Up" should be SS3 A now
inkey('up', vt)
expect_output('\x1bOA')
-- Modified keys should still use CSI
inkey('up', vt, { S = true })
expect_output('\x1b[1;2A')
inkey('up', vt, { C = true })
expect_output('\x1b[1;5A')
-- Shift-Tab should be different
inkey('tab', vt)
expect_output('\x09')
inkey('tab', vt, { S = true })
expect_output('\x1b[Z')
inkey('tab', vt, { C = true })
expect_output('\x1b[9;5u')
inkey('tab', vt, { A = true })
expect_output('\x1b\x09')
inkey('tab', vt, { C = true, A = true })
expect_output('\x1b[9;7u')
-- Enter in linefeed mode
inkey('enter', vt)
expect_output('\x0d')
-- Enter in newline mode
push('\x1b[20h', vt)
inkey('enter', vt)
expect_output('\x0d\x0a')
-- Unmodified F1 is SS3 P
inkey('f1', vt)
expect_output('\x1bOP')
-- Modified F1 is CSI P
inkey('f1', vt, { S = true })
expect_output('\x1b[1;2P')
inkey('f1', vt, { A = true })
expect_output('\x1b[1;3P')
inkey('f1', vt, { C = true })
expect_output('\x1b[1;5P')
-- Keypad in DECKPNM
inkey('kp0', vt)
expect_output('0')
-- Keypad in DECKPAM
push('\x1b=', vt)
inkey('kp0', vt)
expect_output('\x1bOp')
-- Bracketed paste mode off
vterm.vterm_keyboard_start_paste(vt)
vterm.vterm_keyboard_end_paste(vt)
-- Bracketed paste mode on
push('\x1b[?2004h', vt)
vterm.vterm_keyboard_start_paste(vt)
expect_output('\x1b[200~')
vterm.vterm_keyboard_end_paste(vt)
expect_output('\x1b[201~')
-- Focus reporting disabled
vterm.vterm_state_focus_in(state)
vterm.vterm_state_focus_out(state)
-- Focus reporting enabled
state = wantstate(vt, { p = true })
push('\x1b[?1004h', vt)
expect('settermprop 9 true')
vterm.vterm_state_focus_in(state)
expect_output('\x1b[I')
vterm.vterm_state_focus_out(state)
expect_output('\x1b[O')
end)
itp('26state_query', function()
local vt = init()
local state = wantstate(vt)
-- DA
reset(state, nil)
push('\x1b[c', vt)
expect_output('\x1b[?1;2c')
-- XTVERSION
reset(state, nil)
push('\x1b[>q', vt)
expect_output('\x1bP>|libvterm(0.3)\x1b\\')
-- DSR
reset(state, nil)
push('\x1b[5n', vt)
expect_output('\x1b[0n')
-- CPR
push('\x1b[6n', vt)
expect_output('\x1b[1;1R')
push('\x1b[10;10H\x1b[6n', vt)
expect_output('\x1b[10;10R')
-- DECCPR
push('\x1b[?6n', vt)
expect_output('\x1b[?10;10R')
-- DECRQSS on DECSCUSR
push('\x1b[3 q', vt)
push('\x1bP$q q\x1b\\', vt)
expect_output('\x1bP1$r3 q\x1b\\')
-- DECRQSS on SGR
push('\x1b[1;5;7m', vt)
push('\x1bP$qm\x1b\\', vt)
expect_output('\x1bP1$r1;5;7m\x1b\\')
-- DECRQSS on SGR ANSI colours
push('\x1b[0;31;42m', vt)
push('\x1bP$qm\x1b\\', vt)
expect_output('\x1bP1$r31;42m\x1b\\')
-- DECRQSS on SGR ANSI hi-bright colours
push('\x1b[0;93;104m', vt)
push('\x1bP$qm\x1b\\', vt)
expect_output('\x1bP1$r93;104m\x1b\\')
-- DECRQSS on SGR 256-palette colours
push('\x1b[0;38:5:56;48:5:78m', vt)
push('\x1bP$qm\x1b\\', vt)
expect_output('\x1bP1$r38:5:56;48:5:78m\x1b\\')
-- DECRQSS on SGR RGB8 colours
push('\x1b[0;38:2:24:68:112;48:2:13:57:101m', vt)
push('\x1bP$qm\x1b\\', vt)
expect_output('\x1bP1$r38:2:24:68:112;48:2:13:57:101m\x1b\\')
-- S8C1T on DSR
push('\x1b G', vt)
push('\x1b[5n', vt)
expect_output('\x9b0n')
push('\x1b F', vt)
end)
itp('27state_reset', function()
local vt = init()
local state = wantstate(vt)
reset(state, nil)
-- RIS homes cursor
push('\x1b[5;5H', vt)
cursor(4, 4, state)
state = wantstate(vt, { m = true })
push('\x1bc', vt)
cursor(0, 0, state)
wantstate(vt)
-- RIS cancels scrolling region
push('\x1b[5;10r', vt)
wantstate(vt, { s = true })
push('\x1bc\x1b[25H\n', vt)
expect('scrollrect 0..25,0..80 => +1,+0')
wantstate(vt)
-- RIS erases screen
push('ABCDE', vt)
state = wantstate(vt, { e = true })
push('\x1bc', vt)
expect('erase 0..25,0..80')
wantstate(vt)
-- RIS clears tabstops
push('\x1b[5G\x1bH\x1b[G\t', vt)
cursor(0, 4, state)
push('\x1bc\t', vt)
cursor(0, 8, state)
end)
itp('28state_dbl_wh', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- Single Width, Single Height
reset(state, nil)
push('\x1b#5', vt)
push('Hello', vt)
expect(
'putglyph 48 1 0,0\nputglyph 65 1 0,1\nputglyph 6c 1 0,2\nputglyph 6c 1 0,3\nputglyph 6f 1 0,4'
)
-- Double Width, Single Height
reset(state, nil)
push('\x1b#6', vt)
push('Hello', vt)
expect(
'putglyph 48 1 0,0 dwl\nputglyph 65 1 0,1 dwl\nputglyph 6c 1 0,2 dwl\nputglyph 6c 1 0,3 dwl\nputglyph 6f 1 0,4 dwl'
)
cursor(0, 5, state)
push('\x1b[40GAB', vt)
expect('putglyph 41 1 0,39 dwl\nputglyph 42 1 1,0')
cursor(1, 1, state)
-- Double Height
reset(state, nil)
push('\x1b#3', vt)
push('Hello', vt)
expect(
'putglyph 48 1 0,0 dwl dhl-top\nputglyph 65 1 0,1 dwl dhl-top\nputglyph 6c 1 0,2 dwl dhl-top\nputglyph 6c 1 0,3 dwl dhl-top\nputglyph 6f 1 0,4 dwl dhl-top'
)
cursor(0, 5, state)
push('\r\n\x1b#4', vt)
push('Hello', vt)
expect(
'putglyph 48 1 1,0 dwl dhl-bottom\nputglyph 65 1 1,1 dwl dhl-bottom\nputglyph 6c 1 1,2 dwl dhl-bottom\nputglyph 6c 1 1,3 dwl dhl-bottom\nputglyph 6f 1 1,4 dwl dhl-bottom'
)
cursor(1, 5, state)
-- Double Width scrolling
reset(state, nil)
push('\x1b[20H\x1b#6ABC', vt)
expect('putglyph 41 1 19,0 dwl\nputglyph 42 1 19,1 dwl\nputglyph 43 1 19,2 dwl')
push('\x1b[25H\n', vt)
push('\x1b[19;4HDE', vt)
expect('putglyph 44 1 18,3 dwl\nputglyph 45 1 18,4 dwl')
push('\x1b[H\x1bM', vt)
push('\x1b[20;6HFG', vt)
expect('putglyph 46 1 19,5 dwl\nputglyph 47 1 19,6 dwl')
end)
itp('29state_fallback', function()
local vt = init()
local state = wantstate(vt, { f = true })
reset(state, nil)
-- Unrecognised control
push('\x03', vt)
expect('control 03')
-- Unrecognised CSI
push('\x1b[?15;2z', vt)
expect('csi 7a L=3f 15,2')
-- Unrecognised OSC
push('\x1b]27;Something\x1b\\', vt)
expect('osc [27;Something]')
-- Unrecognised DCS
push('\x1bPz123\x1b\\', vt)
expect('dcs [z123]')
-- Unrecognised APC
push('\x1b_z123\x1b\\', vt)
expect('apc [z123]')
-- Unrecognised PM
push('\x1b^z123\x1b\\', vt)
expect('pm [z123]')
-- Unrecognised SOS
push('\x1bXz123\x1b\\', vt)
expect('sos [z123]')
end)
itp('30state_pen', function()
local vt = init()
local state = wantstate(vt)
-- Reset
push('\x1b[m', vt)
pen('bold', false, state)
pen('underline', 0, state)
pen('italic', false, state)
pen('blink', false, state)
pen('reverse', false, state)
pen('font', 0, state)
-- TODO(dundargoc): fix
-- ?pen foreground = rgb(240,240,240,is_default_fg)
-- ?pen background = rgb(0,0,0,is_default_bg)
-- Bold
push('\x1b[1m', vt)
pen('bold', true, state)
push('\x1b[22m', vt)
pen('bold', false, state)
push('\x1b[1m\x1b[m', vt)
pen('bold', false, state)
-- Underline
push('\x1b[4m', vt)
pen('underline', 1, state)
push('\x1b[21m', vt)
pen('underline', 2, state)
push('\x1b[24m', vt)
pen('underline', 0, state)
push('\x1b[4m\x1b[4:0m', vt)
pen('underline', 0, state)
push('\x1b[4:1m', vt)
pen('underline', 1, state)
push('\x1b[4:2m', vt)
pen('underline', 2, state)
push('\x1b[4:3m', vt)
pen('underline', 3, state)
push('\x1b[4m\x1b[m', vt)
pen('underline', 0, state)
-- Italic
push('\x1b[3m', vt)
pen('italic', true, state)
push('\x1b[23m', vt)
pen('italic', false, state)
push('\x1b[3m\x1b[m', vt)
pen('italic', false, state)
-- Blink
push('\x1b[5m', vt)
pen('blink', true, state)
push('\x1b[25m', vt)
pen('blink', false, state)
push('\x1b[5m\x1b[m', vt)
pen('blink', false, state)
-- Reverse
push('\x1b[7m', vt)
pen('reverse', true, state)
push('\x1b[27m', vt)
pen('reverse', false, state)
push('\x1b[7m\x1b[m', vt)
pen('reverse', false, state)
-- Font Selection
push('\x1b[11m', vt)
pen('font', 1, state)
push('\x1b[19m', vt)
pen('font', 9, state)
push('\x1b[10m', vt)
pen('font', 0, state)
push('\x1b[11m\x1b[m', vt)
pen('font', 0, state)
-- TODO(dundargoc): fix
-- Foreground
-- push "\x1b[31m"
-- ?pen foreground = idx(1)
-- push "\x1b[32m"
-- ?pen foreground = idx(2)
-- push "\x1b[34m"
-- ?pen foreground = idx(4)
-- push "\x1b[91m"
-- ?pen foreground = idx(9)
-- push "\x1b[38:2:10:20:30m"
-- ?pen foreground = rgb(10,20,30)
-- push "\x1b[38:5:1m"
-- ?pen foreground = idx(1)
-- push "\x1b[39m"
-- ?pen foreground = rgb(240,240,240,is_default_fg)
--
-- Background
-- push "\x1b[41m"
-- ?pen background = idx(1)
-- push "\x1b[42m"
-- ?pen background = idx(2)
-- push "\x1b[44m"
-- ?pen background = idx(4)
-- push "\x1b[101m"
-- ?pen background = idx(9)
-- push "\x1b[48:2:10:20:30m"
-- ?pen background = rgb(10,20,30)
-- push "\x1b[48:5:1m"
-- ?pen background = idx(1)
-- push "\x1b[49m"
-- ?pen background = rgb(0,0,0,is_default_bg)
--
-- Bold+ANSI colour == highbright
-- push "\x1b[m\x1b[1;37m"
-- ?pen bold = on
-- ?pen foreground = idx(15)
-- push "\x1b[m\x1b[37;1m"
-- ?pen bold = on
-- ?pen foreground = idx(15)
--
-- Super/Subscript
-- push "\x1b[73m"
-- ?pen small = on
-- ?pen baseline = raise
-- push "\x1b[74m"
-- ?pen small = on
-- ?pen baseline = lower
-- push "\x1b[75m"
-- ?pen small = off
-- ?pen baseline = normal
--
-- DECSTR resets pen attributes
-- push "\x1b[1;4m"
-- ?pen bold = on
-- ?pen underline = 1
-- push "\x1b[!p"
-- ?pen bold = off
-- ?pen underline = 0
end)
itp('31state_rep', function()
local vt = init()
local state = wantstate(vt, { g = true })
-- REP no argument
reset(state, nil)
push('a\x1b[b', vt)
expect('putglyph 61 1 0,0\nputglyph 61 1 0,1')
-- REP zero (zero should be interpreted as one)
reset(state, nil)
push('a\x1b[0b', vt)
expect('putglyph 61 1 0,0\nputglyph 61 1 0,1')
-- REP lowercase a times two
reset(state, nil)
push('a\x1b[2b', vt)
expect('putglyph 61 1 0,0\nputglyph 61 1 0,1\nputglyph 61 1 0,2')
-- REP with UTF-8 1 char
-- U+00E9 = C3 A9 name: LATIN SMALL LETTER E WITH ACUTE
reset(state, nil)
push('\xC3\xA9\x1b[b', vt)
expect('putglyph e9 1 0,0\nputglyph e9 1 0,1')
-- REP with UTF-8 wide char
-- U+00E9 = C3 A9 name: LATIN SMALL LETTER E WITH ACUTE
reset(state, nil)
push('\xEF\xBC\x90\x1b[b', vt)
expect('putglyph ff10 2 0,0\nputglyph ff10 2 0,2')
-- REP with UTF-8 combining character
reset(state, nil)
push('e\xCC\x81\x1b[b', vt)
expect('putglyph 65,301 1 0,0\nputglyph 65,301 1 0,1')
-- REP till end of line
reset(state, nil)
push('a\x1b[1000bb', vt)
expect(
'putglyph 61 1 0,0\nputglyph 61 1 0,1\nputglyph 61 1 0,2\nputglyph 61 1 0,3\nputglyph 61 1 0,4\nputglyph 61 1 0,5\nputglyph 61 1 0,6\nputglyph 61 1 0,7\nputglyph 61 1 0,8\nputglyph 61 1 0,9\nputglyph 61 1 0,10\nputglyph 61 1 0,11\nputglyph 61 1 0,12\nputglyph 61 1 0,13\nputglyph 61 1 0,14\nputglyph 61 1 0,15\nputglyph 61 1 0,16\nputglyph 61 1 0,17\nputglyph 61 1 0,18\nputglyph 61 1 0,19\nputglyph 61 1 0,20\nputglyph 61 1 0,21\nputglyph 61 1 0,22\nputglyph 61 1 0,23\nputglyph 61 1 0,24\nputglyph 61 1 0,25\nputglyph 61 1 0,26\nputglyph 61 1 0,27\nputglyph 61 1 0,28\nputglyph 61 1 0,29\nputglyph 61 1 0,30\nputglyph 61 1 0,31\nputglyph 61 1 0,32\nputglyph 61 1 0,33\nputglyph 61 1 0,34\nputglyph 61 1 0,35\nputglyph 61 1 0,36\nputglyph 61 1 0,37\nputglyph 61 1 0,38\nputglyph 61 1 0,39\nputglyph 61 1 0,40\nputglyph 61 1 0,41\nputglyph 61 1 0,42\nputglyph 61 1 0,43\nputglyph 61 1 0,44\nputglyph 61 1 0,45\nputglyph 61 1 0,46\nputglyph 61 1 0,47\nputglyph 61 1 0,48\nputglyph 61 1 0,49\nputglyph 61 1 0,50\nputglyph 61 1 0,51\nputglyph 61 1 0,52\nputglyph 61 1 0,53\nputglyph 61 1 0,54\nputglyph 61 1 0,55\nputglyph 61 1 0,56\nputglyph 61 1 0,57\nputglyph 61 1 0,58\nputglyph 61 1 0,59\nputglyph 61 1 0,60\nputglyph 61 1 0,61\nputglyph 61 1 0,62\nputglyph 61 1 0,63\nputglyph 61 1 0,64\nputglyph 61 1 0,65\nputglyph 61 1 0,66\nputglyph 61 1 0,67\nputglyph 61 1 0,68\nputglyph 61 1 0,69\nputglyph 61 1 0,70\nputglyph 61 1 0,71\nputglyph 61 1 0,72\nputglyph 61 1 0,73\nputglyph 61 1 0,74\nputglyph 61 1 0,75\nputglyph 61 1 0,76\nputglyph 61 1 0,77\nputglyph 61 1 0,78\nputglyph 61 1 0,79\nputglyph 62 1 1,0'
)
end)
itp('32state_flow', function()
local vt = init()
local state = wantstate(vt)
-- Many of these test cases inspired by
-- https://blueprints.launchpad.net/libvterm/+spec/reflow-cases
-- Spillover text marks continuation on second line
reset(state, nil)
push(string.rep('A', 100), vt)
push('\r\n', vt)
lineinfo(0, {}, state)
lineinfo(1, { cont = true }, state)
-- CRLF in column 80 does not mark continuation
reset(state, nil)
push(string.rep('B', 80), vt)
push('\r\n', vt)
push(string.rep('B', 20), vt)
push('\r\n', vt)
lineinfo(0, {}, state)
lineinfo(1, {}, state)
-- EL cancels continuation of following line
reset(state, nil)
push(string.rep('D', 100), vt)
lineinfo(1, { cont = true }, state)
push('\x1bM\x1b[79G\x1b[K', vt)
lineinfo(1, {}, state)
end)
itp('40state_selection', function()
local vt = init()
wantstate(vt)
-- Set clipboard; final chunk len 4
push('\x1b]52;c;SGVsbG8s\x1b\\', vt)
expect('selection-set mask=0001 [Hello,]')
-- Set clipboard; final chunk len 3
push('\x1b]52;c;SGVsbG8sIHc=\x1b\\', vt)
expect('selection-set mask=0001 [Hello, w]')
-- Set clipboard; final chunk len 2
push('\x1b]52;c;SGVsbG8sIHdvcmxkCg==\x1b\\', vt)
expect('selection-set mask=0001 [Hello, world\n]')
-- Set clipboard; split between chunks
push('\x1b]52;c;SGVs', vt)
expect('selection-set mask=0001 [Hel')
push('bG8s\x1b\\', vt)
expect('selection-set mask=0001 lo,]')
-- Set clipboard; split within chunk
push('\x1b]52;c;SGVsbG', vt)
expect('selection-set mask=0001 [Hel')
push('8s\x1b\\', vt)
expect('selection-set mask=0001 lo,]')
-- Set clipboard; empty first chunk
push('\x1b]52;c;', vt)
push('SGVsbG8s\x1b\\', vt)
expect('selection-set mask=0001 [Hello,]')
-- Set clipboard; empty final chunk
push('\x1b]52;c;SGVsbG8s', vt)
expect('selection-set mask=0001 [Hello,')
push('\x1b\\', vt)
expect('selection-set mask=0001 ]')
-- Set clipboard; longer than buffer
push('\x1b]52;c;' .. string.rep('LS0t', 10) .. '\x1b\\', vt)
expect('selection-set mask=0001 [---------------\nselection-set mask=0001 ---------------]')
-- Clear clipboard
push('\x1b]52;c;\x1b\\', vt)
expect('selection-set mask=0001 []')
-- Set invalid data clears and ignores
push('\x1b]52;c;SGVs*SGVsbG8s\x1b\\', vt)
expect('selection-set mask=0001 []')
-- Query clipboard
push('\x1b]52;c;?\x1b\\', vt)
expect('selection-query mask=0001')
-- TODO(dundargoc): fix
-- Send clipboard; final chunk len 4
-- SELECTION 1 ["Hello,"]
-- output "\x1b]52;c;"
-- output "SGVsbG8s"
-- output "\x1b\\"
--
-- Send clipboard; final chunk len 3
-- SELECTION 1 ["Hello, w"]
-- output "\x1b]52;c;"
-- output "SGVsbG8s"
-- output "IHc=\x1b\\"
--
-- Send clipboard; final chunk len 2
-- SELECTION 1 ["Hello, world\n"]
-- output "\x1b]52;c;"
-- output "SGVsbG8sIHdvcmxk"
-- output "Cg==\x1b\\"
--
-- Send clipboard; split between chunks
-- SELECTION 1 ["Hel"
-- output "\x1b]52;c;"
-- output "SGVs"
-- SELECTION 1 "lo,"]
-- output "bG8s"
-- output "\x1b\\"
--
-- Send clipboard; split within chunk
-- SELECTION 1 ["Hello"
-- output "\x1b]52;c;"
-- output "SGVs"
-- SELECTION 1 ","]
-- output "bG8s"
-- output "\x1b\\"
end)
itp('60screen_ascii', function()
local vt = init()
local screen = wantscreen(vt, { a = true, c = true })
-- Get
reset(nil, screen)
push('ABC', vt)
expect('movecursor 0,3')
screen_chars(0, 0, 1, 3, 'ABC', screen)
screen_chars(0, 0, 1, 80, 'ABC', screen)
screen_text(0, 0, 1, 3, '41,42,43', screen)
screen_text(0, 0, 1, 80, '41,42,43', screen)
screen_cell(0, 0, '{41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 1, '{42} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 2, '{43} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_row(0, 'ABC', screen)
screen_eol(0, 0, 0, screen)
screen_eol(0, 2, 0, screen)
screen_eol(0, 3, 1, screen)
push('\x1b[H', vt)
expect('movecursor 0,0')
screen_row(0, 'ABC', screen)
screen_text(0, 0, 1, 80, '41,42,43', screen)
push('E', vt)
expect('movecursor 0,1')
screen_row(0, 'EBC', screen)
screen_text(0, 0, 1, 80, '45,42,43', screen)
screen = wantscreen(vt, { a = true })
-- Erase
reset(nil, screen)
push('ABCDE\x1b[H\x1b[K', vt)
-- TODO(dundargoc): fix
-- screen_row(0, '', screen)
screen_text(0, 0, 1, 80, '', screen)
-- Copycell
reset(nil, screen)
push('ABC\x1b[H\x1b[@', vt)
push('1', vt)
screen_row(0, '1ABC', screen)
reset(nil, screen)
push('ABC\x1b[H\x1b[P', vt)
screen_chars(0, 0, 1, 1, 'B', screen)
screen_chars(0, 1, 1, 2, 'C', screen)
screen_chars(0, 0, 1, 80, 'BC', screen)
-- Space padding
reset(nil, screen)
push('Hello\x1b[CWorld', vt)
screen_row(0, 'Hello World', screen)
screen_text(0, 0, 1, 80, '48,65,6c,6c,6f,20,57,6f,72,6c,64', screen)
-- Linefeed padding
reset(nil, screen)
push('Hello\r\nWorld', vt)
screen_chars(0, 0, 2, 80, 'Hello\nWorld', screen)
screen_text(0, 0, 2, 80, '48,65,6c,6c,6f,0a,57,6f,72,6c,64', screen)
-- Altscreen
reset(nil, screen)
push('P', vt)
screen_row(0, 'P', screen)
-- TODO(dundargoc): fix
-- push('\x1b[?1049h', vt)
-- screen_row(0, '', screen)
-- push('\x1b[2K\x1b[HA', vt)
-- screen_row(0, 'A', screen)
-- push('\x1b[?1049l', vt)
-- screen_row(0, 'P', screen)
end)
itp('61screen_unicode', function()
local vt = init()
local screen = wantscreen(vt)
-- Single width UTF-8
-- U+00C1 = C3 81 name: LATIN CAPITAL LETTER A WITH ACUTE
-- U+00E9 = C3 A9 name: LATIN SMALL LETTER E WITH ACUTE
reset(nil, screen)
push('\xC3\x81\xC3\xA9', vt)
screen_row(0, 'Áé', screen)
screen_text(0, 0, 1, 80, 'c3,81,c3,a9', screen)
screen_cell(0, 0, '{c1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Wide char
-- U+FF10 = EF BC 90 name: FULLWIDTH DIGIT ZERO
reset(nil, screen)
push('0123\x1b[H', vt)
push('\xEF\xBC\x90', vt)
screen_row(0, '23', screen)
screen_text(0, 0, 1, 80, 'ef,bc,90,32,33', screen)
screen_cell(0, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Combining char
-- U+0301 = CC 81 name: COMBINING ACUTE
reset(nil, screen)
push('0123\x1b[H', vt)
push('e\xCC\x81', vt)
screen_row(0, 'é123', screen)
screen_text(0, 0, 1, 80, '65,cc,81,31,32,33', screen)
screen_cell(0, 0, '{65,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- 10 combining accents should not crash
reset(nil, screen)
push('e\xCC\x81\xCC\x82\xCC\x83\xCC\x84\xCC\x85\xCC\x86\xCC\x87\xCC\x88\xCC\x89\xCC\x8A', vt)
screen_cell(
0,
0,
'{65,301,302,303,304,305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
screen
)
-- 40 combining accents in two split writes of 20 should not crash
reset(nil, screen)
push(
'e\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81',
vt
)
push(
'\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81\xCC\x81',
vt
)
screen_cell(
0,
0,
'{65,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
screen
)
-- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash"
reset(nil, screen)
push('\x1b[80G\xEF\xBC\x90', vt)
screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(1, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
end)
pending('62screen_damage', function() end)
itp('63screen_resize', function()
local vt = init()
local state = wantstate(vt)
local screen = wantscreen(vt)
-- Resize wider preserves cells
reset(state, screen)
resize(25, 80, vt)
push('AB\r\nCD', vt)
screen_chars(0, 0, 1, 80, 'AB', screen)
screen_chars(1, 0, 2, 80, 'CD', screen)
resize(25, 100, vt)
screen_chars(0, 0, 1, 100, 'AB', screen)
screen_chars(1, 0, 2, 100, 'CD', screen)
-- Resize wider allows print in new area
reset(state, screen)
resize(25, 80, vt)
push('AB\x1b[79GCD', vt)
screen_chars(0, 0, 1, 2, 'AB', screen)
screen_chars(0, 78, 1, 80, 'CD', screen)
resize(25, 100, vt)
screen_chars(0, 0, 1, 2, 'AB', screen)
screen_chars(0, 78, 1, 80, 'CD', screen)
push('E', vt)
screen_chars(0, 78, 1, 81, 'CDE', screen)
-- Resize shorter with blanks just truncates
reset(state, screen)
resize(25, 80, vt)
push('Top\x1b[10HLine 10', vt)
screen_row(0, 'Top', screen)
screen_row(9, 'Line 10', screen)
cursor(9, 7, state)
resize(20, 80, vt)
screen_row(0, 'Top', screen)
screen_row(9, 'Line 10', screen)
cursor(9, 7, state)
-- Resize shorter with content must scroll
reset(state, screen)
resize(25, 80, vt)
push('Top\x1b[25HLine 25\x1b[15H', vt)
screen_row(0, 'Top', screen)
screen_row(24, 'Line 25', screen)
cursor(14, 0, state)
screen = wantscreen(vt, { b = true })
resize(20, 80, vt)
expect(
'sb_pushline 80 = 54 6F 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
)
-- TODO(dundargoc): fix or remove
-- screen_row( 0 , "",screen)
screen_row(19, 'Line 25', screen)
cursor(9, 0, state)
-- Resize shorter does not lose line with cursor
-- See also https://github.com/neovim/libvterm/commit/1b745d29d45623aa8d22a7b9288c7b0e331c7088
reset(state, screen)
wantscreen(vt)
resize(25, 80, vt)
screen = wantscreen(vt, { b = true })
push('\x1b[24HLine 24\r\nLine 25\r\n', vt)
expect('sb_pushline 80 =')
screen_row(23, 'Line 25', screen)
cursor(24, 0, state)
resize(24, 80, vt)
expect('sb_pushline 80 =')
screen_row(22, 'Line 25', screen)
cursor(23, 0, state)
-- Resize shorter does not send the cursor to a negative row
-- See also https://github.com/vim/vim/pull/6141
reset(state, screen)
wantscreen(vt)
resize(25, 80, vt)
screen = wantscreen(vt, { b = true })
push('\x1b[24HLine 24\r\nLine 25\x1b[H', vt)
cursor(0, 0, state)
resize(20, 80, vt)
expect(
'sb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
)
cursor(0, 0, state)
-- Resize taller attempts to pop scrollback
reset(state, screen)
screen = wantscreen(vt)
resize(25, 80, vt)
push('Line 1\x1b[25HBottom\x1b[15H', vt)
screen_row(0, 'Line 1', screen)
screen_row(24, 'Bottom', screen)
cursor(14, 0, state)
screen = wantscreen(vt, { b = true })
resize(30, 80, vt)
expect('sb_popline 80\nsb_popline 80\nsb_popline 80\nsb_popline 80\nsb_popline 80')
screen_row(0, 'ABCDE', screen)
screen_row(5, 'Line 1', screen)
screen_row(29, 'Bottom', screen)
cursor(19, 0, state)
screen = wantscreen(vt)
-- Resize can operate on altscreen
reset(state, screen)
screen = wantscreen(vt, { a = true })
resize(25, 80, vt)
push('Main screen\x1b[?1049h\x1b[HAlt screen', vt)
resize(30, 80, vt)
screen_row(0, 'Alt screen', screen)
push('\x1b[?1049l', vt)
screen_row(0, 'Main screen', screen)
end)
itp('64screen_pen', function()
local vt = init()
local screen = wantscreen(vt)
reset(nil, screen)
-- Plain
push('A', vt)
screen_cell(0, 0, '{41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Bold
push('\x1b[1mB', vt)
screen_cell(0, 1, '{42} width=1 attrs={B} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Italic
push('\x1b[3mC', vt)
screen_cell(0, 2, '{43} width=1 attrs={BI} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Underline
push('\x1b[4mD', vt)
screen_cell(0, 3, '{44} width=1 attrs={BU1I} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Reset
push('\x1b[mE', vt)
screen_cell(0, 4, '{45} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Font
push('\x1b[11mF\x1b[m', vt)
screen_cell(0, 5, '{46} width=1 attrs={F1} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Foreground
push('\x1b[31mG\x1b[m', vt)
screen_cell(0, 6, '{47} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)', screen)
-- Background
push('\x1b[42mH\x1b[m', vt)
screen_cell(0, 7, '{48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0)', screen)
-- Super/subscript
push('x\x1b[74m0\x1b[73m2\x1b[m', vt)
screen_cell(0, 8, '{78} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 9, '{30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 10, '{32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- EL sets only colours to end of line, not other attrs
push('\x1b[H\x1b[7;33;44m\x1b[K', vt)
screen_cell(0, 0, '{} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)', screen)
screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)', screen)
-- DECSCNM xors reverse for entire screen
push('R\x1b[?5h', vt)
screen_cell(0, 0, '{52} width=1 attrs={} fg=rgb(224,224,0) bg=rgb(0,0,224)', screen)
screen_cell(1, 0, '{} width=1 attrs={R} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
push('\x1b[?5$p', vt)
expect_output('\x1b[?5;1$y')
push('\x1b[?5l', vt)
screen_cell(0, 0, '{52} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)', screen)
screen_cell(1, 0, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- TODO(dundargoc): fix
-- push('\x1b[?5$p')
-- expect_output('\x1b[?5;2$y')
-- Set default colours
reset(nil, screen)
push('ABC\x1b[31mDEF\x1b[m', vt)
screen_cell(0, 0, '{41} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(0, 3, '{44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)', screen)
-- TODO(dundargoc): fix
-- SETDEFAULTCOL rgb(252,253,254)
-- ?screen_cell 0,0 = {41} width=1 attrs={} fg=rgb(252,253,254) bg=rgb(0,0,0)
-- ?screen_cell 0,3 = {44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(0,0,0)
-- SETDEFAULTCOL rgb(250,250,250) rgb(10,20,30)
-- ?screen_cell 0,0 = {41} width=1 attrs={} fg=rgb(250,250,250) bg=rgb(10,20,30)
-- ?screen_cell 0,3 = {44} width=1 attrs={} fg=rgb(224,0,0) bg=rgb(10,20,30)
end)
itp('65screen_protect', function()
local vt = init()
local screen = wantscreen(vt)
-- Selective erase
reset(nil, screen)
push('A\x1b[1"qB\x1b["qC', vt)
screen_row(0, 'ABC', screen)
push('\x1b[G\x1b[?J', vt)
screen_row(0, ' B', screen)
-- Non-selective erase
reset(nil, screen)
push('A\x1b[1"qB\x1b["qC', vt)
screen_row(0, 'ABC', screen)
-- TODO(dundargoc): fix
-- push('\x1b[G\x1b[J', vt)
-- screen_row(0, '', screen)
end)
itp('66screen_extent', function()
local vt = init()
local screen = wantscreen(vt)
-- Bold extent
reset(nil, screen)
push('AB\x1b[1mCD\x1b[mE', vt)
screen_attrs_extent(0, 0, '0,0-1,1', screen)
screen_attrs_extent(0, 1, '0,0-1,1', screen)
screen_attrs_extent(0, 2, '0,2-1,3', screen)
screen_attrs_extent(0, 3, '0,2-1,3', screen)
screen_attrs_extent(0, 4, '0,4-1,79', screen)
end)
itp('67screen_dbl_wh', function()
local vt = init()
local screen = wantscreen(vt)
reset(nil, screen)
-- Single Width, Single Height
reset(nil, screen)
push('\x1b#5', vt)
push('abcde', vt)
screen_cell(0, 0, '{61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Double Width, Single Height
reset(nil, screen)
push('\x1b#6', vt)
push('abcde', vt)
screen_cell(0, 0, '{61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- Double Height
reset(nil, screen)
push('\x1b#3', vt)
push('abcde', vt)
push('\r\n\x1b#4', vt)
push('abcde', vt)
screen_cell(0, 0, '{61} width=1 attrs={} dwl dhl-top fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(
1,
0,
'{61} width=1 attrs={} dwl dhl-bottom fg=rgb(240,240,240) bg=rgb(0,0,0)',
screen
)
-- Late change
reset(nil, screen)
push('abcde', vt)
screen_cell(0, 0, '{61} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
push('\x1b#6', vt)
screen_cell(0, 0, '{61} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
-- DWL doesn't spill over on scroll
reset(nil, screen)
push('\x1b[25H\x1b#6Final\r\n', vt)
screen_cell(23, 0, '{46} width=1 attrs={} dwl fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
screen_cell(24, 0, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
end)
itp('68screen_termprops', function()
local vt = init()
local screen = wantscreen(vt, { p = true })
reset(nil, screen)
expect('settermprop 1 true\nsettermprop 2 true\nsettermprop 7 1')
-- Cursor visibility
push('\x1b[?25h', vt)
expect('settermprop 1 true')
push('\x1b[?25l', vt)
expect('settermprop 1 false')
-- Title
push('\x1b]2;Here is my title\a', vt)
expect('settermprop 4 ["Here is my title"]')
end)
itp('69screen_pushline', function()
local vt = init()
-- Run these tests on a much smaller default screen, so debug output is nowhere near as noisy
resize(5, 10, vt)
local state = wantstate(vt)
local screen = wantscreen(vt, { r = true })
reset(state, screen)
-- Resize wider reflows wide lines
reset(state, screen)
push(string.rep('A', 12), vt)
screen_row(0, 'AAAAAAAAAA', screen, vt.cols)
screen_row(1, 'AA', screen, vt.cols)
lineinfo(1, { cont = true }, state)
cursor(1, 2, state)
resize(5, 15, vt)
screen_row(0, 'AAAAAAAAAAAA', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row(1, '', screen, vt.cols)
lineinfo(1, {}, state)
cursor(0, 12, state)
resize(5, 20, vt)
screen_row(0, 'AAAAAAAAAAAA', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 ,'',screen, vt.cols)
lineinfo(1, {}, state)
cursor(0, 12, state)
-- Resize narrower can create continuation lines
reset(state, screen)
resize(5, 10, vt)
push('ABCDEFGHI', vt)
screen_row(0, 'ABCDEFGHI', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 , "",screen, vt.cols)
lineinfo(1, {}, state)
cursor(0, 9, state)
resize(5, 8, vt)
-- TODO(dundargoc): fix
-- screen_row( 0 , "ABCDEFGH",screen,vt.cols)
screen_row(1, 'I', screen, vt.cols)
lineinfo(1, { cont = true }, state)
cursor(1, 1, state)
resize(5, 6, vt)
screen_row(0, 'ABCDEF', screen, vt.cols)
screen_row(1, 'GHI', screen, vt.cols)
lineinfo(1, { cont = true }, state)
cursor(1, 3, state)
-- Shell wrapped prompt behaviour
reset(state, screen)
resize(5, 10, vt)
push('PROMPT GOES HERE\r\n> \r\n\r\nPROMPT GOES HERE\r\n> ', vt)
screen_row(0, '> ', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 , "",screen,vt.cols)
screen_row(2, 'PROMPT GOE', screen, vt.cols)
screen_row(3, 'S HERE', screen, vt.cols)
lineinfo(3, { cont = true }, state)
screen_row(4, '> ', screen, vt.cols)
cursor(4, 2, state)
resize(5, 11, vt)
screen_row(0, '> ', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 , "",screen,vt.cols)
screen_row(2, 'PROMPT GOES', screen, vt.cols)
screen_row(3, ' HERE', screen, vt.cols)
lineinfo(3, { cont = true }, state)
screen_row(4, '> ', screen, vt.cols)
cursor(4, 2, state)
resize(5, 12, vt)
screen_row(0, '> ', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 , "",screen,vt.cols)
screen_row(2, 'PROMPT GOES ', screen, vt.cols)
screen_row(3, 'HERE', screen, vt.cols)
lineinfo(3, { cont = true }, state)
screen_row(4, '> ', screen, vt.cols)
cursor(4, 2, state)
resize(5, 16, vt)
screen_row(0, '> ', screen, vt.cols)
-- TODO(dundargoc): fix
-- screen_row( 1 , "",screen,vt.cols)
-- screen_row( 2 , "PROMPT GOES HERE",screen,vt.cols)
lineinfo(3, {}, state)
screen_row(3, '> ', screen, vt.cols)
cursor(3, 2, state)
-- Cursor goes missing
-- For more context: https://github.com/neovim/neovim/pull/21124
reset(state, screen)
resize(5, 5, vt)
resize(3, 1, vt)
push('\x1b[2;1Habc\r\n\x1b[H', vt)
resize(1, 1, vt)
cursor(0, 0, state)
end)
pending('90vttest_01-movement-1', function() end)
pending('90vttest_01-movement-2', function() end)
itp('90vttest_01-movement-3', function()
-- Test of cursor-control characters inside ESC sequences
local vt = init()
local state = wantstate(vt)
local screen = wantscreen(vt)
reset(state, screen)
push('A B C D E F G H I', vt)
push('\x0d\x0a', vt)
push('A\x1b[2\bCB\x1b[2\bCC\x1b[2\bCD\x1b[2\bCE\x1b[2\bCF\x1b[2\bCG\x1b[2\bCH\x1b[2\bCI', vt)
push('\x0d\x0a', vt)
push(
'A \x1b[\x0d2CB\x1b[\x0d4CC\x1b[\x0d6CD\x1b[\x0d8CE\x1b[\x0d10CF\x1b[\x0d12CG\x1b[\x0d14CH\x1b[\x0d16CI',
vt
)
push('\x0d\x0a', vt)
push(
'A \x1b[1\x0bAB \x1b[1\x0bAC \x1b[1\x0bAD \x1b[1\x0bAE \x1b[1\x0bAF \x1b[1\x0bAG \x1b[1\x0bAH \x1b[1\x0bAI \x1b[1\x0bA',
vt
)
-- Output
for i = 0, 2 do
screen_row(i, 'A B C D E F G H I', screen)
end
screen_row(3, 'A B C D E F G H I ', screen)
cursor(3, 18, state)
end)
itp('90vttest_01-movement-4', function()
-- Test of leading zeroes in ESC sequences
local vt = init()
local screen = wantscreen(vt)
reset(nil, screen)
push('\x1b[00000000004;000000001HT', vt)
push('\x1b[00000000004;000000002Hh', vt)
push('\x1b[00000000004;000000003Hi', vt)
push('\x1b[00000000004;000000004Hs', vt)
push('\x1b[00000000004;000000005H ', vt)
push('\x1b[00000000004;000000006Hi', vt)
push('\x1b[00000000004;000000007Hs', vt)
push('\x1b[00000000004;000000008H ', vt)
push('\x1b[00000000004;000000009Ha', vt)
push('\x1b[00000000004;0000000010H ', vt)
push('\x1b[00000000004;0000000011Hc', vt)
push('\x1b[00000000004;0000000012Ho', vt)
push('\x1b[00000000004;0000000013Hr', vt)
push('\x1b[00000000004;0000000014Hr', vt)
push('\x1b[00000000004;0000000015He', vt)
push('\x1b[00000000004;0000000016Hc', vt)
push('\x1b[00000000004;0000000017Ht', vt)
push('\x1b[00000000004;0000000018H ', vt)
push('\x1b[00000000004;0000000019Hs', vt)
push('\x1b[00000000004;0000000020He', vt)
push('\x1b[00000000004;0000000021Hn', vt)
push('\x1b[00000000004;0000000022Ht', vt)
push('\x1b[00000000004;0000000023He', vt)
push('\x1b[00000000004;0000000024Hn', vt)
push('\x1b[00000000004;0000000025Hc', vt)
push('\x1b[00000000004;0000000026He', vt)
-- Output
screen_row(3, 'This is a correct sentence', screen)
end)
pending('90vttest_02-screen-1', function() end)
pending('90vttest_02-screen-2', function() end)
itp('90vttest_02-screen-3', function()
-- Origin mode
local vt = init()
local screen = wantscreen(vt)
reset(nil, screen)
push('\x1b[?6h', vt)
push('\x1b[23;24r', vt)
push('\n', vt)
push('Bottom', vt)
push('\x1b[1;1H', vt)
push('Above', vt)
-- Output
screen_row(22, 'Above', screen)
screen_row(23, 'Bottom', screen)
end)
itp('90vttest_02-screen-4', function()
-- Origin mode (2)
local vt = init()
local screen = wantscreen(vt)
reset(nil, screen)
push('\x1b[?6l', vt)
push('\x1b[23;24r', vt)
push('\x1b[24;1H', vt)
push('Bottom', vt)
push('\x1b[1;1H', vt)
push('Top', vt)
-- Output
screen_row(23, 'Bottom', screen)
screen_row(0, 'Top', screen)
end)
itp('Mouse reporting should not break by idempotent DECSM 1002', function()
-- Regression test for https://bugs.launchpad.net/libvterm/+bug/1640917
-- Related: https://github.com/neovim/neovim/issues/5583
local vt = init()
wantstate(vt, {})
push('\x1b[?1002h', vt)
mousemove(0, 0, vt)
mousebtn('d', 1, vt)
expect_output('\x1b[M\x20\x21\x21')
mousemove(1, 0, vt)
expect_output('\x1b[M\x40\x21\x22')
push('\x1b[?1002h', vt)
mousemove(2, 0, vt)
expect_output('\x1b[M\x40\x21\x23')
end)
end)