Use a stub rather than TYPE_CHECKING

This commit is contained in:
Kovid Goyal 2020-03-15 13:27:40 +05:30
parent 871ca4dda6
commit 382c31ddf2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
27 changed files with 267 additions and 326 deletions

View File

@ -9,13 +9,13 @@ from typing import TYPE_CHECKING, List, Optional, Tuple
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import AskCLIOptions from kitty.cli_stub import AskCLIOptions
from kitty.constants import cache_dir from kitty.constants import cache_dir
from kitty.typing import BossType
from ..tui.handler import result_handler from ..tui.handler import result_handler
from ..tui.operations import alternate_screen, styled from ..tui.operations import alternate_screen, styled
if TYPE_CHECKING: if TYPE_CHECKING:
import readline import readline
import kitty
else: else:
readline = None readline = None
@ -127,7 +127,7 @@ def main(args: List[str]) -> Response:
@result_handler() @result_handler()
def handle_result(args: List[str], data: Response, target_window_id: int, boss: 'kitty.boss.Boss') -> None: def handle_result(args: List[str], data: Response, target_window_id: int, boss: BossType) -> None:
if data['response'] is not None: if data['response'] is not None:
func, *args = data['items'] func, *args = data['items']
getattr(boss, func)(data['response'], *args) getattr(boss, func)(data['response'], *args)

View File

@ -10,8 +10,8 @@ from functools import lru_cache
from gettext import gettext as _ from gettext import gettext as _
from itertools import repeat from itertools import repeat
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Optional, Any, Callable, Dict, Generator, Iterable, List, Optional, Pattern,
Pattern, Sequence, Set, Tuple, Type, cast Sequence, Set, Tuple, Type, cast
) )
from kitty.cli import parse_args from kitty.cli import parse_args
@ -20,30 +20,26 @@ from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import ( from kitty.key_encoding import (
KeyEvent, backspace_key, enter_key, key_defs as K KeyEvent, backspace_key, enter_key, key_defs as K
) )
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import ScreenSize, screen_size_function from kitty.utils import ScreenSize, screen_size_function
from ..tui.handler import Handler, result_handler from ..tui.handler import Handler, result_handler
from ..tui.loop import Loop from ..tui.loop import Loop
from ..tui.operations import faint, styled from ..tui.operations import faint, styled
if TYPE_CHECKING:
from kitty.config import KittyCommonOpts
from kitty.boss import Boss
@lru_cache() @lru_cache()
def kitty_common_opts() -> 'KittyCommonOpts': def kitty_common_opts() -> KittyCommonOpts:
import json import json
v = os.environ.get('KITTY_COMMON_OPTS') v = os.environ.get('KITTY_COMMON_OPTS')
if v: if v:
return cast('KittyCommonOpts', json.loads(v)) return cast(KittyCommonOpts, json.loads(v))
from kitty.config import common_opts_as_dict from kitty.config import common_opts_as_dict
return common_opts_as_dict() return common_opts_as_dict()
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$' DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
screen_size = screen_size_function()
ESCAPE = K['ESCAPE'] ESCAPE = K['ESCAPE']
@ -346,7 +342,7 @@ def parse_input(text: str) -> str:
try: try:
cols = int(os.environ['OVERLAID_WINDOW_COLS']) cols = int(os.environ['OVERLAID_WINDOW_COLS'])
except KeyError: except KeyError:
cols = screen_size().cols cols = screen_size_function()().cols
return convert_text(text, cols) return convert_text(text, cols)
@ -560,7 +556,7 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
return run(opts, text, items) return run(opts, text, items)
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: 'Boss', extra_cli_args: Sequence[str], *a: Any) -> None: def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
for m, g in zip(data['match'], data['groupdicts']): for m, g in zip(data['match'], data['groupdicts']):
if m: if m:
path, line = g['path'], g['line'] path, line = g['path'], g['line']
@ -589,7 +585,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
@result_handler(type_of_input='screen') @result_handler(type_of_input='screen')
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: 'Boss') -> None: def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
if data['customize_processing']: if data['customize_processing']:
m = load_custom_processor(data['customize_processing']) m = load_custom_processor(data['customize_processing'])
if 'handle_result' in m: if 'handle_result' in m:

View File

@ -13,13 +13,13 @@ from functools import lru_cache
from math import ceil from math import ceil
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import ( from typing import (
TYPE_CHECKING, Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
Union
) )
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname from kitty.constants import appname
from kitty.typing import GRT_f, GRT_t
from kitty.utils import ( from kitty.utils import (
TTYIO, ScreenSize, ScreenSizeGetter, fit_image, screen_size_function TTYIO, ScreenSize, ScreenSizeGetter, fit_image, screen_size_function
) )
@ -30,9 +30,6 @@ from ..tui.images import (
) )
from ..tui.operations import clear_images_on_screen from ..tui.operations import clear_images_on_screen
if TYPE_CHECKING:
from ..tui.images import GRT_f, GRT_t # noqa
OPTIONS = '''\ OPTIONS = '''\
--align --align
type=choices type=choices

View File

@ -3,35 +3,29 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from types import TracebackType
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Union
Union
) )
if TYPE_CHECKING: from kitty.typing import (
from kitty.utils import ScreenSize AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
from .loop import TermManager, Loop, Debug, MouseEvent KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
from .images import ImageManager )
from kitty.conf.utils import KittensKeyAction
from kitty.boss import Boss
from kitty.key_encoding import KeyEvent
from types import TracebackType
ScreenSize, TermManager, Loop, Debug, KeyEvent, MouseEvent, TracebackType, Boss, ImageManager
import asyncio
class Handler: class Handler:
image_manager_class: Optional[Type['ImageManager']] = None image_manager_class: Optional[Type[ImageManagerType]] = None
def _initialize( def _initialize(
self, self,
screen_size: 'ScreenSize', screen_size: ScreenSize,
term_manager: 'TermManager', term_manager: TermManagerType,
schedule_write: Callable[[bytes], None], schedule_write: Callable[[bytes], None],
tui_loop: 'Loop', tui_loop: LoopType,
debug: 'Debug', debug: Debug,
image_manager: Optional['ImageManager'] = None image_manager: Optional[ImageManagerType] = None
) -> None: ) -> None:
from .operations import commander from .operations import commander
self.screen_size = screen_size self.screen_size = screen_size
@ -43,15 +37,15 @@ class Handler:
self._image_manager = image_manager self._image_manager = image_manager
@property @property
def image_manager(self) -> 'ImageManager': def image_manager(self) -> ImageManagerType:
assert self._image_manager is not None assert self._image_manager is not None
return self._image_manager return self._image_manager
@property @property
def asyncio_loop(self) -> 'asyncio.AbstractEventLoop': def asyncio_loop(self) -> AbstractEventLoop:
return self._tui_loop.asycio_loop return self._tui_loop.asycio_loop
def add_shortcut(self, action: 'KittensKeyAction', key: str, mods: Optional[int] = None, is_text: Optional[bool] = False) -> None: def add_shortcut(self, action: KittensKeyActionType, key: str, mods: Optional[int] = None, is_text: Optional[bool] = False) -> None:
if not hasattr(self, '_text_shortcuts'): if not hasattr(self, '_text_shortcuts'):
self._text_shortcuts, self._key_shortcuts = {}, {} self._text_shortcuts, self._key_shortcuts = {}, {}
if is_text: if is_text:
@ -59,7 +53,7 @@ class Handler:
else: else:
self._key_shortcuts[(key, mods or 0)] = action self._key_shortcuts[(key, mods or 0)] = action
def shortcut_action(self, key_event_or_text: Union[str, 'KeyEvent']) -> Optional['KittensKeyAction']: def shortcut_action(self, key_event_or_text: Union[str, KeyEventType]) -> Optional[KittensKeyActionType]:
if isinstance(key_event_or_text, str): if isinstance(key_event_or_text, str):
return self._text_shortcuts.get(key_event_or_text) return self._text_shortcuts.get(key_event_or_text)
return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods)) return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods))
@ -70,7 +64,7 @@ class Handler:
self.debug.fobj = self self.debug.fobj = self
self.initialize() self.initialize()
def __exit__(self, etype: type, value: Exception, tb: 'TracebackType') -> None: def __exit__(self, etype: type, value: Exception, tb: TracebackType) -> None:
del self.debug.fobj del self.debug.fobj
self.finalize() self.finalize()
if self._image_manager is not None: if self._image_manager is not None:
@ -82,7 +76,7 @@ class Handler:
def finalize(self) -> None: def finalize(self) -> None:
pass pass
def on_resize(self, screen_size: 'ScreenSize') -> None: def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size self.screen_size = screen_size
def quit_loop(self, return_code: Optional[int] = None) -> None: def quit_loop(self, return_code: Optional[int] = None) -> None:
@ -94,7 +88,7 @@ class Handler:
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
pass pass
def on_key(self, key_event: 'KeyEvent') -> None: def on_key(self, key_event: KeyEventType) -> None:
pass pass
def on_mouse(self, mouse_event: 'MouseEvent') -> None: def on_mouse(self, mouse_event: 'MouseEvent') -> None:
@ -127,7 +121,7 @@ class Handler:
data = sep.join(map(str, args)) + end data = sep.join(map(str, args)) + end
self.write(data) self.write(data)
def suspend(self) -> ContextManager['TermManager']: def suspend(self) -> ContextManager[TermManagerType]:
return self._term_manager.suspend() return self._term_manager.suspend()
@ -141,7 +135,7 @@ class HandleResult:
self.no_ui = no_ui self.no_ui = no_ui
self.type_of_input = type_of_input self.type_of_input = type_of_input
def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: 'Boss') -> Any: def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: BossType) -> Any:
return self.impl(args, data, target_window_id, boss) return self.impl(args, data, target_window_id, boss)

