mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 01:05:48 +02:00
Implement reporting of multicell commands
This commit is contained in:
@@ -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]
|
||||
@@ -85,7 +85,7 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any
|
||||
|
||||
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:
|
||||
@@ -95,7 +95,10 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any
|
||||
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)
|
||||
|
||||
@@ -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;"
|
||||
|
||||
@@ -268,8 +268,13 @@ class DumpCommands: # {{{
|
||||
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))
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
@@ -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]'
|
||||
|
||||
@@ -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:
|
||||
@@ -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]
|
||||
|
||||
@@ -308,6 +308,7 @@ WINDOW_NORMAL: int = 0
|
||||
WINDOW_FULLSCREEN: int
|
||||
WINDOW_MAXIMIZED: int
|
||||
WINDOW_MINIMIZED: int
|
||||
TEXT_SIZE_CODE: int
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
4
kitty/parse-graphics-command.h
generated
4
kitty/parse-graphics-command.h
generated
@@ -359,7 +359,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
|
||||
|
||||
REPORT_VA_COMMAND(
|
||||
"K s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI "
|
||||
"sI sI sI sI si si si sI} y#",
|
||||
"sI sI sI sI si si si ss#}",
|
||||
self->window_id, "graphics_command",
|
||||
|
||||
"action", g.action, "delete_action", g.delete_action, "transmission_type",
|
||||
@@ -384,7 +384,7 @@ static inline void parse_graphics_code(PS *self, uint8_t *parser_buf,
|
||||
(int)g.offset_from_parent_x, "offset_from_parent_y",
|
||||
(int)g.offset_from_parent_y,
|
||||
|
||||
"payload_sz", g.payload_sz, parser_buf, g.payload_sz);
|
||||
"", (char *)parser_buf, g.payload_sz);
|
||||
|
||||
screen_handle_graphics_command(self->screen, &g, parser_buf);
|
||||
}
|
||||
|
||||
4
kitty/parse-multicell-command.h
generated
4
kitty/parse-multicell-command.h
generated
@@ -183,14 +183,14 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf,
|
||||
}
|
||||
|
||||
REPORT_VA_COMMAND(
|
||||
"K s { sI sI sI sI sI sI} y#", self->window_id, "multicell_command",
|
||||
"K s { sI sI sI sI sI ss#}", self->window_id, "multicell_command",
|
||||
|
||||
"width", (unsigned int)g.width, "scale", (unsigned int)g.scale,
|
||||
"subscale_n", (unsigned int)g.subscale_n, "subscale_d",
|
||||
(unsigned int)g.subscale_d, "vertical_align",
|
||||
(unsigned int)g.vertical_align,
|
||||
|
||||
"payload_sz", g.payload_sz, parser_buf, g.payload_sz);
|
||||
"", (char *)parser_buf + payload_start, g.payload_sz);
|
||||
|
||||
screen_handle_multicell_command(self->screen, &g, parser_buf + payload_start);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -385,7 +385,7 @@ class TestParser(BaseTest):
|
||||
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)
|
||||
@@ -397,10 +397,10 @@ class TestParser(BaseTest):
|
||||
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'))
|
||||
@@ -408,7 +408,7 @@ class TestParser(BaseTest):
|
||||
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)
|
||||
@@ -584,9 +584,9 @@ class TestParser(BaseTest):
|
||||
' 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))
|
||||
@@ -601,8 +601,8 @@ class TestParser(BaseTest):
|
||||
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')
|
||||
@@ -616,9 +616,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):
|
||||
@@ -629,7 +629,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 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):
|
||||
@@ -641,7 +641,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):
|
||||
|
||||
Reference in New Issue
Block a user