From 382c31ddf20d7b1323ae4636f36518d4b2f2c243 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Mar 2020 13:27:40 +0530 Subject: [PATCH] Use a stub rather than TYPE_CHECKING --- kittens/ask/main.py | 4 +- kittens/hints/main.py | 20 ++++---- kittens/icat/main.py | 7 +-- kittens/tui/handler.py | 48 +++++++++---------- kittens/tui/images.py | 42 ++++++----------- kittens/tui/loop.py | 22 +++------ kittens/tui/operations.py | 20 +++----- kittens/unicode_input/main.py | 10 ++-- kitty/borders.py | 21 +++------ kitty/boss.py | 15 ++---- kitty/choose_entry.py | 10 ++-- kitty/cli.py | 41 +++++++--------- kitty/config.py | 7 +-- kitty/constants.py | 15 +++--- kitty/fonts/core_text.py | 24 ++++------ kitty/fonts/fontconfig.py | 24 ++++------ kitty/fonts/render.py | 11 ++--- kitty/key_encoding.py | 8 ++-- kitty/keys.py | 13 ++--- kitty/layout.py | 89 +++++++++++++++-------------------- kitty/remote_control.py | 9 ++-- kitty/session.py | 12 ++--- kitty/tabs.py | 12 ++--- kitty/typing.py | 21 +++++++++ kitty/typing.pyi | 51 ++++++++++++++++++++ kitty/utils.py | 16 +++---- kitty/window.py | 21 +++------ 27 files changed, 267 insertions(+), 326 deletions(-) create mode 100644 kitty/typing.py create mode 100644 kitty/typing.pyi diff --git a/kittens/ask/main.py b/kittens/ask/main.py index 666c18e25..57f432de3 100644 --- a/kittens/ask/main.py +++ b/kittens/ask/main.py @@ -9,13 +9,13 @@ from typing import TYPE_CHECKING, List, Optional, Tuple from kitty.cli import parse_args from kitty.cli_stub import AskCLIOptions from kitty.constants import cache_dir +from kitty.typing import BossType from ..tui.handler import result_handler from ..tui.operations import alternate_screen, styled if TYPE_CHECKING: import readline - import kitty else: readline = None @@ -127,7 +127,7 @@ def main(args: List[str]) -> Response: @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: func, *args = data['items'] getattr(boss, func)(data['response'], *args) diff --git a/kittens/hints/main.py b/kittens/hints/main.py index 3a57907d1..7f01d7aeb 100644 --- a/kittens/hints/main.py +++ b/kittens/hints/main.py @@ -10,8 +10,8 @@ from functools import lru_cache from gettext import gettext as _ from itertools import repeat from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Optional, - Pattern, Sequence, Set, Tuple, Type, cast + Any, Callable, Dict, Generator, Iterable, List, Optional, Pattern, + Sequence, Set, Tuple, Type, cast ) from kitty.cli import parse_args @@ -20,30 +20,26 @@ from kitty.fast_data_types import set_clipboard_string from kitty.key_encoding import ( KeyEvent, backspace_key, enter_key, key_defs as K ) +from kitty.typing import BossType, KittyCommonOpts from kitty.utils import ScreenSize, screen_size_function from ..tui.handler import Handler, result_handler from ..tui.loop import Loop from ..tui.operations import faint, styled -if TYPE_CHECKING: - from kitty.config import KittyCommonOpts - from kitty.boss import Boss - @lru_cache() -def kitty_common_opts() -> 'KittyCommonOpts': +def kitty_common_opts() -> KittyCommonOpts: import json v = os.environ.get('KITTY_COMMON_OPTS') if v: - return cast('KittyCommonOpts', json.loads(v)) + return cast(KittyCommonOpts, json.loads(v)) from kitty.config import common_opts_as_dict return common_opts_as_dict() DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$' -screen_size = screen_size_function() ESCAPE = K['ESCAPE'] @@ -346,7 +342,7 @@ def parse_input(text: str) -> str: try: cols = int(os.environ['OVERLAID_WINDOW_COLS']) except KeyError: - cols = screen_size().cols + cols = screen_size_function()().cols return convert_text(text, cols) @@ -560,7 +556,7 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]: 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']): if m: 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') -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']: m = load_custom_processor(data['customize_processing']) if 'handle_result' in m: diff --git a/kittens/icat/main.py b/kittens/icat/main.py index 690df93b1..4b1e9adbc 100755 --- a/kittens/icat/main.py +++ b/kittens/icat/main.py @@ -13,13 +13,13 @@ from functools import lru_cache from math import ceil from tempfile import NamedTemporaryFile from typing import ( - TYPE_CHECKING, Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, - Union + Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union ) from kitty.cli import parse_args from kitty.cli_stub import IcatCLIOptions from kitty.constants import appname +from kitty.typing import GRT_f, GRT_t from kitty.utils import ( TTYIO, ScreenSize, ScreenSizeGetter, fit_image, screen_size_function ) @@ -30,9 +30,6 @@ from ..tui.images import ( ) from ..tui.operations import clear_images_on_screen -if TYPE_CHECKING: - from ..tui.images import GRT_f, GRT_t # noqa - OPTIONS = '''\ --align type=choices diff --git a/kittens/tui/handler.py b/kittens/tui/handler.py index 0fe893cc7..22f1fc488 100644 --- a/kittens/tui/handler.py +++ b/kittens/tui/handler.py @@ -3,35 +3,29 @@ # License: GPL v3 Copyright: 2018, Kovid Goyal +from types import TracebackType from typing import ( - TYPE_CHECKING, Any, Callable, ContextManager, Dict, Optional, Sequence, Type, - Union + Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Union ) -if TYPE_CHECKING: - from kitty.utils import ScreenSize - from .loop import TermManager, Loop, Debug, MouseEvent - 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 +from kitty.typing import ( + AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType, + KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType +) class Handler: - image_manager_class: Optional[Type['ImageManager']] = None + image_manager_class: Optional[Type[ImageManagerType]] = None def _initialize( self, - screen_size: 'ScreenSize', - term_manager: 'TermManager', + screen_size: ScreenSize, + term_manager: TermManagerType, schedule_write: Callable[[bytes], None], - tui_loop: 'Loop', - debug: 'Debug', - image_manager: Optional['ImageManager'] = None + tui_loop: LoopType, + debug: Debug, + image_manager: Optional[ImageManagerType] = None ) -> None: from .operations import commander self.screen_size = screen_size @@ -43,15 +37,15 @@ class Handler: self._image_manager = image_manager @property - def image_manager(self) -> 'ImageManager': + def image_manager(self) -> ImageManagerType: assert self._image_manager is not None return self._image_manager @property - def asyncio_loop(self) -> 'asyncio.AbstractEventLoop': + def asyncio_loop(self) -> AbstractEventLoop: 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'): self._text_shortcuts, self._key_shortcuts = {}, {} if is_text: @@ -59,7 +53,7 @@ class Handler: else: 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): return self._text_shortcuts.get(key_event_or_text) 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.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 self.finalize() if self._image_manager is not None: @@ -82,7 +76,7 @@ class Handler: def finalize(self) -> None: pass - def on_resize(self, screen_size: 'ScreenSize') -> None: + def on_resize(self, screen_size: ScreenSize) -> None: self.screen_size = screen_size 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: pass - def on_key(self, key_event: 'KeyEvent') -> None: + def on_key(self, key_event: KeyEventType) -> None: pass def on_mouse(self, mouse_event: 'MouseEvent') -> None: @@ -127,7 +121,7 @@ class Handler: data = sep.join(map(str, args)) + end self.write(data) - def suspend(self) -> ContextManager['TermManager']: + def suspend(self) -> ContextManager[TermManagerType]: return self._term_manager.suspend() @@ -141,7 +135,7 @@ class HandleResult: self.no_ui = no_ui 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) diff --git a/kittens/tui/images.py b/kittens/tui/images.py index 71bd4fac7..8e71900ca 100644 --- a/kittens/tui/images.py +++ b/kittens/tui/images.py @@ -10,10 +10,13 @@ from collections import defaultdict, deque from contextlib import suppress from itertools import count from typing import ( - TYPE_CHECKING, Any, DefaultDict, Deque, Dict, List, Optional, Sequence, - Tuple, Union + Any, DefaultDict, Deque, Dict, List, Optional, Sequence, 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 .operations import cursor @@ -25,28 +28,11 @@ except Exception: 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: def __init__(self, fmt: str, width: int, height: int, mode: str): 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): @@ -71,7 +57,7 @@ class NoImageMagick(Exception): 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 try: 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: - a: 'GRT_a' = 't' # action - f: 'GRT_f' = 32 # image data format - t: 'GRT_t' = 'd' # transmission medium + a: GRT_a = 't' # action + f: GRT_f = 32 # image data format + t: GRT_t = 'd' # transmission medium s: int = 0 # sent image width v: int = 0 # sent image height S: int = 0 # size of data to read from file O: int = 0 # offset of data to read from file i: int = 0 # image id - o: Optional['GRT_o'] = None # type of compression - m: 'GRT_m' = 0 # 0 or 1 whether there is more chunked data + o: Optional[GRT_o] = None # type of compression + m: GRT_m = 0 # 0 or 1 whether there is more chunked data x: int = 0 # left edge of image area to display y: int = 0 # top edge of image area 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 r: int = 0 # number of rows to display image over 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: items = [] @@ -193,7 +179,7 @@ class Placement(TypedDict): class ImageManager: - def __init__(self, handler: 'Handler'): + def __init__(self, handler: HandlerType): self.image_id_counter = count() self.handler = handler self.filesystem_ok: Optional[bool] = None diff --git a/kittens/tui/loop.py b/kittens/tui/loop.py index 6dfcc3e8a..5530e4798 100644 --- a/kittens/tui/loop.py +++ b/kittens/tui/loop.py @@ -12,9 +12,7 @@ import signal import sys from contextlib import contextmanager from functools import partial -from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Generator, List, NamedTuple, Optional -) +from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional from kitty.constants import is_macos 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, 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 .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'] @@ -156,7 +146,7 @@ class UnhandledException(Handler): self.write('\r\n') 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: self.quit_loop(1) @@ -355,7 +345,7 @@ class Loop: self.return_code = return_code 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 = [] tty_fd = term_manager.tty_fd tb = None @@ -399,7 +389,7 @@ class Loop: signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term) 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 if handler.image_manager_class is not None: image_manager = handler.image_manager_class(handler) diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index efcd48a92..9541d78cd 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -6,20 +6,15 @@ import sys from contextlib import contextmanager from functools import wraps from typing import ( - IO, TYPE_CHECKING, Any, Callable, Dict, Generator, Optional, Tuple, - TypeVar, Union + IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union ) from kitty.rgb import Color, color_as_sharp, to_color +from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize from .operations_stub import CMD -if TYPE_CHECKING: - from kitty.utils import ScreenSize - from .images import GraphicsCommand - from .handler import Handler - ScreenSize, GraphicsCommand, Handler - +GraphicsCommandType, ScreenSize # needed for stub generation S7C1T = '\033 F' SAVE_CURSOR = '\0337' RESTORE_CURSOR = '\0338' @@ -232,7 +227,7 @@ def serialize_gr_command(cmd: Dict[str, Union[int, str]], payload: Optional[byte @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): raw = serialize_gr_command(cmd, payload) else: @@ -349,14 +344,14 @@ def request_from_clipboard(use_primary: bool = False) -> str: # Boilerplate to make operations availble via Handler.cmd {{{ -def writer(handler: 'Handler', func: Callable) -> Callable: +def writer(handler: HandlerType, func: Callable) -> Callable: @wraps(func) def f(*a: Any, **kw: Any) -> None: handler.write(func(*a, **kw)) return f -def commander(handler: 'Handler') -> CMD: +def commander(handler: HandlerType) -> CMD: ans = CMD() for name, func in all_cmds.items(): setattr(ans, name, writer(handler, func)) @@ -374,8 +369,7 @@ def func_sig(func: Callable) -> Generator[str, None, None]: def as_type_stub() -> str: ans = [ 'from typing import * # noqa', - 'from kitty.utils import ScreenSize', - 'from kittens.tui.images import GraphicsCommand', + 'from kitty.typing import GraphicsCommandType, ScreenSize', 'from kitty.rgb import Color', 'import kitty.rgb', ] diff --git a/kittens/unicode_input/main.py b/kittens/unicode_input/main.py index 0681f731a..f05fadf61 100644 --- a/kittens/unicode_input/main.py +++ b/kittens/unicode_input/main.py @@ -10,7 +10,7 @@ from contextlib import suppress from functools import lru_cache from gettext import gettext as _ from typing import ( - TYPE_CHECKING, Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple, + Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple, Union ) @@ -22,6 +22,7 @@ from kitty.fast_data_types import is_emoji_presentation_base, wcswidth from kitty.key_encoding import ( CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K ) +from kitty.typing import BossType from kitty.utils import ScreenSize, get_editor from ..tui.handler import Handler, result_handler @@ -32,11 +33,6 @@ from ..tui.operations import ( sgr, styled ) -if TYPE_CHECKING: - from kitty.boss import Boss - Boss - - HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES' UP = K['UP'] DOWN = K['DOWN'] @@ -579,7 +575,7 @@ def main(args: List[str]) -> Optional[str]: @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) if w is not None: w.paste(current_char) diff --git a/kitty/borders.py b/kitty/borders.py index 98d8974e0..726400284 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -2,8 +2,9 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +from enum import IntFlag 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 .fast_data_types import ( @@ -12,17 +13,7 @@ from .fast_data_types import ( ) from .options_stub import Options from .utils import load_shaders - -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 +from .typing import WindowType, LayoutType class BorderColor(IntFlag): @@ -63,9 +54,9 @@ class Borders: def __call__( self, - windows: List['Window'], - active_window: Optional['Window'], - current_layout: 'Layout', + windows: List[WindowType], + active_window: Optional[WindowType], + current_layout: LayoutType, extra_blank_rects: Sequence[Tuple[int, int, int, int]], padding_width: int, border_width: int, diff --git a/kitty/boss.py b/kitty/boss.py index 7cde99e67..448c77ed7 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -10,8 +10,7 @@ from contextlib import suppress from functools import partial from gettext import gettext as _ from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, Optional, - Tuple, Union + Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union ) from weakref import WeakValueDictionary @@ -44,6 +43,7 @@ from .session import Session, create_sessions from .tabs import ( SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager ) +from .typing import PopenType, TypedDict from .utils import ( func_name, get_editor, get_primary_selection, is_path_in_temp_dir, log_error, open_url, parse_address_spec, remove_socket_file, safe_print, @@ -51,13 +51,6 @@ from .utils import ( ) 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): id: int @@ -149,7 +142,7 @@ class Boss: ): set_layout_options(opts) 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.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)} self.startup_cursor_text_color = opts.cursor_text_color @@ -1209,7 +1202,7 @@ class Boss: with suppress(FileNotFoundError): 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: with suppress(Exception): if self.update_check_process.poll() is None: diff --git a/kitty/choose_entry.py b/kitty/choose_entry.py index 05b511eac..2e9d0e6aa 100644 --- a/kitty/choose_entry.py +++ b/kitty/choose_entry.py @@ -4,15 +4,13 @@ import re -from typing import TYPE_CHECKING, List, Generator, Any, Type +from typing import List, Generator, Any, Type -if TYPE_CHECKING: - from kitty.cli_stub import HintsCLIOptions - from kittens.hints.main import Mark as MarkClass - HintsCLIOptions, MarkClass +from .cli_stub import HintsCLIOptions +from .typing import MarkType -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)): start, end = m.span() mark_text = text[start:end].replace('\n', '').replace('\0', '') diff --git a/kitty/cli.py b/kitty/cli.py index a391dde4f..81883c1bb 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -7,32 +7,27 @@ import re import sys from collections import deque from typing import ( - TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, - Match, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast + Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, Match, Optional, + Sequence, Set, Tuple, Type, TypeVar, Union, cast ) from .cli_stub import CLIOptions from .conf.utils import resolve_config +from .config import KeyAction from .constants import appname, defconf, is_macos, is_wayland, str_version from .options_stub import Options as OptionsStub +from .typing import BadLineType, KeySpec, SequenceMap, TypedDict -try: - from typing import TypedDict - class OptionDict(TypedDict): - dest: str - aliases: FrozenSet[str] - help: str - choices: FrozenSet[str] - type: str - default: Optional[str] - condition: bool -except ImportError: - OptionDict = Dict[str, Any] # type: ignore +class OptionDict(TypedDict): + dest: str + aliases: FrozenSet[str] + help: str + choices: FrozenSet[str] + type: str + default: Optional[str] + condition: bool -if TYPE_CHECKING: - from .config import KeyAction, KeySpec, SequenceMap # noqa - from .conf.utils import BadLine # noqa CONFIG_HELP = '''\ 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' -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): from kitty.keys import defines v = vars(defines) @@ -764,7 +759,7 @@ def print_shortcut(key_sequence: Iterable['KeySpec'], action: 'KeyAction') -> No 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: print(title(text)) @@ -781,8 +776,8 @@ def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None: print_shortcut_changes(final, 'Changed shortcuts:', changed) -def flatten_sequence_map(m: 'SequenceMap') -> ShortcutMap: - ans: Dict[Tuple['KeySpec', ...], 'KeyAction'] = {} +def flatten_sequence_map(m: SequenceMap) -> ShortcutMap: + ans: Dict[Tuple[KeySpec, ...], KeyAction] = {} for key_spec, rest_map in m.items(): for r, action in rest_map.items(): ans[(key_spec,) + (r)] = action @@ -811,7 +806,7 @@ def compare_opts(opts: OptionsStub) -> None: 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 config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config)) if debug_config: diff --git a/kitty/config.py b/kitty/config.py index 2c44b034a..aeaa8e87a 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -23,14 +23,9 @@ from .config_data import all_options, parse_mods, type_convert from .constants import cache_dir, defconf, is_macos from .key_names import get_key_name_lookup, key_name_aliases from .options_stub import Options as OptionsStub +from .typing import TypedDict from .utils import log_error -try: - from typing import TypedDict -except ImportError: - TypedDict = dict - - KeySpec = Tuple[int, bool, int] KeyMap = Dict[KeySpec, 'KeyAction'] KeySequence = Tuple[KeySpec, ...] diff --git a/kitty/constants.py b/kitty/constants.py index 7326ed60b..469cf9cf8 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -8,10 +8,9 @@ import pwd import sys from contextlib import suppress 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 # noqa +from .options_stub import Options class Version(NamedTuple): @@ -20,11 +19,11 @@ class Version(NamedTuple): patch: int -appname = 'kitty' -version = Version(0, 16, 0) -str_version = '.'.join(map(str, version)) +appname: str = 'kitty' +version: Version = Version(0, 16, 0) +str_version: str = '.'.join(map(str, version)) _plat = sys.platform.lower() -is_macos = 'darwin' in _plat +is_macos: bool = 'darwin' in _plat base = os.path.dirname(os.path.abspath(__file__)) @@ -179,7 +178,7 @@ def detect_if_wayland_ok() -> bool: return ans == b'YES' -def is_wayland(opts: Optional['Options'] = None) -> bool: +def is_wayland(opts: Optional[Options] = None) -> bool: if is_macos: return False if opts is None: diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index ee5417ed9..20567a3a1 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -3,31 +3,25 @@ # License: GPL v3 Copyright: 2017, Kovid Goyal import re -from typing import ( - TYPE_CHECKING, Dict, Generator, Iterable, List, Optional, Tuple -) +from typing import Dict, Generator, Iterable, List, Optional, Tuple from kitty.fast_data_types import coretext_all_fonts from kitty.options_stub import Options +from kitty.typing import CoreTextFont from kitty.utils import log_error from . import ListedFont -if TYPE_CHECKING: - from kitty.fast_data_types import CoreTextFont as C - CoreTextFont = C - - attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): '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': {}} for x in all_fonts: 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} -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()) 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[ 'italic' ] == italic else 0 @@ -90,8 +84,8 @@ def resolve_family(f: str, main_family: str, bold: bool = False, italic: bool = return f -def get_font_files(opts: Options) -> Dict[str, 'CoreTextFont']: - ans: Dict[str, 'CoreTextFont'] = {} +def get_font_files(opts: Options) -> Dict[str, CoreTextFont]: + ans: Dict[str, CoreTextFont] = {} for (bold, italic), attr in attr_map.items(): face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic) key = {(False, False): 'medium', @@ -104,6 +98,6 @@ def get_font_files(opts: Options) -> Dict[str, 'CoreTextFont']: 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'))) return ans, ans['bold'], ans['italic'] diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index dfa9d073b..a50cd3e68 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -4,31 +4,27 @@ import re 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 ( FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, fc_list, fc_match as fc_match_impl ) from kitty.options_stub import Options +from kitty.typing import FontConfigPattern from . import ListedFont -if TYPE_CHECKING: - from kitty.fast_data_types import FontConfigPattern as F - FontConfigPattern = F - - attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): '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': {}} for x in all_fonts: if 'path' not in x: @@ -69,15 +65,15 @@ def family_name_to_key(family: str) -> str: @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) -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) 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)) 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 @@ -118,8 +114,8 @@ def resolve_family(f: str, main_family: str, bold: bool, italic: bool) -> str: return f -def get_font_files(opts: Options) -> Dict[str, 'FontConfigPattern']: - ans: Dict[str, 'FontConfigPattern'] = {} +def get_font_files(opts: Options) -> Dict[str, FontConfigPattern]: + ans: Dict[str, FontConfigPattern] = {} for (bold, italic), attr in attr_map.items(): rf = resolve_family(getattr(opts, attr), opts.font_family, bold, italic) font = find_best_match(rf, bold, italic) @@ -131,6 +127,6 @@ def get_font_files(opts: Options) -> Dict[str, 'FontConfigPattern']: 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) return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 02aed38b3..157834845 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -6,9 +6,7 @@ import ctypes import sys from functools import partial from math import ceil, cos, floor, pi -from typing import ( - TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union, cast -) +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast from kitty.config import defaults from kitty.constants import is_macos @@ -21,18 +19,15 @@ from kitty.fonts.box_drawing import ( BufType, render_box_char, render_missing_glyph ) from kitty.options_stub import Options as OptionsStub +from kitty.typing import CoreTextFont, FontConfigPattern from kitty.utils import log_error if is_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: 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]] = [] diff --git a/kitty/key_encoding.py b/kitty/key_encoding.py index fa8598c6c..befd88700 100644 --- a/kitty/key_encoding.py +++ b/kitty/key_encoding.py @@ -3,7 +3,7 @@ # License: GPL v3 Copyright: 2017, Kovid Goyal import string -from typing import NamedTuple, Optional +from typing import Dict, NamedTuple, Optional from . import fast_data_types as defines from .key_names import key_name_aliases @@ -457,14 +457,16 @@ class KeyEvent(NamedTuple): 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 type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE} rtype_map = {v: k for k, v in type_map.items()} mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')} rmod_map = {v: k for k, v in mod_map.items()} key_rmap = {} -key_defs = {} +key_defs: Dict[str, str] = {} config_key_map = {} config_mod_map = { 'SHIFT': SHIFT, diff --git a/kitty/keys.py b/kitty/keys.py index df0222244..1533e7b46 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -3,20 +3,15 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import string -from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Iterable, Optional, Tuple, Union -) +from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union from . import fast_data_types as defines from .config import KeyAction, KeyMap, KeySpec, SequenceMap, SubSequenceMap from .key_encoding import KEY_MAP from .terminfo import key_as_bytes, modify_key_bytes +from .typing import ScreenType, WindowType 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: 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} -def keyboard_mode_name(screen: 'Screen') -> str: +def keyboard_mode_name(screen: ScreenType) -> str: if screen.extended_keyboard: return 'kitty' 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) -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 if ( action == defines.GLFW_PRESS or diff --git a/kitty/layout.py b/kitty/layout.py index 9032bea49..59d13d0b6 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -5,7 +5,7 @@ from functools import lru_cache, partial from itertools import islice, repeat 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 ) @@ -14,20 +14,7 @@ from .fast_data_types import ( Region, set_active_window, swap_windows, viewport_for_window ) from .options_stub import Options - -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 +from .typing import WindowType, EdgeLiteral, TypedDict class Borders(NamedTuple): @@ -53,7 +40,7 @@ draw_active_borders = True align_top_left = False LayoutDimension = Generator[Tuple[int, int], None, None] DecorationPairs = Sequence[Tuple[int, int]] -WindowList = Union[List[Window], Deque[Window]] +WindowList = Union[List[WindowType], Deque[WindowType]] class InternalNeighborsMap(TypedDict): @@ -70,7 +57,7 @@ class NeighborsMap(TypedDict): 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): if w.id == win_id: return i @@ -134,7 +121,7 @@ class Rect(NamedTuple): 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} 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] @@ -151,25 +138,25 @@ def layout_single_window(xdecoration_pairs: DecorationPairs, ydecoration_pairs: 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 if lt > central.left: 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 if r < central.right: 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 if t > central.top: 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 # 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 @@ -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)) -def blank_rects_for_window(w: Window) -> List[Rect]: +def blank_rects_for_window(w: WindowType) -> List[Rect]: ans: List[Rect] = [] left_blank_rect(w, ans) top_blank_rect(w, ans) @@ -288,7 +275,7 @@ class Layout: # {{{ data[k] = v 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] w = windows[min(num, len(windows) - 1)] return w @@ -370,7 +357,7 @@ class Layout: # {{{ 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] - 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 if window.overlay_for is not None: 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) 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 if location is not None: 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) 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: active_window = all_windows[current_active_window_idx] except Exception: @@ -443,7 +430,7 @@ class Layout: # {{{ self(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: overlaid_windows = process_overlaid_windows(all_windows)[0] for i, w in enumerate(all_windows): @@ -492,7 +479,7 @@ class Layout: # {{{ return cast(int, idx_for_id(active_window.id, all_windows)) # 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 decoration_pairs = ((self.padding_width + mw, self.padding_width + mw),) wg = layout_single_window(decoration_pairs, decoration_pairs) @@ -521,19 +508,19 @@ class Layout: # {{{ height = central.height 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 left_blank_rect(first_window, br) top_blank_rect(first_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: self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1)) else: 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)) # }}} @@ -543,24 +530,24 @@ class Layout: # {{{ def do_layout_all_windows(self, windows: WindowList, active_window_idx: int, all_windows: WindowList) -> None: 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': []} - 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} - 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: needs_borders_map = self.compute_needs_borders_map(windows, active_window) yield from self.minimal_borders(windows, active_window, needs_borders_map) else: 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 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: if (w is active_window and draw_active_borders) or w.needs_attention: yield all_borders @@ -592,7 +579,7 @@ class Stack(Layout): # {{{ # 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) prev = None if idx == 0 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 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) - 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 for i, w in enumerate(windows): if needs_borders_map[w.id]: @@ -778,7 +765,7 @@ class Fat(Tall): # {{{ # left, top and right blank rects 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) prev = None if idx == 0 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): 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) ncols, nrows, special_rows, special_col = calc_grid_size(n) 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] ) - def neighbors_for_window(self, window: Window, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: n = len(windows) if n < 4: return neighbors_for_tall_window(1, window, windows) @@ -1051,7 +1038,7 @@ class Vertical(Layout): # {{{ # left, top and right blank rects 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 for i, w in enumerate(windows): if needs_borders_map[w.id]: @@ -1065,7 +1052,7 @@ class Vertical(Layout): # {{{ else: 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) before = [] if window is windows[0] 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)) 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.set_geometry(id_idx_map[window_id], window_geometry) if w.overlay_window_id is not None: @@ -1228,7 +1215,7 @@ class Pair: if q is not None: 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 bottom = top + height - 1 g = window.geometry @@ -1249,7 +1236,7 @@ class Pair: def layout_pair( self, 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], layout_object: Layout ) -> None: @@ -1436,7 +1423,7 @@ class Splits(Layout): def do_add_window( self, all_windows: WindowList, - window: Window, + window: WindowType, current_active_window_idx: Optional[int], location: Optional[str] ) -> int: @@ -1490,14 +1477,14 @@ class Splits(Layout): pair.bias = 0.5 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: return for pair in self.pairs_root.self_and_descendants(): if pair.between_border is not None: 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 pair = self.pairs_root.pair_for_window(window_id) ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 6c570ccbb..01b7febe3 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -10,7 +10,7 @@ import types from contextlib import suppress from functools import partial from typing import ( - TYPE_CHECKING, Any, Dict, Generator, Iterable, List, Optional, Tuple, + Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast ) @@ -22,14 +22,11 @@ from .rc.base import ( PayloadGetter, all_command_names, command_for_name, no_response as no_response_sentinel, parse_subcommand_cli ) +from .typing import BossType, WindowType from .utils import TTYIO, parse_address_spec -if TYPE_CHECKING: - from .boss import Boss # noqa - from .window import Window # noqa - -def handle_cmd(boss: 'Boss', window: Optional['Window'], serialized_cmd: str) -> Optional[Dict[str, Any]]: +def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str) -> Optional[Dict[str, Any]]: cmd = json.loads(serialized_cmd) v = cmd['version'] no_response = cmd.get('no_response', False) diff --git a/kitty/session.py b/kitty/session.py index 514c0ef19..b31bffd67 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -4,20 +4,16 @@ import shlex import sys -from typing import ( - TYPE_CHECKING, Generator, List, NamedTuple, Optional, Tuple, Union -) +from typing import Generator, List, NamedTuple, Optional, Tuple, Union +from .cli_stub import CLIOptions from .config_data import to_layout_names from .constants import kitty_exe from .layout import all_layouts from .options_stub import Options +from .typing import SpecialWindowInstance from .utils import log_error, resolved_shell -if TYPE_CHECKING: - from .tabs import SpecialWindowInstance # noqa - from .cli_stub import CLIOptions # noqa - class WindowSizeOpts(NamedTuple): @@ -135,7 +131,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None) def create_sessions( opts: Options, - args: Optional['CLIOptions'] = None, + args: Optional[CLIOptions] = None, special_window: Optional['SpecialWindowInstance'] = None, cwd_from: Optional[int] = None, respect_cwd: bool = False, diff --git a/kitty/tabs.py b/kitty/tabs.py index e735d933a..2fe7a2fa2 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -7,7 +7,7 @@ from collections import deque from contextlib import suppress from functools import partial from typing import ( - TYPE_CHECKING, Deque, Dict, Generator, Iterator, List, NamedTuple, + Deque, Dict, Generator, Iterator, List, NamedTuple, Optional, Pattern, Sequence, Tuple, cast ) @@ -27,13 +27,7 @@ from .options_stub import Options from .tab_bar import TabBar, TabBarData from .utils import log_error, resolved_shell from .window import Window, WindowDict - -if TYPE_CHECKING: - from .session import Session, Tab as SessionTab - SessionTab, Session - from typing import TypedDict -else: - TypedDict = dict +from .typing import TypedDict, SessionTab, SessionType class TabDict(TypedDict): @@ -555,7 +549,7 @@ class Tab: # {{{ 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.last_active_tab_id = None self.opts, self.args = opts, args diff --git a/kitty/typing.py b/kitty/typing.py new file mode 100644 index 000000000..268bba293 --- /dev/null +++ b/kitty/typing.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2020, Kovid Goyal + +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 diff --git a/kitty/typing.pyi b/kitty/typing.pyi new file mode 100644 index 000000000..2bd837bde --- /dev/null +++ b/kitty/typing.pyi @@ -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'] diff --git a/kitty/utils.py b/kitty/utils.py index f7ea1b5c9..eef9197a9 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -14,8 +14,8 @@ from contextlib import suppress from functools import lru_cache from time import monotonic from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, - NamedTuple, Optional, Tuple, Union, cast + Any, Callable, Dict, Generator, Iterable, List, NamedTuple, Optional, + Tuple, Union, cast ) from .constants import ( @@ -23,11 +23,7 @@ from .constants import ( ) from .options_stub import Options from .rgb import Color, to_color - -if TYPE_CHECKING: - from subprocess import Popen # noqa - from .fast_data_types import StartupCtx # noqa - from socket import socket as Socket, AddressFamily # noqa +from .typing import AddressFamily, PopenType, Socket, StartupCtx 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 -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 if arg is not None: 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) -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) @@ -367,7 +363,7 @@ class 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 protocol, rest = spec.split(':', 1) socket_path = None diff --git a/kitty/window.py b/kitty/window.py index b814b6984..541a37105 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -10,8 +10,8 @@ from collections import deque from enum import IntEnum from itertools import chain from typing import ( - TYPE_CHECKING, Any, Callable, Deque, Dict, List, Optional, Pattern, - Sequence, Tuple, Union + Any, Callable, Deque, Dict, List, Optional, Pattern, Sequence, Tuple, + Union ) from .child import ProcessDesc @@ -32,20 +32,13 @@ from .keys import defines, extended_key_event, keyboard_mode_name from .options_stub import Options from .rgb import to_color from .terminfo import get_capabilities +from .typing import ChildType, TabType, TypedDict from .utils import ( color_as_int, get_primary_selection, load_shaders, open_cmd, open_url, parse_color_set, read_shell_environment, sanitize_title, 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]]]] @@ -183,8 +176,8 @@ class Window: def __init__( self, - tab: 'Tab', - child: 'Child', + tab: TabType, + child: ChildType, opts: Options, args: CLIOptions, 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)) self.tab_id = tab.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.destroyed = False self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) @@ -221,7 +214,7 @@ class Window: else: 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.os_window_id = tab.os_window_id self.tabref = weakref.ref(tab)