View File

@ -10,10 +10,13 @@ from collections import defaultdict, deque
from contextlib import suppress from contextlib import suppress
from itertools import count from itertools import count
from typing import ( from typing import (
TYPE_CHECKING, Any, DefaultDict, Deque, Dict, List, Optional, Sequence, Any, DefaultDict, Deque, Dict, List, Optional, Sequence, Tuple, Union
Tuple, Union
) )
from kitty.typing import (
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType,
TypedDict
)
from kitty.utils import ScreenSize, fit_image from kitty.utils import ScreenSize, fit_image
from .operations import cursor from .operations import cursor
@ -25,28 +28,11 @@ except Exception:
fsenc = 'utf-8' fsenc = 'utf-8'
try:
from typing import TypedDict, Literal
GRT_a = Literal['t', 'T', 'q', 'p', 'd']
GRT_f = Literal[24, 32, 100]
GRT_t = Literal['d', 'f', 't', 's']
GRT_o = Literal['z']
GRT_m = Literal[0, 1]
GRT_d = Literal['a', 'A', 'c', 'C', 'i', 'I', 'p', 'P', 'q', 'Q', 'x', 'X', 'y', 'Y', 'z', 'Z']
except ImportError:
TypedDict = dict
if TYPE_CHECKING:
import subprocess
from .handler import Handler
class ImageData: class ImageData:
def __init__(self, fmt: str, width: int, height: int, mode: str): def __init__(self, fmt: str, width: int, height: int, mode: str):
self.width, self.height, self.fmt, self.mode = width, height, fmt, mode self.width, self.height, self.fmt, self.mode = width, height, fmt, mode
self.transmit_fmt: 'GRT_f' = (24 if self.mode == 'rgb' else 32) self.transmit_fmt: GRT_f = (24 if self.mode == 'rgb' else 32)
class OpenFailed(ValueError): class OpenFailed(ValueError):
@ -71,7 +57,7 @@ class NoImageMagick(Exception):
pass pass
def run_imagemagick(path: str, cmd: Sequence[str], keep_stdout: bool = True) -> 'subprocess.CompletedProcess[bytes]': def run_imagemagick(path: str, cmd: Sequence[str], keep_stdout: bool = True) -> CompletedProcess:
import subprocess import subprocess
try: try:
p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE) p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE)
@ -140,16 +126,16 @@ SentImageKey = Tuple[int, int, int]
class GraphicsCommand: class GraphicsCommand:
a: 'GRT_a' = 't' # action a: GRT_a = 't' # action
f: 'GRT_f' = 32 # image data format f: GRT_f = 32 # image data format
t: 'GRT_t' = 'd' # transmission medium t: GRT_t = 'd' # transmission medium
s: int = 0 # sent image width s: int = 0 # sent image width
v: int = 0 # sent image height v: int = 0 # sent image height
S: int = 0 # size of data to read from file S: int = 0 # size of data to read from file
O: int = 0 # offset of data to read from file O: int = 0 # offset of data to read from file
i: int = 0 # image id i: int = 0 # image id
o: Optional['GRT_o'] = None # type of compression o: Optional[GRT_o] = None # type of compression
m: 'GRT_m' = 0 # 0 or 1 whether there is more chunked data m: GRT_m = 0 # 0 or 1 whether there is more chunked data
x: int = 0 # left edge of image area to display x: int = 0 # left edge of image area to display
y: int = 0 # top edge of image area to display y: int = 0 # top edge of image area to display
w: int = 0 # image width to display w: int = 0 # image width to display
@ -159,7 +145,7 @@ class GraphicsCommand:
c: int = 0 # number of cols to display image over c: int = 0 # number of cols to display image over
r: int = 0 # number of rows to display image over r: int = 0 # number of rows to display image over
z: int = 0 # z-index z: int = 0 # z-index
d: 'GRT_d' = 'a' # what to delete d: GRT_d = 'a' # what to delete
def serialize(self, payload: bytes = b'') -> bytes: def serialize(self, payload: bytes = b'') -> bytes:
items = [] items = []
@ -193,7 +179,7 @@ class Placement(TypedDict):
class ImageManager: class ImageManager:
def __init__(self, handler: 'Handler'): def __init__(self, handler: HandlerType):
self.image_id_counter = count() self.image_id_counter = count()
self.handler = handler self.handler = handler
self.filesystem_ok: Optional[bool] = None self.filesystem_ok: Optional[bool] = None

View File

@ -12,9 +12,7 @@ import signal
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from functools import partial from functools import partial
from typing import ( from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
TYPE_CHECKING, Any, Callable, Dict, Generator, List, NamedTuple, Optional
)
from kitty.constants import is_macos from kitty.constants import is_macos
from kitty.fast_data_types import ( from kitty.fast_data_types import (
@ -24,20 +22,12 @@ from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, backspace_key, decode_key_event, ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, backspace_key, decode_key_event,
enter_key, key_defs as K enter_key, key_defs as K
) )
from kitty.utils import screen_size_function, write_all from kitty.typing import ImageManagerType, KeyEventType, Protocol
from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
from .handler import Handler from .handler import Handler
from .operations import init_state, reset_state from .operations import init_state, reset_state
if TYPE_CHECKING:
from kitty.key_encoding import KeyEvent
from .images import ImageManager
KeyEvent, ImageManager
from typing import Protocol
else:
Protocol = object
C, D = K['C'], K['D'] C, D = K['C'], K['D']
@ -156,7 +146,7 @@ class UnhandledException(Handler):
self.write('\r\n') self.write('\r\n')
self.write('Press the Enter key to quit') self.write('Press the Enter key to quit')
def on_key(self, key_event: 'KeyEvent') -> None: def on_key(self, key_event: KeyEventType) -> None:
if key_event is enter_key: if key_event is enter_key:
self.quit_loop(1) self.quit_loop(1)
@ -355,7 +345,7 @@ class Loop:
self.return_code = return_code self.return_code = return_code
self.asycio_loop.stop() self.asycio_loop.stop()
def loop_impl(self, handler: Handler, term_manager: TermManager, image_manager: Optional['ImageManager'] = None) -> Optional[str]: def loop_impl(self, handler: Handler, term_manager: TermManager, image_manager: Optional[ImageManagerType] = None) -> Optional[str]:
self.write_buf = [] self.write_buf = []
tty_fd = term_manager.tty_fd tty_fd = term_manager.tty_fd
tb = None tb = None
@ -399,7 +389,7 @@ class Loop:
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term) signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
with TermManager() as term_manager, signal_manager: with TermManager() as term_manager, signal_manager:
self._get_screen_size = screen_size_function(term_manager.tty_fd) self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
image_manager = None image_manager = None
if handler.image_manager_class is not None: if handler.image_manager_class is not None:
image_manager = handler.image_manager_class(handler) image_manager = handler.image_manager_class(handler)

View File

