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, '023', 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)