diff --git a/docs/conf.py b/docs/conf.py index 7816b3a42..b3944e6cf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,28 +7,28 @@ # full list see the documentation: # https://www.sphinx-doc.org/en/master/config -import importlib import os import re import subprocess import sys import time from functools import partial +from typing import Optional, Dict, Tuple from docutils import nodes from docutils.parsers.rst.roles import set_classes -from pygments.lexer import RegexLexer, bygroups -from pygments.token import ( +from pygments.lexer import RegexLexer, bygroups # type: ignore +from pygments.token import ( # type: ignore Comment, Keyword, Literal, Name, Number, String, Whitespace ) -from sphinx import addnodes -from sphinx.environment.adapters.toctree import TocTree -from sphinx.util.logging import getLogger +from sphinx import addnodes # type: ignore +from sphinx.environment.adapters.toctree import TocTree # type: ignore +from sphinx.util.logging import getLogger # type: ignore kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if kitty_src not in sys.path: sys.path.insert(0, kitty_src) -str_version = importlib.import_module('kitty.constants').str_version +from kitty.constants import str_version # noqa # config {{{ # -- Project information ----------------------------------------------------- @@ -77,7 +77,7 @@ master_doc = 'index' # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language: Optional[str] = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -205,7 +205,8 @@ def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[] def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): ' Link to a github commit ' try: - commit_id = subprocess.check_output(f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip() + commit_id = subprocess.check_output( + f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip() except Exception: msg = inliner.reporter.error( f'GitHub commit id "{text}" not recognized.', line=lineno) @@ -213,7 +214,8 @@ def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]): return [prb], [msg] url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}' set_classes(options) - short_id = subprocess.check_output(f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip() + short_id = subprocess.check_output( + f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip() node = nodes.reference(rawtext, f'commit: {short_id}', refuri=url, **options) return [node], [] # }}} @@ -409,8 +411,8 @@ def expand_opt_references(conf_name, text): return re.sub(r':opt:`(.+?)`', expand, text) -opt_aliases = {} -shortcut_slugs = {} +opt_aliases: Dict[str, str] = {} +shortcut_slugs: Dict[str, Tuple[str, str]] = {} def parse_opt_node(env, sig, signode): diff --git a/kitty/boss.py b/kitty/boss.py index 6457264fd..06fa14f2e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -10,6 +10,7 @@ from contextlib import suppress from functools import partial from gettext import gettext as _ from weakref import WeakValueDictionary +from typing import Optional from .child import cached_process_data, cwd_of_process from .cli import create_opts, parse_args @@ -43,8 +44,8 @@ from .window import Window def notification_activated(identifier: str) -> None: if identifier == 'new-version': - from .update_check import notification_activated - notification_activated() + from .update_check import notification_activated as do + do() def listen_on(spec: str) -> int: @@ -57,7 +58,7 @@ def listen_on(spec: str) -> int: return s.fileno() -def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> str: +def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> Optional[str]: def as_text(**kw) -> str: kw['add_wrap_markers'] = add_wrap_markers return w.as_text(**kw) @@ -80,6 +81,7 @@ def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> str: return as_text(as_ansi=True, alternate_screen=True) if arg == '@ansi_alternate_scrollback': return as_text(as_ansi=True, alternate_screen=True, add_history=True) + return None class DumpCommands: # {{{ @@ -935,7 +937,7 @@ class Boss: stdin = stdin.encode('utf-8') return env, stdin - def data_for_at(self, which, window=None, add_wrap_markers=False): + def data_for_at(self, which, window=None, add_wrap_markers=False) -> Optional[str]: return data_for_at(window or self.active_window, which, add_wrap_markers=add_wrap_markers) def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False): diff --git a/kitty/child.py b/kitty/child.py index ba7239007..55074b10b 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -7,7 +7,7 @@ import os import sys from collections import defaultdict from contextlib import contextmanager, suppress -from typing import DefaultDict, List +from typing import DefaultDict, List, Optional import kitty.fast_data_types as fast_data_types @@ -158,7 +158,8 @@ def openpty(): class Child: - child_fd = pid = None + child_fd: Optional[int] = None + pid: Optional[int] = None forked = False def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None, allow_remote_control=False): diff --git a/kitty/cli.py b/kitty/cli.py index 4608859b5..8426e9fd8 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -7,13 +7,13 @@ import re import sys from collections import deque from typing import ( - Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Tuple, Type, - TypeVar, Union, cast + Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Sequence, Tuple, + Type, TypeVar, Union, cast ) +from .cli_stub import CLIOptions from .conf.utils import resolve_config from .constants import appname, defconf, is_macos, is_wayland, str_version -from .cli_stub import CLIOptions from .options_stub import Options as OptionsStub try: @@ -135,7 +135,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option disabled: OptionSpecSeq = [] mpat = re.compile('([a-z]+)=(.+)') current_cmd: OptionDict = { - 'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(), 'type': '', 'condition': False, 'default': None + 'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(), + 'type': '', 'condition': False, 'default': None } empty_cmd = current_cmd @@ -150,7 +151,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option if line.startswith('--'): parts = line.split(' ') current_cmd = { - 'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '', 'choices': frozenset(), 'type': '', + 'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '', + 'choices': frozenset(), 'type': '', 'default': None, 'condition': True } state = METADATA @@ -335,7 +337,13 @@ class PrintHelpForSeq: print_help_for_seq = PrintHelpForSeq() -def seq_as_rst(seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str], heading_char: str = '-') -> str: +def seq_as_rst( + seq: OptionSpecSeq, + usage: Optional[str], + message: Optional[str], + appname: Optional[str], + heading_char: str = '-' +) -> str: import textwrap blocks: List[str] = [] a = blocks.append @@ -382,7 +390,7 @@ def seq_as_rst(seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], return text -def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str) -> str: +def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, extra_fields: Sequence[str] = ()) -> str: from itertools import chain ans: List[str] = ['class {}:'.format(class_name)] for opt in chain(seq, disabled): @@ -401,6 +409,8 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str) - else: raise ValueError('Unknown CLI option type: {}'.format(otype)) ans.append(' {}: {}'.format(name, t)) + for x in extra_fields: + ans.append(' {}'.format(x)) return '\n'.join(ans) + '\n\n\n' diff --git a/kitty/cli_stub.py b/kitty/cli_stub.py index fc196fca0..bc256b9ad 100644 --- a/kitty/cli_stub.py +++ b/kitty/cli_stub.py @@ -3,6 +3,9 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal +from typing import Sequence + + class CLIOptions: pass @@ -17,9 +20,9 @@ def generate_stub() -> None: from .conf.definition import save_type_stub text = 'import typing\n\n\n' - def do(otext=None, cls: str = 'CLIOptions'): + def do(otext=None, cls: str = 'CLIOptions', extra_fields: Sequence[str] = ()): nonlocal text - text += as_type_stub(*parse_option_spec(otext), class_name=cls) + text += as_type_stub(*parse_option_spec(otext), class_name=cls, extra_fields=extra_fields) do() @@ -27,7 +30,7 @@ def generate_stub() -> None: do(options_spec(), 'LaunchCLIOptions') from .remote_control import global_options_spec - do(global_options_spec(), 'RCOptions') + do(global_options_spec(), 'RCOptions', extra_fields=['no_command_response: typing.Optional[bool]']) from kittens.ask.main import option_text do(option_text(), 'AskCLIOptions') diff --git a/kitty/complete.py b/kitty/complete.py index ee1d2ea26..1f4439dd1 100644 --- a/kitty/complete.py +++ b/kitty/complete.py @@ -6,7 +6,7 @@ import os import shlex import sys from functools import lru_cache -from typing import Tuple +from typing import Callable, Dict, Tuple from kittens.runner import all_kitten_names, get_kitten_cli_docs @@ -33,7 +33,8 @@ taking the results from kitty's completion system and converting them into something your shell will understand. ''' -parsers, serializers = {}, {} +parsers: Dict[str, Callable] = {} +serializers: Dict[str, Callable] = {} def debug(*a, **kw): diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index e0d74442e..468d5f6c1 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -7,7 +7,7 @@ import re import shlex from collections import namedtuple from typing import ( - Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Sequence, Tuple, + Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Tuple, Type, Union ) @@ -118,7 +118,7 @@ def parse_line( def _parse( - lines: Iterator[str], + lines: Iterable[str], type_convert: Callable[[str, Any], Any], special_handling: Callable, ans: Dict[str, Any], @@ -144,7 +144,7 @@ def _parse( def parse_config_base( - lines: Iterator[str], + lines: Iterable[str], defaults: Any, type_convert: Callable[[str, Any], Any], special_handling: Callable, @@ -158,7 +158,7 @@ def parse_config_base( ) -def create_options_class(all_keys: Iterator[str]) -> Type: +def create_options_class(all_keys: Iterable[str]) -> Type: keys = tuple(sorted(all_keys)) slots = keys + ('_fields', ) @@ -223,10 +223,10 @@ def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Seq def load_config( Options: Type, defaults: Any, - parse_config: Callable[[Iterator[str]], Dict[str, Any]], + parse_config: Callable[[Iterable[str]], Dict[str, Any]], merge_configs: Callable[[Dict, Dict], Dict], *paths: str, - overrides: Optional[Iterator[str]] = None + overrides: Optional[Iterable[str]] = None ): ans: Dict = defaults._asdict() for path in paths: @@ -244,7 +244,7 @@ def load_config( return Options(ans) -def init_config(default_config_lines: Iterator[str], parse_config: Callable): +def init_config(default_config_lines: Iterable[str], parse_config: Callable): defaults = parse_config(default_config_lines, check_keys=False) Options = create_options_class(defaults.keys()) defaults = Options(defaults) diff --git a/kitty/config.py b/kitty/config.py index e8a4f9620..51fdcb5e0 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -10,8 +10,8 @@ from collections import namedtuple from contextlib import contextmanager, suppress from functools import partial from typing import ( - TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Sequence, Tuple, Type, - cast + TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, + Sequence, Set, Tuple, Type, cast ) from . import fast_data_types as defines @@ -310,7 +310,7 @@ def scroll_to_mark(func, rest): return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))] -def parse_key_action(action): +def parse_key_action(action: str) -> Optional[KeyAction]: parts = action.strip().split(maxsplit=1) func = parts[0] if len(parts) == 1: @@ -324,9 +324,10 @@ def parse_key_action(action): log_error('Ignoring invalid key action: {} with err: {}'.format(action, err)) else: return KeyAction(func, args) + return None -all_key_actions = set() +all_key_actions: Set[str] = set() sequence_sep = '>' @@ -454,16 +455,17 @@ def parse_send_text(val, key_definitions): return parse_key(key_str, key_definitions) -special_handlers = {} +SpecialHandlerFunc = Callable[[str, str, Dict[str, Any]], None] +special_handlers: Dict[str, SpecialHandlerFunc] = {} -def special_handler(func): +def special_handler(func: SpecialHandlerFunc) -> SpecialHandlerFunc: special_handlers[func.__name__.partition('_')[2]] = func return func -def deprecated_handler(*names): - def special_handler(func): +def deprecated_handler(*names: str) -> Callable[[SpecialHandlerFunc], SpecialHandlerFunc]: + def special_handler(func: SpecialHandlerFunc) -> SpecialHandlerFunc: for name in names: special_handlers[name] = func return func @@ -588,7 +590,7 @@ def option_names_for_completion(): yield from special_handlers -def parse_config(lines: Iterator[str], check_keys=True, accumulate_bad_lines: Optional[List[BadLine]] = None): +def parse_config(lines: Iterable[str], check_keys=True, accumulate_bad_lines: Optional[List[BadLine]] = None): ans: Dict[str, Any] = { 'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_features': {} @@ -777,7 +779,7 @@ def finalize_keys(opts: OptionsStub) -> None: opts.sequence_map = sequence_map -def load_config(*paths: str, overrides: Optional[Iterator[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> OptionsStub: +def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> OptionsStub: parser = parse_config if accumulate_bad_lines is not None: parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines) diff --git a/kitty/constants.py b/kitty/constants.py index d53b7a5da..5fa033b30 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -175,4 +175,4 @@ supports_primary_selection = not is_macos def running_in_kitty(set_val: Optional[bool] = None) -> bool: if set_val is not None: setattr(running_in_kitty, 'ans', set_val) - return getattr(running_in_kitty, 'ans', False) + return bool(getattr(running_in_kitty, 'ans', False)) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 60dfaab6d..0879632e0 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -348,7 +348,11 @@ StartupCtx = NewType('StartupCtx', int) Display = NewType('Display', int) -def init_x11_startup_notification(display: Display, window_id: int, startup_id: Optional[str] = None) -> StartupCtx: +def init_x11_startup_notification( + display: Display, + window_id: int, + startup_id: Optional[str] = None +) -> StartupCtx: pass @@ -387,7 +391,10 @@ def default_color_table() -> Tuple[int, ...]: FontConfigPattern = Dict[str, Union[str, int, bool, float]] -def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False) -> Tuple[FontConfigPattern, ...]: +def fc_list( + spacing: int = -1, + allow_bitmapped_fonts: bool = False +) -> Tuple[FontConfigPattern, ...]: pass @@ -407,7 +414,11 @@ def coretext_all_fonts() -> Tuple[Dict[str, Any], ...]: pass -def add_timer(callback: Callable[[Optional[int]], bool], interval: float, repeats: bool = True) -> int: +def add_timer( + callback: Callable[[Optional[int]], bool], + interval: float, + repeats: bool = True +) -> int: pass @@ -419,7 +430,9 @@ def add_window(os_window_id: int, tab_id: int, title: str) -> int: pass -def compile_program(which: int, vertex_shader: str, fragment_shader: str) -> int: +def compile_program( + which: int, vertex_shader: str, fragment_shader: str +) -> int: pass @@ -431,7 +444,10 @@ def set_titlebar_color(os_window_id: int, color: int) -> bool: pass -def add_borders_rect(os_window_id: int, tab_id: int, left: int, top: int, right: int, bottom: int, color: int) -> None: +def add_borders_rect( + os_window_id: int, tab_id: int, left: int, top: int, right: int, + bottom: int, color: int +) -> None: pass @@ -443,16 +459,30 @@ def os_window_has_background_image(os_window_id: int) -> bool: pass -def dbus_send_notification(app_name: str, icon: str, summary: str, body: str, action_name: str, timeout: int = -1) -> int: +def dbus_send_notification( + app_name: str, + icon: str, + summary: str, + body: str, + action_name: str, + timeout: int = -1 +) -> int: pass -def cocoa_send_notification(identifier: Optional[str], title: str, informative_text: str, path_to_img: Optional[str], subtitle: Optional[str] = None) -> None: +def cocoa_send_notification( + identifier: Optional[str], + title: str, + informative_text: str, + path_to_img: Optional[str], + subtitle: Optional[str] = None +) -> None: pass def create_os_window( - get_window_size: Callable[[int, int, int, int, float, float], Tuple[int, int]], + get_window_size: Callable[[int, int, int, int, float, float], Tuple[int, + int]], pre_show_callback: Callable[[object], None], title: str, wm_class_name: str, @@ -464,11 +494,16 @@ def create_os_window( pass -def update_window_title(os_window_id: int, tab_id: int, window_id: int, title: str) -> None: +def update_window_title( + os_window_id: int, tab_id: int, window_id: int, title: str +) -> None: pass -def update_window_visibility(os_window_id: int, tab_id: int, window_id: int, window_idx: int, visible: bool) -> None: +def update_window_visibility( + os_window_id: int, tab_id: int, window_id: int, window_idx: int, + visible: bool +) -> None: pass @@ -501,7 +536,12 @@ def set_default_window_icon(data: bytes, width: int, height: int) -> None: pass -def set_custom_cursor(cursor_type: int, images: Tuple[Tuple[bytes, int, int], ...], x: int = 0, y: int = 0) -> None: +def set_custom_cursor( + cursor_type: int, + images: Tuple[Tuple[bytes, int, int], ...], + x: int = 0, + y: int = 0 +) -> None: pass @@ -542,8 +582,10 @@ def set_clipboard_string(data: bytes) -> None: def set_background_image( - path: Optional[str], os_window_ids: Tuple[int, ...], - configured: bool = True, layout_name: Optional[str] = None + path: Optional[str], + os_window_ids: Tuple[int, ...], + configured: bool = True, + layout_name: Optional[str] = None ) -> None: pass @@ -564,7 +606,20 @@ def patch_global_colors(spec: Dict[str, int], configured: bool) -> None: pass -def os_window_font_size(os_window_id: int, new_sz: float = -1., force: bool = False) -> float: +class ColorProfile: + pass + + +def patch_color_profiles( + spec: Dict[str, int], cursor_text_color: Optional[Union[bool, int]], + profiles: Tuple[ColorProfile, ...], change_configured: bool +) -> None: + pass + + +def os_window_font_size( + os_window_id: int, new_sz: float = -1., force: bool = False +) -> float: pass @@ -692,7 +747,9 @@ class Region: pass -def viewport_for_window(os_window_id: int) -> Tuple[Region, Region, int, int, int, int]: +def viewport_for_window( + os_window_id: int +) -> Tuple[Region, Region, int, int, int, int]: pass @@ -716,14 +773,10 @@ def open_tty(read_with_timeout: bool = False) -> Tuple[int, TermiosPtr]: def parse_input_from_terminal( - text_callback: Callable[[str], None], - dcs_callback: Callable[[str], None], - csi_callback: Callable[[str], None], - osc_callback: Callable[[str], None], - pm_callback: Callable[[str], None], - apc_callback: Callable[[str], None], - data: str, - in_bracketed_paste: bool + text_callback: Callable[[str], None], dcs_callback: Callable[[str], None], + csi_callback: Callable[[str], None], osc_callback: Callable[[str], None], + pm_callback: Callable[[str], None], apc_callback: Callable[[str], None], + data: str, in_bracketed_paste: bool ): pass @@ -732,7 +785,9 @@ class Line: pass -def test_shape(line: Line, path: Optional[str] = None, index: int = 0) -> List[Tuple[int, int, int, Tuple[int, ...]]]: +def test_shape(line: Line, + path: Optional[str] = None, + index: int = 0) -> List[Tuple[int, int, int, Tuple[int, ...]]]: pass @@ -744,17 +799,21 @@ def sprite_map_set_limits(w: int, h: int) -> None: pass -def set_send_sprite_to_gpu(func: Callable[[int, int, int, bytes], None]) -> None: +def set_send_sprite_to_gpu( + func: Callable[[int, int, int, bytes], None] +) -> None: pass def set_font_data( - box_drawing_func: Callable[[int, int, int, float], Tuple[int, Union[bytearray, bytes]]], - prerender_func: Callable[[int, int, int, int, int, float, float, float, float], Tuple[int, ...]], - descriptor_for_idx: Callable[[int], Tuple[dict, bool, bool]], + box_drawing_func: Callable[[int, int, int, float], + Tuple[int, Union[bytearray, bytes]]], + prerender_func: Callable[ + [int, int, int, int, int, float, float, float, float], + Tuple[int, ...]], descriptor_for_idx: Callable[[int], Tuple[dict, bool, + bool]], bold: int, italic: int, bold_italic: int, num_symbol_fonts: int, - symbol_maps: Tuple[Tuple[int, int, int], ...], - font_sz_in_pts: float, + symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float, font_feature_settings: Dict[str, Tuple[bytes, ...]] ): pass @@ -764,7 +823,8 @@ def get_fallback_font(text: str, bold: bool, italic: bool): pass -def create_test_font_group(sz: float, dpix: float, dpiy: float) -> Tuple[int, int]: +def create_test_font_group(sz: float, dpix: float, + dpiy: float) -> Tuple[int, int]: pass @@ -772,30 +832,35 @@ class Screen: pass -def set_tab_bar_render_data(os_window_id: int, xstart: float, ystart: float, dx: float, dy: float, screen: Screen) -> None: +def set_tab_bar_render_data( + os_window_id: int, xstart: float, ystart: float, dx: float, dy: float, + screen: Screen +) -> None: pass def set_window_render_data( - os_window_id: int, tab_id: int, window_id: int, window_idx: int, - xstart: float, ystart: float, dx: float, dy: float, - screen: Screen, - left: int, top: int, right: int, bottom: int + os_window_id: int, tab_id: int, window_id: int, window_idx: int, + xstart: float, ystart: float, dx: float, dy: float, screen: Screen, + left: int, top: int, right: int, bottom: int ): pass -def truncate_point_for_length(text: str, num_cells: int, start_pos: int = 0) -> int: +def truncate_point_for_length( + text: str, num_cells: int, start_pos: int = 0 +) -> int: pass class ChildMonitor: def __init__( - self, - death_notify: Callable[[int], None], - dump_callback: Optional[Callable], - talk_fd: int = -1, listen_fd: int = -1 + self, + death_notify: Callable[[int], None], + dump_callback: Optional[Callable], + talk_fd: int = -1, + listen_fd: int = -1 ): pass diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index ad30a68c5..a18b1247f 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -45,10 +45,14 @@ def all_fonts_map(monospaced=True): def list_fonts() -> Generator[ListedFont, None, None]: for fd in fc_list(): f = fd.get('family') - if f: - fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip() + if f and isinstance(f, str): + fn_ = fd.get('full_name') + if fn_: + fn = str(fn_) + else: + fn = (f + ' ' + str(fd.get('style', ''))).strip() is_mono = fd.get('spacing') in ('MONO', 'DUAL') - yield {'family': f, 'full_name': fn, 'postscript_name': fd.get('postscript_name', ''), 'is_monospace': is_mono} + yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono} def family_name_to_key(family): diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index cb1c7ec36..bfe81892d 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -5,7 +5,8 @@ import ctypes import sys from functools import partial -from math import ceil, pi, cos, floor +from math import ceil, cos, floor, pi +from typing import Any, List, Tuple from kitty.config import defaults from kitty.constants import is_macos @@ -22,7 +23,7 @@ if is_macos: else: from .fontconfig import get_font_files, font_for_family -current_faces = None +current_faces: List[Tuple[Any, bool, bool]] = [] def coalesce_symbol_maps(maps): diff --git a/kitty/keys.py b/kitty/keys.py index 1586d9e94..8ff34b4bd 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -11,10 +11,9 @@ from .terminfo import key_as_bytes, modify_key_bytes from .utils import base64_encode -def modify_complex_key(name, amt): - if not isinstance(name, bytes): - name = key_as_bytes(name) - return modify_key_bytes(name, amt) +def modify_complex_key(name: Union[str, bytes], amt: int) -> bytes: + q = name if isinstance(name, bytes) else key_as_bytes(name) + return modify_key_bytes(q, amt) control_codes: Dict[int, Union[bytes, Tuple[int, ...]]] = { @@ -48,7 +47,7 @@ SHIFTED_KEYS = { control_alt_codes = { defines.GLFW_KEY_SPACE: b'\x1b\0', } -control_alt_shift_codes = {} +control_alt_shift_codes: Dict[int, bytes] = {} ASCII_C0_SHIFTED = { # ^@ '2': b'\x00', diff --git a/kitty/rc/base.py b/kitty/rc/base.py index b75d63ff5..b28498631 100644 --- a/kitty/rc/base.py +++ b/kitty/rc/base.py @@ -5,7 +5,7 @@ from contextlib import suppress from typing import ( TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Generator, List, NoReturn, - Optional, Tuple, Union + Optional, Tuple, Type, Union, cast ) from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec @@ -42,12 +42,26 @@ class UnknownLayout(ValueError): hide_traceback = True +class PayloadGetter: + + def __init__(self, cmd: 'RemoteCommand', payload: Dict[str, Any]): + self.payload = payload + self.cmd = cmd + + def __call__(self, key: str, opt_name: Optional[str] = None, missing: Any = None): + ans = self.payload.get(key, payload_get) + if ans is not payload_get: + return ans + return self.cmd.get_default(opt_name or key, missing=missing) + + no_response = NoResponse() +payload_get = object() ResponseType = Optional[Union[bool, str]] CmdReturnType = Union[Dict[str, Any], List, Tuple, str, int, float, bool] CmdGenerator = Generator[CmdReturnType, None, None] PayloadType = Optional[Union[CmdReturnType, CmdGenerator]] -PayloadGetType = Callable[[str, str], Any] +PayloadGetType = PayloadGetter ArgsType = List[str] @@ -106,6 +120,7 @@ class RemoteCommand: args_count: Optional[int] = None args_completion: Optional[Dict[str, Tuple[str, Tuple[str, ...]]]] = None defaults: Optional[Dict[str, Any]] = None + options_class: Type = RCOptions def __init__(self): self.desc = self.desc or self.short_desc @@ -124,13 +139,6 @@ class RemoteCommand: return self.defaults.get(name, missing) return missing - def payload_get(self, payload: Dict[str, Any], key: str, opt_name: Optional[str] = None) -> Any: - payload_get = object() - ans = payload.get(key, payload_get) - if ans is not payload_get: - return ans - return self.get_default(opt_name or key) - def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType: raise NotImplementedError() @@ -143,7 +151,7 @@ def cli_params_for(command: RemoteCommand) -> Tuple[Callable[[], str], str, str, def parse_subcommand_cli(command: RemoteCommand, args: ArgsType) -> Tuple[Any, ArgsType]: - opts, items = parse_args(args[1:], *cli_params_for(command)) + opts, items = parse_args(args[1:], *cli_params_for(command), result_class=command.options_class) if command.args_count is not None and command.args_count != len(items): if command.args_count == 0: raise SystemExit('Unknown extra argument(s) supplied to {}'.format(command.name)) @@ -163,17 +171,17 @@ def command_for_name(cmd_name: str) -> RemoteCommand: m = import_module(f'kitty.rc.{cmd_name}') except ImportError: raise KeyError(f'{cmd_name} is not a known kitty remote control command') - return getattr(m, cmd_name) + return cast(RemoteCommand, getattr(m, cmd_name)) def all_command_names() -> FrozenSet[str]: try: from importlib.resources import contents except ImportError: - from importlib_resources import contents + from importlib_resources import contents # type:ignore def ok(name: str) -> bool: root, _, ext = name.rpartition('.') - return ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__') + return bool(ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__')) return frozenset({x.rpartition('.')[0] for x in filter(ok, contents('kitty.rc'))}) diff --git a/kitty/rc/close_tab.py b/kitty/rc/close_tab.py index 551bb030d..0b88ba080 100644 --- a/kitty/rc/close_tab.py +++ b/kitty/rc/close_tab.py @@ -39,7 +39,7 @@ If specified close the tab this command is run in, rather than the active tab. if not tabs: raise MatchError(match, 'tabs') else: - tabs = [boss.tab_for_window(window) if window and payload_get('self') else boss.active_tab] + tabs = tuple(boss.tab_for_window(window) if window and payload_get('self') else boss.active_tab) for tab in tabs: if window: if tab: diff --git a/kitty/rc/close_window.py b/kitty/rc/close_window.py index 54eea0da0..b6959affc 100644 --- a/kitty/rc/close_window.py +++ b/kitty/rc/close_window.py @@ -38,7 +38,7 @@ If specified close the window this command is run in, rather than the active win if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) for window in windows: if window: boss.close_window(window) diff --git a/kitty/rc/create_marker.py b/kitty/rc/create_marker.py index d07432247..614b20482 100644 --- a/kitty/rc/create_marker.py +++ b/kitty/rc/create_marker.py @@ -48,7 +48,7 @@ If specified apply marker to the window this command is run in, rather than the if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) args = payload_get('marker_spec') for window in windows: diff --git a/kitty/rc/detach_tab.py b/kitty/rc/detach_tab.py index d333c1ffe..cb60f19d9 100644 --- a/kitty/rc/detach_tab.py +++ b/kitty/rc/detach_tab.py @@ -43,7 +43,7 @@ If specified detach the tab this command is run in, rather than the active tab. if not tabs: raise MatchError(match) else: - tabs = [window.tabref() if payload_get('self') and window and window.tabref() else boss.active_tab] + tabs = tuple(window.tabref() if payload_get('self') and window and window.tabref() else boss.active_tab) match = payload_get('target_tab') kwargs = {} if match: diff --git a/kitty/rc/detach_window.py b/kitty/rc/detach_window.py index c5d53fb61..7609d0cf7 100644 --- a/kitty/rc/detach_window.py +++ b/kitty/rc/detach_window.py @@ -45,7 +45,7 @@ If specified detach the window this command is run in, rather than the active wi if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) match = payload_get('target_tab') kwargs = {} if match: diff --git a/kitty/rc/focus_tab.py b/kitty/rc/focus_tab.py index 59eb80e06..5cd953ae7 100644 --- a/kitty/rc/focus_tab.py +++ b/kitty/rc/focus_tab.py @@ -42,7 +42,7 @@ using this option means that you will not be notified of failures. if match: tabs = tuple(boss.match_tabs(match)) else: - tabs = [boss.tab_for_window(window) if window else boss.active_tab] + tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab) if not tabs: raise MatchError(match, 'tabs') tab = tabs[0] diff --git a/kitty/rc/focus_window.py b/kitty/rc/focus_window.py index c87568708..8c618ddd6 100644 --- a/kitty/rc/focus_window.py +++ b/kitty/rc/focus_window.py @@ -41,7 +41,7 @@ the command will exit with a success code. windows = [window or boss.active_window] match = payload_get('match') if match: - windows = tuple(boss.match_windows(match)) + windows = [boss.match_windows(match)] if not windows: raise MatchError(match) for window in windows: diff --git a/kitty/rc/get_colors.py b/kitty/rc/get_colors.py index 5ee2c387d..7c797769c 100644 --- a/kitty/rc/get_colors.py +++ b/kitty/rc/get_colors.py @@ -42,9 +42,9 @@ configured colors. def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType: ans = {k: getattr(boss.opts, k) for k in boss.opts if isinstance(getattr(boss.opts, k), Color)} if not payload_get('configured'): - windows = (window or boss.active_window,) + windows = [window or boss.active_window] if payload_get('match'): - windows = tuple(boss.match_windows(payload_get('match'))) + windows = list(boss.match_windows(payload_get('match'))) if not windows: raise MatchError(payload_get('match')) ans.update({k: color_from_int(v) for k, v in windows[0].current_colors.items()}) diff --git a/kitty/rc/get_text.py b/kitty/rc/get_text.py index ffcf22ae3..0cd8b3b6e 100644 --- a/kitty/rc/get_text.py +++ b/kitty/rc/get_text.py @@ -55,7 +55,7 @@ If specified get text from the window this command is run in, rather than the ac if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) window = windows[0] if payload_get('extent') == 'selection': ans = window.text_for_selection() diff --git a/kitty/rc/goto_layout.py b/kitty/rc/goto_layout.py index 29ed0f6eb..bd96696b2 100644 --- a/kitty/rc/goto_layout.py +++ b/kitty/rc/goto_layout.py @@ -44,7 +44,7 @@ class GotoLayout(RemoteCommand): if not tabs: raise MatchError(match, 'tabs') else: - tabs = [boss.tab_for_window(window) if window else boss.active_tab] + tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab) for tab in tabs: if tab: try: diff --git a/kitty/rc/kitten.py b/kitty/rc/kitten.py index 78f6141a5..24d6afca8 100644 --- a/kitty/rc/kitten.py +++ b/kitty/rc/kitten.py @@ -40,7 +40,7 @@ class Kitten(RemoteCommand): windows = [window or boss.active_window] match = payload_get('match') if match: - windows = tuple(boss.match_windows(match)) + windows = list(boss.match_windows(match)) if not windows: raise MatchError(match) for window in windows: diff --git a/kitty/rc/last_used_layout.py b/kitty/rc/last_used_layout.py index 249418f2b..fcb01a61b 100644 --- a/kitty/rc/last_used_layout.py +++ b/kitty/rc/last_used_layout.py @@ -39,7 +39,7 @@ class LastUsedLayout(RemoteCommand): if not tabs: raise MatchError(match, 'tabs') else: - tabs = [boss.tab_for_window(window) if window else boss.active_tab] + tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab) for tab in tabs: if tab: tab.last_used_layout() diff --git a/kitty/rc/launch.py b/kitty/rc/launch.py index 5d830453b..c3fdb4684 100644 --- a/kitty/rc/launch.py +++ b/kitty/rc/launch.py @@ -80,7 +80,7 @@ instead of the active tab setattr(opts, key, val) match = payload_get('match') if match: - tabs = tuple(boss.match_tabs(match)) + tabs = list(boss.match_tabs(match)) if not tabs: raise MatchError(match, 'tabs') else: @@ -88,7 +88,7 @@ instead of the active tab if payload_get('self') and window and window.tabref(): tabs = [window.tabref()] tab = tabs[0] - w = do_launch(boss, opts, payload_get('args') or None, target_tab=tab) + w = do_launch(boss, opts, payload_get('args') or [], target_tab=tab) return None if payload_get('no_response') else str(getattr(w, 'id', 0)) diff --git a/kitty/rc/new_window.py b/kitty/rc/new_window.py index 1a6e72c0b..4c823d7ff 100644 --- a/kitty/rc/new_window.py +++ b/kitty/rc/new_window.py @@ -32,7 +32,8 @@ class NewWindow(RemoteCommand): desc = ( 'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option' ' the first matching tab is used. Otherwise the currently active tab is used.' - ' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments' + ' Prints out the id of the newly opened window' + ' (unless :option:`--no-response` is used). Any command line arguments' ' are assumed to be the command line used to run in the new window, if none' ' are provided, the default shell is run. For example:\n' ':italic:`kitty @ new-window --title Email mutt`' @@ -109,7 +110,7 @@ the id of the new window will not be printed out. match = payload_get('match') if match: - tabs = tuple(boss.match_tabs(match)) + tabs = list(boss.match_tabs(match)) if not tabs: raise MatchError(match, 'tabs') else: diff --git a/kitty/rc/remove_marker.py b/kitty/rc/remove_marker.py index 8b1076160..eb5a04aad 100644 --- a/kitty/rc/remove_marker.py +++ b/kitty/rc/remove_marker.py @@ -39,7 +39,7 @@ If specified apply marker to the window this command is run in, rather than the if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) for window in windows: window.remove_marker() diff --git a/kitty/rc/resize_window.py b/kitty/rc/resize_window.py index 07d5d2996..a69526c12 100644 --- a/kitty/rc/resize_window.py +++ b/kitty/rc/resize_window.py @@ -22,7 +22,10 @@ class ResizeWindow(RemoteCommand): ''' short_desc = 'Resize the specified window' - desc = 'Resize the specified window in the current layout. Note that not all layouts can resize all windows in all directions.' + desc = ( + 'Resize the specified window in the current layout.' + ' Note that not all layouts can resize all windows in all directions.' + ) options_spec = MATCH_WINDOW_OPTION + '''\n --increment -i type=int @@ -34,9 +37,10 @@ The number of cells to change the size by, can be negative to decrease the size. type=choices choices=horizontal,vertical,reset default=horizontal -The axis along which to resize. If :italic:`horizontal`, it will make the window wider or narrower by the specified increment. -If :italic:`vertical`, it will make the window taller or shorter by the specified increment. The special value :italic:`reset` will -reset the layout to its default configuration. +The axis along which to resize. If :italic:`horizontal`, +it will make the window wider or narrower by the specified increment. +If :italic:`vertical`, it will make the window taller or shorter by the specified increment. +The special value :italic:`reset` will reset the layout to its default configuration. --self @@ -56,7 +60,7 @@ If specified resize the window this command is run in, rather than the active wi if not windows: raise MatchError(match) else: - windows = [window if window and payload_get('self') else boss.active_window] + windows = tuple(window if window and payload_get('self') else boss.active_window) resized = False if windows and windows[0]: resized = boss.resize_layout_window( diff --git a/kitty/rc/scroll_window.py b/kitty/rc/scroll_window.py index ad204a5ff..3699a7ce4 100644 --- a/kitty/rc/scroll_window.py +++ b/kitty/rc/scroll_window.py @@ -3,7 +3,7 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple, Union from .base import ( MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, @@ -35,23 +35,22 @@ class ScrollWindow(RemoteCommand): def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: amt = args[0] - ans = {'match': opts.match} - if amt in ('start', 'end'): - ans['amount'] = amt, None - else: + amount: Tuple[Union[str, int], Optional[str]] = amt, None + if amt not in ('start', 'end'): pages = 'p' in amt amt = amt.replace('p', '') mult = -1 if amt.endswith('-') else 1 - amt = int(amt.replace('-', '')) - ans['amount'] = [amt * mult, 'p' if pages else 'l'] - return ans + q = int(amt.replace('-', '')) + amount = q * mult, 'p' if pages else 'l' + + return {'match': opts.match, 'amount': amount} def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType: windows = [window or boss.active_window] match = payload_get('match') amt = payload_get('amount') if match: - windows = tuple(boss.match_windows(match)) + windows = list(boss.match_windows(match)) if not windows: raise MatchError(match) for window in windows: diff --git a/kitty/rc/send_text.py b/kitty/rc/send_text.py index be6337f2c..04fe5cfde 100644 --- a/kitty/rc/send_text.py +++ b/kitty/rc/send_text.py @@ -4,7 +4,7 @@ import os import sys -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Generator, Dict from kitty.config import parse_send_text_bytes @@ -51,8 +51,7 @@ are sent as is, not interpreted for escapes. limit = 1024 ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab} - def pipe(): - ret['is_binary'] = True + def pipe() -> Generator[Dict, None, None]: if sys.stdin.isatty(): import select fd = sys.stdin.fileno() @@ -64,28 +63,29 @@ are sent as is, not interpreted for escapes. data = os.read(fd, limit) if not data: break # eof - data = data.decode('utf-8') - if '\x04' in data: - data = data[:data.index('\x04')] + decoded_data = data.decode('utf-8') + if '\x04' in decoded_data: + decoded_data = decoded_data[:decoded_data.index('\x04')] keep_going = False - ret['text'] = data + ret['text'] = decoded_data yield ret else: + ret['is_binary'] = True while True: - data = sys.stdin.read(limit) + data = sys.stdin.buffer.read(limit) if not data: break ret['text'] = data[:limit] yield ret - def chunks(text): + def chunks(text: str) -> Generator[Dict, None, None]: ret['is_binary'] = False while text: ret['text'] = text[:limit] yield ret text = text[limit:] - def file_pipe(path): + def file_pipe(path: str) -> Generator[Dict, None, None]: ret['is_binary'] = True with open(path, encoding='utf-8') as f: while True: @@ -105,7 +105,7 @@ are sent as is, not interpreted for escapes. text = ' '.join(args) sources.append(chunks(text)) - def chain(): + def chain() -> Generator[Dict, None, None]: for src in sources: yield from src return chain() @@ -114,7 +114,7 @@ are sent as is, not interpreted for escapes. windows = [boss.active_window] match = payload_get('match') if match: - windows = tuple(boss.match_windows(match)) + windows = list(boss.match_windows(match)) mt = payload_get('match_tab') if mt: windows = [] @@ -123,7 +123,8 @@ are sent as is, not interpreted for escapes. raise MatchError(payload_get('match_tab'), 'tabs') for tab in tabs: windows += tuple(tab) - data = payload_get('text').encode('utf-8') if payload_get('is_binary') else parse_send_text_bytes(payload_get('text')) + data = payload_get('text').encode('utf-8') if payload_get('is_binary') else parse_send_text_bytes( + payload_get('text')) for window in windows: if window is not None: window.write_to_child(data) diff --git a/kitty/rc/set_background_image.py b/kitty/rc/set_background_image.py index b3f2585ce..15a3188a9 100644 --- a/kitty/rc/set_background_image.py +++ b/kitty/rc/set_background_image.py @@ -5,12 +5,12 @@ import imghdr import tempfile from base64 import standard_b64decode, standard_b64encode -from typing import TYPE_CHECKING, BinaryIO, Optional +from typing import IO, TYPE_CHECKING, Dict, Generator, Optional from uuid import uuid4 from .base import ( MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, - RCOptions, RemoteCommand, ResponseType, Window, no_response, + RCOptions, RemoteCommand, ResponseType, Window, windows_for_payload ) @@ -59,20 +59,26 @@ How the image should be displayed. The value of configured will use the configur args_count = 1 args_completion = {'files': ('PNG Images', ('*.png',))} current_img_id: Optional[str] = None - current_file_obj: Optional[BinaryIO] = None + current_file_obj: Optional[IO[bytes]] = None def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if not args: self.fatal('Must specify path to PNG image') path = args[0] - ret = {'match': opts.match, 'configured': opts.configured, 'layout': opts.layout, 'all': opts.all, 'img_id': str(uuid4())} + ret = { + 'match': opts.match, + 'configured': opts.configured, + 'layout': opts.layout, + 'all': opts.all, + 'img_id': str(uuid4()) + } if path.lower() == 'none': ret['data'] = '-' return ret if imghdr.what(path) != 'png': self.fatal('{} is not a PNG image'.format(path)) - def file_pipe(path): + def file_pipe(path) -> Generator[Dict, None, None]: with open(path, 'rb') as f: while True: data = f.read(512) @@ -92,8 +98,9 @@ How the image should be displayed. The value of configured will use the configur set_background_image.current_img_id = img_id set_background_image.current_file_obj = tempfile.NamedTemporaryFile() if data: + assert set_background_image.current_file_obj is not None set_background_image.current_file_obj.write(standard_b64decode(data)) - return no_response + return None windows = windows_for_payload(boss, window, payload_get) os_windows = tuple({w.os_window_id for w in windows}) @@ -101,6 +108,7 @@ How the image should be displayed. The value of configured will use the configur if data == '-': path = None else: + assert set_background_image.current_file_obj is not None f = set_background_image.current_file_obj path = f.name set_background_image.current_file_obj = None @@ -109,7 +117,7 @@ How the image should be displayed. The value of configured will use the configur try: boss.set_background_image(path, os_windows, payload_get('configured'), layout) except ValueError as err: - err.hide_traceback = True + err.hide_traceback = True # type: ignore raise diff --git a/kitty/rc/set_colors.py b/kitty/rc/set_colors.py index d21d60a02..89be01130 100644 --- a/kitty/rc/set_colors.py +++ b/kitty/rc/set_colors.py @@ -4,7 +4,7 @@ import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional from kitty.config import parse_config from kitty.fast_data_types import patch_color_profiles @@ -34,7 +34,8 @@ class SetColors(RemoteCommand): short_desc = 'Set terminal colors' desc = ( - 'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file' + 'Set the terminal colors for the specified windows/tabs (defaults to active window).' + ' You can either specify the path to a conf file' ' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,' ' for example: kitty @ set-colors foreground=red background=white' ) @@ -59,21 +60,28 @@ this option, any color arguments are ignored and --configured and --all are impl argspec = 'COLOR_OR_FILE ...' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: - colors, cursor_text_color = {}, False + final_colors: Dict[str, int] = {} + cursor_text_color: Optional[int] = None if not opts.reset: + colors: Dict[str, Optional[Color]] = {} for spec in args: if '=' in spec: colors.update(parse_config((spec.replace('=', ' '),))) else: with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f: colors.update(parse_config(f)) - cursor_text_color = colors.pop('cursor_text_color', False) - colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)} - return { + ctc = colors.pop('cursor_text_color') + if isinstance(ctc, Color): + cursor_text_color = color_as_int(ctc) + final_colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)} + ans = { 'match_window': opts.match, 'match_tab': opts.match_tab, 'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, - 'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color + 'colors': final_colors, 'reset': opts.reset } + if cursor_text_color is not None: + ans['cursor_text_color'] = cursor_text_color + return ans def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType: windows = windows_for_payload(boss, window, payload_get) diff --git a/kitty/rc/set_tab_title.py b/kitty/rc/set_tab_title.py index 389865633..8e6b39052 100644 --- a/kitty/rc/set_tab_title.py +++ b/kitty/rc/set_tab_title.py @@ -41,7 +41,7 @@ class SetTabTitle(RemoteCommand): if not tabs: raise MatchError(match, 'tabs') else: - tabs = [boss.tab_for_window(window) if window else boss.active_tab] + tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab) for tab in tabs: if tab: tab.set_title(payload_get('title')) diff --git a/kitty/rc/set_window_title.py b/kitty/rc/set_window_title.py index 23e96e931..4c2af7e58 100644 --- a/kitty/rc/set_window_title.py +++ b/kitty/rc/set_window_title.py @@ -44,7 +44,7 @@ want to allow other programs to change it afterwards, use this option. windows = [window or boss.active_window] match = payload_get('match') if match: - windows = tuple(boss.match_windows(match)) + windows = list(boss.match_windows(match)) if not windows: raise MatchError(match) for window in windows: diff --git a/kitty/remote_control.py b/kitty/remote_control.py index fa9b10b83..3a706094c 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -9,15 +9,15 @@ import sys import types from contextlib import suppress from functools import partial -from typing import Any, List, Optional +from typing import List from .cli import emph, parse_args from .cli_stub import RCOptions from .constants import appname, version from .fast_data_types import read_command_response from .rc.base import ( - all_command_names, command_for_name, no_response as no_response_sentinel, - parse_subcommand_cli + PayloadGetter, all_command_names, command_for_name, + no_response as no_response_sentinel, parse_subcommand_cli ) from .utils import TTYIO, parse_address_spec @@ -33,11 +33,8 @@ def handle_cmd(boss, window, cmd): c = command_for_name(cmd['cmd']) payload = cmd.get('payload') or {} - def payload_get(key: str, opt_name: Optional[str] = None) -> Any: - return c.payload_get(payload, key, opt_name) - try: - ans = c.response_from_kitty(boss, window, payload_get) + ans = c.response_from_kitty(boss, window, PayloadGetter(c, payload)) except Exception: if no_response: # don't report errors if --no-response was used return diff --git a/kitty/shell.py b/kitty/shell.py index 30b63d809..3dbf1e37d 100644 --- a/kitty/shell.py +++ b/kitty/shell.py @@ -48,7 +48,7 @@ def cmd_names_matching(prefix): @lru_cache() def options_for_cmd(cmd: str) -> Tuple[Tuple[str, ...], Dict[str, OptionDict]]: - alias_map = {} + alias_map: Dict[str, OptionDict] = {} try: func = command_for_name(cmd) except KeyError: diff --git a/kitty/terminfo.py b/kitty/terminfo.py index 81de4ae40..35ca3b81f 100644 --- a/kitty/terminfo.py +++ b/kitty/terminfo.py @@ -7,15 +7,15 @@ from binascii import hexlify, unhexlify from typing import cast, Dict -def modify_key_bytes(keybytes, amt): +def modify_key_bytes(keybytes: bytes, amt: int) -> bytes: if amt == 0: return keybytes ans = bytearray(keybytes) - amt = str(amt).encode('ascii') + samt = str(amt).encode('ascii') if ans[-1] == ord('~'): - return bytes(ans[:-1] + bytearray(b';' + amt + b'~')) + return bytes(ans[:-1] + bytearray(b';' + samt + b'~')) if ans[1] == ord('O'): - return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:]) + return bytes(ans[:1] + bytearray(b'[1;' + samt) + ans[-1:]) raise ValueError('Unknown key type in key: {!r}'.format(keybytes)) @@ -433,7 +433,7 @@ octal_escape = re.compile(r'\\([0-7]{3})') escape_escape = re.compile(r'\\[eE]') -def key_as_bytes(name): +def key_as_bytes(name: str) -> bytes: ans = string_capabilities[name] ans = octal_escape.sub(lambda m: chr(int(m.group(1), 8)), ans) ans = escape_escape.sub('\033', ans) diff --git a/kitty/window.py b/kitty/window.py index 31845e174..f935cae59 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -9,6 +9,7 @@ import weakref from collections import deque from enum import IntEnum from itertools import chain +from typing import List from .config import build_ansi_color_table from .constants import ScreenGeometry, WindowGeometry, appname, wakeup @@ -483,7 +484,7 @@ class Window: self.title_stack.append(self.child_title) # }}} - def text_for_selection(self): + def text_for_selection(self) -> str: lines = self.screen.text_for_selection() if self.opts.strip_trailing_spaces == 'always' or ( self.opts.strip_trailing_spaces == 'smart' and not self.screen.is_rectangle_select()): @@ -497,8 +498,8 @@ class Window: self.screen.reset_callbacks() self.screen = None - def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False): - lines = [] + def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False) -> str: + lines: List[str] = [] add_history = add_history and not (self.screen.is_using_alternate_linebuf() ^ alternate_screen) if alternate_screen: f = self.screen.as_text_alternate @@ -506,7 +507,7 @@ class Window: f = self.screen.as_text_non_visual if add_history else self.screen.as_text f(lines.append, as_ansi, add_wrap_markers) if add_history: - h = [] + h: List[str] = [] self.screen.historybuf.pagerhist_as_text(h.append) if h and (not as_ansi or not add_wrap_markers): sanitizer = text_sanitizer(as_ansi, add_wrap_markers) @@ -517,7 +518,7 @@ class Window: h[-1] += '\n' if as_ansi: h[-1] += '\x1b[m' - lines = chain(h, lines) + return ''.join(chain(h, lines)) return ''.join(lines) @property diff --git a/session.vim b/session.vim index 0949e19a0..a18757647 100644 --- a/session.vim +++ b/session.vim @@ -1,6 +1,6 @@ " Scan the following dirs recursively for tags -let g:project_tags_dirs = ['kitty'] -let g:syntastic_python_flake8_exec = 'flake8' +let g:project_tags_dirs = ['kitty', 'kittens'] +let g:syntastic_python_checkers = ['pylama'] let g:ycm_python_binary_path = 'python3' set wildignore+==template.py set wildignore+=tags diff --git a/setup.cfg b/setup.cfg index 88d29e3fd..ea044e589 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,8 +13,15 @@ blank_line_before_nested_class_or_def = True combine_as_imports = True multi_line_output = 5 -[mypy] -files = kitty,kittens,glfw,*.py +[pylama] +linters=mypy,pycodestyle,pyflakes + +[pylama:pycodestyle] +max_line_length = 120 +exclude==template.py,linux-package + +[pylama:mypy] +files = kitty,kittens,glfw,*.py,docs/conf.py no_implicit_optional = True sqlite_cache = True cache_fine_grained = True @@ -22,4 +29,17 @@ warn_redundant_casts = True warn_unused_ignores = True warn_return_any = True warn_unreachable = True +warn_no_return = False check_untyped_defs = True + +[mypy] +files = kitty,kittens,glfw,*.py,docs/conf.py +no_implicit_optional = True +sqlite_cache = True +cache_fine_grained = True +warn_redundant_casts = True +warn_unused_ignores = True +warn_return_any = True +warn_unreachable = True +warn_no_return = False +# check_untyped_defs = True diff --git a/setup.py b/setup.py index 9299e8472..a6e12b3b2 100755 --- a/setup.py +++ b/setup.py @@ -741,7 +741,7 @@ def compile_python(base_path): kw = {} compileall.compile_dir( base_path, ddir='', force=True, optimize=optimize, quiet=1, - workers=num_workers, **kw # type: ignore + workers=num_workers, **kw )