@ -6,20 +6,15 @@ import sys
from contextlib import contextmanager from contextlib import contextmanager
from functools import wraps from functools import wraps
from typing import ( from typing import (
IO, TYPE_CHECKING, Any, Callable, Dict, Generator, Optional, Tuple, IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union
TypeVar, Union
) )
from kitty.rgb import Color, color_as_sharp, to_color from kitty.rgb import Color, color_as_sharp, to_color
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize
from .operations_stub import CMD from .operations_stub import CMD
if TYPE_CHECKING: GraphicsCommandType, ScreenSize # needed for stub generation
from kitty.utils import ScreenSize
from .images import GraphicsCommand
from .handler import Handler
ScreenSize, GraphicsCommand, Handler
S7C1T = '\033 F' S7C1T = '\033 F'
SAVE_CURSOR = '\0337' SAVE_CURSOR = '\0337'
RESTORE_CURSOR = '\0338' RESTORE_CURSOR = '\0338'
@ -232,7 +227,7 @@ def serialize_gr_command(cmd: Dict[str, Union[int, str]], payload: Optional[byte
@cmd @cmd
def gr_command(cmd: Union[Dict, 'GraphicsCommand'], payload: Optional[bytes] = None) -> str: def gr_command(cmd: Union[Dict, 'GraphicsCommandType'], payload: Optional[bytes] = None) -> str:
if isinstance(cmd, dict): if isinstance(cmd, dict):
raw = serialize_gr_command(cmd, payload) raw = serialize_gr_command(cmd, payload)
else: else:
@ -349,14 +344,14 @@ def request_from_clipboard(use_primary: bool = False) -> str:
# Boilerplate to make operations availble via Handler.cmd {{{ # Boilerplate to make operations availble via Handler.cmd {{{
def writer(handler: 'Handler', func: Callable) -> Callable: def writer(handler: HandlerType, func: Callable) -> Callable:
@wraps(func) @wraps(func)
def f(*a: Any, **kw: Any) -> None: def f(*a: Any, **kw: Any) -> None:
handler.write(func(*a, **kw)) handler.write(func(*a, **kw))
return f return f
def commander(handler: 'Handler') -> CMD: def commander(handler: HandlerType) -> CMD:
ans = CMD() ans = CMD()
for name, func in all_cmds.items(): for name, func in all_cmds.items():
setattr(ans, name, writer(handler, func)) setattr(ans, name, writer(handler, func))
@ -374,8 +369,7 @@ def func_sig(func: Callable) -> Generator[str, None, None]:
def as_type_stub() -> str: def as_type_stub() -> str:
ans = [ ans = [
'from typing import * # noqa', 'from typing import * # noqa',
'from kitty.utils import ScreenSize', 'from kitty.typing import GraphicsCommandType, ScreenSize',
'from kittens.tui.images import GraphicsCommand',
'from kitty.rgb import Color', 'from kitty.rgb import Color',
'import kitty.rgb', 'import kitty.rgb',
] ]

View File

@ -10,7 +10,7 @@ from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
TYPE_CHECKING, Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple, Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple,
Union Union
) )
@ -22,6 +22,7 @@ from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
from kitty.key_encoding import ( from kitty.key_encoding import (
CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
) )
from kitty.typing import BossType
from kitty.utils import ScreenSize, get_editor from kitty.utils import ScreenSize, get_editor
from ..tui.handler import Handler, result_handler from ..tui.handler import Handler, result_handler
@ -32,11 +33,6 @@ from ..tui.operations import (
sgr, styled sgr, styled
) )
if TYPE_CHECKING:
from kitty.boss import Boss
Boss
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES' HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
UP = K['UP'] UP = K['UP']
DOWN = K['DOWN'] DOWN = K['DOWN']
@ -579,7 +575,7 @@ def main(args: List[str]) -> Optional[str]:
@result_handler() @result_handler()
def handle_result(args: List[str], current_char: str, target_window_id: int, boss: 'Boss') -> None: def handle_result(args: List[str], current_char: str, target_window_id: int, boss: BossType) -> None:
w = boss.window_id_map.get(target_window_id) w = boss.window_id_map.get(target_window_id)
if w is not None: if w is not None:
w.paste(current_char) w.paste(current_char)

View File

@ -2,8 +2,9 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from enum import IntFlag
from itertools import chain from itertools import chain
from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple from typing import List, Optional, Sequence, Tuple
from .constants import WindowGeometry from .constants import WindowGeometry
from .fast_data_types import ( from .fast_data_types import (
@ -12,17 +13,7 @@ from .fast_data_types import (
) )
from .options_stub import Options from .options_stub import Options
from .utils import load_shaders from .utils import load_shaders
from .typing import WindowType, LayoutType
try:
from enum import IntFlag
except ImportError:
from enum import IntEnum as IntFlag # type: ignore
if TYPE_CHECKING:
from .window import Window
from .layout import Layout
Window, Layout
class BorderColor(IntFlag): class BorderColor(IntFlag):
@ -63,9 +54,9 @@ class Borders:
def __call__( def __call__(
self, self,
windows: List['Window'], windows: List[WindowType],
active_window: Optional['Window'], active_window: Optional[WindowType],
current_layout: 'Layout', current_layout: LayoutType,
extra_blank_rects: Sequence[Tuple[int, int, int, int]], extra_blank_rects: Sequence[Tuple[int, int, int, int]],
padding_width: int, padding_width: int,
border_width: int, border_width: int,

View File

@ -10,8 +10,7 @@ from contextlib import suppress
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Optional, Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union
Tuple, Union
) )
from weakref import WeakValueDictionary from weakref import WeakValueDictionary
@ -44,6 +43,7 @@ from .session import Session, create_sessions
from .tabs import ( from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
) )
from .typing import PopenType, TypedDict
from .utils import ( from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir, func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
log_error, open_url, parse_address_spec, remove_socket_file, safe_print, log_error, open_url, parse_address_spec, remove_socket_file, safe_print,
@ -51,13 +51,6 @@ from .utils import (
) )
from .window import MatchPatternType, Window from .window import MatchPatternType, Window
if TYPE_CHECKING:
from typing import TypedDict
from .rc.base import RemoteCommand # noqa
from subprocess import Popen # noqa
else:
TypedDict = dict
class OSWindowDict(TypedDict): class OSWindowDict(TypedDict):
id: int id: int
@ -149,7 +142,7 @@ class Boss:
): ):
set_layout_options(opts) set_layout_options(opts)
self.clipboard_buffers: Dict[str, str] = {} self.clipboard_buffers: Dict[str, str] = {}
self.update_check_process: Optional['Popen'] = None self.update_check_process: Optional[PopenType] = None
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary() self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)} self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.startup_cursor_text_color = opts.cursor_text_color self.startup_cursor_text_color = opts.cursor_text_color
@ -1209,7 +1202,7 @@ class Boss:
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
os.remove(path) os.remove(path)
def set_update_check_process(self, process: Optional['Popen'] = None) -> None: def set_update_check_process(self, process: Optional[PopenType] = None) -> None:
if self.update_check_process is not None: if self.update_check_process is not None:
with suppress(Exception): with suppress(Exception):
if self.update_check_process.poll() is None: if self.update_check_process.poll() is None:

View File

@ -4,15 +4,13 @@
import re import re
from typing import TYPE_CHECKING, List, Generator, Any, Type from typing import List, Generator, Any, Type
if TYPE_CHECKING: from .cli_stub import HintsCLIOptions
from kitty.cli_stub import HintsCLIOptions from .typing import MarkType
from kittens.hints.main import Mark as MarkClass
HintsCLIOptions, MarkClass
def mark(text: str, args: 'HintsCLIOptions', Mark: Type['MarkClass'], extra_cli_args: List[str], *a: Any) -> Generator['MarkClass', None, None]: def mark(text: str, args: HintsCLIOptions, Mark: Type[MarkType], extra_cli_args: List[str], *a: Any) -> Generator[MarkType, None, None]:
for idx, m in enumerate(re.finditer(args.regex, text)): for idx, m in enumerate(re.finditer(args.regex, text)):
start, end = m.span() start, end = m.span()
mark_text = text[start:end].replace('\n', '').replace('\0', '') mark_text = text[start:end].replace('\n', '').replace('\0', '')

View File

@ -7,32 +7,27 @@ import re
import sys import sys
from collections import deque from collections import deque
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Match, Optional,
Match, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast Sequence, Set, Tuple, Type, TypeVar, Union, cast
) )
from .cli_stub import CLIOptions from .cli_stub import CLIOptions
from .conf.utils import resolve_config from .conf.utils import resolve_config
from .config import KeyAction
from .constants import appname, defconf, is_macos, is_wayland, str_version from .constants import appname, defconf, is_macos, is_wayland, str_version
from .options_stub import Options as OptionsStub from .options_stub import Options as OptionsStub
from .typing import BadLineType, KeySpec, SequenceMap, TypedDict
try:
from typing import TypedDict
class OptionDict(TypedDict): class OptionDict(TypedDict):
dest: str dest: str
aliases: FrozenSet[str] aliases: FrozenSet[str]
help: str help: str
choices: FrozenSet[str] choices: FrozenSet[str]
type: str type: str
default: Optional[str] default: Optional[str]
condition: bool condition: bool
except ImportError:
OptionDict = Dict[str, Any] # type: ignore
if TYPE_CHECKING:
from .config import KeyAction, KeySpec, SequenceMap # noqa
from .conf.utils import BadLine # noqa
CONFIG_HELP = '''\ CONFIG_HELP = '''\
Specify a path to the configuration file(s) to use. All configuration files are Specify a path to the configuration file(s) to use. All configuration files are
@ -733,10 +728,10 @@ def parse_args(
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf' SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
ShortcutMap = Dict[Tuple['KeySpec', ...], 'KeyAction'] ShortcutMap = Dict[Tuple[KeySpec, ...], KeyAction]
def print_shortcut(key_sequence: Iterable['KeySpec'], action: 'KeyAction') -> None: def print_shortcut(key_sequence: Iterable[KeySpec], action: KeyAction) -> None:
if not getattr(print_shortcut, 'maps', None): if not getattr(print_shortcut, 'maps', None):
from kitty.keys import defines from kitty.keys import defines
v = vars(defines) v = vars(defines)
@ -764,7 +759,7 @@ def print_shortcut(key_sequence: Iterable['KeySpec'], action: 'KeyAction') -> No
print('\t', ' > '.join(keys), action) print('\t', ' > '.join(keys), action)
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple['KeySpec', ...]]) -> None: def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[KeySpec, ...]]) -> None:
if changes: if changes:
print(title(text)) print(title(text))
@ -781,8 +776,8 @@ def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None:
print_shortcut_changes(final, 'Changed shortcuts:', changed) print_shortcut_changes(final, 'Changed shortcuts:', changed)
def flatten_sequence_map(m: 'SequenceMap') -> ShortcutMap: def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
ans: Dict[Tuple['KeySpec', ...], 'KeyAction'] = {} ans: Dict[Tuple[KeySpec, ...], KeyAction] = {}
for key_spec, rest_map in m.items(): for key_spec, rest_map in m.items():
for r, action in rest_map.items(): for r, action in rest_map.items():
ans[(key_spec,) + (r)] = action ans[(key_spec,) + (r)] = action
@ -811,7 +806,7 @@ def compare_opts(opts: OptionsStub) -> None:
compare_keymaps(final, initial) compare_keymaps(final, initial)
def create_opts(args: CLIOptions, debug_config: bool = False, accumulate_bad_lines: Optional[List['BadLine']] = None) -> OptionsStub: def create_opts(args: CLIOptions, debug_config: bool = False, accumulate_bad_lines: Optional[List[BadLineType]] = None) -> OptionsStub:
from .config import load_config from .config import load_config
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config)) config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
if debug_config: if debug_config:

