mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-18 06:25:13 +02:00
Refactor VT parser for more speed
No longer copy bytes into a separate buffer, instead parse them in place in the read buffer
This commit is contained in:
@@ -13,7 +13,7 @@ from kitty.fast_data_types import (
|
||||
LineBuf,
|
||||
expand_ansi_c_escapes,
|
||||
parse_input_from_terminal,
|
||||
replace_c0_codes_except_for_newline_and_space,
|
||||
replace_c0_codes_except_nl_space_tab,
|
||||
strip_csi,
|
||||
truncate_point_for_length,
|
||||
wcswidth,
|
||||
@@ -41,13 +41,14 @@ class TestDataTypes(BaseTest):
|
||||
|
||||
def test_replace_c0_codes(self):
|
||||
def t(x: str, expected: str):
|
||||
q = replace_c0_codes_except_for_newline_and_space(x)
|
||||
q = replace_c0_codes_except_nl_space_tab(x)
|
||||
self.ae(expected, q)
|
||||
q = replace_c0_codes_except_for_newline_and_space(x.encode('utf-8'))
|
||||
q = replace_c0_codes_except_nl_space_tab(x.encode('utf-8'))
|
||||
self.ae(expected.encode('utf-8'), q)
|
||||
t('abc', 'abc')
|
||||
t('a\0\x01b\x03\x04\t\rc', 'a\u2400\u2401b\u2403\u2404\u2409\u240dc')
|
||||
t('a\0\x01😸\x03\x04\t\rc', 'a\u2400\u2401😸\u2403\u2404\u2409\u240dc')
|
||||
t('a\0\x01b\x03\x04\t\rc', 'a\u2400\u2401b\u2403\u2404\t\u240dc')
|
||||
t('a\0\x01😸\x03\x04\t\rc', 'a\u2400\u2401😸\u2403\u2404\t\u240dc')
|
||||
t('a\nb\tc d', 'a\nb\tc d')
|
||||
|
||||
def test_to_color(self):
|
||||
for x in 'xxx #12 #1234 rgb:a/b'.split():
|
||||
|
||||
@@ -25,6 +25,8 @@ class CmdDump(list):
|
||||
a = a[1:]
|
||||
if a and a[0] == 'bytes':
|
||||
return
|
||||
if a and a[0] == 'error':
|
||||
a = a[1:]
|
||||
self.append(tuple(map(cnv, a)))
|
||||
|
||||
|
||||
@@ -75,7 +77,7 @@ class TestParser(BaseTest):
|
||||
self.ae(str(s.line(1)), '6')
|
||||
self.ae(str(s.line(2)), ' 123')
|
||||
self.ae(str(s.line(3)), '45')
|
||||
s.test_write_data(b'\rabcde')
|
||||
pb(b'\rabcde', ('screen_carriage_return',), 'abcde')
|
||||
self.ae(str(s.line(3)), 'abcde')
|
||||
pb('\rßxyz1', ('screen_carriage_return',), 'ßxyz1')
|
||||
self.ae(str(s.line(3)), 'ßxyz1')
|
||||
@@ -126,7 +128,7 @@ class TestParser(BaseTest):
|
||||
pb('x\033[2;7@y', 'x', ('CSI code @ has 2 > 1 parameters',), 'y')
|
||||
pb('x\033[2;-7@y', 'x', ('CSI code @ has 2 > 1 parameters',), 'y')
|
||||
pb('x\033[-2@y', 'x', ('CSI code @ is not allowed to have negative parameter (-2)',), 'y')
|
||||
pb('x\033[2-3@y', 'x', ('CSI code can contain hyphens only at the start of numbers',), 'y')
|
||||
pb('x\033[2-3@y', 'x', ('Invalid character in CSI: 3 (0x33), ignoring the sequence',), '@y')
|
||||
pb('x\033[@y', 'x', ('screen_insert_characters', 1), 'y')
|
||||
pb('x\033[345@y', 'x', ('screen_insert_characters', 345), 'y')
|
||||
pb('x\033[345;@y', 'x', ('screen_insert_characters', 345), 'y')
|
||||
@@ -148,31 +150,36 @@ class TestParser(BaseTest):
|
||||
pb('\033[=c', ('report_device_attributes', 0, 61))
|
||||
s.reset()
|
||||
|
||||
def sgr(params):
|
||||
return (('select_graphic_rendition', f'{x} ') for x in params.split())
|
||||
def sgr(*params):
|
||||
return (('select_graphic_rendition', f'{x}') for x in params)
|
||||
|
||||
pb('\033[1;2;3;4;7;9;34;44m', *sgr('1 2 3 4 7 9 34 44'))
|
||||
for attr in 'bold italic reverse strikethrough dim'.split():
|
||||
self.assertTrue(getattr(s.cursor, attr))
|
||||
self.assertTrue(getattr(s.cursor, attr), attr)
|
||||
self.ae(s.cursor.decoration, 1)
|
||||
self.ae(s.cursor.fg, 4 << 8 | 1)
|
||||
self.ae(s.cursor.bg, 4 << 8 | 1)
|
||||
pb('\033[38;5;1;48;5;7m', ('select_graphic_rendition', '38 5 1 '), ('select_graphic_rendition', '48 5 7 '))
|
||||
pb('\033[38;5;1;48;5;7m', ('select_graphic_rendition', '38:5:1'), ('select_graphic_rendition', '48:5:7'))
|
||||
self.ae(s.cursor.fg, 1 << 8 | 1)
|
||||
self.ae(s.cursor.bg, 7 << 8 | 1)
|
||||
pb('\033[38;2;1;2;3;48;2;7;8;9m', ('select_graphic_rendition', '38 2 1 2 3 '), ('select_graphic_rendition', '48 2 7 8 9 '))
|
||||
pb('\033[38;2;1;2;3;48;2;7;8;9m', ('select_graphic_rendition', '38:2:1:2:3'), ('select_graphic_rendition', '48:2:7:8:9'))
|
||||
self.ae(s.cursor.fg, 1 << 24 | 2 << 16 | 3 << 8 | 2)
|
||||
self.ae(s.cursor.bg, 7 << 24 | 8 << 16 | 9 << 8 | 2)
|
||||
pb('\033[0;2m', *sgr('0 2'))
|
||||
pb('\033[;2m', *sgr('0 2'))
|
||||
pb('\033[m', *sgr('0 '))
|
||||
pb('\033[m', *sgr('0'))
|
||||
pb('\033[1;;2m', *sgr('1 0 2'))
|
||||
pb('\033[38;5;1m', ('select_graphic_rendition', '38 5 1 '))
|
||||
pb('\033[58;2;1;2;3m', ('select_graphic_rendition', '58 2 1 2 3 '))
|
||||
pb('\033[38;2;1;2;3m', ('select_graphic_rendition', '38 2 1 2 3 '))
|
||||
pb('\033[1001:2:1:2:3m', ('select_graphic_rendition', '1001 2 1 2 3 '))
|
||||
pb('\033[38;5;1m', ('select_graphic_rendition', '38:5:1'))
|
||||
pb('\033[58;2;1;2;3m', ('select_graphic_rendition', '58:2:1:2:3'))
|
||||
pb('\033[38;2;1;2;3m', ('select_graphic_rendition', '38:2:1:2:3'))
|
||||
pb('\033[1001:2:1:2:3m', ('select_graphic_rendition', '1001:2:1:2:3'))
|
||||
pb('\033[38:2:1:2:3;48:5:9;58;5;7m', (
|
||||
'select_graphic_rendition', '38 2 1 2 3 '), ('select_graphic_rendition', '48 5 9 '), ('select_graphic_rendition', '58 5 7 '))
|
||||
'select_graphic_rendition', '38:2:1:2:3'), ('select_graphic_rendition', '48:5:9'), ('select_graphic_rendition', '58:5:7'))
|
||||
s.reset()
|
||||
pb('\033[1;2;3;4:5;7;9;34;44m', *sgr('1 2 3', '4:5', '7 9 34 44'))
|
||||
for attr in 'bold italic reverse strikethrough dim'.split():
|
||||
self.assertTrue(getattr(s.cursor, attr), attr)
|
||||
self.ae(s.cursor.decoration, 5)
|
||||
c = s.callbacks
|
||||
pb('\033[5n', ('report_device_status', 5, 0))
|
||||
self.ae(c.wtcbuf, b'\033[0n')
|
||||
@@ -241,7 +248,7 @@ class TestParser(BaseTest):
|
||||
c.clear()
|
||||
pb('\033]\x07', ('set_title', ''), ('set_icon', ''))
|
||||
self.ae(c.titlebuf, ['']), self.ae(c.iconbuf, '')
|
||||
pb('\033]ab\x07', ('set_title', 'ab'), ('set_icon', 'ab'))
|
||||
pb('1\033]ab\x072', '1', ('set_title', 'ab'), ('set_icon', 'ab'), '2')
|
||||
self.ae(c.titlebuf, ['', 'ab']), self.ae(c.iconbuf, 'ab')
|
||||
c.clear()
|
||||
pb('\033]2;;;;\x07', ('set_title', ';;;'))
|
||||
@@ -378,18 +385,19 @@ class TestParser(BaseTest):
|
||||
pb('\033P'), pb('='), pb('2s')
|
||||
pb('\033\\', ('draw', 'e'), ('screen_stop_pending_mode',))
|
||||
pb('\033P=1sxyz;.;\033\\''\033P=2skjf".,><?_+)98\033\\', ('screen_start_pending_mode',), ('screen_stop_pending_mode',))
|
||||
pb('\033P=1s\033\\f\033P=1s\033\\', ('screen_start_pending_mode',), ('screen_start_pending_mode',))
|
||||
pb('\033P=1s\033\\f\033P=1s\033\\', ('screen_start_pending_mode',),)
|
||||
pb('\033P=2s\033\\', ('draw', 'f'), ('screen_stop_pending_mode',))
|
||||
pb('\033P=1s\033\\XXX\033P=2s\033\\', ('screen_start_pending_mode',), ('draw', 'XXX'), ('screen_stop_pending_mode',))
|
||||
|
||||
pb('\033[?2026hXXX\033[?2026l', ('screen_set_mode', 2026, 1), ('draw', 'XXX'), ('screen_reset_mode', 2026, 1))
|
||||
pb('\033[?2026h\033[32ma\033[?2026l', ('screen_set_mode', 2026, 1), ('select_graphic_rendition', '32 '), ('draw', 'a'), ('screen_reset_mode', 2026, 1))
|
||||
pb('\033[?2026hXXX\033[?2026l', ('screen_start_pending_mode',), ('draw', 'XXX'), ('screen_stop_pending_mode',))
|
||||
pb('\033[?2026h\033[32ma\033[?2026l', ('screen_start_pending_mode',), ('select_graphic_rendition', '32'),
|
||||
('draw', 'a'), ('screen_stop_pending_mode',))
|
||||
pb('\033[?2026h\033P+q544e\033\\ama\033P=2s\033\\',
|
||||
('screen_set_mode', 2026, 1), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'), ('screen_stop_pending_mode',))
|
||||
('screen_start_pending_mode',), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'), ('screen_stop_pending_mode',))
|
||||
|
||||
s.reset()
|
||||
s.set_pending_timeout(timeout)
|
||||
pb('\033[?2026h', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h', ('screen_start_pending_mode',),)
|
||||
pb('\033P+q')
|
||||
time.sleep(1.2 * timeout)
|
||||
pb('544e\033\\', ('screen_request_capabilities', 43, '544e'))
|
||||
@@ -398,28 +406,27 @@ class TestParser(BaseTest):
|
||||
('Pending mode stop command issued while not in pending mode, this can be '
|
||||
'either a bug in the terminal application or caused by a timeout with no '
|
||||
'data received for too long or by too much data in pending mode',),
|
||||
('screen_stop_pending_mode',)
|
||||
)
|
||||
self.assertEqual(str(s.line(0)), '')
|
||||
|
||||
pb('\033[?2026h', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h', ('screen_start_pending_mode',),)
|
||||
pb('ab')
|
||||
s.set_pending_activated_at(0.00001)
|
||||
pb('cd', ('draw', 'abcd'))
|
||||
pb('\033[?2026h', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h', ('screen_start_pending_mode',),)
|
||||
pb('\033')
|
||||
s.set_pending_activated_at(0.00001)
|
||||
pb('7', ('screen_save_cursor',))
|
||||
pb('\033[?2026h\033]', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h\033]', ('screen_start_pending_mode',),)
|
||||
s.set_pending_activated_at(0.00001)
|
||||
pb('8;;\x07', ('set_active_hyperlink', None, None))
|
||||
pb('\033[?2026h\033', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h\033', ('screen_start_pending_mode',),)
|
||||
s.set_pending_activated_at(0.00001)
|
||||
pb(']8;;\x07', ('set_active_hyperlink', None, None))
|
||||
pb('😀'.encode()[:-1])
|
||||
pb('\033[?2026h', ('screen_set_mode', 2026, 1),)
|
||||
pb('\033[?2026h', ('screen_start_pending_mode',),)
|
||||
pb('😀'.encode()[-1:])
|
||||
pb('\033[?2026l', '\ufffd', ('screen_reset_mode', 2026, 1),)
|
||||
pb('\033[?2026l', '\ufffd', ('screen_stop_pending_mode',),)
|
||||
pb('a', ('draw', 'a'))
|
||||
|
||||
def test_oth_codes(self):
|
||||
@@ -477,9 +484,9 @@ class TestParser(BaseTest):
|
||||
def test_deccara(self):
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
pb('\033[$r', ('deccara', '0 0 0 0 0 '))
|
||||
pb('\033[$r', ('deccara', '0 0 0 0 0'))
|
||||
pb('\033[;;;;4:3;38:5:10;48:2:1:2:3;1$r',
|
||||
('deccara', '0 0 0 0 4 3 '), ('deccara', '0 0 0 0 38 5 10 '), ('deccara', '0 0 0 0 48 2 1 2 3 '), ('deccara', '0 0 0 0 1 '))
|
||||
('deccara', '0 0 0 0 4:3'), ('deccara', '0 0 0 0 38:5:10'), ('deccara', '0 0 0 0 48:2:1:2:3'), ('deccara', '0 0 0 0 1'))
|
||||
for y in range(s.lines):
|
||||
line = s.line(y)
|
||||
for x in range(s.columns):
|
||||
@@ -490,7 +497,7 @@ class TestParser(BaseTest):
|
||||
self.ae(c.fg, (10 << 8) | 1)
|
||||
self.ae(c.bg, (1 << 24 | 2 << 16 | 3 << 8 | 2))
|
||||
self.ae(s.line(0).cursor_from(0).bold, True)
|
||||
pb('\033[1;2;2;3;22;39$r', ('deccara', '1 2 2 3 22 '), ('deccara', '1 2 2 3 39 '))
|
||||
pb('\033[1;2;2;3;22;39$r', ('deccara', '1 2 2 3 22 39'))
|
||||
self.ae(s.line(0).cursor_from(0).bold, True)
|
||||
line = s.line(0)
|
||||
for x in range(1, s.columns):
|
||||
@@ -502,7 +509,7 @@ class TestParser(BaseTest):
|
||||
c = line.cursor_from(x)
|
||||
self.ae(c.bold, False)
|
||||
self.ae(line.cursor_from(3).bold, True)
|
||||
pb('\033[2*x\033[3;2;4;3;34$r\033[*x', ('screen_decsace', 2), ('deccara', '3 2 4 3 34 '), ('screen_decsace', 0))
|
||||
pb('\033[2*x\033[3;2;4;3;34$r\033[*x', ('screen_decsace', 2), ('deccara', '3 2 4 3 34'), ('screen_decsace', 0))
|
||||
for y in range(2, 4):
|
||||
line = s.line(y)
|
||||
for x in range(s.columns):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from kitty.fast_data_types import DECAWM, DECCOLM, DECOM, IRM, Cursor
|
||||
from kitty.fast_data_types import DECAWM, DECCOLM, DECOM, IRM, VT_PARSER_BUFFER_SIZE, Cursor
|
||||
from kitty.marks import marker_from_function, marker_from_regex
|
||||
from kitty.window import pagerhist
|
||||
|
||||
@@ -912,23 +912,27 @@ class TestScreen(BaseTest):
|
||||
def send(what: str):
|
||||
return parse_bytes(s, f'\033]52;p;{what}\a'.encode('ascii'))
|
||||
|
||||
def t(q, use_pending_mode, *expected):
|
||||
def t(q, use_pending_mode, *expected, expect_pending=True):
|
||||
c.clear()
|
||||
if use_pending_mode:
|
||||
parse_bytes(s, b'\033[?2026h')
|
||||
send(q)
|
||||
del q
|
||||
if use_pending_mode:
|
||||
t.ex = list(expected)
|
||||
del expected
|
||||
if use_pending_mode and expect_pending:
|
||||
self.ae(c.cc_buf, [])
|
||||
parse_bytes(s, b'\033[?2026l')
|
||||
try:
|
||||
self.ae(c.cc_buf, list(expected))
|
||||
self.ae(tuple(map(len, c.cc_buf)), tuple(map(len, t.ex)))
|
||||
self.ae(c.cc_buf, t.ex)
|
||||
finally:
|
||||
del expected
|
||||
del t.ex
|
||||
|
||||
for use_pending_mode in (False, True):
|
||||
t('XYZ', use_pending_mode, ('p;XYZ', False))
|
||||
t('a' * 8192, use_pending_mode, ('p;' + 'a' * (8192 - 6), True), (';' + 'a' * 6, False))
|
||||
t('a' * VT_PARSER_BUFFER_SIZE, use_pending_mode, ('p;' + 'a' * (VT_PARSER_BUFFER_SIZE - 8), True),
|
||||
(';' + 'a' * 8, False), expect_pending=False)
|
||||
t('', use_pending_mode, ('p;', False))
|
||||
t('!', use_pending_mode, ('p;!', False))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user