2019-05-20 13:38:06 +03:00
|
|
|
#!/usr/bin/env python3
|
2018-02-04 17:53:52 +03:00
|
|
|
# vim:fileencoding=utf-8
|
|
|
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
|
2018-03-23 15:33:43 +03:00
|
|
|
import sys
|
2018-02-10 04:10:54 +03:00
|
|
|
from contextlib import contextmanager
|
2018-05-06 13:04:11 +03:00
|
|
|
from functools import wraps
|
2020-03-10 17:55:09 +03:00
|
|
|
from typing import (
|
2020-03-15 10:57:40 +03:00
|
|
|
IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union
|
2020-03-10 17:55:09 +03:00
|
|
|
)
|
2018-02-10 04:10:54 +03:00
|
|
|
|
2018-04-24 08:03:18 +03:00
|
|
|
from kitty.rgb import Color, color_as_sharp, to_color
|
2020-03-15 10:57:40 +03:00
|
|
|
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize
|
2018-02-08 08:30:57 +03:00
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
from .operations_stub import CMD
|
|
|
|
|
2020-03-15 10:57:40 +03:00
|
|
|
GraphicsCommandType, ScreenSize # needed for stub generation
|
2018-02-10 10:27:57 +03:00
|
|
|
S7C1T = '\033 F'
|
|
|
|
SAVE_CURSOR = '\0337'
|
|
|
|
RESTORE_CURSOR = '\0338'
|
|
|
|
SAVE_PRIVATE_MODE_VALUES = '\033[?s'
|
|
|
|
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
|
2018-02-04 17:53:52 +03:00
|
|
|
|
|
|
|
MODES = dict(
|
|
|
|
LNM=(20, ''),
|
|
|
|
IRM=(4, ''),
|
|
|
|
DECKM=(1, '?'),
|
|
|
|
DECSCNM=(5, '?'),
|
|
|
|
DECOM=(6, '?'),
|
2018-02-13 08:20:06 +03:00
|
|
|
DECAWM=(7, '?'),
|
2018-02-04 17:53:52 +03:00
|
|
|
DECARM=(8, '?'),
|
|
|
|
DECTCEM=(25, '?'),
|
|
|
|
MOUSE_BUTTON_TRACKING=(1000, '?'),
|
|
|
|
MOUSE_MOTION_TRACKING=(1002, '?'),
|
|
|
|
MOUSE_MOVE_TRACKING=(1003, '?'),
|
|
|
|
FOCUS_TRACKING=(1004, '?'),
|
|
|
|
MOUSE_UTF8_MODE=(1005, '?'),
|
|
|
|
MOUSE_SGR_MODE=(1006, '?'),
|
|
|
|
MOUSE_URXVT_MODE=(1015, '?'),
|
|
|
|
ALTERNATE_SCREEN=(1049, '?'),
|
|
|
|
BRACKETED_PASTE=(2004, '?'),
|
|
|
|
EXTENDED_KEYBOARD=(2017, '?'),
|
|
|
|
)
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
F = TypeVar('F')
|
|
|
|
all_cmds: Dict[str, Callable] = {}
|
|
|
|
|
|
|
|
|
|
|
|
def cmd(f: F) -> F:
|
|
|
|
all_cmds[f.__name__] = f # type: ignore
|
|
|
|
return f
|
|
|
|
|
2018-02-04 17:53:52 +03:00
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def set_mode(which: str, private: bool = True) -> str:
|
|
|
|
num, private_ = MODES[which]
|
|
|
|
return '\033[{}{}h'.format(private_, num)
|
2018-02-04 17:53:52 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-04 05:01:22 +03:00
|
|
|
def reset_mode(which: str) -> str:
|
2018-02-04 17:53:52 +03:00
|
|
|
num, private = MODES[which]
|
2018-02-10 10:27:57 +03:00
|
|
|
return '\033[{}{}l'.format(private, num)
|
2018-02-04 17:53:52 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2018-05-06 13:04:11 +03:00
|
|
|
def clear_screen() -> str:
|
|
|
|
return '\033[H\033[2J'
|
2018-05-06 12:08:46 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2018-05-06 13:04:11 +03:00
|
|
|
def clear_to_eol() -> str:
|
|
|
|
return '\033[K'
|
2018-05-06 12:08:46 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2018-05-06 13:04:11 +03:00
|
|
|
def bell() -> str:
|
2018-05-06 12:08:46 +03:00
|
|
|
return '\a'
|
2018-02-08 08:30:57 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2018-05-17 20:56:41 +03:00
|
|
|
def beep() -> str:
|
|
|
|
return '\a'
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-05 17:27:28 +03:00
|
|
|
def set_window_title(value: str) -> str:
|
|
|
|
return '\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\'
|
2018-02-08 19:46:39 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-04 05:01:22 +03:00
|
|
|
def set_line_wrapping(yes_or_no: bool) -> str:
|
|
|
|
return set_mode('DECAWM') if yes_or_no else reset_mode('DECAWM')
|
2018-02-08 19:46:39 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-04 05:01:22 +03:00
|
|
|
def set_cursor_visible(yes_or_no: bool) -> str:
|
2020-03-04 05:19:28 +03:00
|
|
|
return set_mode('DECTCEM') if yes_or_no else reset_mode('DECTCEM')
|
2018-02-14 10:04:01 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def set_cursor_position(x: int, y: int) -> str: # (0, 0) is top left
|
2018-05-06 12:08:46 +03:00
|
|
|
return '\033[{};{}H'.format(y + 1, x + 1)
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str:
|
2018-06-15 11:58:42 +03:00
|
|
|
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
|
|
|
if not blink:
|
|
|
|
val += 1
|
|
|
|
return '\033[{} q'.format(val)
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def set_scrolling_region(screen_size: Optional['ScreenSize'] = None, top: Optional[int] = None, bottom: Optional[int] = None) -> str:
|
2018-05-10 13:50:58 +03:00
|
|
|
if screen_size is None:
|
|
|
|
return '\033[r'
|
2018-05-06 12:08:46 +03:00
|
|
|
if top is None:
|
|
|
|
top = 0
|
|
|
|
if bottom is None:
|
|
|
|
bottom = screen_size.rows - 1
|
|
|
|
if bottom < 0:
|
|
|
|
bottom = screen_size.rows - 1 + bottom
|
|
|
|
else:
|
|
|
|
bottom += 1
|
|
|
|
return '\033[{};{}r'.format(top + 1, bottom + 1)
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def scroll_screen(amt: int = 1) -> str:
|
2018-05-06 12:08:46 +03:00
|
|
|
return '\033[' + str(abs(amt)) + ('T' if amt < 0 else 'S')
|
|
|
|
|
|
|
|
|
2018-02-08 19:46:39 +03:00
|
|
|
STANDARD_COLORS = {name: i for i, name in enumerate(
|
|
|
|
'black red green yellow blue magenta cyan gray'.split())}
|
2018-05-17 20:56:41 +03:00
|
|
|
STANDARD_COLORS['white'] = STANDARD_COLORS['gray']
|
2018-02-10 04:10:54 +03:00
|
|
|
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
|
|
|
|
'straight double curly'.split())}
|
2018-02-08 19:46:39 +03:00
|
|
|
|
|
|
|
|
2020-03-10 17:44:04 +03:00
|
|
|
ColorSpec = Union[int, str, Tuple[int, int, int]]
|
|
|
|
|
|
|
|
|
|
|
|
def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str:
|
2018-02-08 19:46:39 +03:00
|
|
|
if isinstance(color, str):
|
2018-02-10 04:10:54 +03:00
|
|
|
e = str((base + 60 if intense else base) + STANDARD_COLORS[color])
|
2018-02-08 19:46:39 +03:00
|
|
|
elif isinstance(color, int):
|
2018-02-10 04:10:54 +03:00
|
|
|
e = '{}:5:{}'.format(base + 8, max(0, min(color, 255)))
|
2018-02-08 19:46:39 +03:00
|
|
|
else:
|
2018-02-10 04:10:54 +03:00
|
|
|
e = '{}:2:{}:{}:{}'.format(base + 8, *color)
|
|
|
|
return e
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def sgr(*parts: str) -> str:
|
2018-02-12 11:38:54 +03:00
|
|
|
return '\033[{}m'.format(';'.join(parts))
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def colored(
|
|
|
|
text: str,
|
|
|
|
color: ColorSpec,
|
|
|
|
intense: bool = False,
|
|
|
|
reset_to: Optional[ColorSpec] = None,
|
|
|
|
reset_to_intense: bool = False
|
|
|
|
) -> str:
|
2018-02-12 11:38:54 +03:00
|
|
|
e = color_code(color, intense)
|
|
|
|
return '\033[{}m{}\033[{}m'.format(e, text, 39 if reset_to is None else color_code(reset_to, reset_to_intense))
|
2018-02-08 19:46:39 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def faint(text: str) -> str:
|
2018-02-14 08:49:58 +03:00
|
|
|
return colored(text, 'black', True)
|
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def styled(
|
|
|
|
text: str,
|
|
|
|
fg: Optional[ColorSpec] = None,
|
|
|
|
bg: Optional[ColorSpec] = None,
|
|
|
|
fg_intense: bool = False,
|
|
|
|
bg_intense: bool = False,
|
|
|
|
italic: Optional[bool] = None,
|
|
|
|
bold: Optional[bool] = None,
|
|
|
|
underline: Optional[str] = None,
|
|
|
|
underline_color: Optional[ColorSpec] = None,
|
|
|
|
reverse: Optional[bool] = None
|
|
|
|
) -> str:
|
2018-02-10 04:10:54 +03:00
|
|
|
start, end = [], []
|
|
|
|
if fg is not None:
|
2018-02-12 11:38:54 +03:00
|
|
|
start.append(color_code(fg, fg_intense))
|
2018-02-10 04:10:54 +03:00
|
|
|
end.append('39')
|
|
|
|
if bg is not None:
|
2018-02-12 11:38:54 +03:00
|
|
|
start.append(color_code(bg, bg_intense, 40))
|
2018-02-10 04:10:54 +03:00
|
|
|
end.append('49')
|
|
|
|
if underline_color is not None:
|
|
|
|
if isinstance(underline_color, str):
|
|
|
|
underline_color = STANDARD_COLORS[underline_color]
|
2018-02-12 11:38:54 +03:00
|
|
|
start.append(color_code(underline_color, base=50))
|
2018-02-10 04:10:54 +03:00
|
|
|
end.append('59')
|
|
|
|
if underline is not None:
|
|
|
|
start.append('4:{}'.format(UNDERLINE_STYLES[underline]))
|
|
|
|
end.append('4:0')
|
2018-02-10 10:27:57 +03:00
|
|
|
if italic is not None:
|
|
|
|
s, e = (start, end) if italic else (end, start)
|
2020-03-04 05:01:22 +03:00
|
|
|
s.append('3')
|
|
|
|
e.append('23')
|
2018-02-10 10:27:57 +03:00
|
|
|
if bold is not None:
|
|
|
|
s, e = (start, end) if bold else (end, start)
|
2020-03-04 05:01:22 +03:00
|
|
|
s.append('1')
|
|
|
|
e.append('22')
|
2018-02-10 10:27:57 +03:00
|
|
|
if reverse is not None:
|
|
|
|
s, e = (start, end) if reverse else (end, start)
|
2020-03-04 05:01:22 +03:00
|
|
|
s.append('7')
|
|
|
|
e.append('27')
|
2018-02-10 04:10:54 +03:00
|
|
|
if not start:
|
|
|
|
return text
|
|
|
|
return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end))
|
|
|
|
|
|
|
|
|
2020-03-10 17:44:04 +03:00
|
|
|
def serialize_gr_command(cmd: Dict[str, Union[int, str]], payload: Optional[bytes] = None) -> bytes:
|
|
|
|
from .images import GraphicsCommand
|
|
|
|
gc = GraphicsCommand()
|
|
|
|
for k, v in cmd.items():
|
|
|
|
setattr(gc, k, v)
|
|
|
|
return gc.serialize(payload or b'')
|
2018-05-09 11:36:15 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-15 10:57:40 +03:00
|
|
|
def gr_command(cmd: Union[Dict, 'GraphicsCommandType'], payload: Optional[bytes] = None) -> str:
|
2020-03-10 17:44:04 +03:00
|
|
|
if isinstance(cmd, dict):
|
|
|
|
raw = serialize_gr_command(cmd, payload)
|
|
|
|
else:
|
|
|
|
raw = cmd.serialize(payload or b'')
|
|
|
|
return raw.decode('ascii')
|
2018-05-10 12:57:35 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:44:04 +03:00
|
|
|
def clear_images_on_screen(delete_data: bool = False) -> str:
|
|
|
|
from .images import GraphicsCommand
|
|
|
|
gc = GraphicsCommand()
|
|
|
|
gc.a = 'd'
|
|
|
|
gc.d = 'A' if delete_data else 'a'
|
|
|
|
return gc.serialize().decode('ascii')
|
2018-05-09 11:36:15 +03:00
|
|
|
|
|
|
|
|
2020-03-10 17:55:09 +03:00
|
|
|
def init_state(alternate_screen: bool = True) -> str:
|
2018-02-04 17:53:52 +03:00
|
|
|
ans = (
|
|
|
|
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
|
|
|
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
|
|
|
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
|
|
|
|
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
2018-06-14 20:55:13 +03:00
|
|
|
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
|
|
|
|
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
2018-02-04 17:53:52 +03:00
|
|
|
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
|
2018-02-10 10:27:57 +03:00
|
|
|
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
|
2018-06-15 12:51:46 +03:00
|
|
|
'\033]30001\033\\' +
|
2018-02-10 10:27:57 +03:00
|
|
|
'\033[*x' # reset DECSACE to default region select
|
2018-02-04 17:53:52 +03:00
|
|
|
)
|
|
|
|
if alternate_screen:
|
|
|
|
ans += set_mode('ALTERNATE_SCREEN')
|
2018-02-08 08:30:57 +03:00
|
|
|
ans += clear_screen()
|
2018-02-04 17:53:52 +03:00
|
|
|
return ans
|
|
|
|
|
|
|
|
|
2020-03-10 17:55:09 +03:00
|
|
|
def reset_state(normal_screen: bool = True) -> str:
|
2018-02-10 10:27:57 +03:00
|
|
|
ans = ''
|
2018-02-04 17:53:52 +03:00
|
|
|
if normal_screen:
|
|
|
|
ans += reset_mode('ALTERNATE_SCREEN')
|
|
|
|
ans += RESTORE_PRIVATE_MODE_VALUES
|
|
|
|
ans += RESTORE_CURSOR
|
2018-06-15 12:51:46 +03:00
|
|
|
ans += '\033]30101\033\\'
|
2018-02-04 17:53:52 +03:00
|
|
|
return ans
|
2018-02-10 04:10:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
2020-03-10 17:55:09 +03:00
|
|
|
def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
|
2018-02-10 04:10:54 +03:00
|
|
|
write(SAVE_CURSOR)
|
|
|
|
yield
|
|
|
|
write(RESTORE_CURSOR)
|
2018-03-23 15:33:43 +03:00
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
2020-03-10 17:55:09 +03:00
|
|
|
def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]:
|
2018-03-23 15:33:43 +03:00
|
|
|
f = f or sys.stdout
|
|
|
|
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
|
|
|
|
yield
|
|
|
|
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
2018-04-22 19:14:47 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:55:09 +03:00
|
|
|
def set_default_colors(
|
|
|
|
fg: Optional[Union[Color, str]] = None,
|
|
|
|
bg: Optional[Union[Color, str]] = None,
|
|
|
|
cursor: Optional[Union[Color, str]] = None,
|
|
|
|
select_bg: Optional[Union[Color, str]] = None,
|
|
|
|
select_fg: Optional[Union[Color, str]] = None
|
|
|
|
) -> str:
|
2018-04-22 19:14:47 +03:00
|
|
|
ans = ''
|
2018-06-15 12:13:39 +03:00
|
|
|
|
2020-03-08 19:38:18 +03:00
|
|
|
def item(which: Optional[Union[Color, str]], num: int) -> None:
|
2018-06-15 12:13:39 +03:00
|
|
|
nonlocal ans
|
2018-06-15 12:18:59 +03:00
|
|
|
if which is None:
|
2018-06-15 12:13:39 +03:00
|
|
|
ans += '\x1b]1{}\x1b\\'.format(num)
|
|
|
|
else:
|
2020-03-08 19:38:18 +03:00
|
|
|
if isinstance(which, Color):
|
|
|
|
q = color_as_sharp(which)
|
|
|
|
else:
|
|
|
|
x = to_color(which)
|
|
|
|
assert x is not None
|
|
|
|
q = color_as_sharp(x)
|
|
|
|
ans += '\x1b]{};{}\x1b\\'.format(num, q)
|
2018-06-15 12:13:39 +03:00
|
|
|
|
|
|
|
item(fg, 10)
|
|
|
|
item(bg, 11)
|
|
|
|
item(cursor, 12)
|
|
|
|
item(select_bg, 17)
|
|
|
|
item(select_fg, 19)
|
2018-04-22 19:14:47 +03:00
|
|
|
return ans
|
2018-05-06 13:04:11 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:55:09 +03:00
|
|
|
def write_to_clipboard(data: Union[str, bytes], use_primary: bool = False) -> str:
|
2018-05-21 20:54:27 +03:00
|
|
|
if isinstance(data, str):
|
|
|
|
data = data.encode('utf-8')
|
|
|
|
from base64 import standard_b64encode
|
2018-05-28 19:20:42 +03:00
|
|
|
fmt = 'p' if use_primary else 'c'
|
|
|
|
|
2020-03-05 17:27:28 +03:00
|
|
|
def esc(chunk: str) -> str:
|
2018-05-28 19:20:42 +03:00
|
|
|
return '\x1b]52;{};{}\x07'.format(fmt, chunk)
|
2020-03-05 17:27:28 +03:00
|
|
|
|
2018-05-28 19:20:42 +03:00
|
|
|
ans = esc('!') # clear clipboard buffer
|
|
|
|
for chunk in (data[i:i+512] for i in range(0, len(data), 512)):
|
2020-03-08 19:38:18 +03:00
|
|
|
s = standard_b64encode(chunk).decode('ascii')
|
|
|
|
ans += esc(s)
|
2018-05-28 19:20:42 +03:00
|
|
|
return ans
|
2018-05-21 20:54:27 +03:00
|
|
|
|
|
|
|
|
2020-03-10 19:55:57 +03:00
|
|
|
@cmd
|
2020-03-10 17:55:09 +03:00
|
|
|
def request_from_clipboard(use_primary: bool = False) -> str:
|
2018-05-21 20:54:27 +03:00
|
|
|
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
|
|
|
|
|
|
|
|
|
2020-03-11 07:05:59 +03:00
|
|
|
# Boilerplate to make operations availble via Handler.cmd {{{
|
|
|
|
|
|
|
|
|
2020-03-15 10:57:40 +03:00
|
|
|
def writer(handler: HandlerType, func: Callable) -> Callable:
|
2018-05-06 13:04:11 +03:00
|
|
|
@wraps(func)
|
2020-03-10 19:55:57 +03:00
|
|
|
def f(*a: Any, **kw: Any) -> None:
|
2018-05-06 13:04:11 +03:00
|
|
|
handler.write(func(*a, **kw))
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
2020-03-15 10:57:40 +03:00
|
|
|
def commander(handler: HandlerType) -> CMD:
|
2020-03-10 19:55:57 +03:00
|
|
|
ans = CMD()
|
|
|
|
for name, func in all_cmds.items():
|
|
|
|
setattr(ans, name, writer(handler, func))
|
|
|
|
return ans
|
|
|
|
|
|
|
|
|
|
|
|
def func_sig(func: Callable) -> Generator[str, None, None]:
|
|
|
|
import inspect
|
|
|
|
import re
|
|
|
|
s = inspect.signature(func)
|
|
|
|
for val in s.parameters.values():
|
|
|
|
yield re.sub(r'ForwardRef\([\'"](\w+?)[\'"]\)', r'\1', str(val).replace('NoneType', 'None'))
|
|
|
|
|
|
|
|
|
|
|
|
def as_type_stub() -> str:
|
|
|
|
ans = [
|
|
|
|
'from typing import * # noqa',
|
2020-03-15 10:57:40 +03:00
|
|
|
'from kitty.typing import GraphicsCommandType, ScreenSize',
|
2020-03-11 07:05:59 +03:00
|
|
|
'from kitty.rgb import Color',
|
|
|
|
'import kitty.rgb',
|
2020-03-10 19:55:57 +03:00
|
|
|
]
|
|
|
|
methods = []
|
|
|
|
for name, func in all_cmds.items():
|
|
|
|
args = ', '.join(func_sig(func))
|
|
|
|
if args:
|
|
|
|
args = ', ' + args
|
|
|
|
methods.append(' def {}(self{}) -> str: pass'.format(name, args))
|
|
|
|
ans += ['', '', 'class CMD:'] + methods
|
|
|
|
|
|
|
|
return '\n'.join(ans) + '\n\n\n'
|
2020-03-11 07:05:59 +03:00
|
|
|
# }}}
|