View File

@ -23,14 +23,9 @@ from .config_data import all_options, parse_mods, type_convert
from .constants import cache_dir, defconf, is_macos from .constants import cache_dir, defconf, is_macos
from .key_names import get_key_name_lookup, key_name_aliases from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub from .options_stub import Options as OptionsStub
from .typing import TypedDict
from .utils import log_error from .utils import log_error
try:
from typing import TypedDict
except ImportError:
TypedDict = dict
KeySpec = Tuple[int, bool, int] KeySpec = Tuple[int, bool, int]
KeyMap = Dict[KeySpec, 'KeyAction'] KeyMap = Dict[KeySpec, 'KeyAction']
KeySequence = Tuple[KeySpec, ...] KeySequence = Tuple[KeySpec, ...]

View File

@ -8,10 +8,9 @@ import pwd
import sys import sys
from contextlib import suppress from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from typing import TYPE_CHECKING, NamedTuple, Optional, Set from typing import NamedTuple, Optional, Set
if TYPE_CHECKING: from .options_stub import Options
from .options_stub import Options # noqa
class Version(NamedTuple): class Version(NamedTuple):
@ -20,11 +19,11 @@ class Version(NamedTuple):
patch: int patch: int
appname = 'kitty' appname: str = 'kitty'
version = Version(0, 16, 0) version: Version = Version(0, 16, 0)
str_version = '.'.join(map(str, version)) str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos = 'darwin' in _plat is_macos: bool = 'darwin' in _plat
base = os.path.dirname(os.path.abspath(__file__)) base = os.path.dirname(os.path.abspath(__file__))
@ -179,7 +178,7 @@ def detect_if_wayland_ok() -> bool:
return ans == b'YES' return ans == b'YES'
def is_wayland(opts: Optional['Options'] = None) -> bool: def is_wayland(opts: Optional[Options] = None) -> bool:
if is_macos: if is_macos:
return False return False
if opts is None: if opts is None:

View File

@ -3,31 +3,25 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import re import re
from typing import ( from typing import Dict, Generator, Iterable, List, Optional, Tuple
TYPE_CHECKING, Dict, Generator, Iterable, List, Optional, Tuple
)
from kitty.fast_data_types import coretext_all_fonts from kitty.fast_data_types import coretext_all_fonts
from kitty.options_stub import Options from kitty.options_stub import Options
from kitty.typing import CoreTextFont
from kitty.utils import log_error from kitty.utils import log_error
from . import ListedFont from . import ListedFont
if TYPE_CHECKING:
from kitty.fast_data_types import CoreTextFont as C
CoreTextFont = C
attr_map = {(False, False): 'font_family', attr_map = {(False, False): 'font_family',
(True, False): 'bold_font', (True, False): 'bold_font',
(False, True): 'italic_font', (False, True): 'italic_font',
(True, True): 'bold_italic_font'} (True, True): 'bold_italic_font'}
FontMap = Dict[str, Dict[str, List['CoreTextFont']]] FontMap = Dict[str, Dict[str, List[CoreTextFont]]]
def create_font_map(all_fonts: Iterable['CoreTextFont']) -> FontMap: def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap:
ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}} ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}}
for x in all_fonts: for x in all_fonts:
f = (x['family'] or '').lower() f = (x['family'] or '').lower()
@ -56,11 +50,11 @@ def list_fonts() -> Generator[ListedFont, None, None]:
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono} yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}
def find_best_match(family: str, bold: bool = False, italic: bool = False) -> 'CoreTextFont': def find_best_match(family: str, bold: bool = False, italic: bool = False) -> CoreTextFont:
q = re.sub(r'\s+', ' ', family.lower()) q = re.sub(r'\s+', ' ', family.lower())
font_map = all_fonts_map() font_map = all_fonts_map()
def score(candidate: 'CoreTextFont') -> Tuple[int, int]: def score(candidate: CoreTextFont) -> Tuple[int, int]:
style_match = 1 if candidate['bold'] == bold and candidate[ style_match = 1 if candidate['bold'] == bold and candidate[
'italic' 'italic'
] == italic else 0 ] == italic else 0
@ -90,8 +84,8 @@ def resolve_family(f: str, main_family: str, bold: bool = False, italic: bool =
return f return f
def get_font_files(opts: Options) -> Dict[str, 'CoreTextFont']: def get_font_files(opts: Options) -> Dict[str, CoreTextFont]:
ans: Dict[str, 'CoreTextFont'] = {} ans: Dict[str, CoreTextFont] = {}
for (bold, italic), attr in attr_map.items(): for (bold, italic), attr in attr_map.items():
face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic) face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic)
key = {(False, False): 'medium', key = {(False, False): 'medium',
@ -104,6 +98,6 @@ def get_font_files(opts: Options) -> Dict[str, 'CoreTextFont']:
return ans return ans
def font_for_family(family: str) -> Tuple['CoreTextFont', bool, bool]: def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]:
ans = find_best_match(resolve_family(family, getattr(get_font_files, 'medium_family'))) ans = find_best_match(resolve_family(family, getattr(get_font_files, 'medium_family')))
return ans, ans['bold'], ans['italic'] return ans, ans['bold'], ans['italic']

View File

@ -4,31 +4,27 @@
import re import re
from functools import lru_cache from functools import lru_cache
from typing import TYPE_CHECKING, Dict, Generator, List, Optional, Tuple, cast from typing import Dict, Generator, List, Optional, Tuple, cast
from kitty.fast_data_types import ( from kitty.fast_data_types import (
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD,
FC_WEIGHT_REGULAR, fc_list, fc_match as fc_match_impl FC_WEIGHT_REGULAR, fc_list, fc_match as fc_match_impl
) )
from kitty.options_stub import Options from kitty.options_stub import Options
from kitty.typing import FontConfigPattern
from . import ListedFont from . import ListedFont
if TYPE_CHECKING:
from kitty.fast_data_types import FontConfigPattern as F
FontConfigPattern = F
attr_map = {(False, False): 'font_family', attr_map = {(False, False): 'font_family',
(True, False): 'bold_font', (True, False): 'bold_font',
(False, True): 'italic_font', (False, True): 'italic_font',
(True, True): 'bold_italic_font'} (True, True): 'bold_italic_font'}
FontMap = Dict[str, Dict[str, List['FontConfigPattern']]] FontMap = Dict[str, Dict[str, List[FontConfigPattern]]]
def create_font_map(all_fonts: Tuple['FontConfigPattern', ...]) -> FontMap: def create_font_map(all_fonts: Tuple[FontConfigPattern, ...]) -> FontMap:
ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}} ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}}
for x in all_fonts: for x in all_fonts:
if 'path' not in x: if 'path' not in x:
@ -69,15 +65,15 @@ def family_name_to_key(family: str) -> str:
@lru_cache() @lru_cache()
def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> 'FontConfigPattern': def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> FontConfigPattern:
return fc_match_impl(family, bold, italic, spacing) return fc_match_impl(family, bold, italic, spacing)
def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> 'FontConfigPattern': def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
q = family_name_to_key(family) q = family_name_to_key(family)
font_map = all_fonts_map(monospaced) font_map = all_fonts_map(monospaced)
def score(candidate: 'FontConfigPattern') -> Tuple[int, int]: def score(candidate: FontConfigPattern) -> Tuple[int, int]:
bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0)) bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0))
italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0)) italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0))
monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1 monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1
@ -118,8 +114,8 @@ def resolve_family(f: str, main_family: str, bold: bool, italic: bool) -> str:
return f return f
def get_font_files(opts: Options) -> Dict[str, 'FontConfigPattern']: def get_font_files(opts: Options) -> Dict[str, FontConfigPattern]:
ans: Dict[str, 'FontConfigPattern'] = {} ans: Dict[str, FontConfigPattern] = {}
for (bold, italic), attr in attr_map.items(): for (bold, italic), attr in attr_map.items():
rf = resolve_family(getattr(opts, attr), opts.font_family, bold, italic) rf = resolve_family(getattr(opts, attr), opts.font_family, bold, italic)
font = find_best_match(rf, bold, italic) font = find_best_match(rf, bold, italic)
@ -131,6 +127,6 @@ def get_font_files(opts: Options) -> Dict[str, 'FontConfigPattern']:
return ans return ans
def font_for_family(family: str) -> Tuple['FontConfigPattern', bool, bool]: def font_for_family(family: str) -> Tuple[FontConfigPattern, bool, bool]:
ans = find_best_match(family, monospaced=False) ans = find_best_match(family, monospaced=False)
return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN

