Skip to content

Commit

Permalink
Implement reporting of multicell commands
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Dec 10, 2024
1 parent 19d18d9 commit 067fb24
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 34 deletions.
11 changes: 7 additions & 4 deletions gen/apc_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def parse_number(keymap: KeymapType) -> tuple[str, str]:
return '; '.join(int_keys), '; '.join(uint_keys)


def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool) -> str:
def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool, payload_is_base64: bool) -> str:
def group(atype: str, conv: str) -> tuple[str, str]:
flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
Expand All @@ -85,7 +85,7 @@ def group(atype: str, conv: str) -> tuple[str, str]:

fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
if payload_allowed:
ans = [f'REPORT_VA_COMMAND("K s {{{fmt} sI}} y#", self->window_id, "{report_name}",\n']
ans = [f'REPORT_VA_COMMAND("K s {{{fmt} ss#}}", self->window_id, "{report_name}",\n']
else:
ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n']
if flag_attrs:
Expand All @@ -95,7 +95,10 @@ def group(atype: str, conv: str) -> tuple[str, str]:
if int_attrs:
ans.append(f'{int_attrs},\n')
if payload_allowed:
ans.append('"payload_sz", g.payload_sz, parser_buf, g.payload_sz')
if payload_is_base64:
ans.append('"", (char*)parser_buf, g.payload_sz')
else:
ans.append('"", (char*)parser_buf + payload_start, g.payload_sz')
ans.append(');')
return '\n'.join(ans)

Expand All @@ -117,7 +120,7 @@ def generate(
handle_key = parse_key(keymap)
flag_keys = parse_flag(keymap, type_map, command_class)
int_keys, uint_keys = parse_number(keymap)
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed, payload_is_base64)
extra_init = ''
if payload_allowed:
payload_after_value = "case ';': state = PAYLOAD; break;"
Expand Down
9 changes: 7 additions & 2 deletions kitty/boss.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,13 @@ def __call__(self, window_id: int, what: str, *a: Any) -> None:
if self.draw_dump_buf:
safe_print('draw', ''.join(self.draw_dump_buf))
self.draw_dump_buf = []
a = tuple(str(x, 'utf-8', 'replace') if isinstance(x, memoryview) else x for x in a)
safe_print(what, *a)
def fmt(x: Any) -> Any:
if isinstance(x, (bytes, memoryview)):
return str(x, 'utf-8', 'replace')
if isinstance(x, dict):
return json.dumps(x)
return x
safe_print(what, *map(fmt, a))
# }}}


Expand Down
30 changes: 24 additions & 6 deletions kitty/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
# kitty --replay-commands file.txt
# will replay the commands and pause at the end waiting for user to press enter

import json
import sys
from contextlib import suppress
from typing import Any

from .fast_data_types import TEXT_SIZE_CODE

CSI = '\x1b['
OSC = '\x1b]'

Expand Down Expand Up @@ -74,8 +77,8 @@ def screen_designate_charset(which: int, to: int) -> None:
write(f'\x1b{w}{t}')


def select_graphic_rendition(*a: int) -> None:
write(f'{CSI}{";".join(map(str, a))}m')
def select_graphic_rendition(payload: str) -> None:
write(f'{CSI}{payload}m')


def deccara(*a: int) -> None:
Expand Down Expand Up @@ -267,11 +270,26 @@ def clipboard_control(payload: str) -> None:
write(f'{OSC}{payload}\x07')


def multicell_command(payload: str) -> None:
c = json.loads(payload)
text = c.pop('', '')
m = ''
if (w := c.get('width')) is not None and w > 0:
m += f'w={w}:'
if (s := c.get('scale')) is not None and s > 1:
m += f's={s}:'
if (n := c.get('subscale_n')) is not None and n > 0:
m += f'n={n}:'
if (d := c.get('subscale_d')) is not None and d > 0:
m += f'd={d}:'
write(f'{OSC}{TEXT_SIZE_CODE};{m.rstrip(":")};{text}\a')


