diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index bf6bd32d8..b22117274 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1067,7 +1067,7 @@ class Screen: cursor_key_mode: bool auto_repeat_enabled: bool render_unfocused_cursor: int - last_reported_cwd: Optional[str] + last_reported_cwd: Optional[bytes] def __init__( self, diff --git a/kitty/screen.c b/kitty/screen.c index 366b3fb20..5b569434d 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2196,11 +2196,13 @@ set_color_table_color(Screen *self, unsigned int code, PyObject *color) { } void -process_cwd_notification(Screen *self, unsigned int code, PyObject *cwd) { +process_cwd_notification(Screen *self, unsigned int code, const char *data, size_t sz) { if (code == 7) { - Py_CLEAR(self->last_reported_cwd); - self->last_reported_cwd = cwd; - Py_INCREF(self->last_reported_cwd); + PyObject *x = PyBytes_FromStringAndSize(data, sz); + if (x) { + Py_CLEAR(self->last_reported_cwd); + self->last_reported_cwd = x; + } else { PyErr_Clear(); } } // we ignore OSC 6 document reporting as we dont have a use for it } diff --git a/kitty/screen.h b/kitty/screen.h index ca5c50567..58c3dfefc 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -226,7 +226,7 @@ void clipboard_control(Screen *self, int code, PyObject*); void shell_prompt_marking(Screen *self, char *buf); void file_transmission(Screen *self, PyObject*); void set_color_table_color(Screen *self, unsigned int code, PyObject*); -void process_cwd_notification(Screen *self, unsigned int code, PyObject*); +void process_cwd_notification(Screen *self, unsigned int code, const char*, size_t); void screen_request_capabilities(Screen *, char, const char *); void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier); void select_graphic_rendition(Screen *self, int *params, unsigned int count, Region*); diff --git a/kitty/utils.py b/kitty/utils.py index 2f5b6cb5f..9a1e21946 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -1050,7 +1050,9 @@ def cleanup_ssh_control_masters() -> None: os.remove(x) -def path_from_osc7_url(url: str) -> str: +def path_from_osc7_url(url: Union[str, bytes]) -> str: + if isinstance(url, bytes): + url = url.decode('utf-8') if url.startswith('kitty-shell-cwd://'): return '/' + url.split('/', 3)[-1] if url.startswith('file://'): diff --git a/kitty/vt-parser.c b/kitty/vt-parser.c index 0403bb521..59e0f5c25 100644 --- a/kitty/vt-parser.c +++ b/kitty/vt-parser.c @@ -311,7 +311,8 @@ dispatch_esc_mode_byte(PS *self) { case '0': case 'U': case 'V': - REPORT_ERROR("Ignoring attempt to designate charset as we support only UTF-8"); + // dont report this error as fish shell designates charsets for some unholy reason, creating lot of noise in the tests + /* REPORT_ERROR("Ignoring attempt to designate charset as we support only UTF-8"); */ break; default: REPORT_ERROR("Unknown charset: 0x%x", ch); break; @@ -466,7 +467,8 @@ dispatch_osc(PS *self) { case 6: case 7: START_DISPATCH - DISPATCH_OSC_WITH_CODE(process_cwd_notification); + REPORT_OSC2(shell_prompt_marking, code, mv); + process_cwd_notification(self->screen, code, (char*)self->parser_buf + i, limit-i); END_DISPATCH case 8: dispatch_hyperlink(self, i, limit-i); @@ -1047,7 +1049,11 @@ dispatch_csi(PS *self) { Region r = {0}; unsigned int consumed = parse_region(&r, buf, num); num -= consumed; buf += consumed; +#ifdef DUMP_COMMANDS + parse_sgr_dump(self, buf, num, params, "deccara", &r); +#else parse_sgr(self->screen, buf, num, params, "deccara", &r); +#endif return; } @@ -1474,7 +1480,7 @@ pending_csi(PS *self) { static void queue_pending_bytes(PS *self) { - for (; self->input_pos < self->input_sz; self->input_pos++) { + while (self->input_pos < self->input_sz) { dispatch_single_byte(pending, if (!self->pending_mode.activated_at) goto end); } end: @@ -1485,7 +1491,7 @@ static void parse_pending_bytes(PS *self) { SAVE_INPUT_DATA; self->input_data = self->pending_mode.buf; self->input_sz = self->pending_mode.used; - for (self->input_pos = 0; self->input_pos < self->input_sz; self->input_pos++) { + while (self->input_pos < self->input_sz) { dispatch_single_byte(dispatch, ;); } RESTORE_INPUT_DATA; @@ -1504,7 +1510,7 @@ dump_partial_escape_code_to_pending(PS *self) { static void parse_bytes_watching_for_pending(PS *self) { - for (; self->input_pos < self->input_sz; self->input_pos++) { + while (self->input_pos < self->input_sz) { dispatch_single_byte(dispatch, if (self->pending_mode.activated_at) goto end); } end: diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index bade0e1e5..023c72674 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -37,21 +37,21 @@ class Callbacks: self.set_pointer_shape = lambda data: None def write(self, data) -> None: - self.wtcbuf += data + self.wtcbuf += bytes(data) def title_changed(self, data, is_base64=False) -> None: - self.titlebuf.append(process_title_from_child(data, is_base64)) + self.titlebuf.append(process_title_from_child(data, is_base64, '')) def icon_changed(self, data) -> None: - self.iconbuf += data + self.iconbuf += str(data, 'utf-8') - def set_dynamic_color(self, code, data) -> None: + def set_dynamic_color(self, code, data='') -> None: if code == 22: self.set_pointer_shape(data) else: - self.colorbuf += data or '' + self.colorbuf += str(data or b'', 'utf-8') - def set_color_table_color(self, code, data) -> None: + def set_color_table_color(self, code, data='') -> None: self.ctbuf += '' def color_profile_popped(self, x) -> None: @@ -65,23 +65,19 @@ class Callbacks: for c in get_capabilities(q, None): self.write(c.encode('ascii')) - def use_utf8(self, on) -> None: - self.iutf8 = on - - def desktop_notify(self, osc_code: int, raw_data: str) -> None: - self.notifications.append((osc_code, raw_data)) + def desktop_notify(self, osc_code: int, raw_data: memoryview) -> None: + self.notifications.append((osc_code, str(raw_data, 'utf-8'))) def open_url(self, url: str, hyperlink_id: int) -> None: self.open_urls.append((url, hyperlink_id)) - def clipboard_control(self, data: str, is_partial: bool = False) -> None: - self.cc_buf.append((data, is_partial)) + def clipboard_control(self, data: memoryview, is_partial: bool = False) -> None: + self.cc_buf.append((str(data, 'utf-8'), is_partial)) def clear(self) -> None: self.wtcbuf = b'' self.iconbuf = self.colorbuf = self.ctbuf = '' self.titlebuf = [] - self.iutf8 = True self.notifications = [] self.open_urls = [] self.cc_buf = [] @@ -112,6 +108,7 @@ class Callbacks: print(text, file=sys.__stdout__, end='', flush=True) def handle_remote_clone(self, msg): + msg = str(msg, 'utf-8') if not msg: if self.current_clone_data: cdata, self.current_clone_data = self.current_clone_data, '' diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 63e562e06..c52ca5a32 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -9,13 +9,19 @@ from functools import partial from kitty.fast_data_types import CURSOR_BLOCK, base64_decode, base64_encode from kitty.notify import NotificationCommand, handle_notification_cmd, notification_activated, reset_registry -from . import BaseTest +from . import BaseTest, parse_bytes + + +def cnv(x): + if isinstance(x, memoryview): + x = str(x, 'utf-8') + return x class CmdDump(list): def __call__(self, *a): - self.append(a) + self.append(tuple(map(cnv, a))) class TestParser(BaseTest): @@ -24,7 +30,7 @@ class TestParser(BaseTest): cd = CmdDump() if isinstance(x, str): x = x.encode('utf-8') - cmds = tuple(('draw', x) if isinstance(x, str) else x for x in cmds) + cmds = tuple(('draw', x) if isinstance(x, str) else tuple(map(cnv, x)) for x in cmds) s.vt_parser.parse_bytes(s, x, cd) current = '' q = [] @@ -82,23 +88,6 @@ class TestParser(BaseTest): pb('\033c123', ('screen_reset', ), '123') self.ae(str(s.line(0)), '123') - def test_charsets(self): - s = self.create_screen() - pb = partial(self.parse_bytes_dump, s) - pb(b'\xc3') - pb(b'\xa1', ('draw', b'\xc3\xa1'.decode('utf-8'))) - s = self.create_screen() - pb = partial(self.parse_bytes_dump, s) - pb('\033)0\x0e/_', ('screen_designate_charset', 1, ord('0')), ('screen_change_charset', 1), '/_') - self.ae(str(s.line(0)), '/\xa0') - self.assertTrue(s.callbacks.iutf8) - pb('\033%@_', ('screen_use_latin1', 1), '_') - self.assertFalse(s.callbacks.iutf8) - s = self.create_screen() - pb = partial(self.parse_bytes_dump, s) - pb('\033(0/_', ('screen_designate_charset', 0, ord('0')), '/_') - self.ae(str(s.line(0)), '/\xa0') - def test_csi_codes(self): s = self.create_screen() pb = partial(self.parse_bytes_dump, s) @@ -218,7 +207,7 @@ class TestParser(BaseTest): s = self.create_screen() pb = partial(self.parse_bytes_dump, s) c = s.callbacks - pb('a\033]2;x\\ryz\x9cbcde', 'a', ('set_title', 'x\\ryz'), 'bcde') + pb('a\033]2;x\\ryz\033\\bcde', 'a', ('set_title', 'x\\ryz'), 'bcde') self.ae(str(s.line(0)), 'abcde') self.ae(c.titlebuf, ['x\\ryz']) c.clear() @@ -319,7 +308,7 @@ class TestParser(BaseTest): c = s.callbacks pb = partial(self.parse_bytes_dump, s) q = hexlify(b'kind').decode('ascii') - pb(f'a\033P+q{q}\x9cbcde', 'a', ('screen_request_capabilities', 43, q), 'bcde') + pb(f'a\033P+q{q}\033\\bcde', 'a', ('screen_request_capabilities', 43, q), 'bcde') self.ae(str(s.line(0)), 'abcde') self.ae(c.wtcbuf, '1+r{}={}'.format(q, '1b5b313b3242').encode('ascii')) c.clear() @@ -331,24 +320,13 @@ class TestParser(BaseTest): for sgr in '0;34;102;1;2;3;4 0;38:5:200;58:2:10:11:12'.split(): expected = set(sgr.split(';')) - {'0'} c.clear() - s.vte_parser.parse_bytes(s, f'\033[{sgr}m\033P$qm\033\\'.encode('ascii')) + parse_bytes(s, f'\033[{sgr}m\033P$qm\033\\'.encode('ascii')) r = c.wtcbuf.decode('ascii').partition('r')[2].partition('m')[0] self.ae(expected, set(r.split(';'))) c.clear() pb('\033P$qr\033\\', ('screen_request_capabilities', ord('$'), 'r')) self.ae(c.wtcbuf, f'\033P1$r{s.margin_top + 1};{s.margin_bottom + 1}r\033\\'.encode('ascii')) - def test_sc81t(self): - s = self.create_screen() - pb = partial(self.parse_bytes_dump, s) - pb('\033 G', ('screen_set_8bit_controls', 1)) - c = s.callbacks - pb('\033P$qm\033\\', ('screen_request_capabilities', ord('$'), 'm')) - self.ae(c.wtcbuf, b'\x901$rm\x9c') - c.clear() - pb('\033[0c', ('report_device_attributes', 0, 0)) - self.ae(c.wtcbuf, b'\x9b?62;c') - def test_pending(self): s = self.create_screen() timeout = 0.1 @@ -400,12 +378,8 @@ class TestParser(BaseTest): def test_oth_codes(self): s = self.create_screen() pb = partial(self.parse_bytes_dump, s) - for prefix in '\033_', '\u009f': - for suffix in '\u009c', '\033\\': - pb(f'a{prefix}+\\++{suffix}bcde', ('draw', 'a'), ('Unrecognized APC code: 0x2b',), ('draw', 'bcde')) - for prefix in '\033^', '\u009e': - for suffix in '\u009c', '\033\\': - pb(f'a{prefix}+\\++{suffix}bcde', ('draw', 'a'), ('Unrecognized PM code: 0x2b',), ('draw', 'bcde')) + pb('a\033_+\\+\033\\bcde', ('draw', 'a'), ('Unrecognized APC code: 0x2b',), ('draw', 'bcde')) + pb('a\033^+\\+\033\\bcde', ('draw', 'a'), ('Unrecognized PM code: 0x2b',), ('draw', 'bcde')) def test_graphics_command(self): from base64 import standard_b64encode diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 2dda83d37..2be7655d9 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -1188,7 +1188,7 @@ class TestScreen(BaseTest): def cb(data): nonlocal response - response = set_pointer_shape(s, data) + response = set_pointer_shape(s, str(data, 'utf-8')) c.set_pointer_shape = cb def send(a): diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index 526e59f03..ed4ad92ac 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -152,12 +152,12 @@ RPS1="{rps1}" pty.callbacks.clear() pty.send_cmd_to_child('printf "%s\x16\a%s" "a" "b"') pty.wait_till(lambda: 'ab' in pty.screen_contents()) - self.assertTrue(pty.screen.last_reported_cwd.endswith(self.home_dir)) + self.assertTrue(pty.screen.last_reported_cwd.decode().endswith(self.home_dir)) self.assertIn('%s^G%s', pty.screen_contents()) q = os.path.join(self.home_dir, 'testing-cwd-notification-🐱') os.mkdir(q) pty.send_cmd_to_child(f'cd {q}') - pty.wait_till(lambda: pty.screen.last_reported_cwd.endswith(q)) + pty.wait_till(lambda: pty.screen.last_reported_cwd.decode().endswith(q)) with self.run_shell(rc=f'''PS1="{ps1}"\nexport ES="a\n b c\nd"''') as pty: pty.callbacks.clear() pty.send_cmd_to_child('clone-in-kitty') @@ -188,13 +188,13 @@ function _set_status_prompt; function fish_prompt; echo -n "$pipestatus $status pty.wait_till(lambda: 'XDD_OK' in pty.screen_contents()) # CWD reporting - self.assertTrue(pty.screen.last_reported_cwd.endswith(self.home_dir)) + self.assertTrue(pty.screen.last_reported_cwd.decode().endswith(self.home_dir)) q = os.path.join(self.home_dir, 'testing-cwd-notification-🐱') os.mkdir(q) pty.send_cmd_to_child(f'cd {q}') - pty.wait_till(lambda: pty.screen.last_reported_cwd.endswith(q)) + pty.wait_till(lambda: pty.screen.last_reported_cwd.decode().endswith(q)) pty.send_cmd_to_child('cd -') - pty.wait_till(lambda: pty.screen.last_reported_cwd.endswith(self.home_dir)) + pty.wait_till(lambda: pty.screen.last_reported_cwd.decode().endswith(self.home_dir)) # completion and prompt marking pty.wait_till(lambda: 'cd -' not in pty.screen_contents().splitlines()[-1]) @@ -304,13 +304,13 @@ PS1="{ps1}" pty.send_cmd_to_child('printf "%s\x16\a%s" "a" "b"') pty.wait_till(lambda: pty.screen_contents().count(ps1) == 2) self.ae(pty.screen_contents(), f'{ps1}printf "%s^G%s" "a" "b"\nab{ps1}') - self.assertTrue(pty.screen.last_reported_cwd.endswith(self.home_dir)) + self.assertTrue(pty.screen.last_reported_cwd.decode().endswith(self.home_dir)) pty.send_cmd_to_child('echo $HISTFILE') pty.wait_till(lambda: '.bash_history' in pty.screen_contents()) q = os.path.join(self.home_dir, 'testing-cwd-notification-🐱') os.mkdir(q) pty.send_cmd_to_child(f'cd {q}') - pty.wait_till(lambda: pty.screen.last_reported_cwd.endswith(q)) + pty.wait_till(lambda: pty.screen.last_reported_cwd.decode().endswith(q)) for ps1 in ('line1\\nline\\2\\prompt> ', 'line1\nprompt> ', 'line1\\nprompt> ',): with self.subTest(ps1=ps1), self.run_shell( diff --git a/tools/tui/loop/terminal-state.go b/tools/tui/loop/terminal-state.go index a7087a200..645105854 100644 --- a/tools/tui/loop/terminal-state.go +++ b/tools/tui/loop/terminal-state.go @@ -23,7 +23,6 @@ const ( const ( SAVE_CURSOR = "\0337" RESTORE_CURSOR = "\0338" - S7C1T = "\033 F" SAVE_PRIVATE_MODE_VALUES = "\033[?s" RESTORE_PRIVATE_MODE_VALUES = "\033[?r" SAVE_COLORS = "\033[#P" @@ -115,7 +114,6 @@ func reset_modes(sb *strings.Builder, modes ...Mode) { func (self *TerminalStateOptions) SetStateEscapeCodes() string { var sb strings.Builder sb.Grow(256) - sb.WriteString(S7C1T) if self.alternate_screen { sb.WriteString(SAVE_CURSOR) }