View File

@ -6,9 +6,7 @@ import ctypes
import sys import sys
from functools import partial from functools import partial
from math import ceil, cos, floor, pi from math import ceil, cos, floor, pi
from typing import ( from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union, cast
)
from kitty.config import defaults from kitty.config import defaults
from kitty.constants import is_macos from kitty.constants import is_macos
@ -21,18 +19,15 @@ from kitty.fonts.box_drawing import (
BufType, render_box_char, render_missing_glyph BufType, render_box_char, render_missing_glyph
) )
from kitty.options_stub import Options as OptionsStub from kitty.options_stub import Options as OptionsStub
from kitty.typing import CoreTextFont, FontConfigPattern
from kitty.utils import log_error from kitty.utils import log_error
if is_macos: if is_macos:
from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos
if TYPE_CHECKING:
from .core_text import CoreTextFont # noqa
else: else:
from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig
if TYPE_CHECKING:
from .fontconfig import FontConfigPattern # noqa
FontObject = Union['CoreTextFont', 'FontConfigPattern'] FontObject = Union[CoreTextFont, FontConfigPattern]
current_faces: List[Tuple[FontObject, bool, bool]] = [] current_faces: List[Tuple[FontObject, bool, bool]] = []

8
kitty/key_encoding.py generated
View File