def replay(raw: str) -> None:
specials = {
'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color',
'process_cwd_notification', 'clipboard_control', 'shell_prompt_marking'
}
specials = frozenset({
'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color', 'select_graphic_rendition',
'process_cwd_notification', 'clipboard_control', 'shell_prompt_marking', 'multicell_command',
})
for line in raw.splitlines():
if line.strip() and not line.startswith('#'):
cmd, rest = line.partition(' ')[::2]
Expand Down
1 change: 1 addition & 0 deletions kitty/fast_data_types.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ WINDOW_NORMAL: int = 0
WINDOW_FULLSCREEN: int
WINDOW_MAXIMIZED: int
WINDOW_MINIMIZED: int
TEXT_SIZE_CODE: int
# }}}


Expand Down
2 changes: 0 additions & 2 deletions kitty/line.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@

// TODO: Test setting of ch_and_idx to make sure the right ch_is_idx bit is set
// TODO: Test handling of calt ligatures with scale see is_group_calt_ligature()
// TODO: Font Rendering of scale > 1 and width > 1
// TODO: Handle selection with multicell
// TODO: URL detection with multicell
// TODO: Cursor rendering over multicell
// TODO: Handle replay of dumped graphics_command and multicell_command
// TODO: Handle rewrap and restitch of multiline chars
// TODO: Handle rewrap when a character is too wide/tall to fit on resized screen
// TODO: Implement baseline align for box drawing
Expand Down
4 changes: 2 additions & 2 deletions kitty/parse-graphics-command.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions kitty/parse-multicell-command.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions kitty/vt-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ static void
_report_params(PyObject *dump_callback, id_type window_id, const char *name, int *params, unsigned int count, bool is_group, Region *r) {
static char buf[MAX_CSI_PARAMS*3] = {0};
unsigned int i, p=0;
if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right);
const char *fmt = is_group ? "%i:" : "%i ";
if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u;%u;%u;%u;", r->top, r->left, r->bottom, r->right);
const char *fmt = is_group ? "%i:" : "%i;";
for(i = 0; i < count && p < arraysz(buf)-20; i++) {
int n = snprintf(buf + p, arraysz(buf) - p, fmt, params[i]);
if (n < 0) break;
Expand Down
28 changes: 14 additions & 14 deletions kitty_tests/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def test_csi_codes(self):
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'))
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), attr)
self.ae(s.cursor.decoration, 1)
Expand All @@ -397,18 +397,18 @@ def sgr(*params):
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[0;2m', *sgr('0;2'))
pb('\033[;2m', *sgr('0;2'))
pb('\033[m', *sgr('0'))
pb('\033[1;;2m', *sgr('1 0 2'))
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: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'))
s.reset()
pb('\033[1;2;3;4:5;7;9;34;44m', *sgr('1 2 3', '4:5', '7 9 34 44'))
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)
Expand Down Expand Up @@ -584,9 +584,9 @@ def c(**k):
' parent_id parent_placement_id offset_from_parent_x offset_from_parent_y'
).split():
k.setdefault(f, 0)
p = k.pop('payload', '').encode('utf-8')
k['payload_sz'] = len(p)
return ('graphics_command', k, p)
p = k.pop('payload', '')
k[''] = p
return ('graphics_command', k)

def t(cmd, **kw):
pb('\033_G{};{}\033\\'.format(cmd, enc(kw.get('payload', ''))), c(**kw))
Expand All @@ -601,8 +601,8 @@ def e(cmd, err):
t('i=3,p=4', id=3, placement_id=4)
e('i=%d' % (uint32_max + 1), 'Malformed GraphicsCommand control block, number is too large')
pb('\033_Gi=12\033\\', c(id=12))
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1)
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7)
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9)
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9)
t('a=t,t=d,s=100,z=9,q=2', action='t', transmission_type='d', data_width=100, z_index=9, quiet=2)
e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c')
e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
Expand All @@ -616,9 +616,9 @@ def e(cmd, err):
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):
Expand All @@ -629,7 +629,7 @@ def test_deccara(self):
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 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):
Expand All @@ -641,7 +641,7 @@ def test_deccara(self):
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):
Expand Down

0 comments on commit 067fb24

Please sign in to comment.