@ -3,7 +3,7 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import string import string
from typing import NamedTuple, Optional from typing import Dict, NamedTuple, Optional
from . import fast_data_types as defines from . import fast_data_types as defines
from .key_names import key_name_aliases from .key_names import key_name_aliases
@ -457,14 +457,16 @@ class KeyEvent(NamedTuple):
key: str key: str
PRESS, REPEAT, RELEASE = 1, 2, 4 PRESS: int = 1
REPEAT: int = 2
RELEASE: int = 4
SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8 SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8
type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE} type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
rtype_map = {v: k for k, v in type_map.items()} rtype_map = {v: k for k, v in type_map.items()}
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')} mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
rmod_map = {v: k for k, v in mod_map.items()} rmod_map = {v: k for k, v in mod_map.items()}
key_rmap = {} key_rmap = {}
key_defs = {} key_defs: Dict[str, str] = {}
config_key_map = {} config_key_map = {}
config_mod_map = { config_mod_map = {
'SHIFT': SHIFT, 'SHIFT': SHIFT,

View File

@ -3,20 +3,15 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import string import string
from typing import ( from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union
TYPE_CHECKING, Any, Callable, Dict, Iterable, Optional, Tuple, Union
)
from . import fast_data_types as defines from . import fast_data_types as defines
from .config import KeyAction, KeyMap, KeySpec, SequenceMap, SubSequenceMap from .config import KeyAction, KeyMap, KeySpec, SequenceMap, SubSequenceMap
from .key_encoding import KEY_MAP from .key_encoding import KEY_MAP
from .terminfo import key_as_bytes, modify_key_bytes from .terminfo import key_as_bytes, modify_key_bytes
from .typing import ScreenType, WindowType
from .utils import base64_encode from .utils import base64_encode
if TYPE_CHECKING:
from .fast_data_types import Screen # noqa
from .window import Window # noqa
def modify_complex_key(name: Union[str, bytes], amt: int) -> bytes: def modify_complex_key(name: Union[str, bytes], amt: int) -> bytes:
q = name if isinstance(name, bytes) else key_as_bytes(name) q = name if isinstance(name, bytes) else key_as_bytes(name)
@ -149,7 +144,7 @@ rmkx_key_map.update({
cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map} cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map}
def keyboard_mode_name(screen: 'Screen') -> str: def keyboard_mode_name(screen: ScreenType) -> str:
if screen.extended_keyboard: if screen.extended_keyboard:
return 'kitty' return 'kitty'
return 'application' if screen.cursor_key_mode else 'normal' return 'application' if screen.cursor_key_mode else 'normal'
@ -270,7 +265,7 @@ def key_to_bytes(key: int, smkx: bool, extended: bool, mods: int, action: int) -
return bytes(data) return bytes(data)
def interpret_key_event(key: int, native_key: int, mods: int, window: 'Window', action: int) -> bytes: def interpret_key_event(key: int, native_key: int, mods: int, window: WindowType, action: int) -> bytes:
screen = window.screen screen = window.screen
if ( if (
action == defines.GLFW_PRESS or action == defines.GLFW_PRESS or

View File

@ -5,7 +5,7 @@
from functools import lru_cache, partial from functools import lru_cache, partial
from itertools import islice, repeat from itertools import islice, repeat
from typing import ( from typing import (
TYPE_CHECKING, Callable, Collection, Deque, Dict, FrozenSet, Generator, Callable, Collection, Deque, Dict, FrozenSet, Generator,
Iterable, List, NamedTuple, Optional, Sequence, Tuple, Union, cast Iterable, List, NamedTuple, Optional, Sequence, Tuple, Union, cast
) )
@ -14,20 +14,7 @@ from .fast_data_types import (
Region, set_active_window, swap_windows, viewport_for_window Region, set_active_window, swap_windows, viewport_for_window
) )
from .options_stub import Options from .options_stub import Options
from .typing import WindowType, EdgeLiteral, TypedDict
try:
from typing import TypedDict, Literal
EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
except ImportError:
TypedDict = dict
EdgeLiteral = str # type: ignore
if TYPE_CHECKING:
from .window import Window
Window
else:
Window = object
class Borders(NamedTuple): class Borders(NamedTuple):
@ -53,7 +40,7 @@ draw_active_borders = True
align_top_left = False align_top_left = False
LayoutDimension = Generator[Tuple[int, int], None, None] LayoutDimension = Generator[Tuple[int, int], None, None]
DecorationPairs = Sequence[Tuple[int, int]] DecorationPairs = Sequence[Tuple[int, int]]
WindowList = Union[List[Window], Deque[Window]] WindowList = Union[List[WindowType], Deque[WindowType]]
class InternalNeighborsMap(TypedDict): class InternalNeighborsMap(TypedDict):
@ -70,7 +57,7 @@ class NeighborsMap(TypedDict):
bottom: Tuple[int, ...] bottom: Tuple[int, ...]
def idx_for_id(win_id: int, windows: Iterable[Window]) -> Optional[int]: def idx_for_id(win_id: int, windows: Iterable[WindowType]) -> Optional[int]:
for i, w in enumerate(windows): for i, w in enumerate(windows):
if w.id == win_id: if w.id == win_id:
return i return i
@ -134,7 +121,7 @@ class Rect(NamedTuple):
bottom: int bottom: int
def process_overlaid_windows(all_windows: WindowList) -> Tuple[FrozenSet[Window], WindowList]: def process_overlaid_windows(all_windows: WindowList) -> Tuple[FrozenSet[WindowType], WindowList]:
id_map = {w.id: w for w in all_windows} id_map = {w.id: w for w in all_windows}
overlaid_windows = frozenset(w for w in all_windows if w.overlay_window_id is not None and w.overlay_window_id in id_map) overlaid_windows = frozenset(w for w in all_windows if w.overlay_window_id is not None and w.overlay_window_id in id_map)
windows = [w for w in all_windows if w not in overlaid_windows] windows = [w for w in all_windows if w not in overlaid_windows]
@ -151,25 +138,25 @@ def layout_single_window(xdecoration_pairs: DecorationPairs, ydecoration_pairs:
return window_geometry(xstart, xnum, ystart, ynum) return window_geometry(xstart, xnum, ystart, ynum)
def left_blank_rect(w: Window, rects: List[Rect]) -> None: def left_blank_rect(w: WindowType, rects: List[Rect]) -> None:
lt = w.geometry.left lt = w.geometry.left
if lt > central.left: if lt > central.left:
rects.append(Rect(central.left, central.top, lt, central.bottom + 1)) rects.append(Rect(central.left, central.top, lt, central.bottom + 1))
def right_blank_rect(w: Window, rects: List[Rect]) -> None: def right_blank_rect(w: WindowType, rects: List[Rect]) -> None:
r = w.geometry.right r = w.geometry.right
if r < central.right: if r < central.right:
rects.append(Rect(r, central.top, central.right + 1, central.bottom + 1)) rects.append(Rect(r, central.top, central.right + 1, central.bottom + 1))
def top_blank_rect(w: Window, rects: List[Rect]) -> None: def top_blank_rect(w: WindowType, rects: List[Rect]) -> None:
t = w.geometry.top t = w.geometry.top
if t > central.top: if t > central.top:
rects.append(Rect(central.left, central.top, central.right + 1, t)) rects.append(Rect(central.left, central.top, central.right + 1, t))
def bottom_blank_rect(w: Window, rects: List[Rect]) -> None: def bottom_blank_rect(w: WindowType, rects: List[Rect]) -> None:
b = w.geometry.bottom b = w.geometry.bottom
# Need to use <= here as otherwise a single pixel row at the bottom of the # Need to use <= here as otherwise a single pixel row at the bottom of the
# window is sometimes not covered. See https://github.com/kovidgoyal/kitty/issues/506 # window is sometimes not covered. See https://github.com/kovidgoyal/kitty/issues/506
@ -177,7 +164,7 @@ def bottom_blank_rect(w: Window, rects: List[Rect]) -> None:
rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1)) rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1))
def blank_rects_for_window(w: Window) -> List[Rect]: def blank_rects_for_window(w: WindowType) -> List[Rect]:
ans: List[Rect] = [] ans: List[Rect] = []
left_blank_rect(w, ans) left_blank_rect(w, ans)
top_blank_rect(w, ans) top_blank_rect(w, ans)
@ -288,7 +275,7 @@ class Layout: # {{{
data[k] = v data[k] = v
return type(self.layout_opts)(data) return type(self.layout_opts)(data)
def nth_window(self, all_windows: WindowList, num: int) -> Window: def nth_window(self, all_windows: WindowList, num: int) -> WindowType:
windows = process_overlaid_windows(all_windows)[1] windows = process_overlaid_windows(all_windows)[1]
w = windows[min(num, len(windows) - 1)] w = windows[min(num, len(windows) - 1)]
return w return w
@ -370,7 +357,7 @@ class Layout: # {{{
def swap_windows_in_layout(self, all_windows: WindowList, a: int, b: int) -> None: def swap_windows_in_layout(self, all_windows: WindowList, a: int, b: int) -> None:
all_windows[a], all_windows[b] = all_windows[b], all_windows[a] all_windows[a], all_windows[b] = all_windows[b], all_windows[a]
def add_window(self, all_windows: WindowList, window: Window, current_active_window_idx: int, location: Optional[str] = None) -> int: def add_window(self, all_windows: WindowList, window: WindowType, current_active_window_idx: int, location: Optional[str] = None) -> int:
active_window_idx = None active_window_idx = None
if window.overlay_for is not None: if window.overlay_for is not None:
i = idx_for_id(window.overlay_for, all_windows) i = idx_for_id(window.overlay_for, all_windows)
@ -390,7 +377,7 @@ class Layout: # {{{
self.set_active_window_in_os_window(active_window_idx) self.set_active_window_in_os_window(active_window_idx)
return active_window_idx return active_window_idx
def do_add_window(self, all_windows: WindowList, window: Window, current_active_window_idx: Optional[int], location: Optional[str]) -> int: def do_add_window(self, all_windows: WindowList, window: WindowType, current_active_window_idx: Optional[int], location: Optional[str]) -> int:
active_window_idx = None active_window_idx = None
if location is not None: if location is not None:
if location in ('after', 'vsplit', 'hsplit') and current_active_window_idx is not None and len(all_windows) > 1: if location in ('after', 'vsplit', 'hsplit') and current_active_window_idx is not None and len(all_windows) > 1:
@ -409,7 +396,7 @@ class Layout: # {{{
all_windows.append(window) all_windows.append(window)
return active_window_idx return active_window_idx
def remove_window(self, all_windows: WindowList, window: Window, current_active_window_idx: int, swapped: bool = False) -> int: def remove_window(self, all_windows: WindowList, window: WindowType, current_active_window_idx: int, swapped: bool = False) -> int:
try: try:
active_window = all_windows[current_active_window_idx] active_window = all_windows[current_active_window_idx]
except Exception: except Exception:
@ -443,7 +430,7 @@ class Layout: # {{{
self(all_windows, active_window_idx) self(all_windows, active_window_idx)
return self.set_active_window(all_windows, active_window_idx) return self.set_active_window(all_windows, active_window_idx)
def update_visibility(self, all_windows: WindowList, active_window: Window, overlaid_windows: Optional[FrozenSet[Window]] = None) -> None: def update_visibility(self, all_windows: WindowList, active_window: WindowType, overlaid_windows: Optional[FrozenSet[WindowType]] = None) -> None:
if overlaid_windows is None: if overlaid_windows is None:
overlaid_windows = process_overlaid_windows(all_windows)[0] overlaid_windows = process_overlaid_windows(all_windows)[0]
for i, w in enumerate(all_windows): for i, w in enumerate(all_windows):
@ -492,7 +479,7 @@ class Layout: # {{{
return cast(int, idx_for_id(active_window.id, all_windows)) return cast(int, idx_for_id(active_window.id, all_windows))
# Utils {{{ # Utils {{{
def layout_single_window(self, w: Window) -> None: def layout_single_window(self, w: WindowType) -> None:
mw = self.margin_width if self.single_window_margin_width < 0 else self.single_window_margin_width mw = self.margin_width if self.single_window_margin_width < 0 else self.single_window_margin_width
decoration_pairs = ((self.padding_width + mw, self.padding_width + mw),) decoration_pairs = ((self.padding_width + mw, self.padding_width + mw),)
wg = layout_single_window(decoration_pairs, decoration_pairs) wg = layout_single_window(decoration_pairs, decoration_pairs)
@ -521,19 +508,19 @@ class Layout: # {{{
height = central.height height = central.height
return layout_dimension(top, height, cell_height, decoration_pairs, bias=bias, left_align=align_top_left) return layout_dimension(top, height, cell_height, decoration_pairs, bias=bias, left_align=align_top_left)
def simple_blank_rects(self, first_window: Window, last_window: Window) -> None: def simple_blank_rects(self, first_window: WindowType, last_window: WindowType) -> None:
br = self.blank_rects br = self.blank_rects
left_blank_rect(first_window, br) left_blank_rect(first_window, br)
top_blank_rect(first_window, br) top_blank_rect(first_window, br)
right_blank_rect(last_window, br) right_blank_rect(last_window, br)
def between_blank_rect(self, left_window: Window, right_window: Window, vertical: bool = True) -> None: def between_blank_rect(self, left_window: WindowType, right_window: WindowType, vertical: bool = True) -> None:
if vertical: if vertical:
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1)) self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
else: else:
self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom)) self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom))
def bottom_blank_rect(self, window: Window) -> None: def bottom_blank_rect(self, window: WindowType) -> None:
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1)) self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
# }}} # }}}
@ -543,24 +530,24 @@ class Layout: # {{{
def do_layout_all_windows(self, windows: WindowList, active_window_idx: int, all_windows: WindowList) -> None: def do_layout_all_windows(self, windows: WindowList, active_window_idx: int, all_windows: WindowList) -> None:
raise NotImplementedError() raise NotImplementedError()
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
return {'left': [], 'right': [], 'top': [], 'bottom': []} return {'left': [], 'right': [], 'top': [], 'bottom': []}
def compute_needs_borders_map(self, windows: WindowList, active_window: Optional[Window]) -> Dict[int, bool]: def compute_needs_borders_map(self, windows: WindowList, active_window: Optional[WindowType]) -> Dict[int, bool]:
return {w.id: ((w is active_window and draw_active_borders) or w.needs_attention) for w in windows} return {w.id: ((w is active_window and draw_active_borders) or w.needs_attention) for w in windows}
def resolve_borders(self, windows: WindowList, active_window: Optional[Window]) -> Generator[Borders, None, None]: def resolve_borders(self, windows: WindowList, active_window: Optional[WindowType]) -> Generator[Borders, None, None]:
if draw_minimal_borders: if draw_minimal_borders:
needs_borders_map = self.compute_needs_borders_map(windows, active_window) needs_borders_map = self.compute_needs_borders_map(windows, active_window)
yield from self.minimal_borders(windows, active_window, needs_borders_map) yield from self.minimal_borders(windows, active_window, needs_borders_map)
else: else:
yield from Layout.minimal_borders(self, windows, active_window, {}) yield from Layout.minimal_borders(self, windows, active_window, {})
def window_independent_borders(self, windows: WindowList, active_window: Optional[Window] = None) -> Generator[Tuple[int, int, int, int], None, None]: def window_independent_borders(self, windows: WindowList, active_window: Optional[WindowType] = None) -> Generator[Tuple[int, int, int, int], None, None]:
return return
yield (0, 0, 0, 0) # type: ignore yield (0, 0, 0, 0) # type: ignore
def minimal_borders(self, windows: WindowList, active_window: Optional[Window], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
for w in windows: for w in windows:
if (w is active_window and draw_active_borders) or w.needs_attention: if (w is active_window and draw_active_borders) or w.needs_attention:
yield all_borders yield all_borders
@ -592,7 +579,7 @@ class Stack(Layout): # {{{
# Tall {{{ # Tall {{{
def neighbors_for_tall_window(num_full_size_windows: int, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
idx = windows.index(window) idx = windows.index(window)
prev = None if idx == 0 else windows[idx-1] prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1] nxt = None if idx == len(windows) - 1 else windows[idx+1]
@ -713,10 +700,10 @@ class Tall(Layout):
# left, top and right blank rects # left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1]) self.simple_blank_rects(windows[0], windows[-1])
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
return neighbors_for_tall_window(self.num_full_size_windows, window, windows) return neighbors_for_tall_window(self.num_full_size_windows, window, windows)
def minimal_borders(self, windows: WindowList, active_window: Optional[Window], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
last_i = len(windows) - 1 last_i = len(windows) - 1
for i, w in enumerate(windows): for i, w in enumerate(windows):
if needs_borders_map[w.id]: if needs_borders_map[w.id]:
@ -778,7 +765,7 @@ class Fat(Tall): # {{{
# left, top and right blank rects # left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1]) self.simple_blank_rects(windows[0], windows[-1])
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
idx = windows.index(window) idx = windows.index(window)
prev = None if idx == 0 else windows[idx-1] prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1] nxt = None if idx == len(windows) - 1 else windows[idx+1]
@ -925,7 +912,7 @@ class Grid(Layout):
for i in range(ncols - 1): for i in range(ncols - 1):
self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0]) self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0])
def minimal_borders(self, windows: WindowList, active_window: Optional[Window], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
n = len(windows) n = len(windows)
ncols, nrows, special_rows, special_col = calc_grid_size(n) ncols, nrows, special_rows, special_col = calc_grid_size(n)
blank_row: List[Optional[int]] = [None for i in range(ncols)] blank_row: List[Optional[int]] = [None for i in range(ncols)]
@ -962,7 +949,7 @@ class Grid(Layout):
bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id] bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id]
) )
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
n = len(windows) n = len(windows)
if n < 4: if n < 4:
return neighbors_for_tall_window(1, window, windows) return neighbors_for_tall_window(1, window, windows)
@ -1051,7 +1038,7 @@ class Vertical(Layout): # {{{
# left, top and right blank rects # left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1]) self.simple_blank_rects(windows[0], windows[-1])
def minimal_borders(self, windows: WindowList, active_window: Optional[Window], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
last_i = len(windows) - 1 last_i = len(windows) - 1
for i, w in enumerate(windows): for i, w in enumerate(windows):
if needs_borders_map[w.id]: if needs_borders_map[w.id]:
@ -1065,7 +1052,7 @@ class Vertical(Layout): # {{{
else: else:
yield self.only_between_border yield self.only_between_border
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
idx = windows.index(window) idx = windows.index(window)
before = [] if window is windows[0] else [windows[idx-1].id] before = [] if window is windows[0] else [windows[idx-1].id]
after = [] if window is windows[-1] else [windows[idx+1].id] after = [] if window is windows[-1] else [windows[idx+1].id]
@ -1220,7 +1207,7 @@ class Pair:
tuple(map(pair.balanced_add, q)) tuple(map(pair.balanced_add, q))
return pair return pair
def apply_window_geometry(self, window_id: int, window_geometry: WindowGeometry, id_window_map: Dict[int, Window], id_idx_map: Dict[int, int]) -> None: def apply_window_geometry(self, window_id: int, window_geometry: WindowGeometry, id_window_map: Dict[int, WindowType], id_idx_map: Dict[int, int]) -> None:
w = id_window_map[window_id] w = id_window_map[window_id]
w.set_geometry(id_idx_map[window_id], window_geometry) w.set_geometry(id_idx_map[window_id], window_geometry)
if w.overlay_window_id is not None: if w.overlay_window_id is not None:
@ -1228,7 +1215,7 @@ class Pair:
if q is not None: if q is not None:
q.set_geometry(id_idx_map[q.id], window_geometry) q.set_geometry(id_idx_map[q.id], window_geometry)
def blank_rects_for_window(self, layout_object: Layout, window: Window, left: int, top: int, width: int, height: int) -> None: def blank_rects_for_window(self, layout_object: Layout, window: WindowType, left: int, top: int, width: int, height: int) -> None:
right = left + width - 1 right = left + width - 1
bottom = top + height - 1 bottom = top + height - 1
g = window.geometry g = window.geometry
@ -1249,7 +1236,7 @@ class Pair:
def layout_pair( def layout_pair(
self, self,
left: int, top: int, width: int, height: int, left: int, top: int, width: int, height: int,
id_window_map: Dict[int, Window], id_window_map: Dict[int, WindowType],
id_idx_map: Dict[int, int], id_idx_map: Dict[int, int],
layout_object: Layout layout_object: Layout
) -> None: ) -> None:
@ -1436,7 +1423,7 @@ class Splits(Layout):
def do_add_window( def do_add_window(
self, self,
all_windows: WindowList, all_windows: WindowList,
window: Window, window: WindowType,
current_active_window_idx: Optional[int], current_active_window_idx: Optional[int],
location: Optional[str] location: Optional[str]
) -> int: ) -> int:
@ -1490,14 +1477,14 @@ class Splits(Layout):
pair.bias = 0.5 pair.bias = 0.5
return True return True
def window_independent_borders(self, windows: WindowList, active_window: Optional[Window] = None) -> Generator[Tuple[int, int, int, int], None, None]: def window_independent_borders(self, windows: WindowList, active_window: Optional[WindowType] = None) -> Generator[Tuple[int, int, int, int], None, None]:
if not draw_minimal_borders: if not draw_minimal_borders:
return return
for pair in self.pairs_root.self_and_descendants(): for pair in self.pairs_root.self_and_descendants():
if pair.between_border is not None: if pair.between_border is not None:
yield pair.between_border yield pair.between_border
def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
window_id = window.overlay_for or window.id window_id = window.overlay_for or window.id
pair = self.pairs_root.pair_for_window(window_id) pair = self.pairs_root.pair_for_window(window_id)
ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}

View File

@ -10,7 +10,7 @@ import types
from contextlib import suppress from contextlib import suppress
from functools import partial from functools import partial
from typing import ( from typing import (
TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Tuple, Any, Dict, Generator, Iterable, List, Optional, Tuple,
Union, cast Union, cast
) )
@ -22,14 +22,11 @@ from .rc.base import (
PayloadGetter, all_command_names, command_for_name, PayloadGetter, all_command_names, command_for_name,
no_response as no_response_sentinel, parse_subcommand_cli no_response as no_response_sentinel, parse_subcommand_cli
) )
from .typing import BossType, WindowType
from .utils import TTYIO, parse_address_spec from .utils import TTYIO, parse_address_spec
if TYPE_CHECKING:
from .boss import Boss # noqa
from .window import Window # noqa
def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str) -> Optional[Dict[str, Any]]:
def handle_cmd(boss: 'Boss', window: Optional['Window'], serialized_cmd: str) -> Optional[Dict[str, Any]]:
cmd = json.loads(serialized_cmd) cmd = json.loads(serialized_cmd)
v = cmd['version'] v = cmd['version']
no_response = cmd.get('no_response', False) no_response = cmd.get('no_response', False)

View File

@ -4,20 +4,16 @@
import shlex import shlex
import sys import sys
from typing import ( from typing import Generator, List, NamedTuple, Optional, Tuple, Union
TYPE_CHECKING, Generator, List, NamedTuple, Optional, Tuple, Union
)
from .cli_stub import CLIOptions
from .config_data import to_layout_names from .config_data import to_layout_names
from .constants import kitty_exe from .constants import kitty_exe
from .layout import all_layouts from .layout import all_layouts
from .options_stub import Options from .options_stub import Options
from .typing import SpecialWindowInstance
from .utils import log_error, resolved_shell from .utils import log_error, resolved_shell
if TYPE_CHECKING:
from .tabs import SpecialWindowInstance # noqa
from .cli_stub import CLIOptions # noqa
class WindowSizeOpts(NamedTuple): class WindowSizeOpts(NamedTuple):
@ -135,7 +131,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
def create_sessions( def create_sessions(
opts: Options, opts: Options,
args: Optional['CLIOptions'] = None, args: Optional[CLIOptions] = None,
special_window: Optional['SpecialWindowInstance'] = None, special_window: Optional['SpecialWindowInstance'] = None,
cwd_from: Optional[int] = None, cwd_from: Optional[int] = None,
respect_cwd: bool = False, respect_cwd: bool = False,

View File

@ -7,7 +7,7 @@ from collections import deque
from contextlib import suppress from contextlib import suppress
from functools import partial from functools import partial
from typing import ( from typing import (
TYPE_CHECKING, Deque, Dict, Generator, Iterator, List, NamedTuple, Deque, Dict, Generator, Iterator, List, NamedTuple,
Optional, Pattern, Sequence, Tuple, cast Optional, Pattern, Sequence, Tuple, cast
) )
@ -27,13 +27,7 @@ from .options_stub import Options
from .tab_bar import TabBar, TabBarData from .tab_bar import TabBar, TabBarData
from .utils import log_error, resolved_shell from .utils import log_error, resolved_shell
from .window import Window, WindowDict from .window import Window, WindowDict
from .typing import TypedDict, SessionTab, SessionType
if TYPE_CHECKING:
from .session import Session, Tab as SessionTab
SessionTab, Session
from typing import TypedDict
else:
TypedDict = dict
class TabDict(TypedDict): class TabDict(TypedDict):
@ -555,7 +549,7 @@ class Tab: # {{{
class TabManager: # {{{ class TabManager: # {{{
def __init__(self, os_window_id: int, opts: Options, args: CLIOptions, startup_session: Optional['Session'] = None): def __init__(self, os_window_id: int, opts: Options, args: CLIOptions, startup_session: Optional[SessionType] = None):
self.os_window_id = os_window_id self.os_window_id = os_window_id
self.last_active_tab_id = None self.last_active_tab_id = None
self.opts, self.args = opts, args self.opts, self.args = opts, args

21
kitty/typing.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Tuple
BossType = ChildType = TabType = WindowType = ScreenType = None
BadLineType = KeySpec = SequenceMap = KeyActionType = None
AddressFamily = PopenType = Socket = StartupCtx = None
SessionTab = SessionType = LayoutType = SpecialWindowInstance = None
MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None
KeyEventType = ImageManagerType = KittyCommonOpts = HandlerType = None
GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = None
ScreenSize = KittensKeyActionType = MouseEvent = AbstractEventLoop = None
TermManagerType = LoopType = Debug = GraphicsCommandType = None
CompletedProcess = Tuple
TypedDict = dict
EdgeLiteral = str
Protocol = object

51
kitty/typing.pyi Normal file
View File

@ -0,0 +1,51 @@
from asyncio import AbstractEventLoop as AbstractEventLoop # noqa
from socket import AddressFamily as AddressFamily, socket as Socket # noqa
from typing import ( # noqa
Literal, Protocol as Protocol, TypedDict as TypedDict
)
from kittens.hints.main import Mark as MarkType # noqa
from kittens.tui.handler import Handler as HandlerType # noqa
from kittens.tui.images import ( # noqa
GraphicsCommand as GraphicsCommandType, ImageManager as ImageManagerType
)
from kittens.tui.loop import ( # noqa
Debug as Debug, Loop as LoopType, MouseEvent as MouseEvent,
TermManager as TermManagerType
)
from kitty.conf.utils import KittensKeyAction as KittensKeyActionType # noqa
from .boss import Boss as BossType # noqa
from .child import Child as ChildType # noqa
from .conf.utils import BadLine as BadLineType # noqa
from .fast_data_types import ( # noqa
CoreTextFont as CoreTextFont, FontConfigPattern as FontConfigPattern,
Screen as ScreenType, StartupCtx as StartupCtx
)
from .key_encoding import KeyEvent as KeyEventType # noqa
from .layout import Layout as LayoutType # noqa
from .rc.base import RemoteCommand as RemoteCommandType # noqa
from .session import Session as SessionType, Tab as SessionTab # noqa
from .tabs import ( # noqa
SpecialWindowInstance as SpecialWindowInstance, Tab as TabType
)
from .utils import ScreenSize as ScreenSize # noqa
from .window import Window as WindowType # noqa
from subprocess import ( # noqa; noqa
CompletedProcess as CompletedProcess, Popen as PopenType
)
from .config import ( # noqa; noqa
KeyAction as KeyActionType, KeyMap as KeyMap, KeySpec as KeySpec,
KittyCommonOpts as KittyCommonOpts, SequenceMap as SequenceMap
)
EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
GRT_a = Literal['t', 'T', 'q', 'p', 'd']
GRT_f = Literal[24, 32, 100]
GRT_t = Literal['d', 'f', 't', 's']
GRT_o = Literal['z']
GRT_m = Literal[0, 1]
GRT_d = Literal['a', 'A', 'c', 'C', 'i', 'I', 'p', 'P', 'q', 'Q', 'x', 'X', 'y', 'Y', 'z', 'Z']

View File

@ -14,8 +14,8 @@ from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from time import monotonic from time import monotonic
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Any, Callable, Dict, Generator, Iterable, List, NamedTuple, Optional,
NamedTuple, Optional, Tuple, Union, cast Tuple, Union, cast
) )
from .constants import ( from .constants import (
@ -23,11 +23,7 @@ from .constants import (
) )
from .options_stub import Options from .options_stub import Options
from .rgb import Color, to_color from .rgb import Color, to_color
from .typing import AddressFamily, PopenType, Socket, StartupCtx
if TYPE_CHECKING:
from subprocess import Popen # noqa
from .fast_data_types import StartupCtx # noqa
from socket import socket as Socket, AddressFamily # noqa
BASE = os.path.dirname(os.path.abspath(__file__)) BASE = os.path.dirname(os.path.abspath(__file__))
@ -182,7 +178,7 @@ def command_for_open(program: Union[str, List[str]] = 'default') -> List[str]:
return cmd return cmd
def open_cmd(cmd: Union[Iterable[str], List[str]], arg: Union[None, Iterable[str], str] = None, cwd: Optional[str] = None) -> 'Popen': def open_cmd(cmd: Union[Iterable[str], List[str]], arg: Union[None, Iterable[str], str] = None, cwd: Optional[str] = None) -> PopenType:
import subprocess import subprocess
if arg is not None: if arg is not None:
cmd = list(cmd) cmd = list(cmd)
@ -193,7 +189,7 @@ def open_cmd(cmd: Union[Iterable[str], List[str]], arg: Union[None, Iterable[str
return subprocess.Popen(tuple(cmd), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None) return subprocess.Popen(tuple(cmd), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None)
def open_url(url: str, program: Union[str, List[str]] = 'default', cwd: Optional[str] = None) -> 'Popen': def open_url(url: str, program: Union[str, List[str]] = 'default', cwd: Optional[str] = None) -> PopenType:
return open_cmd(command_for_open(program), url, cwd=cwd) return open_cmd(command_for_open(program), url, cwd=cwd)
@ -367,7 +363,7 @@ class SingleInstance:
single_instance = SingleInstance() single_instance = SingleInstance()
def parse_address_spec(spec: str) -> Tuple['AddressFamily', Union[Tuple[str, int], str], Optional[str]]: def parse_address_spec(spec: str) -> Tuple[AddressFamily, Union[Tuple[str, int], str], Optional[str]]:
import socket import socket
protocol, rest = spec.split(':', 1) protocol, rest = spec.split(':', 1)
socket_path = None socket_path = None

View File

@ -10,8 +10,8 @@ from collections import deque
from enum import IntEnum from enum import IntEnum
from itertools import chain from itertools import chain
from typing import ( from typing import (
TYPE_CHECKING, Any, Callable, Deque, Dict, List, Optional, Pattern, Any, Callable, Deque, Dict, List, Optional, Pattern, Sequence, Tuple,
Sequence, Tuple, Union Union
) )
from .child import ProcessDesc from .child import ProcessDesc
@ -32,20 +32,13 @@ from .keys import defines, extended_key_event, keyboard_mode_name
from .options_stub import Options from .options_stub import Options
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .typing import ChildType, TabType, TypedDict
from .utils import ( from .utils import (
color_as_int, get_primary_selection, load_shaders, open_cmd, open_url, color_as_int, get_primary_selection, load_shaders, open_cmd, open_url,
parse_color_set, read_shell_environment, sanitize_title, parse_color_set, read_shell_environment, sanitize_title,
set_primary_selection set_primary_selection
) )
if TYPE_CHECKING:
from .tabs import Tab
from .child import Child
from typing import TypedDict
Tab, Child
else:
TypedDict = dict
MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]] MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]]
@ -183,8 +176,8 @@ class Window:
def __init__( def __init__(
self, self,
tab: 'Tab', tab: TabType,
child: 'Child', child: ChildType,
opts: Options, opts: Options,
args: CLIOptions, args: CLIOptions,
override_title: Optional[str] = None, override_title: Optional[str] = None,
@ -207,7 +200,7 @@ class Window:
raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id)) raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id))
self.tab_id = tab.id self.tab_id = tab.id
self.os_window_id = tab.os_window_id self.os_window_id = tab.os_window_id
self.tabref: Callable[[], Optional['Tab']] = weakref.ref(tab) self.tabref: Callable[[], Optional[TabType]] = weakref.ref(tab)
self.clipboard_control_buffers = {'p': '', 'c': ''} self.clipboard_control_buffers = {'p': '', 'c': ''}
self.destroyed = False self.destroyed = False
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
@ -221,7 +214,7 @@ class Window:
else: else:
setup_colors(self.screen, opts) setup_colors(self.screen, opts)
def change_tab(self, tab: 'Tab') -> None: def change_tab(self, tab: TabType) -> None:
self.tab_id = tab.id self.tab_id = tab.id
self.os_window_id = tab.os_window_id self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab) self.tabref = weakref.ref(tab)