From f1d0d0949b6662d2ca43a1917845cf081ea51594 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 31 Jul 2024 07:55:27 +0530 Subject: [PATCH] Run pyupgrade to update the codebase to python 3.9 Gets rid of a lot of typing ugliness by using type annotations on builtin types --- kitty/actions.py | 12 ++-- kitty/bash.py | 9 ++- kitty/borders.py | 3 +- kitty/boss.py | 134 ++++++++++++++++------------------- kitty/child.py | 53 +++++++------- kitty/choose_entry.py | 5 +- kitty/cli.py | 62 ++++++++-------- kitty/cli_stub.py | 2 +- kitty/clipboard.py | 23 +++--- kitty/config.py | 27 +++---- kitty/constants.py | 9 +-- kitty/debug_config.py | 9 +-- kitty/entry_points.py | 25 ++++--- kitty/file_transmission.py | 37 +++++----- kitty/fonts/__init__.py | 25 +++---- kitty/fonts/box_drawing.py | 37 +++++----- kitty/fonts/common.py | 34 ++++----- kitty/fonts/core_text.py | 19 ++--- kitty/fonts/features.py | 4 +- kitty/fonts/fontconfig.py | 25 +++---- kitty/fonts/list.py | 7 +- kitty/fonts/render.py | 39 +++++----- kitty/key_encoding.py | 10 +-- kitty/key_names.py | 6 +- kitty/keys.py | 15 ++-- kitty/launch.py | 49 ++++++------- kitty/main.py | 15 ++-- kitty/marks.py | 12 ++-- kitty/multiprocessing.py | 5 +- kitty/notifications.py | 23 +++--- kitty/open_actions.py | 27 +++---- kitty/os_window_size.py | 8 +-- kitty/remote_control.py | 56 +++++++-------- kitty/render_cache.py | 6 +- kitty/search_query_parser.py | 31 ++++---- kitty/session.py | 13 ++-- kitty/shaders.py | 7 +- kitty/shell_integration.py | 22 +++--- kitty/short_uuid.py | 5 +- kitty/tab_bar.py | 29 ++++---- kitty/tabs.py | 57 +++++++-------- kitty/terminfo.py | 5 +- kitty/types.py | 7 +- kitty/typing.py | 5 +- kitty/update_check.py | 6 +- kitty/utils.py | 60 +++++++--------- kitty/window.py | 91 +++++++++++------------- kitty/window_list.py | 25 +++---- 48 files changed, 593 insertions(+), 602 deletions(-) diff --git a/kitty/actions.py b/kitty/actions.py index 87caef884..2fa59ef82 100644 --- a/kitty/actions.py +++ b/kitty/actions.py @@ -2,7 +2,7 @@ # License: GPLv3 Copyright: 2021, Kovid Goyal import inspect -from typing import Dict, List, NamedTuple, cast +from typing import NamedTuple, cast from .boss import Boss from .tabs import Tab @@ -17,7 +17,7 @@ class Action(NamedTuple): long_help: str -groups: Dict[ActionGroup, str] = { +groups: dict[ActionGroup, str] = { 'cp': 'Copy/paste', 'sc': 'Scrolling', 'win': 'Window management', @@ -32,10 +32,10 @@ group_title = groups.__getitem__ @run_once -def get_all_actions() -> Dict[ActionGroup, List[Action]]: +def get_all_actions() -> dict[ActionGroup, list[Action]]: ' test docstring ' - ans: Dict[ActionGroup, List[Action]] = {} + ans: dict[ActionGroup, list[Action]] = {} def is_action(x: object) -> bool: return isinstance(getattr(x, 'action_spec', None), ActionSpec) @@ -73,9 +73,9 @@ def as_rst() -> str: from .conf.types import Mapping from .options.definition import definition allg = get_all_actions() - lines: List[str] = [] + lines: list[str] = [] a = lines.append - maps: Dict[str, List[Mapping]] = {} + maps: dict[str, list[Mapping]] = {} for m in definition.iter_all_maps(): if m.documented: func = m.action_def.split()[0] diff --git a/kitty/bash.py b/kitty/bash.py index ae47bb794..649ba8564 100644 --- a/kitty/bash.py +++ b/kitty/bash.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal -from typing import Dict, List, Tuple from .utils import shlex_split @@ -10,9 +9,9 @@ def decode_ansi_c_quoted_string(text: str) -> str: return next(shlex_split(text, True)) -def decode_double_quoted_string(text: str, pos: int) -> Tuple[str, int]: +def decode_double_quoted_string(text: str, pos: int) -> tuple[str, int]: escapes = r'"\$`' - buf: List[str] = [] + buf: list[str] = [] a = buf.append while pos < len(text): ch = text[pos] @@ -30,7 +29,7 @@ def decode_double_quoted_string(text: str, pos: int) -> Tuple[str, int]: return ''.join(buf), pos -def parse_modern_bash_env(text: str) -> Dict[str, str]: +def parse_modern_bash_env(text: str) -> dict[str, str]: ans = {} for line in text.splitlines(): idx = line.find('=') @@ -46,7 +45,7 @@ def parse_modern_bash_env(text: str) -> Dict[str, str]: return ans -def parse_bash_env(text: str, bash_version: str) -> Dict[str, str]: +def parse_bash_env(text: str, bash_version: str) -> dict[str, str]: # See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html parts = bash_version.split('.') bv = tuple(map(int, parts[:2])) diff --git a/kitty/borders.py b/kitty/borders.py index 1ccb49fe4..9575308e5 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal +from collections.abc import Iterable, Sequence from enum import IntFlag -from typing import Iterable, NamedTuple, Sequence +from typing import NamedTuple from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, get_options, init_borders_program, os_window_has_background_image from .shaders import program_for diff --git a/kitty/boss.py b/kitty/boss.py index 767c07132..342d7ca0b 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -8,6 +8,7 @@ import json import os import re import sys +from collections.abc import Container, Generator, Iterable, Iterator, Sequence from contextlib import contextmanager, suppress from functools import partial from gettext import gettext as _ @@ -17,17 +18,8 @@ from typing import ( TYPE_CHECKING, Any, Callable, - Container, - Dict, - Generator, - Iterable, - Iterator, - List, Literal, Optional, - Sequence, - Set, - Tuple, Union, ) from weakref import WeakValueDictionary @@ -159,7 +151,7 @@ if TYPE_CHECKING: from .rc.base import ResponseType # }}} -RCResponse = Union[Dict[str, Any], None, AsyncResponse] +RCResponse = Union[dict[str, Any], None, AsyncResponse] class OSWindowDict(TypedDict): @@ -168,13 +160,13 @@ class OSWindowDict(TypedDict): is_focused: bool is_active: bool last_focused: bool - tabs: List[TabDict] + tabs: list[TabDict] wm_class: str wm_name: str background_opacity: float -def listen_on(spec: str) -> Tuple[int, str]: +def listen_on(spec: str) -> tuple[int, str]: import socket family, address, socket_path = parse_address_spec(spec) s = socket.socket(family) @@ -232,7 +224,7 @@ def data_for_at(w: Optional[Window], arg: str, add_wrap_markers: bool = False) - class DumpCommands: # {{{ def __init__(self, args: CLIOptions): - self.draw_dump_buf: List[str] = [] + self.draw_dump_buf: list[str] = [] if args.dump_bytes: self.dump_bytes_to = open(args.dump_bytes, 'wb') @@ -270,7 +262,7 @@ class VisualSelect: self.prev_tab_id = prev_tab_id self.prev_os_window_id = prev_os_window_id self.callback = callback - self.window_ids: List[int] = [] + self.window_ids: list[int] = [] self.window_used_for_selection_id = 0 self.reactivate_prev_tab = reactivate_prev_tab set_os_window_title(self.os_window_id, title) @@ -326,8 +318,8 @@ class Boss: self, opts: Options, args: CLIOptions, - cached_values: Dict[str, Any], - global_shortcuts: Dict[str, SingleKey], + cached_values: dict[str, Any], + global_shortcuts: dict[str, SingleKey], talk_fd: int = -1, ): set_layout_options(opts) @@ -335,23 +327,23 @@ class Boss: self.window_for_dispatch: Optional[Window] = None self.primary_selection = Clipboard(ClipboardType.primary_selection) self.update_check_started = False - self.peer_data_map: Dict[int, Optional[Dict[str, Sequence[str]]]] = {} - self.background_process_death_notify_map: Dict[int, Callable[[int, Optional[Exception]], None]] = {} + self.peer_data_map: dict[int, Optional[dict[str, Sequence[str]]]] = {} + self.background_process_death_notify_map: dict[int, Callable[[int, Optional[Exception]], None]] = {} self.encryption_key = EllipticCurveKey() self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}' - self.clipboard_buffers: Dict[str, str] = {} + self.clipboard_buffers: dict[str, str] = {} self.update_check_process: Optional['PopenType[bytes]'] = None self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary() - self.color_settings_at_startup: Dict[str, Optional[Color]] = { + self.color_settings_at_startup: dict[str, Optional[Color]] = { k: opts[k] for k in opts if isinstance(opts[k], Color) or k in nullable_colors} self.current_visual_select: Optional[VisualSelect] = None # A list of events received so far that are potentially part of a sequence keybinding. self.cached_values = cached_values - self.os_window_map: Dict[int, TabManager] = {} - self.os_window_death_actions: Dict[int, Callable[[], None]] = {} + self.os_window_map: dict[int, TabManager] = {} + self.os_window_death_actions: dict[int, Callable[[], None]] = {} self.cursor_blinking = True self.shutting_down = False - self.misc_config_errors: List[str] = [] + self.misc_config_errors: list[str] = [] # we dont allow reloading the config file to change # allow_remote_control self.allow_remote_control = opts.allow_remote_control @@ -478,7 +470,7 @@ class Boss: tab = tm.active_tab window_id_limit = max(self.window_id_map, default=-1) + 1 - def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]: + def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location == 'id' and query.startswith('-'): try: q = int(query) @@ -512,7 +504,7 @@ class Boss: tab_id_limit = max(tim, default=-1) + 1 window_id_limit = max(self.window_id_map, default=-1) + 1 - def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]: + def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location in ('id', 'window_id') and query.startswith('-'): try: q = int(query) @@ -575,7 +567,7 @@ class Boss: w = self.window_for_dispatch or self.active_window_for_cwd self._new_os_window(args, CwdRequest(w)) - def new_os_window_with_wd(self, wd: Union[str, List[str]], str_is_multiple_paths: bool = False) -> None: + def new_os_window_with_wd(self, wd: Union[str, list[str]], str_is_multiple_paths: bool = False) -> None: if isinstance(wd, str): wd = wd.split(os.pathsep) if str_is_multiple_paths else [wd] for path in wd: @@ -618,7 +610,7 @@ class Boss: if swid > 0: self_window = self.window_id_map.get(swid) - extra_data: Dict[str, Any] = {} + extra_data: dict[str, Any] = {} try: allowed_unconditionally = ( self.allow_remote_control == 'y' or @@ -645,7 +637,7 @@ class Boss: return response def ask_if_remote_cmd_is_allowed( - self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None + self, pcmd: dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None ) -> bool: from kittens.tui.operations import styled in_flight = 0 @@ -674,7 +666,7 @@ class Boss: overlay_window.window_custom_type = 'remote_command_permission_dialog' return True - def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, self_window: Optional[Window], choice: str) -> None: + def remote_cmd_permission_received(self, pcmd: dict[str, Any], window_id: int, peer_id: int, self_window: Optional[Window], choice: str) -> None: from .remote_control import encode_response_for_peer, set_user_password_allowed response: RCResponse = None window = self.window_id_map.get(window_id) @@ -698,7 +690,7 @@ class Boss: send_data_to_peer(peer_id, encode_response_for_peer(response)) def _execute_remote_command( - self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None + self, pcmd: dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None ) -> RCResponse: from .remote_control import handle_cmd try: @@ -742,7 +734,7 @@ class Boss: return self.run_background_process([path] + list(args), allow_remote_control=True) - def call_remote_control(self, self_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType': + def call_remote_control(self, self_window: Optional[Window], args: tuple[str, ...]) -> 'ResponseType': from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli from .remote_control import parse_rc_args aa = list(args) @@ -1003,7 +995,7 @@ class Boss: ) -> Window: result: bool = False - def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: + def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') == 'y' @@ -1032,7 +1024,7 @@ class Boss: ) -> Optional[Window]: result: str = '' - def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: + def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') or '' @@ -1072,7 +1064,7 @@ class Boss: ) -> None: result: str = '' - def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: + def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') or '' @@ -1184,7 +1176,7 @@ class Boss: self.io_thread_started = True for signum in self.child_monitor.handled_signals(): handled_signals.add(signum) - urls: List[str] = getattr(sys, 'cmdline_args_for_open', []) + urls: list[str] = getattr(sys, 'cmdline_args_for_open', []) if urls: delattr(sys, 'cmdline_args_for_open') sess = create_sessions(get_options(), self.args, special_window=SpecialWindow([kitty_exe(), '+runpy', 'input()'])) @@ -1313,7 +1305,7 @@ class Boss: if final_windows: self._change_font_size(final_windows) - def _change_font_size(self, sz_map: Dict[int, float]) -> None: + def _change_font_size(self, sz_map: dict[int, float]) -> None: for os_window_id, sz in sz_map.items(): tm = self.os_window_map.get(os_window_id) if tm is not None: @@ -1492,8 +1484,8 @@ class Boss: self.mouse_handler(ev) def select_window_in_tab_using_overlay(self, tab: Tab, msg: str, only_window_ids: Container[int] = ()) -> Optional[Window]: - windows: List[Tuple[Optional[int], str]] = [] - selectable_windows: List[Tuple[int, str]] = [] + windows: list[tuple[Optional[int], str]] = [] + selectable_windows: list[tuple[int, str]] = [] for i, w in tab.windows.iter_windows_with_number(only_visible=False): if only_window_ids and w.id not in only_window_ids: windows.append((None, f'Current window: {w.title}' if w is self.active_window else w.title)) @@ -1892,9 +1884,9 @@ class Boss: args: Iterable[str] = (), input_data: Optional[Union[bytes, str]] = None, window: Optional[Window] = None, - custom_callback: Optional[Callable[[Dict[str, Any], int, 'Boss'], None]] = None, + custom_callback: Optional[Callable[[dict[str, Any], int, 'Boss'], None]] = None, action_on_removal: Optional[Callable[[int, 'Boss'], None]] = None, - default_data: Optional[Dict[str, Any]] = None + default_data: Optional[dict[str, Any]] = None ) -> Any: orig_args, args = list(args), list(args) from kittens.runner import CLIOnlyKitten, KittenMetadata, create_kitten_handler @@ -1936,7 +1928,7 @@ class Boss: else: data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8') copts = common_opts_as_dict(get_options()) - final_args: List[str] = [] + final_args: list[str] = [] for x in args: if x == '@selection': sel = self.data_for_at(which='@selection', window=w) @@ -1987,9 +1979,9 @@ class Boss: self.run_kitten_with_metadata(kitten, args) def on_kitten_finish( - self, target_window_id: int, end_kitten: Callable[[Dict[str, Any], int, 'Boss'], None], + self, target_window_id: int, end_kitten: Callable[[dict[str, Any], int, 'Boss'], None], source_window: Window, - default_data: Optional[Dict[str, Any]] = None + default_data: Optional[dict[str, Any]] = None ) -> None: data, source_window.kitten_result = source_window.kitten_result, None if data is None: @@ -2064,7 +2056,7 @@ class Boss: if w: spec = None - def done(data: Dict[str, Any], target_window_id: int, self: Boss) -> None: + def done(data: dict[str, Any], target_window_id: int, self: Boss) -> None: nonlocal spec spec = data['response'] @@ -2084,7 +2076,7 @@ class Boss: @ac('misc', 'Run the kitty shell to control kitty with commands') def kitty_shell(self, window_type: str = 'window') -> None: - kw: Dict[str, Any] = {} + kw: dict[str, Any] = {} cmd = [kitty_exe(), '@'] aw = self.window_for_dispatch or self.active_window if aw is not None: @@ -2126,7 +2118,7 @@ class Boss: self.open_url(website_url()) @ac('misc', 'Open the specified URL') - def open_url(self, url: str, program: Optional[Union[str, List[str]]] = None, cwd: Optional[str] = None) -> None: + def open_url(self, url: str, program: Optional[Union[str, list[str]]] = None, cwd: Optional[str] = None) -> None: if not url: return if isinstance(program, str): @@ -2163,7 +2155,7 @@ class Boss: def open_url_with_hints(self) -> None: self.run_kitten_with_metadata('hints', window=self.window_for_dispatch) - def drain_actions(self, actions: List[KeyAction], window_for_dispatch: Optional[Window] = None, dispatch_type: str = 'KeyPress') -> None: + def drain_actions(self, actions: list[KeyAction], window_for_dispatch: Optional[Window] = None, dispatch_type: str = 'KeyPress') -> None: def callback(timer_id: Optional[int]) -> None: self.dispatch_action(actions.pop(0), window_for_dispatch, dispatch_type) @@ -2309,8 +2301,8 @@ class Boss: def process_stdin_source( self, window: Optional[Window] = None, - stdin: Optional[str] = None, copy_pipe_data: Optional[Dict[str, Any]] = None - ) -> Tuple[Optional[Dict[str, str]], Optional[bytes]]: + stdin: Optional[str] = None, copy_pipe_data: Optional[dict[str, Any]] = None + ) -> tuple[Optional[dict[str, str]], Optional[bytes]]: w = window or self.active_window if not w: return None, None @@ -2340,7 +2332,7 @@ class Boss: return data_for_at(window, which, add_wrap_markers=add_wrap_markers) def special_window_for_cmd( - self, cmd: List[str], + self, cmd: list[str], window: Optional[Window] = None, stdin: Optional[str] = None, cwd_from: Optional[CwdRequest] = None, @@ -2361,13 +2353,13 @@ class Boss: def run_background_process( self, - cmd: List[str], + cmd: list[str], cwd: Optional[str] = None, - env: Optional[Dict[str, str]] = None, + env: Optional[dict[str, str]] = None, stdin: Optional[bytes] = None, cwd_from: Optional[CwdRequest] = None, allow_remote_control: bool = False, - remote_control_passwords: Optional[Dict[str, Sequence[str]]] = None, + remote_control_passwords: Optional[dict[str, Sequence[str]]] = None, notify_on_death: Optional[Callable[[int, Optional[Exception]], None]] = None, # guaranteed to be called only after event loop tick stdout: Optional[int] = None, stderr: Optional[int] = None, ) -> None: @@ -2389,8 +2381,8 @@ class Boss: def doit(activation_token: str = '') -> None: nonlocal env - pass_fds: List[int] = [] - fds_to_close_on_launch_failure: List[int] = [] + pass_fds: list[int] = [] + fds_to_close_on_launch_failure: list[int] = [] if allow_remote_control: import socket local, remote = socket.socketpair() @@ -2522,7 +2514,7 @@ class Boss: return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor) return None - def _create_tab(self, args: List[str], cwd_from: Optional[CwdRequest] = None) -> None: + def _create_tab(self, args: list[str], cwd_from: Optional[CwdRequest] = None) -> None: as_neighbor = False if args and args[0].startswith('!'): as_neighbor = 'neighbor' in args[0][1:].split(',') @@ -2537,14 +2529,14 @@ class Boss: def new_tab_with_cwd(self, *args: str) -> None: self._create_tab(list(args), cwd_from=CwdRequest(self.window_for_dispatch or self.active_window_for_cwd)) - def new_tab_with_wd(self, wd: Union[str, List[str]], str_is_multiple_paths: bool = False) -> None: + def new_tab_with_wd(self, wd: Union[str, list[str]], str_is_multiple_paths: bool = False) -> None: if isinstance(wd, str): wd = wd.split(os.pathsep) if str_is_multiple_paths else [wd] for path in wd: special_window = SpecialWindow(None, cwd=path) self._new_tab(special_window) - def _new_window(self, args: List[str], cwd_from: Optional[CwdRequest] = None) -> Optional[Window]: + def _new_window(self, args: list[str], cwd_from: Optional[CwdRequest] = None) -> Optional[Window]: if not self.os_window_map: os_window_id = self.add_os_window() tm = self.os_window_map.get(os_window_id) @@ -2609,7 +2601,7 @@ class Boss: def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None: w = self.window_for_dispatch or self.active_window if isinstance(where, str): - windows: List[Window] = [] + windows: list[Window] = [] if where == 'active': if w: windows = [w] @@ -2626,7 +2618,7 @@ class Boss: window.screen.disable_ligatures = strategy window.refresh() - def patch_colors(self, spec: Dict[str, Optional[int]], configured: bool = False) -> None: + def patch_colors(self, spec: dict[str, Optional[int]], configured: bool = False) -> None: opts = get_options() if configured: for k, v in spec.items(): @@ -2697,7 +2689,7 @@ class Boss: old_opts = get_options() prev_paths = old_opts.all_config_paths or default_config_paths(self.args.config) paths = paths or prev_paths - bad_lines: List[BadLine] = [] + bad_lines: list[BadLine] = [] final_overrides = old_opts.config_overrides if apply_overrides else () if overrides: final_overrides += tuple(overrides) @@ -2755,10 +2747,10 @@ class Boss: def format_bad_line(bad_line: BadLine) -> str: return f'{bad_line.number}:{bad_line.exception} in line: {bad_line.line}\n' - groups: Dict[str, List[BadLine]] = {} + groups: dict[str, list[BadLine]] = {} for bl in bad_lines: groups.setdefault(bl.file, []).append(bl) - ans: List[str] = [] + ans: list[str] = [] a = ans.append for file in sorted(groups): if file: @@ -2852,13 +2844,13 @@ class Boss: target_tab.make_active() def choose_entry( - self, title: str, entries: Iterable[Tuple[Union[_T, str, None], str]], + self, title: str, entries: Iterable[tuple[Union[_T, str, None], str]], callback: Callable[[Union[_T, str, None]], None], subtitle: str = '', - hints_args: Optional[Tuple[str, ...]] = None, + hints_args: Optional[tuple[str, ...]] = None, ) -> Optional[Window]: lines = [title, subtitle, ' '] if subtitle else [title, ' '] - idx_map: List[Union[_T, str, None]] = [] + idx_map: list[Union[_T, str, None]] = [] ans: Union[str, _T, None] = None fmt = ': {1}' @@ -2869,7 +2861,7 @@ class Boss: else: lines.append(fmt.format(len(idx_map), text)) - def done(data: Dict[str, Any], target_window_id: int, self: Boss) -> None: + def done(data: dict[str, Any], target_window_id: int, self: Boss) -> None: nonlocal ans ans = idx_map[int(data['groupdicts'][0]['index'])] @@ -2926,7 +2918,7 @@ class Boss: return self._move_window_to(target_tab_id=where) w = self.window_for_dispatch or self.active_window ct = w.tabref() if w else None - items: List[Tuple[Union[str, int], str]] = [(t.id, t.effective_title) for t in self.all_tabs if t is not ct] + items: list[tuple[Union[str, int], str]] = [(t.id, t.effective_title) for t in self.all_tabs if t is not ct] items.append(('new_tab', 'New tab')) items.append(('new_os_window', 'New OS Window')) target_window = w @@ -2952,7 +2944,7 @@ class Boss: if not args or args[0] == 'new': return self._move_tab_to() - items: List[Tuple[Union[str, int], str]] = [] + items: list[tuple[Union[str, int], str]] = [] ct = self.active_tab_manager_with_dispatch for osw_id, tm in self.os_window_map.items(): if tm is not ct and tm.active_tab: @@ -2968,7 +2960,7 @@ class Boss: self.choose_entry('Choose an OS window to move the tab to', items, chosen) - def set_background_image(self, path: Optional[str], os_windows: Tuple[int, ...], configured: bool, layout: Optional[str], png_data: bytes = b'') -> None: + def set_background_image(self, path: Optional[str], os_windows: tuple[int, ...], configured: bool, layout: Optional[str], png_data: bytes = b'') -> None: set_background_image(path, os_windows, configured, layout, png_data) for os_window_id in os_windows: self.default_bg_changed_for(os_window_id) @@ -2998,7 +2990,7 @@ class Boss: def launch_urls(self, *urls: str, no_replace_window: bool = False) -> None: from .launch import force_window_launch from .open_actions import actions_for_launch - actions: List[KeyAction] = [] + actions: list[KeyAction] = [] failures = [] for url in urls: uactions = tuple(actions_for_launch(url)) diff --git a/kitty/child.py b/kitty/child.py index f1a507fed..55dbd5f19 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -4,9 +4,10 @@ import os import sys from collections import defaultdict +from collections.abc import Generator, Sequence from contextlib import contextmanager, suppress from itertools import count -from typing import TYPE_CHECKING, DefaultDict, Dict, Generator, List, Optional, Sequence, Tuple +from typing import TYPE_CHECKING, DefaultDict, Optional import kitty.fast_data_types as fast_data_types @@ -31,17 +32,17 @@ if is_macos: def cwd_of_process(pid: int) -> str: return os.path.realpath(_cwd(pid)) - def process_group_map() -> DefaultDict[int, List[int]]: - ans: DefaultDict[int, List[int]] = defaultdict(list) + def process_group_map() -> DefaultDict[int, list[int]]: + ans: DefaultDict[int, list[int]] = defaultdict(list) for pid, pgid in _process_group_map(): ans[pgid].append(pid) return ans - def cmdline_of_pid(pid: int) -> List[str]: + def cmdline_of_pid(pid: int) -> list[str]: return cmdline_(pid) else: - def cmdline_of_pid(pid: int) -> List[str]: + def cmdline_of_pid(pid: int) -> list[str]: with open(f'/proc/{pid}/cmdline', 'rb') as f: return list(filter(None, f.read().decode('utf-8').split('\0'))) @@ -62,8 +63,8 @@ else: with open(f'/proc/{pid}/environ', 'rb') as f: return f.read().decode('utf-8') - def process_group_map() -> DefaultDict[int, List[int]]: - ans: DefaultDict[int, List[int]] = defaultdict(list) + def process_group_map() -> DefaultDict[int, list[int]]: + ans: DefaultDict[int, list[int]] = defaultdict(list) for x in os.listdir('/proc'): try: pid = int(x) @@ -87,8 +88,8 @@ def checked_terminfo_dir() -> Optional[str]: return terminfo_dir if os.path.isdir(terminfo_dir) else None -def processes_in_group(grp: int) -> List[int]: - gmap: Optional[DefaultDict[int, List[int]]] = getattr(process_group_map, 'cached_map', None) +def processes_in_group(grp: int) -> list[int]: + gmap: Optional[DefaultDict[int, list[int]]] = getattr(process_group_map, 'cached_map', None) if gmap is None: try: gmap = process_group_map() @@ -110,11 +111,11 @@ def cached_process_data() -> Generator[None, None, None]: delattr(process_group_map, 'cached_map') -def parse_environ_block(data: str) -> Dict[str, str]: +def parse_environ_block(data: str) -> dict[str, str]: """Parse a C environ block of environment variables into a dictionary.""" # The block is usually raw data from the target process. It might contain # trailing garbage and lines that do not look like assignments. - ret: Dict[str, str] = {} + ret: dict[str, str] = {} pos = 0 while True: @@ -133,11 +134,11 @@ def parse_environ_block(data: str) -> Dict[str, str]: return ret -def environ_of_process(pid: int) -> Dict[str, str]: +def environ_of_process(pid: int) -> dict[str, str]: return parse_environ_block(_environ_of_process(pid)) -def process_env() -> Dict[str, str]: +def process_env() -> dict[str, str]: ans = dict(os.environ) ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None) if ssl_env_var is not None: @@ -146,14 +147,14 @@ def process_env() -> Dict[str, str]: return ans -def default_env() -> Dict[str, str]: - ans: Optional[Dict[str, str]] = getattr(default_env, 'env', None) +def default_env() -> dict[str, str]: + ans: Optional[dict[str, str]] = getattr(default_env, 'env', None) if ans is None: return process_env() return ans -def set_default_env(val: Optional[Dict[str, str]] = None) -> None: +def set_default_env(val: Optional[dict[str, str]] = None) -> None: env = process_env().copy() has_lctype = False if val: @@ -167,7 +168,7 @@ def set_LANG_in_default_env(val: str) -> None: default_env().setdefault('LANG', val) -def openpty() -> Tuple[int, int]: +def openpty() -> tuple[int, int]: master, slave = os.openpty() # Note that master and slave are in blocking mode os.set_inheritable(slave, True) os.set_inheritable(master, False) @@ -205,7 +206,7 @@ class Child: argv: Sequence[str], cwd: str, stdin: Optional[bytes] = None, - env: Optional[Dict[str, str]] = None, + env: Optional[dict[str, str]] = None, cwd_from: Optional['CwdRequest'] = None, is_clone_launch: str = '', add_listen_on_env_var: bool = True, @@ -225,12 +226,12 @@ class Child: self.cwd = os.path.abspath(cwd) self.stdin = stdin self.env = env or {} - self.final_env:Dict[str, str] = {} + self.final_env:dict[str, str] = {} self.is_default_shell = bool(self.argv and self.argv[0] == shell_path) self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell self.hold = hold - def get_final_env(self) -> Dict[str, str]: + def get_final_env(self) -> dict[str, str]: from kitty.options.utils import DELETE_ENV_VAR env = default_env().copy() opts = fast_data_types.get_options() @@ -363,7 +364,7 @@ class Child: os.close(self.terminal_ready_fd) self.terminal_ready_fd = -1 - def cmdline_of_pid(self, pid: int) -> List[str]: + def cmdline_of_pid(self, pid: int) -> list[str]: try: ans = cmdline_of_pid(pid) except Exception: @@ -373,7 +374,7 @@ class Child: return ans @property - def foreground_processes(self) -> List[ProcessDesc]: + def foreground_processes(self) -> list[ProcessDesc]: if self.child_fd is None: return [] try: @@ -393,7 +394,7 @@ class Child: return [] @property - def cmdline(self) -> List[str]: + def cmdline(self) -> list[str]: try: assert self.pid is not None return self.cmdline_of_pid(self.pid) or list(self.argv) @@ -401,7 +402,7 @@ class Child: return list(self.argv) @property - def foreground_cmdline(self) -> List[str]: + def foreground_cmdline(self) -> list[str]: try: assert self.pid_for_cwd is not None return self.cmdline_of_pid(self.pid_for_cwd) or self.cmdline @@ -409,7 +410,7 @@ class Child: return self.cmdline @property - def environ(self) -> Dict[str, str]: + def environ(self) -> dict[str, str]: try: assert self.pid is not None return environ_of_process(self.pid) or self.final_env.copy() @@ -467,7 +468,7 @@ class Child: return self.get_foreground_cwd() @property - def foreground_environ(self) -> Dict[str, str]: + def foreground_environ(self) -> dict[str, str]: pid = self.pid_for_cwd if pid is not None: with suppress(Exception): diff --git a/kitty/choose_entry.py b/kitty/choose_entry.py index 04b8370fb..d49f20de9 100644 --- a/kitty/choose_entry.py +++ b/kitty/choose_entry.py @@ -2,13 +2,14 @@ # License: GPLv3 Copyright: 2019, Kovid Goyal import re -from typing import Any, Generator, List, Type +from collections.abc import Generator +from typing import Any from .cli_stub import HintsCLIOptions from .typing import MarkType -def mark(text: str, args: HintsCLIOptions, Mark: Type[MarkType], extra_cli_args: List[str], *a: Any) -> Generator[MarkType, None, None]: +def mark(text: str, args: HintsCLIOptions, Mark: type[MarkType], extra_cli_args: list[str], *a: Any) -> Generator[MarkType, None, None]: idx = 0 found_start_line = False for m in re.finditer(r'(?m)^.+$', text): diff --git a/kitty/cli.py b/kitty/cli.py index 07c086065..b4027c4bd 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -5,9 +5,11 @@ import os import re import sys from collections import deque +from collections.abc import Iterator, Sequence from dataclasses import dataclass from enum import Enum, auto -from typing import Any, Callable, Dict, FrozenSet, Iterator, List, Match, Optional, Sequence, Tuple, Type, TypeVar, Union, cast +from re import Match +from typing import Any, Callable, Optional, TypeVar, Union, cast from .cli_stub import CLIOptions from .conf.utils import resolve_config @@ -36,9 +38,9 @@ class CompletionRelativeTo(Enum): class CompletionSpec: type: CompletionType = CompletionType.none - kwds: Tuple[str,...] = () - extensions: Tuple[str,...] = () - mime_patterns: Tuple[str,...] = () + kwds: tuple[str,...] = () + extensions: tuple[str,...] = () + mime_patterns: tuple[str,...] = () group: str = '' relative_to: CompletionRelativeTo = CompletionRelativeTo.cwd @@ -99,9 +101,9 @@ class CompletionSpec: class OptionDict(TypedDict): dest: str name: str - aliases: FrozenSet[str] + aliases: frozenset[str] help: str - choices: FrozenSet[str] + choices: frozenset[str] type: str default: Optional[str] condition: bool @@ -167,7 +169,7 @@ class GoOption: return ans + '})' @property - def sorted_choices(self) -> List[str]: + def sorted_choices(self) -> list[str]: choices = sorted(self.obj_dict['choices']) choices.remove(self.default or '') choices.insert(0, self.default or '') @@ -212,7 +214,7 @@ def surround(x: str, start: int, end: int) -> str: return x -role_map: Dict[str, Callable[[str], str]] = {} +role_map: dict[str, Callable[[str], str]] = {} def role(func: Callable[[str], str]) -> Callable[[str], str]: @@ -280,7 +282,7 @@ def code(x: str) -> str: return cyan(x) -def text_and_target(x: str) -> Tuple[str, str]: +def text_and_target(x: str) -> tuple[str, str]: parts = x.split('<', 1) return parts[0].strip(), parts[-1].rstrip('>') @@ -373,10 +375,10 @@ def disc(x: str) -> str: return ref_hyperlink(x, 'discussions-') -OptionSpecSeq = List[Union[str, OptionDict]] +OptionSpecSeq = list[Union[str, OptionDict]] -def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, OptionSpecSeq]: +def parse_option_spec(spec: Optional[str] = None) -> tuple[OptionSpecSeq, OptionSpecSeq]: if spec is None: spec = options_spec() NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP' @@ -497,9 +499,9 @@ def wrap(text: str, limit: int = 80) -> Iterator[str]: yield '' return in_escape = 0 - current_line: List[str] = [] - escapes: List[str] = [] - current_word: List[str] = [] + current_line: list[str] = [] + escapes: list[str] = [] + current_word: list[str] = [] current_line_length = 0 def print_word(ch: str = '') -> Iterator[str]: @@ -545,8 +547,8 @@ def wrap(text: str, limit: int = 80) -> Iterator[str]: yield ''.join(current_line) -def get_defaults_from_seq(seq: OptionSpecSeq) -> Dict[str, Any]: - ans: Dict[str, Any] = {} +def get_defaults_from_seq(seq: OptionSpecSeq) -> dict[str, Any]: + ans: dict[str, Any] = {} for opt in seq: if not isinstance(opt, str): ans[opt['dest']] = defval_for_opt(opt) @@ -574,14 +576,14 @@ class PrintHelpForSeq: linesz = min(screen_size().cols, 76) except OSError: linesz = 76 - blocks: List[str] = [] + blocks: list[str] = [] a = blocks.append def wa(text: str, indent: int = 0, leading_indent: Optional[int] = None) -> None: if leading_indent is None: leading_indent = indent j = '\n' + (' ' * indent) - lines: List[str] = [] + lines: list[str] = [] for ln in text.splitlines(): lines.extend(wrap(ln, limit=linesz - indent)) a((' ' * leading_indent) + j.join(lines)) @@ -608,7 +610,7 @@ class PrintHelpForSeq: defval = opt.get('default') if not opt.get('type', '').startswith('bool-'): if defval: - dt = '=[{}]'.format(italic(defval)) + dt = f'=[{italic(defval)}]' blocks[-1] += dt if opt.get('help'): t = help_text.replace('%default', str(defval)).strip() @@ -648,7 +650,7 @@ def seq_as_rst( heading_char: str = '-' ) -> str: import textwrap - blocks: List[str] = [] + blocks: list[str] = [] a = blocks.append usage = '[program-to-run ...]' if usage is None else usage @@ -696,7 +698,7 @@ def seq_as_rst( def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, extra_fields: Sequence[str] = ()) -> str: from itertools import chain - ans: List[str] = [f'class {class_name}:'] + ans: list[str] = [f'class {class_name}:'] for opt in chain(seq, disabled): if isinstance(opt, str): continue @@ -743,8 +745,8 @@ class Options: def __init__(self, seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str]): self.alias_map = {} self.seq = seq - self.names_map: Dict[str, OptionDict] = {} - self.values_map: Dict[str, Any] = {} + self.names_map: dict[str, OptionDict] = {} + self.values_map: dict[str, Any] = {} self.usage, self.message, self.appname = usage, message, appname for opt in seq: if isinstance(opt, str): @@ -801,11 +803,11 @@ class Options: self.values_map[name] = val -def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional[List[str]] = None) -> List[str]: +def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional[list[str]] = None) -> list[str]: NORMAL, EXPECTING_ARG = 'NORMAL', 'EXPECTING_ARG' state = NORMAL dargs = deque(sys.argv[1:] if args is None else args) - leftover_args: List[str] = [] + leftover_args: list[str] = [] current_option = None while dargs: @@ -1044,13 +1046,13 @@ T = TypeVar('T') def parse_args( - args: Optional[List[str]] = None, + args: Optional[list[str]] = None, ospec: Callable[[], str] = options_spec, usage: Optional[str] = None, message: Optional[str] = None, appname: Optional[str] = None, - result_class: Optional[Type[T]] = None, -) -> Tuple[T, List[str]]: + result_class: Optional[type[T]] = None, +) -> tuple[T, list[str]]: options = parse_option_spec(ospec()) seq, disabled = options oc = Options(seq, usage, message, appname) @@ -1064,7 +1066,7 @@ def parse_args( SYSTEM_CONF = f'/etc/xdg/{appname}/{appname}.conf' -def default_config_paths(conf_paths: Sequence[str]) -> Tuple[str, ...]: +def default_config_paths(conf_paths: Sequence[str]) -> tuple[str, ...]: return tuple(resolve_config(SYSTEM_CONF, defconf, conf_paths)) @@ -1078,7 +1080,7 @@ def parse_override(x: str) -> str: return override_pat().sub(r'\1 ', x.lstrip()) -def create_opts(args: CLIOptions, accumulate_bad_lines: Optional[List[BadLineType]] = None) -> KittyOpts: +def create_opts(args: CLIOptions, accumulate_bad_lines: Optional[list[BadLineType]] = None) -> KittyOpts: from .config import load_config config = default_config_paths(args.config) overrides = map(parse_override, args.override or ()) diff --git a/kitty/cli_stub.py b/kitty/cli_stub.py index 7ac3f5bcc..8ab01bdfc 100644 --- a/kitty/cli_stub.py +++ b/kitty/cli_stub.py @@ -2,7 +2,7 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal -from typing import Sequence +from collections.abc import Sequence class CLIOptions: diff --git a/kitty/clipboard.py b/kitty/clipboard.py index e74dfa0cb..2800f368f 100644 --- a/kitty/clipboard.py +++ b/kitty/clipboard.py @@ -3,10 +3,11 @@ import io import os +from collections.abc import Mapping from enum import Enum, IntEnum from gettext import gettext as _ from tempfile import TemporaryFile -from typing import IO, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union +from typing import IO, Callable, NamedTuple, Optional, Union from .conf.utils import uniq from .constants import supports_primary_selection @@ -83,7 +84,7 @@ class ClipboardType(IntEnum): class Clipboard: def __init__(self, clipboard_type: ClipboardType = ClipboardType.clipboard) -> None: - self.data: Dict[str, DataType] = {} + self.data: dict[str, DataType] = {} self.clipboard_type = clipboard_type self.enabled = self.clipboard_type is ClipboardType.clipboard or supports_primary_selection @@ -98,7 +99,7 @@ class Clipboard: set_clipboard_data_types(self.clipboard_type, tuple(self.data)) def get_text(self) -> str: - parts: List[bytes] = [] + parts: list[bytes] = [] self.get_mime("text/plain", parts.append) return b''.join(parts).decode('utf-8', 'replace') @@ -120,13 +121,13 @@ class Clipboard: output(q) def get_mime_data(self, mime: str) -> bytes: - ans: List[bytes] = [] + ans: list[bytes] = [] self.get_mime(mime, ans.append) return b''.join(ans) - def get_available_mime_types_for_paste(self) -> Tuple[str, ...]: + def get_available_mime_types_for_paste(self) -> tuple[str, ...]: if self.enabled: - parts: List[bytes] = [] + parts: list[bytes] = [] try: get_clipboard_mime(self.clipboard_type, None, parts.append) except RuntimeError as err: @@ -168,7 +169,7 @@ def get_primary_selection() -> str: return get_boss().primary_selection.get_text() -def develop() -> Tuple[Clipboard, Clipboard]: +def develop() -> tuple[Clipboard, Clipboard]: from .constants import detect_if_wayland_ok, is_macos from .fast_data_types import set_boss from .main import init_glfw_module @@ -201,7 +202,7 @@ def decode_metadata_value(k: str, x: str) -> str: class ReadRequest(NamedTuple): is_primary_selection: bool = False - mime_types: Tuple[str, ...] = ('text/plain',) + mime_types: tuple[str, ...] = ('text/plain',) id: str = '' protocol_type: ProtocolType = ProtocolType.osc_52 @@ -243,10 +244,10 @@ class WriteRequest: self.protocol_type = protocol_type self.max_size_exceeded = False self.tempfile = Tempfile(max_size=rollover_size) - self.mime_map: Dict[str, MimePos] = {} + self.mime_map: dict[str, MimePos] = {} self.currently_writing_mime = '' self.max_size = (get_options().clipboard_max_size * 1024 * 1024) if max_size < 0 else max_size - self.aliases: Dict[str, str] = {} + self.aliases: dict[str, str] = {} self.committed = False def encode_response(self, status: str = 'OK') -> bytes: @@ -320,7 +321,7 @@ class ClipboardRequestManager: else: metadata = str(data, "utf-8", "replace") epayload = data[len(data):] - m: Dict[str, str] = {} + m: dict[str, str] = {} for record in metadata.split(':'): try: k, v = record.split('=', 1) diff --git a/kitty/config.py b/kitty/config.py index 75afbffcf..df8158f81 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -3,9 +3,10 @@ import json import os +from collections.abc import Generator, Iterable from contextlib import contextmanager, suppress from functools import partial -from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple +from typing import Any, Optional from .conf.utils import BadLine, parse_config_base from .conf.utils import load_config as _load_config @@ -16,7 +17,7 @@ from .typing import TypedDict from .utils import log_error -def option_names_for_completion() -> Tuple[str, ...]: +def option_names_for_completion() -> tuple[str, ...]: return option_names @@ -42,9 +43,9 @@ def atomic_save(data: bytes, path: str) -> None: @contextmanager -def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]: +def cached_values_for(name: str) -> Generator[dict[str, Any], None, None]: cached_path = os.path.join(cache_dir(), f'{name}.json') - cached_values: Dict[str, Any] = {} + cached_values: dict[str, Any] = {} try: with open(cached_path, 'rb') as f: cached_values.update(json.loads(f.read().decode('utf-8'))) @@ -77,8 +78,8 @@ def prepare_config_file_for_editing() -> str: return defconf -def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = None) -> None: - defns: List[KeyDefinition] = [] +def finalize_keys(opts: Options, accumulate_bad_lines: Optional[list[BadLine]] = None) -> None: + defns: list[KeyDefinition] = [] for d in opts.map: if d is None: # clear_all_shortcuts defns = [] # type: ignore @@ -116,8 +117,8 @@ def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = opts.keyboard_modes = modes -def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = None) -> None: - defns: List[MouseMapping] = [] +def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[list[BadLine]] = None) -> None: + defns: list[MouseMapping] = [] for d in opts.mouse_map: if d is None: # clear_all_mouse_actions defns = [] # type: ignore @@ -139,9 +140,9 @@ def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[B opts.mousemap = mousemap -def parse_config(lines: Iterable[str], accumulate_bad_lines: Optional[List[BadLine]] = None) -> Dict[str, Any]: +def parse_config(lines: Iterable[str], accumulate_bad_lines: Optional[list[BadLine]] = None) -> dict[str, Any]: from .options.parse import create_result_dict, parse_conf_item - ans: Dict[str, Any] = create_result_dict() + ans: dict[str, Any] = create_result_dict() parse_config_base( lines, parse_conf_item, @@ -151,7 +152,7 @@ def parse_config(lines: Iterable[str], accumulate_bad_lines: Optional[List[BadLi return ans -def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> Options: +def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[list[BadLine]] = None) -> Options: from .options.parse import merge_result_dicts overrides = tuple(overrides) if overrides is not None else () @@ -179,8 +180,8 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula class KittyCommonOpts(TypedDict): select_by_word_characters: str - open_url_with: List[str] - url_prefixes: Tuple[str, ...] + open_url_with: list[str] + url_prefixes: tuple[str, ...] def common_opts_as_dict(opts: Optional[Options] = None) -> KittyCommonOpts: diff --git a/kitty/constants.py b/kitty/constants.py index 92f28d200..3c1c41c4c 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -5,8 +5,9 @@ import errno import os import pwd import sys +from collections.abc import Iterator from contextlib import suppress -from typing import TYPE_CHECKING, Any, FrozenSet, Iterator, NamedTuple, Optional, Set +from typing import TYPE_CHECKING, Any, NamedTuple, Optional from .types import run_once @@ -67,7 +68,7 @@ def kitty_exe() -> str: rpath = getattr(sys, 'kitty_run_data').get('bundle_exe_dir') if not rpath: items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(kitty_base_dir, 'kitty', 'launcher')] - seen: Set[str] = set() + seen: set[str] = set() for candidate in filter(None, items): if candidate not in seen: seen.add(candidate) @@ -275,7 +276,7 @@ def website_url(doc_name: str = '', website: str = website_base_url) -> str: return website + doc_name.lstrip('/') -handled_signals: Set[int] = set() +handled_signals: set[int] = set() def clear_handled_signals(*a: Any) -> None: @@ -315,6 +316,6 @@ def local_docs() -> str: @run_once -def wrapped_kitten_names() -> FrozenSet[str]: +def wrapped_kitten_names() -> frozenset[str]: import kitty.fast_data_types as f return frozenset(f.wrapped_kitten_names()) diff --git a/kitty/debug_config.py b/kitty/debug_config.py index fe6a3f71b..75cc15b0b 100644 --- a/kitty/debug_config.py +++ b/kitty/debug_config.py @@ -7,10 +7,11 @@ import socket import sys import termios import time +from collections.abc import Iterator, Sequence from contextlib import suppress from functools import partial from pprint import pformat -from typing import IO, Callable, Dict, Iterator, Optional, Sequence, Set, TypeVar +from typing import IO, Callable, Optional, TypeVar from kittens.tui.operations import colored, styled @@ -26,7 +27,7 @@ from .types import MouseEvent, Shortcut, mod_to_names AnyEvent = TypeVar('AnyEvent', MouseEvent, Shortcut) Print = Callable[..., None] -ShortcutMap = Dict[Shortcut, str] +ShortcutMap = dict[Shortcut, str] def green(x: str) -> str: @@ -45,7 +46,7 @@ def print_event(ev: str, defn: str, print: Print) -> None: print(f'\t{ev} → {defn}') -def print_mapping_changes(defns: Dict[str, str], changes: Set[str], text: str, print: Print) -> None: +def print_mapping_changes(defns: dict[str, str], changes: set[str], text: str, print: Print) -> None: if changes: print(title(text)) for k in sorted(changes): @@ -53,7 +54,7 @@ def print_mapping_changes(defns: Dict[str, str], changes: Set[str], text: str, p def compare_maps( - final: Dict[AnyEvent, str], final_kitty_mod: int, initial: Dict[AnyEvent, str], initial_kitty_mod: int, print: Print, mode_name: str = '' + final: dict[AnyEvent, str], final_kitty_mod: int, initial: dict[AnyEvent, str], initial_kitty_mod: int, print: Print, mode_name: str = '' ) -> None: ei = {k.human_repr(initial_kitty_mod): v for k, v in initial.items()} ef = {k.human_repr(final_kitty_mod): v for k, v in final.items()} diff --git a/kitty/entry_points.py b/kitty/entry_points.py index 048960cec..7c6900e86 100644 --- a/kitty/entry_points.py +++ b/kitty/entry_points.py @@ -4,33 +4,32 @@ import os import sys -from typing import List -def icat(args: List[str]) -> None: +def icat(args: list[str]) -> None: from kitty.constants import kitten_exe os.execl(kitten_exe(), "kitten", *args) -def list_fonts(args: List[str]) -> None: +def list_fonts(args: list[str]) -> None: from kitty.fonts.list import main as list_main list_main(args) -def runpy(args: List[str]) -> None: +def runpy(args: list[str]) -> None: if len(args) < 2: raise SystemExit('Usage: kitty +runpy "some python code"') sys.argv = ['kitty'] + args[2:] exec(args[1]) -def hold(args: List[str]) -> None: +def hold(args: list[str]) -> None: from kitty.constants import kitten_exe args = ['kitten', '__hold_till_enter__'] + args[1:] os.execvp(kitten_exe(), args) -def complete(args: List[str]) -> None: +def complete(args: list[str]) -> None: # Delegate to kitten to maintain backward compatibility if len(args) < 2 or args[1] not in ('setup', 'zsh', 'fish2', 'bash'): raise SystemExit(1) @@ -43,14 +42,14 @@ def complete(args: List[str]) -> None: os.execvp(kitten_exe(), args) -def open_urls(args: List[str]) -> None: +def open_urls(args: list[str]) -> None: setattr(sys, 'cmdline_args_for_open', True) sys.argv = ['kitty'] + args[1:] from kitty.main import main as kitty_main kitty_main() -def launch(args: List[str]) -> None: +def launch(args: list[str]) -> None: import runpy sys.argv = args[1:] try: @@ -73,7 +72,7 @@ def launch(args: List[str]) -> None: runpy.run_path(exe, run_name='__main__') -def edit(args: List[str]) -> None: +def edit(args: list[str]) -> None: import shutil from .constants import is_macos @@ -93,7 +92,7 @@ def edit(args: List[str]) -> None: os.execv(exe, args[1:]) -def shebang(args: List[str]) -> None: +def shebang(args: list[str]) -> None: from kitty.constants import kitten_exe script_path = args[1] cmd = args[2:] @@ -115,7 +114,7 @@ def shebang(args: List[str]) -> None: os.execvp(kitten_exe(), ['kitten', '__confirm_and_run_shebang__'] + cmd + [script_path]) -def run_kitten(args: List[str]) -> None: +def run_kitten(args: list[str]) -> None: try: kitten = args[1] except IndexError: @@ -127,7 +126,7 @@ def run_kitten(args: List[str]) -> None: rk(kitten) -def edit_config_file(args: List[str]) -> None: +def edit_config_file(args: list[str]) -> None: from kitty.cli import create_default_opts from kitty.fast_data_types import set_options from kitty.utils import edit_config_file as f @@ -135,7 +134,7 @@ def edit_config_file(args: List[str]) -> None: f() -def namespaced(args: List[str]) -> None: +def namespaced(args: list[str]) -> None: try: func = namespaced_entry_points[args[1]] except IndexError: diff --git a/kitty/file_transmission.py b/kitty/file_transmission.py index ed49749ae..2e6115aa3 100644 --- a/kitty/file_transmission.py +++ b/kitty/file_transmission.py @@ -10,6 +10,7 @@ import stat import tempfile from base64 import b85decode from collections import defaultdict, deque +from collections.abc import Iterable, Iterator from contextlib import suppress from dataclasses import Field, dataclass, field, fields from enum import Enum, auto @@ -17,7 +18,7 @@ from functools import partial from gettext import gettext as _ from itertools import count from time import time_ns -from typing import IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import IO, Any, Callable, DefaultDict, Deque, Optional, Union from kittens.transfer.utils import IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path from kitty.fast_data_types import ESC_OSC, FILE_TRANSFER_CODE, AES256GCMDecrypt, add_timer, base64_decode, base64_encode, get_boss, get_options, monotonic @@ -68,11 +69,11 @@ def split_for_transfer( data = data[chunk_size:] -def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union['FileTransmissionCommand', 'TransmissionError']]: - file_map: DefaultDict[Tuple[int, int], List[FileTransmissionCommand]] = defaultdict(list) +def iter_file_metadata(file_specs: Iterable[tuple[str, str]]) -> Iterator[Union['FileTransmissionCommand', 'TransmissionError']]: + file_map: DefaultDict[tuple[int, int], list[FileTransmissionCommand]] = defaultdict(list) counter = count() - def skey(sr: os.stat_result) -> Tuple[int, int]: + def skey(sr: os.stat_result) -> tuple[int, int]: return sr.st_dev, sr.st_ino def make_ftc(path: str, spec_id: str, sr: Optional[os.stat_result] = None, parent: str = '') -> FileTransmissionCommand: @@ -234,16 +235,16 @@ class TransmissionError(Exception): @run_once -def name_to_serialized_map() -> Dict[str, str]: - ans: Dict[str, str] = {} +def name_to_serialized_map() -> dict[str, str]: + ans: dict[str, str] = {} for k in fields(FileTransmissionCommand): ans[k.name] = k.metadata.get('sname', k.name) return ans @run_once -def serialized_to_field_map() -> Dict[bytes, 'Field[Any]']: - ans: Dict[bytes, 'Field[Any]'] = {} +def serialized_to_field_map() -> dict[bytes, 'Field[Any]']: + ans: dict[bytes, 'Field[Any]'] = {} for k in fields(FileTransmissionCommand): ans[k.metadata.get('sname', k.name).encode('ascii')] = k return ans @@ -280,7 +281,7 @@ class FileTransmissionCommand: ans.append(f'data={len(self.data)} bytes') return 'FTC(' + ', '.join(ans) + ')' - def asdict(self, keep_defaults: bool = False) -> Dict[str, Union[str, int, bytes]]: + def asdict(self, keep_defaults: bool = False) -> dict[str, Union[str, int, bytes]]: ans = {} for k in fields(self): val = getattr(self, k.name) @@ -508,7 +509,7 @@ class DestFile: self.existing_stat = None self.needs_unlink = False - def write_data(self, all_files: Dict[str, 'DestFile'], data: bytes, is_last: bool) -> None: + def write_data(self, all_files: dict[str, 'DestFile'], data: bytes, is_last: bool) -> None: if self.ftype is FileType.directory: raise TransmissionError(code=ErrorCode.EISDIR, file_id=self.file_id, msg='Cannot write data to a directory entry') if self.closed: @@ -583,7 +584,7 @@ def check_bypass(password: str, request_id: str, bypass_data: str) -> bool: class ActiveReceive: id: str - files: Dict[str, DestFile] + files: dict[str, DestFile] accepted: bool = False def __init__(self, request_id: str, quiet: int, bypass: str) -> None: @@ -596,7 +597,7 @@ class ActiveReceive: self.last_activity_at = monotonic() self.send_acknowledgements = quiet < 1 self.send_errors = quiet < 2 - self.pending_files_to_transmit_signature_of: Deque[Tuple[PatchFile, str]] = deque() + self.pending_files_to_transmit_signature_of: Deque[tuple[PatchFile, str]] = deque() self.signature_pending_chunks: Deque[FileTransmissionCommand] = deque() @property @@ -684,7 +685,7 @@ class SourceFile: self.open_file = None self.differ = None - def next_chunk(self, sz: int = 1024 * 1024) -> Tuple[bytes, int]: + def next_chunk(self, sz: int = 1024 * 1024) -> tuple[bytes, int]: if self.target: self.transmitted = True data = self.target @@ -726,8 +727,8 @@ class ActiveSend: self.send_acknowledgements = quiet < 1 self.send_errors = quiet < 2 self.last_activity_at = monotonic() - self.file_specs: List[Tuple[str, str]] = [] - self.queued_files_map: Dict[str, SourceFile] = {} + self.file_specs: list[tuple[str, str]] = [] + self.queued_files_map: dict[str, SourceFile] = {} self.active_file: Optional[SourceFile] = None self.pending_chunks: Deque[FileTransmissionCommand] = deque() self.metadata_sent = False @@ -805,8 +806,8 @@ class FileTransmission: def __init__(self, window_id: int): self.window_id = window_id - self.active_receives: Dict[str, ActiveReceive] = {} - self.active_sends: Dict[str, ActiveSend] = {} + self.active_receives: dict[str, ActiveReceive] = {} + self.active_sends: dict[str, ActiveSend] = {} self.pending_receive_responses: Deque[FileTransmissionCommand] = deque() self.pending_timer: Optional[int] = None @@ -1231,7 +1232,7 @@ class TestFileTransmission(FileTransmission): def __init__(self, allow: bool = True) -> None: super().__init__(0) - self.test_responses: List[Dict[str, Union[str, int, bytes]]] = [] + self.test_responses: list[dict[str, Union[str, int, bytes]]] = [] self.allow = allow def write_ftc_to_child(self, payload: FileTransmissionCommand, appendleft: bool = False, use_pending: bool = True) -> bool: diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index 685148f80..f04c76c5d 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -1,5 +1,6 @@ +from collections.abc import Sequence from enum import Enum, IntEnum, auto -from typing import TYPE_CHECKING, Dict, List, Literal, NamedTuple, Optional, Sequence, Tuple, TypedDict, TypeVar, Union +from typing import TYPE_CHECKING, Literal, NamedTuple, Optional, TypedDict, TypeVar, Union from kitty.fast_data_types import ParsedFontFeature from kitty.types import run_once @@ -30,7 +31,7 @@ class VariableAxis(TypedDict): class NamedStyle(TypedDict): - axis_values: Dict[str, float] + axis_values: dict[str, float] name: str psname: str # can be empty string when not present @@ -66,7 +67,7 @@ class DesignAxis(TypedDict): name: str ordering: int tag: str - values: List[DesignValue] + values: list[DesignValue] class AxisValue(TypedDict): @@ -77,16 +78,16 @@ class AxisValue(TypedDict): class MultiAxisStyle(TypedDict): flags: int name: str - values: Tuple[AxisValue, ...] + values: tuple[AxisValue, ...] class VariableData(TypedDict): - axes: Tuple[VariableAxis, ...] - named_styles: Tuple[NamedStyle, ...] + axes: tuple[VariableAxis, ...] + named_styles: tuple[NamedStyle, ...] variations_postscript_name_prefix: str elided_fallback_name: str - design_axes: Tuple[DesignAxis, ...] - multi_axis_styles: Tuple[MultiAxisStyle, ...] + design_axes: tuple[DesignAxis, ...] + multi_axis_styles: tuple[MultiAxisStyle, ...] class ModificationType(Enum): @@ -131,9 +132,9 @@ class FontSpec(NamedTuple): postscript_name: Optional[str] = None full_name: Optional[str] = None system: Optional[str] = None - axes: Tuple[Tuple[str, float], ...] = () + axes: tuple[tuple[str, float], ...] = () variable_name: Optional[str] = None - features: Tuple[ParsedFontFeature, ...] = () + features: tuple[ParsedFontFeature, ...] = () created_from_string: str = '' @classmethod @@ -145,7 +146,7 @@ class FontSpec(NamedTuple): return FontSpec(system=spec, created_from_string=spec) axes = {} defined = {} - features: Tuple[ParsedFontFeature, ...] = () + features: tuple[ParsedFontFeature, ...] = () for item in items: k, sep, v = item.partition('=') if sep != '=': @@ -222,7 +223,7 @@ class Scorer: self.monospaced = monospaced self.prefer_variable = prefer_variable - def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]: + def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: raise NotImplementedError() def __repr__(self) -> str: diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 4c5acab22..a16b6f49e 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -7,10 +7,11 @@ # import math +from collections.abc import Iterable, Iterator, MutableSequence, Sequence from functools import lru_cache, wraps from functools import partial as p from itertools import repeat -from typing import Any, Callable, Dict, Iterable, Iterator, List, Literal, MutableSequence, Optional, Sequence, Tuple +from typing import Any, Callable, Literal, Optional scale = (0.001, 1., 1.5, 2.) _dpi = 96.0 @@ -56,7 +57,7 @@ def half_vline(buf: BufType, width: int, height: int, level: int = 1, which: str draw_vline(buf, width, y1, y2, width // 2, level) -def get_holes(sz: int, hole_sz: int, num: int) -> List[Tuple[int, ...]]: +def get_holes(sz: int, hole_sz: int, num: int) -> list[tuple[int, ...]]: all_holes_use = (num + 1) * hole_sz individual_block_size = (sz - all_holes_use) // (num + 1) half_hole_sz = hole_sz // 2 @@ -256,7 +257,7 @@ def half_triangle(buf: SSByteArray, width: int, height: int, which: str = 'left' fill_region(buf, width, height, limits, inverted) -def thick_line(buf: BufType, width: int, height: int, thickness_in_pixels: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None: +def thick_line(buf: BufType, width: int, height: int, thickness_in_pixels: int, p1: tuple[int, int], p2: tuple[int, int]) -> None: if p1[0] > p2[0]: p1, p2 = p2, p1 leq = line_equation(*p1, *p2) @@ -313,7 +314,7 @@ def half_cross_line(buf: SSByteArray, width: int, height: int, which: str = 'tl' def mid_lines(buf: SSByteArray, width: int, height: int, level: int = 1, pts: Iterable[str] = ('lt',)) -> None: mid_x, mid_y = width // 2, height // 2 - def pt_to_coords(p: str) -> Tuple[int, int]: + def pt_to_coords(p: str) -> tuple[int, int]: if p == 'l': return 0, mid_y if p == 't': @@ -329,7 +330,7 @@ def mid_lines(buf: SSByteArray, width: int, height: int, level: int = 1, pts: It thick_line(buf, width, height, buf.supersample_factor * thickness(level), p1, p2) -def get_fading_lines(total_length: int, num: int = 1, fade: str = 'right') -> Iterator[Tuple[int, int]]: +def get_fading_lines(total_length: int, num: int = 1, fade: str = 'right') -> Iterator[tuple[int, int]]: if fade == 'left' or fade == 'up': d1 = total_length dir = -1 @@ -367,7 +368,7 @@ def fading_vline(buf: SSByteArray, width: int, height: int, level: int = 1, num: ParameterizedFunc = Callable[[float], float] -def cubic_bezier(start: Tuple[int, int], end: Tuple[int, int], c1: Tuple[int, int], c2: Tuple[int, int]) -> Tuple[ParameterizedFunc, ParameterizedFunc]: +def cubic_bezier(start: tuple[int, int], end: tuple[int, int], c1: tuple[int, int], c2: tuple[int, int]) -> tuple[ParameterizedFunc, ParameterizedFunc]: def bezier_eq(p0: int, p1: int, p2: int, p3: int) -> ParameterizedFunc: @@ -397,7 +398,7 @@ def find_bezier_for_D(width: int, height: int) -> int: cx += 1 -def get_bezier_limits(bezier_x: ParameterizedFunc, bezier_y: ParameterizedFunc) -> Iterator[Tuple[float, float]]: +def get_bezier_limits(bezier_x: ParameterizedFunc, bezier_y: ParameterizedFunc) -> Iterator[tuple[float, float]]: start_x = int(bezier_x(0)) max_x = int(bezier_x(0.5)) last_t, t_limit = 0., 0.5 @@ -481,7 +482,7 @@ def draw_parametrized_curve( def circle_equations( origin_x: int = 0, origin_y: int = 0, radius: float = 10., # radius is in pixels as are origin co-ords start_at: float = 0., end_at: float = 360. -) -> Tuple[ParameterizedFunc, ParameterizedFunc]: +) -> tuple[ParameterizedFunc, ParameterizedFunc]: conv = math.pi / 180. start = start_at * conv end = end_at * conv @@ -499,7 +500,7 @@ def circle_equations( def rectircle_equations( cell_width: int, cell_height: int, supersample_factor: int, which: str = '╭' -) -> Tuple[ParameterizedFunc, ParameterizedFunc]: +) -> tuple[ParameterizedFunc, ParameterizedFunc]: ''' Return two functions, x(t) and y(t) that map the parameter t which must be in the range [0, 1] to x and y coordinates in the cell. The rectircle equation @@ -615,7 +616,7 @@ def commit(buf: SSByteArray, width: int, height: int, level: int = 1, scale: flo draw_circle(buf, width, height, scale=scale, gap=thickness(level) * factor, invert=True) -def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> Tuple[int, int]: +def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> tuple[int, int]: x1, x2 = (0, width // 2) if which == 'left' else (width // 2, width) gap = thickness(level + 1, horizontal=False) if only != 'bottom': @@ -625,7 +626,7 @@ def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: st return height // 2 - gap, height // 2 + gap -def half_dvline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'top', only: Optional[str] = None) -> Tuple[int, int]: +def half_dvline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'top', only: Optional[str] = None) -> tuple[int, int]: y1, y2 = (0, height // 2) if which == 'top' else (height // 2, height) gap = thickness(level + 1, horizontal=True) if only != 'right': @@ -635,12 +636,12 @@ def half_dvline(buf: BufType, width: int, height: int, level: int = 1, which: st return width // 2 - gap, width // 2 + gap -def dvline(buf: BufType, width: int, height: int, only: Optional[str] = None, level: int = 1) -> Tuple[int, int]: +def dvline(buf: BufType, width: int, height: int, only: Optional[str] = None, level: int = 1) -> tuple[int, int]: half_dvline(buf, width, height, only=only, level=level) return half_dvline(buf, width, height, only=only, which='bottom', level=level) -def dhline(buf: BufType, width: int, height: int, only: Optional[str] = None, level: int = 1) -> Tuple[int, int]: +def dhline(buf: BufType, width: int, height: int, only: Optional[str] = None, level: int = 1) -> tuple[int, int]: half_dhline(buf, width, height, only=only, level=level) return half_dhline(buf, width, height, only=only, which='bottom', level=level) @@ -901,7 +902,7 @@ def sextant(buf: BufType, width: int, height: int, level: int = 1, which: int = @supersampled() def smooth_mosaic( buf: SSByteArray, width: int, height: int, level: int = 1, - lower: bool = True, a: Tuple[float, float] = (0, 0), b: Tuple[float, float] = (0, 0) + lower: bool = True, a: tuple[float, float] = (0, 0), b: tuple[float, float] = (0, 0) ) -> None: ax, ay = int(a[0] * (width - 1)), int(a[1] * (height - 1)) bx, by = int(b[0] * (width - 1)), int(b[1] * (height - 1)) @@ -953,12 +954,12 @@ def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int buf[offset + x] = 255 -def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tuple[int, ...] = (0,), horizontal: bool = False) -> None: +def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: tuple[int, ...] = (0,), horizontal: bool = False) -> None: for x in which: eight_bar(buf, width, height, level, x, horizontal) -def frame(buf: BufType, width: int, height: int, edges: Tuple[Literal['l', 'r', 't', 'b'], ...] = ('l', 'r', 't', 'b'), level: int = 0) -> None: +def frame(buf: BufType, width: int, height: int, edges: tuple[Literal['l', 'r', 't', 'b'], ...] = ('l', 'r', 't', 'b'), level: int = 0) -> None: h = thickness(level=level, horizontal=True) v = thickness(level=level, horizontal=False) @@ -1010,7 +1011,7 @@ def progress_bar(buf: BufType, width: int, height: int, which: Literal['l', 'm', @lru_cache(maxsize=64) -def distribute_dots(available_space: int, num_of_dots: int) -> Tuple[Tuple[int, ...], int]: +def distribute_dots(available_space: int, num_of_dots: int) -> tuple[tuple[int, ...], int]: dot_size = max(1, available_space // (2 * num_of_dots)) extra = available_space - 2 * num_of_dots * dot_size gaps = list(repeat(dot_size, num_of_dots)) @@ -1048,7 +1049,7 @@ def braille(buf: BufType, width: int, height: int, which: int = 0) -> None: braille_dot(buf, width, height, col, row) -box_chars: Dict[str, List[Callable[[BufType, int, int], Any]]] = { +box_chars: dict[str, list[Callable[[BufType, int, int], Any]]] = { '─': [hline], '━': [p(hline, level=3)], '│': [vline], diff --git a/kitty/fonts/common.py b/kitty/fonts/common.py index f42417334..8d34e2aff 100644 --- a/kitty/fonts/common.py +++ b/kitty/fonts/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union from kitty.constants import is_macos from kitty.fast_data_types import ParsedFontFeature @@ -13,7 +13,7 @@ if TYPE_CHECKING: from kitty.fast_data_types import Face as FT_Face FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map'] - FontMap = Dict[FontCollectionMapType, Dict[str, List[Descriptor]]] + FontMap = dict[FontCollectionMapType, dict[str, list[Descriptor]]] Face = Union[FT_Face, CTFace] def all_fonts_map(monospaced: bool) -> FontMap: ... def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: ... @@ -27,8 +27,8 @@ if TYPE_CHECKING: def is_monospace(descriptor: Descriptor) -> bool: ... def is_variable(descriptor: Descriptor) -> bool: ... def set_named_style(name: str, font: Descriptor, vd: VariableData) -> bool: ... - def set_axis_values(tag_map: Dict[str, float], font: Descriptor, vd: VariableData) -> bool: ... - def get_axis_values(font: Descriptor, vd: VariableData) -> Dict[str, float]: ... + def set_axis_values(tag_map: dict[str, float], font: Descriptor, vd: VariableData) -> bool: ... + def get_axis_values(font: Descriptor, vd: VariableData) -> dict[str, float]: ... else: FontCollectionMapType = FontMap = None from kitty.fast_data_types import specialize_font_descriptor @@ -64,7 +64,7 @@ else: return Face(descriptor=descriptor) -cache_for_variable_data_by_path: Dict[str, VariableData] = {} +cache_for_variable_data_by_path: dict[str, VariableData] = {} attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'} @@ -99,16 +99,16 @@ class FamilyAxisValues: f = getattr(self, f'get_{tag}', None) return None if f is None else f(bold, italic) - def set_regular_values(self, axis_values: Dict[str, float]) -> None: + def set_regular_values(self, axis_values: dict[str, float]) -> None: self.regular_weight = axis_values.get('wght') self.regular_width = axis_values.get('wdth') self.regular_ital = axis_values.get('ital') self.regular_slant = axis_values.get('slnt') - def set_bold_values(self, axis_values: Dict[str, float]) -> None: + def set_bold_values(self, axis_values: dict[str, float]) -> None: self.bold_weight = axis_values.get('wght') - def set_italic_values(self, axis_values: Dict[str, float]) -> None: + def set_italic_values(self, axis_values: dict[str, float]) -> None: self.italic_ital = axis_values.get('ital') self.italic_slant = axis_values.get('slnt') @@ -133,7 +133,7 @@ def get_variable_data_for_face(d: Face) -> VariableData: def find_best_match_in_candidates( - candidates: List[DescriptorVar], scorer: Scorer, is_medium_face: bool, ignore_face: Optional[DescriptorVar] = None + candidates: list[DescriptorVar], scorer: Scorer, is_medium_face: bool, ignore_face: Optional[DescriptorVar] = None ) -> Optional[DescriptorVar]: if candidates: for x in scorer.sorted_candidates(candidates): @@ -232,7 +232,7 @@ def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool, famil return ans -def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced: bool, candidates: List[Descriptor]) -> Descriptor: +def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced: bool, candidates: list[Descriptor]) -> Descriptor: if spec.variable_name is not None: q = spec.variable_name.lower() for font in candidates: @@ -293,7 +293,7 @@ def get_fine_grained_font( return find_last_resort_text_font(bold, italic, monospaced) -def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> Tuple[Descriptor, bool]: +def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> tuple[Descriptor, bool]: vd = face_from_descriptor(pat).get_variable_data() pat = pat.copy() if spec.style: @@ -348,7 +348,7 @@ class FontFiles(TypedDict): bi: Descriptor -actually_variable_cache: Dict[str, bool] = {} +actually_variable_cache: dict[str, bool] = {} def is_actually_variable_despite_fontconfigs_lies(d: Descriptor) -> bool: @@ -368,7 +368,7 @@ def is_actually_variable_despite_fontconfigs_lies(d: Descriptor) -> bool: def get_font_files(opts: Options) -> FontFiles: - ans: Dict[str, Descriptor] = {} + ans: dict[str, Descriptor] = {} match_is_more_specific_than_family = Event() medium_font = get_font_from_spec(opts.font_family, match_is_more_specific_than_family=match_is_more_specific_than_family) medium_font_is_variable = is_variable(medium_font) or is_actually_variable_despite_fontconfigs_lies(medium_font) @@ -397,14 +397,14 @@ def get_font_files(opts: Options) -> FontFiles: return {'medium': ans['medium'], 'bold': ans['bold'], 'italic': ans['italic'], 'bi': ans['bi']} -def axis_values_are_equal(defaults: Dict[str, float], a: Dict[str, float], b: Dict[str, float]) -> bool: +def axis_values_are_equal(defaults: dict[str, float], a: dict[str, float], b: dict[str, float]) -> bool: ad, bd = defaults.copy(), defaults.copy() ad.update(a) bd.update(b) return ad == bd -def _get_named_style(axis_map: Dict[str, float], vd: VariableData) -> Optional[NamedStyle]: +def _get_named_style(axis_map: dict[str, float], vd: VariableData) -> Optional[NamedStyle]: defaults = {ax['tag']: ax['default'] for ax in vd['axes']} for ns in vd['named_styles']: if axis_values_are_equal(defaults, ns['axis_values'], axis_map): @@ -437,9 +437,9 @@ def get_named_style(face_or_descriptor: Union[Face, Descriptor]) -> Optional[Nam return _get_named_style(axis_map, vd) -def get_axis_map(face_or_descriptor: Union[Face, Descriptor]) -> Dict[str, float]: +def get_axis_map(face_or_descriptor: Union[Face, Descriptor]) -> dict[str, float]: base_axis_map = {} - axis_map: Dict[str, float] = {} + axis_map: dict[str, float] = {} if isinstance(face_or_descriptor, dict): d: Descriptor = face_or_descriptor vd = get_variable_data_for_descriptor(d) diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index 1b5965037..bb4d69d65 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -4,8 +4,9 @@ import itertools import operator from collections import defaultdict +from collections.abc import Generator, Iterable, Sequence from functools import lru_cache -from typing import Dict, Generator, Iterable, List, NamedTuple, Optional, Sequence, Tuple +from typing import NamedTuple, Optional from kitty.fast_data_types import CTFace, coretext_all_fonts from kitty.typing import CoreTextFont @@ -19,12 +20,12 @@ attr_map = {(False, False): 'font_family', (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: ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}, 'variable_map': {}} - vmap: Dict[str, List[CoreTextFont]] = defaultdict(list) + vmap: dict[str, list[CoreTextFont]] = defaultdict(list) for x in all_fonts: f = family_name_to_key(x['family']) s = family_name_to_key(x['style']) @@ -85,7 +86,7 @@ class WeightRange(NamedTuple): wr = WeightRange() -@lru_cache() +@lru_cache def weight_range_for_family(family: str) -> WeightRange: faces = all_fonts_map(True)['family_map'].get(family_name_to_key(family), ()) mini, maxi, medium, bold = wr.minimum, wr.maximum, wr.medium, wr.bold @@ -134,7 +135,7 @@ class CTScorer(Scorer): is_regular_width = not candidate['expanded'] and not candidate['condensed'] return Score(variable_score, bold_score + italic_score, monospace_match, 0 if is_regular_width else 1) - def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]: + def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: self.weight_range = None families = {x['family'] for x in candidates} if len(families) == 1: @@ -201,7 +202,7 @@ def find_best_match( return candidates[0] -def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]: +def font_for_family(family: str) -> tuple[CoreTextFont, bool, bool]: ans = find_best_match(family, monospaced=False) return ans, ans['bold'], ans['italic'] @@ -212,7 +213,7 @@ def descriptor(f: ListedFont) -> CoreTextFont: return d -def prune_family_group(g: List[ListedFont]) -> List[ListedFont]: +def prune_family_group(g: list[ListedFont]) -> list[ListedFont]: # CoreText returns a separate font for every style in the variable font, so # merge them. variable_paths = {descriptor(f)['path']: False for f in g if f['is_variable']} @@ -228,7 +229,7 @@ def prune_family_group(g: List[ListedFont]) -> List[ListedFont]: return [x for x in g if is_ok(descriptor(x))] -def set_axis_values(tag_map: Dict[str, float], font: CoreTextFont, vd: VariableData) -> bool: +def set_axis_values(tag_map: dict[str, float], font: CoreTextFont, vd: VariableData) -> bool: known_axes = {ax['tag'] for ax in vd['axes']} previous = font.get('axis_map', {}) new = previous.copy() @@ -256,5 +257,5 @@ def set_named_style(name: str, font: CoreTextFont, vd: VariableData) -> bool: return False -def get_axis_values(font: CoreTextFont, vd: VariableData) -> Dict[str, float]: +def get_axis_values(font: CoreTextFont, vd: VariableData) -> dict[str, float]: return font.get('axis_map', {}) diff --git a/kitty/fonts/features.py b/kitty/fonts/features.py index 5c324c48a..0b244cedf 100644 --- a/kitty/fonts/features.py +++ b/kitty/fonts/features.py @@ -2,7 +2,7 @@ # License: GPLv3 Copyright: 2024, Kovid Goyal from enum import IntEnum -from typing import Dict, NamedTuple +from typing import NamedTuple class Type(IntEnum): @@ -16,7 +16,7 @@ class FeatureDefinition(NamedTuple): type: Type # From: https://learn.microsoft.com/en-ca/typography/opentype/spec/featurelist -known_features: Dict[str, FeatureDefinition] = { # {{{ +known_features: dict[str, FeatureDefinition] = { # {{{ 'aalt': FeatureDefinition('Access All Alternates', Type.index), 'abvf': FeatureDefinition('Above-base Forms', Type.hidden), 'abvm': FeatureDefinition('Above-base Mark Positioning', Type.hidden), diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index 9c25a8de7..59b0f4fc5 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -2,8 +2,9 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import sys +from collections.abc import Generator, Sequence from functools import lru_cache -from typing import Dict, Generator, List, Literal, NamedTuple, Optional, Sequence, Tuple, cast +from typing import Literal, NamedTuple, Optional, cast from kitty.fast_data_types import ( FC_DUAL, @@ -24,10 +25,10 @@ from kitty.typing import FontConfigPattern from . import Descriptor, DescriptorVar, ListedFont, Score, Scorer, VariableData, family_name_to_key FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map'] -FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]] +FontMap = dict[FontCollectionMapType, 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': {}, 'variable_map': {}} for x in all_fonts: if not x.get('path'): @@ -78,7 +79,7 @@ def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None] } -@lru_cache() +@lru_cache def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> FontConfigPattern: return fc_match_impl(family, bold, italic, spacing) @@ -96,7 +97,7 @@ class WeightRange(NamedTuple): wr = WeightRange() -@lru_cache() +@lru_cache def weight_range_for_family(family: str) -> WeightRange: faces = all_fonts_map(True)['family_map'].get(family_name_to_key(family), ()) mini, maxi, medium, bold = wr.minimum, wr.maximum, wr.medium, wr.bold @@ -141,7 +142,7 @@ class FCScorer(Scorer): width_score = abs(candidate['width'] - FC_WIDTH_NORMAL) return Score(variable_score, bold_score / 1000 + italic_score / 110, monospace_match, width_score) - def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> List[DescriptorVar]: + def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: self.weight_range = None families = {x['family'] for x in candidates} if len(families) == 1: @@ -181,7 +182,7 @@ def find_best_match( scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable) is_medium_face = not bold and not italic # First look for an exact match - groups: Tuple[FontCollectionMapType, ...] = ('ps_map', 'full_map', 'family_map') + groups: tuple[FontCollectionMapType, ...] = ('ps_map', 'full_map', 'family_map') for which in groups: m = font_map[which] cq = m.get(q, []) @@ -217,7 +218,7 @@ def find_best_match( return find_last_resort_text_font(bold, italic, monospaced) -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 @@ -228,7 +229,7 @@ def descriptor(f: ListedFont) -> FontConfigPattern: return d -def prune_family_group(g: List[ListedFont]) -> List[ListedFont]: +def prune_family_group(g: list[ListedFont]) -> list[ListedFont]: # fontconfig creates dummy entries for named styles in variable fonts, prune them variable_paths = {descriptor(f)['path'] for f in g if f['is_variable']} if not variable_paths: @@ -269,7 +270,7 @@ def lift_axes_to_named_style_if_possible(font: FontConfigPattern, vd: VariableDa return False -def set_axis_values(tag_map: Dict[str, float], font: FontConfigPattern, vd: VariableData) -> bool: +def set_axis_values(tag_map: dict[str, float], font: FontConfigPattern, vd: VariableData) -> bool: axes = list(font.get('axes', ())) or [ax['default'] for ax in vd['axes']] changed = False for i, ax in enumerate(vd['axes']): @@ -283,8 +284,8 @@ def set_axis_values(tag_map: Dict[str, float], font: FontConfigPattern, vd: Vari return changed -def get_axis_values(font: FontConfigPattern, vd: VariableData) -> Dict[str, float]: - ans: Dict[str, float] = {} +def get_axis_values(font: FontConfigPattern, vd: VariableData) -> dict[str, float]: + ans: dict[str, float] = {} ns = font.get('named_style') if ns is not None: if ns > -1 and ns < len(vd['named_styles']): diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py index 710a1b8ca..d11a98631 100644 --- a/kitty/fonts/list.py +++ b/kitty/fonts/list.py @@ -1,7 +1,8 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal -from typing import Dict, List, Optional, Sequence +from collections.abc import Sequence +from typing import Optional from kitty.constants import is_macos @@ -14,8 +15,8 @@ else: from .fontconfig import list_fonts, prune_family_group -def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]]: - g: Dict[str, List[ListedFont]] = {} +def create_family_groups(monospaced: bool = True) -> dict[str, list[ListedFont]]: + g: dict[str, list[ListedFont]] = {} for f in list_fonts(): if not monospaced or f['is_monospace']: g.setdefault(f['family'], []).append(f) diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index ea25c690d..cf8adb60e 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -4,9 +4,10 @@ import ctypes import os import sys +from collections.abc import Generator from functools import partial from math import ceil, cos, floor, pi -from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Literal, Optional, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast from kitty.constants import fonts_dir, is_macos from kitty.fast_data_types import ( @@ -40,19 +41,19 @@ else: from .fontconfig import font_for_family as font_for_family_fontconfig FontObject = Union[CoreTextFont, FontConfigPattern] -current_faces: List[Tuple[FontObject, bool, bool]] = [] +current_faces: list[tuple[FontObject, bool, bool]] = [] builtin_nerd_font_descriptor: Optional[FontObject] = None -def font_for_family(family: str) -> Tuple[FontObject, bool, bool]: +def font_for_family(family: str) -> tuple[FontObject, bool, bool]: if is_macos: return font_for_family_macos(family) return font_for_family_fontconfig(family) def merge_ranges( - a: Tuple[Tuple[int, int], _T], b: Tuple[Tuple[int, int], _T], priority_map: Dict[Tuple[int, int], int] -) -> Generator[Tuple[Tuple[int, int], _T], None, None]: + a: tuple[tuple[int, int], _T], b: tuple[tuple[int, int], _T], priority_map: dict[tuple[int, int], int] +) -> Generator[tuple[tuple[int, int], _T], None, None]: a_start, a_end = a[0] b_start, b_end = b[0] a_val, b_val = a[1], b[1] @@ -94,8 +95,8 @@ def merge_ranges( after_range = ((b_end + 1, a_end), a_val) after_range_prio = a_prio # check if the before, mid and after ranges can be coalesced - ranges: List[Tuple[Tuple[int, int], _T]] = [] - priorities: List[int] = [] + ranges: list[tuple[tuple[int, int], _T]] = [] + priorities: list[int] = [] for rq, prio in ((before_range, before_range_prio), (mid_range, mid_range_prio), (after_range, after_range_prio)): if rq is None: continue @@ -116,7 +117,7 @@ def merge_ranges( yield from ranges -def coalesce_symbol_maps(maps: Dict[Tuple[int, int], _T]) -> Dict[Tuple[int, int], _T]: +def coalesce_symbol_maps(maps: dict[tuple[int, int], _T]) -> dict[tuple[int, int], _T]: if not maps: return maps priority_map = {r: i for i, r in enumerate(maps.keys())} @@ -140,9 +141,9 @@ def coalesce_symbol_maps(maps: Dict[Tuple[int, int], _T]) -> Dict[Tuple[int, int return dict(ans) -def create_symbol_map(opts: Options) -> Tuple[Tuple[int, int, int], ...]: +def create_symbol_map(opts: Options) -> tuple[tuple[int, int, int], ...]: val = coalesce_symbol_maps(opts.symbol_map) - family_map: Dict[str, int] = {} + family_map: dict[str, int] = {} count = 0 for family in val.values(): if family not in family_map: @@ -158,11 +159,11 @@ def create_symbol_map(opts: Options) -> Tuple[Tuple[int, int, int], ...]: return sm -def create_narrow_symbols(opts: Options) -> Tuple[Tuple[int, int, int], ...]: +def create_narrow_symbols(opts: Options) -> tuple[tuple[int, int, int], ...]: return tuple((a, b, v) for (a, b), v in coalesce_symbol_maps(opts.narrow_symbols).items()) -def descriptor_for_idx(idx: int) -> Tuple[FontObject, bool, bool]: +def descriptor_for_idx(idx: int) -> tuple[FontObject, bool, bool]: return current_faces[idx] @@ -184,7 +185,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional sz = override_font_size or opts.font_size font_map = get_font_files(opts) current_faces = [(font_map['medium'], False, False)] - ftypes: List[Literal['bold', 'italic', 'bi']] = ['bold', 'italic', 'bi'] + ftypes: list[Literal['bold', 'italic', 'bi']] = ['bold', 'italic', 'bi'] indices = {k: 0 for k in ftypes} for k in ftypes: if k in font_map: @@ -387,7 +388,7 @@ def prerender_function( cursor_underline_thickness: float, dpi_x: float, dpi_y: float -) -> Tuple[Tuple[int, ...], Tuple[CBufType, ...]]: +) -> tuple[tuple[int, ...], tuple[CBufType, ...]]: # Pre-render the special underline, strikethrough and missing and cursor cells f = partial( render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline, @@ -410,7 +411,7 @@ def prerender_function( return tuple(map(ctypes.addressof, tcells)), tcells -def render_box_drawing(codepoint: int, cell_width: int, cell_height: int, dpi: float) -> Tuple[int, CBufType]: +def render_box_drawing(codepoint: int, cell_width: int, cell_height: int, dpi: float) -> tuple[int, CBufType]: CharTexture = ctypes.c_ubyte * (cell_width * cell_height) buf = CharTexture() render_box_char( @@ -424,7 +425,7 @@ class setup_for_testing: def __init__(self, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0): self.family, self.size, self.dpi = family, size, dpi - def __enter__(self) -> Tuple[Dict[Tuple[int, int, int], bytes], int, int]: + def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], int, int]: opts = defaults._replace(font_family=parse_font_spec(self.family), font_size=self.size) set_options(opts) sprites = {} @@ -446,7 +447,7 @@ class setup_for_testing: set_send_sprite_to_gpu(None) -def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> Tuple[int, int, List[bytes]]: +def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> tuple[int, int, list[bytes]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) @@ -467,7 +468,7 @@ def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: def shape_string( text: str = "abcd", family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, path: Optional[str] = None -) -> List[Tuple[int, int, int, Tuple[int, ...]]]: +) -> list[tuple[int, int, int, tuple[int, ...]]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) @@ -534,7 +535,7 @@ def test_fallback_font(qtext: Optional[str] = None, bold: bool = False, italic: try: print(text, f) except UnicodeEncodeError: - sys.stdout.buffer.write(f'{text} {f}\n'.encode('utf-8')) + sys.stdout.buffer.write(f'{text} {f}\n'.encode()) def showcase() -> None: diff --git a/kitty/key_encoding.py b/kitty/key_encoding.py index 60a143f10..77ff90b4f 100644 --- a/kitty/key_encoding.py +++ b/kitty/key_encoding.py @@ -3,7 +3,7 @@ from enum import IntEnum from functools import lru_cache -from typing import Dict, NamedTuple, Optional, Tuple, Union +from typing import NamedTuple, Optional, Union from . import fast_data_types as defines from .fast_data_types import KeyEvent as WindowSystemKeyEvent @@ -153,17 +153,17 @@ tilde_trailers = {57348, 57349, 57354, 57355, 57366, 57368, 57369, 57370, 57371, @lru_cache(2) -def get_name_to_functional_number_map() -> Dict[str, int]: +def get_name_to_functional_number_map() -> dict[str, int]: return {v: k for k, v in functional_key_number_to_name_map.items()} @lru_cache(2) -def get_functional_to_csi_number_map() -> Dict[int, int]: +def get_functional_to_csi_number_map() -> dict[int, int]: return {v: k for k, v in csi_number_to_functional_number_map.items()} @lru_cache(2) -def get_csi_number_to_letter_trailer_map() -> Dict[int, str]: +def get_csi_number_to_letter_trailer_map() -> dict[int, str]: return {v: k for k, v in letter_trailer_to_csi_number_map.items()} @@ -314,7 +314,7 @@ config_mod_map = { def decode_key_event(csi: str, csi_type: str) -> KeyEvent: parts = csi.split(';') - def get_sub_sections(x: str, missing: int = 0) -> Tuple[int, ...]: + def get_sub_sections(x: str, missing: int = 0) -> tuple[int, ...]: return tuple(int(y) if y else missing for y in x.split(':')) first_section = get_sub_sections(parts[0]) diff --git a/kitty/key_names.py b/kitty/key_names.py index 5685a7fb2..b07b33b04 100644 --- a/kitty/key_names.py +++ b/kitty/key_names.py @@ -3,11 +3,11 @@ import sys from contextlib import suppress -from typing import Callable, Dict, Optional +from typing import Callable, Optional from .constants import is_macos -functional_key_name_aliases: Dict[str, str] = { +functional_key_name_aliases: dict[str, str] = { 'ESC': 'ESCAPE', 'PGUP': 'PAGE_UP', 'PAGEUP': 'PAGE_UP', @@ -24,7 +24,7 @@ functional_key_name_aliases: Dict[str, str] = { } -character_key_name_aliases: Dict[str, str] = { +character_key_name_aliases: dict[str, str] = { 'SPC': ' ', 'SPACE': ' ', 'STAR': '*', diff --git a/kitty/keys.py b/kitty/keys.py index ae33c7670..274f554f4 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal +from collections.abc import Iterable, Iterator from gettext import gettext as _ -from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional +from typing import TYPE_CHECKING, Any, Callable, Optional from .constants import is_macos from .fast_data_types import ( @@ -37,7 +38,7 @@ def keyboard_mode_name(screen: ScreenType) -> str: return 'application' if screen.cursor_key_mode else 'normal' -def get_shortcut(keymap: KeyMap, ev: KeyEvent) -> Optional[List[KeyDefinition]]: +def get_shortcut(keymap: KeyMap, ev: KeyEvent) -> Optional[list[KeyDefinition]]: mods = ev.mods & mod_mask ans = keymap.get(SingleKey(mods, False, ev.key)) if ans is None and ev.shifted_key and mods & GLFW_MOD_SHIFT: @@ -63,8 +64,8 @@ class Mappings: ' Manage all keyboard mappings ' - def __init__(self, global_shortcuts:Optional[Dict[str, SingleKey]] = None, callback_on_mode_change: Callable[[], Any] = lambda: None) -> None: - self.keyboard_mode_stack: List[KeyboardMode] = [] + def __init__(self, global_shortcuts:Optional[dict[str, SingleKey]] = None, callback_on_mode_change: Callable[[], Any] = lambda: None) -> None: + self.keyboard_mode_stack: list[KeyboardMode] = [] self.update_keymap(global_shortcuts) self.callback_on_mode_change = callback_on_mode_change @@ -72,7 +73,7 @@ class Mappings: def current_keyboard_mode_name(self) -> str: return self.keyboard_mode_stack[-1].name if self.keyboard_mode_stack else '' - def update_keymap(self, global_shortcuts:Optional[Dict[str, SingleKey]] = None) -> None: + def update_keymap(self, global_shortcuts:Optional[dict[str, SingleKey]] = None) -> None: if global_shortcuts is None: global_shortcuts = self.set_cocoa_global_shortcuts(self.get_options()) if is_macos else {} self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()} @@ -114,7 +115,7 @@ class Mappings: mode = self.keyboard_modes[new_mode] self._push_keyboard_mode(mode) - def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> List[KeyDefinition]: + def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> list[KeyDefinition]: w = self.get_active_window() matches = [] has_sequence_match = False @@ -244,7 +245,7 @@ class Mappings: if b.args.debug_keyboard: print(*args, end=end, flush=True) - def set_cocoa_global_shortcuts(self, opts: Options) -> Dict[str, SingleKey]: + def set_cocoa_global_shortcuts(self, opts: Options) -> dict[str, SingleKey]: from .main import set_cocoa_global_shortcuts return set_cocoa_global_shortcuts(opts) # }}} diff --git a/kitty/launch.py b/kitty/launch.py index 61c3388e2..e9a28ef8c 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -4,8 +4,9 @@ import os import shutil +from collections.abc import Container, Iterable, Iterator, Sequence from contextlib import suppress -from typing import Any, Container, Dict, FrozenSet, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple +from typing import Any, NamedTuple, Optional from .boss import Boss from .child import Child @@ -27,7 +28,7 @@ except ImportError: class LaunchSpec(NamedTuple): opts: LaunchCLIOptions - args: List[str] + args: list[str] env_docs = '''\ @@ -373,8 +374,8 @@ def parse_launch_args(args: Optional[Sequence[str]] = None) -> LaunchSpec: return LaunchSpec(opts, args) -def get_env(opts: LaunchCLIOptions, active_child: Optional[Child] = None, base_env: Optional[Dict[str,str]] = None) -> Dict[str, str]: - env: Dict[str, str] = {} +def get_env(opts: LaunchCLIOptions, active_child: Optional[Child] = None, base_env: Optional[dict[str,str]] = None) -> dict[str, str]: + env: dict[str, str] = {} if opts.copy_env and active_child: env.update(active_child.foreground_environ) if base_env is not None: @@ -414,7 +415,7 @@ def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab] return tab -watcher_modules: Dict[str, Any] = {} +watcher_modules: dict[str, Any] = {} def load_watch_modules(watchers: Iterable[str]) -> Optional[Watchers]: @@ -461,14 +462,14 @@ def load_watch_modules(watchers: Iterable[str]) -> Optional[Watchers]: class LaunchKwds(TypedDict): allow_remote_control: bool - remote_control_passwords: Optional[Dict[str, Sequence[str]]] + remote_control_passwords: Optional[dict[str, Sequence[str]]] cwd_from: Optional[CwdRequest] cwd: Optional[str] location: Optional[str] override_title: Optional[str] copy_colors_from: Optional[Window] marker: Optional[str] - cmd: Optional[List[str]] + cmd: Optional[list[str]] overlay_for: Optional[int] stdin: Optional[bytes] hold: bool @@ -482,7 +483,7 @@ def apply_colors(window: Window, spec: Sequence[str]) -> None: patch_color_profiles(colors, profiles, True) -def parse_var(defn: Iterable[str]) -> Iterator[Tuple[str, str]]: +def parse_var(defn: Iterable[str]) -> Iterator[tuple[str, str]]: for item in defn: a, sep, b = item.partition('=') yield a, b @@ -511,8 +512,8 @@ force_window_launch = ForceWindowLaunch() non_window_launch_types = 'background', 'clipboard', 'primary' -def parse_remote_control_passwords(allow_remote_control: bool, passwords: Sequence[str]) -> Optional[Dict[str, Sequence[str]]]: - remote_control_restrictions: Optional[Dict[str, Sequence[str]]] = None +def parse_remote_control_passwords(allow_remote_control: bool, passwords: Sequence[str]) -> Optional[dict[str, Sequence[str]]]: + remote_control_restrictions: Optional[dict[str, Sequence[str]]] = None if allow_remote_control and passwords: from kitty.options.utils import remote_control_password remote_control_restrictions = {} @@ -525,13 +526,13 @@ def parse_remote_control_passwords(allow_remote_control: bool, passwords: Sequen def _launch( boss: Boss, opts: LaunchCLIOptions, - args: List[str], + args: list[str], target_tab: Optional[Tab] = None, force_target_tab: bool = False, active: Optional[Window] = None, is_clone_launch: str = '', rc_from_window: Optional[Window] = None, - base_env: Optional[Dict[str, str]] = None, + base_env: Optional[dict[str, str]] = None, ) -> Optional[Window]: active = active or boss.active_window_for_cwd if active: @@ -589,7 +590,7 @@ def _launch( kw['location'] = opts.location if opts.copy_colors and active: kw['copy_colors_from'] = active - pipe_data: Dict[str, Any] = {} + pipe_data: dict[str, Any] = {} if opts.stdin_source != 'none': q = str(opts.stdin_source) if opts.stdin_add_formatting: @@ -608,7 +609,7 @@ def _launch( if opts.copy_cmdline and active_child: cmd = active_child.foreground_cmdline if cmd: - final_cmd: List[str] = [] + final_cmd: list[str] = [] for x in cmd: if active and not opts.copy_cmdline: if x == '@selection': @@ -699,13 +700,13 @@ def _launch( def launch( boss: Boss, opts: LaunchCLIOptions, - args: List[str], + args: list[str], target_tab: Optional[Tab] = None, force_target_tab: bool = False, active: Optional[Window] = None, is_clone_launch: str = '', rc_from_window: Optional[Window] = None, - base_env: Optional[Dict[str, str]] = None, + base_env: Optional[dict[str, str]] = None, ) -> Optional[Window]: active = active or boss.active_window_for_cwd if opts.keep_focus and active: @@ -717,7 +718,7 @@ def launch( active.ignore_focus_changes = orig @run_once -def clone_safe_opts() -> FrozenSet[str]: +def clone_safe_opts() -> frozenset[str]: return frozenset(( 'window_title', 'tab_title', 'type', 'keep_focus', 'cwd', 'env', 'var', 'hold', 'location', 'os_window_class', 'os_window_name', 'os_window_title', 'os_window_state', @@ -725,7 +726,7 @@ def clone_safe_opts() -> FrozenSet[str]: )) -def parse_opts_for_clone(args: List[str]) -> Tuple[LaunchCLIOptions, List[str]]: +def parse_opts_for_clone(args: list[str]) -> tuple[LaunchCLIOptions, list[str]]: unsafe, unsafe_args = parse_launch_args(args) default_opts, default_args = parse_launch_args() # only copy safe options, those that dont lead to local code exec @@ -734,7 +735,7 @@ def parse_opts_for_clone(args: List[str]) -> Tuple[LaunchCLIOptions, List[str]]: return default_opts, unsafe_args -def parse_null_env(text: str) -> Dict[str, str]: +def parse_null_env(text: str) -> dict[str, str]: ans = {} for line in text.split('\0'): if line: @@ -746,7 +747,7 @@ def parse_null_env(text: str) -> Dict[str, str]: return ans -def parse_message(msg: str, simple: Container[str]) -> Iterator[Tuple[str, str]]: +def parse_message(msg: str, simple: Container[str]) -> Iterator[tuple[str, str]]: from base64 import standard_b64decode for x in msg.split(','): try: @@ -762,7 +763,7 @@ class EditCmd: def __init__(self, msg: str) -> None: self.tdir = '' - self.args: List[str] = [] + self.args: list[str] = [] self.cwd = self.file_name = self.file_localpath = '' self.file_data = b'' self.file_inode = -1, -1 @@ -871,8 +872,8 @@ class EditCmd: class CloneCmd: def __init__(self, msg: str) -> None: - self.args: List[str] = [] - self.env: Optional[Dict[str, str]] = None + self.args: list[str] = [] + self.env: Optional[dict[str, str]] = None self.cwd = '' self.shell = '' self.envfmt = 'default' @@ -917,7 +918,7 @@ class CloneCmd: self.history = v -edits_in_flight: Dict[int, EditCmd] = {} +edits_in_flight: dict[int, EditCmd] = {} def remote_edit(msg: str, window: Window) -> None: diff --git a/kitty/main.py b/kitty/main.py index f5b060608..14f9a62f9 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -5,8 +5,9 @@ import locale import os import shutil import sys +from collections.abc import Generator, Sequence from contextlib import contextmanager, suppress -from typing import Dict, Generator, List, Optional, Sequence, Tuple +from typing import Optional from .borders import load_borders_program from .boss import Boss @@ -99,7 +100,7 @@ def init_glfw(opts: Options, debug_keyboard: bool = False, debug_rendering: bool def get_macos_shortcut_for( - func_map: Dict[Tuple[str, ...], List[SingleKey]], defn: str = 'new_os_window', lookup_name: str = '' + func_map: dict[tuple[str, ...], list[SingleKey]], defn: str = 'new_os_window', lookup_name: str = '' ) -> Optional[SingleKey]: # for maximum robustness we should use opts.alias_map to resolve # aliases however this requires parsing everything on startup which could @@ -167,8 +168,8 @@ def set_x11_window_icon() -> None: log_error(err) -def set_cocoa_global_shortcuts(opts: Options) -> Dict[str, SingleKey]: - global_shortcuts: Dict[str, SingleKey] = {} +def set_cocoa_global_shortcuts(opts: Options) -> dict[str, SingleKey]: + global_shortcuts: dict[str, SingleKey] = {} if is_macos: from collections import defaultdict func_map = defaultdict(list) @@ -309,7 +310,7 @@ def setup_profiling() -> Generator[None, None, None]: print('To view the graphical call data, use: kcachegrind', cg) -def macos_cmdline(argv_args: List[str]) -> List[str]: +def macos_cmdline(argv_args: list[str]) -> list[str]: try: with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f: raw = f.read() @@ -384,7 +385,7 @@ def ensure_kitten_in_path() -> None: os.environ['PATH'] = prepend_if_not_present(os.path.dirname(correct_kitten), env_path) -def setup_manpath(env: Dict[str, str]) -> None: +def setup_manpath(env: dict[str, str]) -> None: # Ensure kitty manpages are available in frozen builds if not getattr(sys, 'frozen', False): return @@ -490,7 +491,7 @@ def _main() -> None: if sep and socket_path: os.unlink(socket_path) atexit.register(cleanup_si) - bad_lines: List[BadLine] = [] + bad_lines: list[BadLine] = [] opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines) setup_environment(opts, cli_opts) diff --git a/kitty/marks.py b/kitty/marks.py index 4b3620735..293d97251 100644 --- a/kitty/marks.py +++ b/kitty/marks.py @@ -2,8 +2,10 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal import re +from collections.abc import Generator, Iterable, Sequence from ctypes import POINTER, c_uint, c_void_p, cast -from typing import Callable, Generator, Iterable, Pattern, Sequence, Tuple, Union +from re import Pattern +from typing import Callable, Union from .utils import resolve_custom_file @@ -13,7 +15,7 @@ pointer_to_uint = POINTER(c_uint) MarkerFunc = Callable[[str, int, int, int], Generator[None, None, None]] -def get_output_variables(left_address: int, right_address: int, color_address: int) -> Tuple[c_uint, c_uint, c_uint]: +def get_output_variables(left_address: int, right_address: int, color_address: int) -> tuple[c_uint, c_uint, c_uint]: return ( cast(c_void_p(left_address), pointer_to_uint).contents, cast(c_void_p(right_address), pointer_to_uint).contents, @@ -39,7 +41,7 @@ def marker_from_regex(expression: Union[str, 'Pattern[str]'], color: int, flags: return marker -def marker_from_multiple_regex(regexes: Iterable[Tuple[int, str]], flags: int = re.UNICODE) -> MarkerFunc: +def marker_from_multiple_regex(regexes: Iterable[tuple[int, str]], flags: int = re.UNICODE) -> MarkerFunc: expr = '' color_map = {} for i, (color, spec) in enumerate(regexes): @@ -65,7 +67,7 @@ def marker_from_text(expression: str, color: int) -> MarkerFunc: return marker_from_regex(re.escape(expression), color) -def marker_from_function(func: Callable[[str], Iterable[Tuple[int, int, int]]]) -> MarkerFunc: +def marker_from_function(func: Callable[[str], Iterable[tuple[int, int, int]]]) -> MarkerFunc: def marker(text: str, left_address: int, right_address: int, color_address: int) -> Generator[None, None, None]: left, right, colorv = get_output_variables(left_address, right_address, color_address) for (ll, r, c) in func(text): @@ -77,7 +79,7 @@ def marker_from_function(func: Callable[[str], Iterable[Tuple[int, int, int]]]) return marker -def marker_from_spec(ftype: str, spec: Union[str, Sequence[Tuple[int, str]]], flags: int) -> MarkerFunc: +def marker_from_spec(ftype: str, spec: Union[str, Sequence[tuple[int, str]]], flags: int) -> MarkerFunc: if ftype == 'regex': assert not isinstance(spec, str) if len(spec) == 1: diff --git a/kitty/multiprocessing.py b/kitty/multiprocessing.py index 84ee5a5c1..a419dd2ec 100644 --- a/kitty/multiprocessing.py +++ b/kitty/multiprocessing.py @@ -6,9 +6,10 @@ import os +from collections.abc import Sequence from concurrent.futures import ProcessPoolExecutor from multiprocessing import context, get_all_start_methods, get_context, spawn, util -from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Optional, Union from .constants import kitty_exe @@ -52,7 +53,7 @@ def get_process_pool_executor( prefer_fork: bool = False, max_workers: Optional[int] = None, initializer: Optional[Callable[..., None]] = None, - initargs: Tuple[Any, ...] = () + initargs: tuple[Any, ...] = () ) -> ProcessPoolExecutor: if prefer_fork and 'fork' in get_all_start_methods(): ctx: Union[context.DefaultContext, context.ForkContext] = get_context('fork') diff --git a/kitty/notifications.py b/kitty/notifications.py index 63daad412..0f30aff82 100644 --- a/kitty/notifications.py +++ b/kitty/notifications.py @@ -4,11 +4,12 @@ import os import re from collections import OrderedDict +from collections.abc import Iterator, Sequence from contextlib import suppress from enum import Enum from functools import partial from itertools import count -from typing import Any, Callable, Dict, FrozenSet, Iterator, List, NamedTuple, Optional, Sequence, Set, Tuple, Union +from typing import Any, Callable, NamedTuple, Optional, Set, Union from weakref import ReferenceType, ref from .constants import cache_dir, config_dir, is_macos, logo_png_file, standard_icon_names @@ -34,7 +35,7 @@ class IconDataCache: def __init__(self, base_cache_dir: str = '', max_cache_size: int = 128 * 1024 * 1024): self.max_cache_size = max_cache_size - self.key_map: Dict[str, str] = {} + self.key_map: dict[str, str] = {} self.hash_map: 'OrderedDict[str, Set[str]]' = OrderedDict() self.base_cache_dir = base_cache_dir self.cache_dir = '' @@ -151,7 +152,7 @@ class Action(Enum): class DataStore: def __init__(self, max_size: int = 4 * 1024 * 1024) -> None: - self.buf: List[bytes] = [] + self.buf: list[bytes] = [] self.current_size = 0 self.max_size = max_size self.truncated = 0 @@ -208,11 +209,11 @@ class NotificationCommand: # data received from client and eventually displayed/processed title: str = '' body: str = '' - actions: FrozenSet[Action] = frozenset((Action.focus,)) + actions: frozenset[Action] = frozenset((Action.focus,)) only_when: OnlyWhen = OnlyWhen.unset urgency: Optional[Urgency] = None icon_data_key: str = '' - icon_names: Tuple[str, ...] = () + icon_names: tuple[str, ...] = () application_name: str = '' notification_types: tuple[str, ...] = () timeout: int = -2 @@ -258,7 +259,7 @@ class NotificationCommand: fields[x] = val return f'NotificationCommand{fields}' - def parse_metadata(self, metadata: str, prev: 'NotificationCommand') -> Tuple[PayloadType, bool]: + def parse_metadata(self, metadata: str, prev: 'NotificationCommand') -> tuple[PayloadType, bool]: payload_type = PayloadType.title payload_is_encoded = False if metadata: @@ -418,7 +419,7 @@ class NotificationCommand: if rule == 'all': return True from .search_query_parser import search - def get_matches(location: str, query: str, candidates: Set['NotificationCommand']) -> Set['NotificationCommand']: + def get_matches(location: str, query: str, candidates: set['NotificationCommand']) -> set['NotificationCommand']: return {x for x in candidates if x.matches_rule_item(location, query)} try: return self in search(rule, ('title', 'body', 'app', 'type'), {self}, get_matches) @@ -468,7 +469,7 @@ class MacOSIntegration(DesktopIntegration): def initialize(self) -> None: from .fast_data_types import cocoa_set_notification_activated_callback self.id_counter = count(start=1) - self.live_notification_queries: List[Tuple[int, str]] = [] + self.live_notification_queries: list[tuple[int, str]] = [] self.failed_icons: OrderedDict[str, bool] = OrderedDict() self.icd_key_prefix = os.urandom(16).hex() cocoa_set_notification_activated_callback(self.notification_activated) @@ -571,7 +572,7 @@ class FreeDesktopIntegration(DesktopIntegration): # map the id returned by the notification daemon to the # desktop_notification_id we use for the notification self.dbus_to_desktop: 'OrderedDict[int, int]' = OrderedDict() - self.desktop_to_dbus: Dict[int, int] = {} + self.desktop_to_dbus: dict[int, int] = {} def query_live_notifications(self, channel_id: int, identifier: str) -> None: self.notification_manager.send_live_response(channel_id, identifier, tuple(self.desktop_to_dbus)) @@ -747,8 +748,8 @@ class NotificationManager: def reset(self) -> None: self.icon_data_cache.clear() self.in_progress_notification_commands: 'OrderedDict[int, NotificationCommand]' = OrderedDict() - self.in_progress_notification_commands_by_client_id: Dict[str, NotificationCommand] = {} - self.pending_commands: Dict[int, NotificationCommand] = {} + self.in_progress_notification_commands_by_client_id: dict[str, NotificationCommand] = {} + self.pending_commands: dict[int, NotificationCommand] = {} def notification_created(self, desktop_notification_id: int) -> None: if n := self.in_progress_notification_commands.get(desktop_notification_id): diff --git a/kitty/open_actions.py b/kitty/open_actions.py index c9dfd18c2..61d269065 100644 --- a/kitty/open_actions.py +++ b/kitty/open_actions.py @@ -5,8 +5,9 @@ import os import posixpath import shlex +from collections.abc import Iterable, Iterator from contextlib import suppress -from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tuple, cast +from typing import Any, NamedTuple, Optional, cast from urllib.parse import ParseResult, unquote, urlparse from .conf.utils import KeyAction, to_cmdline_implementation @@ -25,14 +26,14 @@ class MatchCriteria(NamedTuple): class OpenAction(NamedTuple): - match_criteria: Tuple[MatchCriteria, ...] - actions: Tuple[KeyAction, ...] + match_criteria: tuple[MatchCriteria, ...] + actions: tuple[KeyAction, ...] def parse(lines: Iterable[str]) -> Iterator[OpenAction]: - match_criteria: List[MatchCriteria] = [] - raw_actions: List[str] = [] - alias_map: Dict[str, List[ActionAlias]] = {} + match_criteria: list[MatchCriteria] = [] + raw_actions: list[str] = [] + alias_map: dict[str, list[ActionAlias]] = {} entries = [] for line in lines: @@ -74,7 +75,7 @@ def parse(lines: Iterable[str]) -> Iterator[OpenAction]: SHELL=resolved_shell(get_options())[0] ): for (mc, action_defns) in entries: - actions: List[KeyAction] = [] + actions: list[KeyAction] = [] for defn in action_defns: actions.extend(resolve_aliases_and_parse_actions(defn, alias_map, MapType.OPEN_ACTION)) yield OpenAction(mc, tuple(actions)) @@ -198,10 +199,10 @@ def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Iterat return -actions_cache: Dict[str, Tuple[os.stat_result, Tuple[OpenAction, ...]]] = {} +actions_cache: dict[str, tuple[os.stat_result, tuple[OpenAction, ...]]] = {} -def load_actions_from_path(path: str) -> Tuple[OpenAction, ...]: +def load_actions_from_path(path: str) -> tuple[OpenAction, ...]: try: st = os.stat(path) except OSError: @@ -215,11 +216,11 @@ def load_actions_from_path(path: str) -> Tuple[OpenAction, ...]: return actions_cache[path][1] -def load_open_actions() -> Tuple[OpenAction, ...]: +def load_open_actions() -> tuple[OpenAction, ...]: return load_actions_from_path(os.path.join(config_dir, 'open-actions.conf')) -def load_launch_actions() -> Tuple[OpenAction, ...]: +def load_launch_actions() -> tuple[OpenAction, ...]: return load_actions_from_path(os.path.join(config_dir, 'launch-actions.conf')) @@ -228,7 +229,7 @@ def clear_caches() -> None: @run_once -def default_open_actions() -> Tuple[OpenAction, ...]: +def default_open_actions() -> tuple[OpenAction, ...]: return tuple(parse('''\ # Open kitty HTML docs links protocol kitty+doc @@ -237,7 +238,7 @@ action show_kitty_doc $URL_PATH @run_once -def default_launch_actions() -> Tuple[OpenAction, ...]: +def default_launch_actions() -> tuple[OpenAction, ...]: return tuple(parse('''\ # Open script files protocol file diff --git a/kitty/os_window_size.py b/kitty/os_window_size.py index 20376c2ca..b870a4182 100644 --- a/kitty/os_window_size.py +++ b/kitty/os_window_size.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal -from typing import Any, Callable, Dict, NamedTuple, Optional, Tuple, Union +from typing import Any, Callable, NamedTuple, Optional, Union from .constants import is_macos, is_wayland from .fast_data_types import get_options @@ -51,14 +51,14 @@ def edge_spacing(which: EdgeLiteral, opts:Optional[Union[WindowSizeData, Options -def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]: +def initial_window_size_func(opts: WindowSizeData, cached_values: dict[str, Any]) -> Callable[[int, int, float, float, float, float], tuple[int, int]]: if 'window-size' in cached_values and opts.remember_window_size: ws = cached_values['window-size'] try: w, h = map(sanitize_window_size, ws) - def initial_window_size(*a: Any) -> Tuple[int, int]: + def initial_window_size(*a: Any) -> tuple[int, int]: return w, h return initial_window_size except Exception: @@ -67,7 +67,7 @@ def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any] w, w_unit = opts.initial_window_sizes.width h, h_unit = opts.initial_window_sizes.height - def get_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]: + def get_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> tuple[int, int]: if not is_macos and not is_wayland(): # Not sure what the deal with scaling on X11 is xscale = yscale = 1 diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 02075ac25..5537ed925 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -6,6 +6,7 @@ import json import os import re import sys +from collections.abc import Iterable, Iterator, Sequence from contextlib import suppress from functools import lru_cache, partial from time import time_ns @@ -13,14 +14,7 @@ from types import GeneratorType from typing import ( TYPE_CHECKING, Any, - Dict, - FrozenSet, - Iterable, - Iterator, - List, Optional, - Sequence, - Tuple, Union, cast, ) @@ -43,8 +37,8 @@ from .types import AsyncResponse from .typing import BossType, WindowType from .utils import TTYIO, log_error, parse_address_spec, resolve_custom_file -active_async_requests: Dict[str, float] = {} -active_streams: Dict[str, str] = {} +active_async_requests: dict[str, float] = {} +active_streams: dict[str, str] = {} if TYPE_CHECKING: from .window import Window @@ -53,7 +47,7 @@ def encode_response_for_peer(response: Any) -> bytes: return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\' -def parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> Dict[str, Any]: +def parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> dict[str, Any]: # See https://github.com/python/cpython/issues/74379 for why we cant use # memoryview directly :(( try: @@ -88,7 +82,7 @@ def parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> D class CMDChecker: - def __call__(self, pcmd: Dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: Dict[str, Any]) -> Optional[bool]: + def __call__(self, pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> Optional[bool]: return False @@ -111,8 +105,8 @@ def fnmatch_pattern(pat: str) -> 're.Pattern[str]': def remote_control_allowed( - pcmd: Dict[str, Any], remote_control_passwords: Optional[Dict[str, Sequence[str]]], - window: Optional['Window'], extra_data: Dict[str, Any] + pcmd: dict[str, Any], remote_control_passwords: Optional[dict[str, Sequence[str]]], + window: Optional['Window'], extra_data: dict[str, Any] ) -> bool: if not remote_control_passwords: return True @@ -133,7 +127,7 @@ def remote_control_allowed( class PasswordAuthorizer: - def __init__(self, auth_items: FrozenSet[str]) -> None: + def __init__(self, auth_items: frozenset[str]) -> None: self.command_patterns = [] self.function_checkers = [] self.name = '' @@ -144,7 +138,7 @@ class PasswordAuthorizer: else: self.command_patterns.append(fnmatch_pattern(item)) - def is_cmd_allowed(self, pcmd: Dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: Dict[str, Any]) -> bool: + def is_cmd_allowed(self, pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> bool: cmd_name = pcmd.get('cmd') if not cmd_name: return False @@ -167,14 +161,14 @@ class PasswordAuthorizer: @lru_cache(maxsize=256) -def password_authorizer(auth_items: FrozenSet[str]) -> PasswordAuthorizer: +def password_authorizer(auth_items: frozenset[str]) -> PasswordAuthorizer: return PasswordAuthorizer(auth_items) -user_password_allowed: Dict[str, bool] = {} +user_password_allowed: dict[str, bool] = {} -def is_cmd_allowed(pcmd: Dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: Dict[str, Any]) -> Optional[bool]: +def is_cmd_allowed(pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> Optional[bool]: sid = pcmd.get('stream_id', '') if sid and active_streams.get(sid, '') == pcmd['cmd']: return True @@ -211,8 +205,8 @@ def close_active_stream(stream_id: str) -> None: def handle_cmd( - boss: BossType, window: Optional[WindowType], cmd: Dict[str, Any], peer_id: int, self_window: Optional[WindowType] -) -> Union[Dict[str, Any], None, AsyncResponse]: + boss: BossType, window: Optional[WindowType], cmd: dict[str, Any], peer_id: int, self_window: Optional[WindowType] +) -> Union[dict[str, Any], None, AsyncResponse]: v = cmd['version'] no_response = cmd.get('no_response', False) if tuple(v)[:2] > version[:2]: @@ -255,7 +249,7 @@ def handle_cmd( if stream: return {'ok': True, 'stream': True} return ans - response: Dict[str, Any] = {'ok': True} + response: dict[str, Any] = {'ok': True} if ans is not None: response['data'] = ans if not no_response: @@ -361,14 +355,14 @@ class SocketIO: class RCIO(TTYIO): def simple_recv(self, timeout: float) -> bytes: - ans: List[bytes] = [] + ans: list[bytes] = [] read_command_response(self.tty_fd, timeout, ans) return b''.join(ans) def do_io( - to: Optional[str], original_cmd: Dict[str, Any], no_response: bool, response_timeout: float, encrypter: 'CommandEncrypter' -) -> Dict[str, Any]: + to: Optional[str], original_cmd: dict[str, Any], no_response: bool, response_timeout: float, encrypter: 'CommandEncrypter' +) -> dict[str, Any]: payload = original_cmd.get('payload') if not isinstance(payload, GeneratorType): send_data: Union[bytes, Iterator[bytes]] = encode_send(encrypter(original_cmd)) @@ -387,7 +381,7 @@ def do_io( return {'ok': True} received = io.simple_recv(timeout=response_timeout) - return cast(Dict[str, Any], json.loads(received.decode('ascii'))) + return cast(dict[str, Any], json.loads(received.decode('ascii'))) cli_msg = ( @@ -397,7 +391,7 @@ cli_msg = ( ).format(appname=appname) -def parse_rc_args(args: List[str]) -> Tuple[RCOptions, List[str]]: +def parse_rc_args(args: list[str]) -> tuple[RCOptions, list[str]]: cmap = {name: command_for_name(name) for name in sorted(all_command_names())} cmds = (f' :green:`{cmd.name}`\n {cmd.short_desc}' for c, cmd in cmap.items()) msg = cli_msg + ( @@ -422,7 +416,7 @@ class CommandEncrypter: self.encryption_version = encryption_version self.password = password - def __call__(self, cmd: Dict[str, Any]) -> Dict[str, Any]: + def __call__(self, cmd: dict[str, Any]) -> dict[str, Any]: encrypter = AES256GCMEncrypt(self.secret) cmd['timestamp'] = time_ns() cmd['password'] = self.password @@ -446,14 +440,14 @@ class NoEncryption(CommandEncrypter): def __init__(self) -> None: ... - def __call__(self, cmd: Dict[str, Any]) -> Dict[str, Any]: + def __call__(self, cmd: dict[str, Any]) -> dict[str, Any]: return cmd def adjust_response_timeout_for_password(self, response_timeout: float) -> float: return response_timeout -def create_basic_command(name: str, payload: Any = None, no_response: bool = False, is_asynchronous: bool = False) -> Dict[str, Any]: +def create_basic_command(name: str, payload: Any = None, no_response: bool = False, is_asynchronous: bool = False) -> dict[str, Any]: ans = {'cmd': name, 'version': version, 'no_response': no_response} if payload is not None: ans['payload'] = payload @@ -467,7 +461,7 @@ def send_response_to_client(data: Any = None, error: str = '', peer_id: int = 0, if active_async_requests.pop(async_id, None) is None: return if error: - response: Dict[str, Union[bool, int, str]] = {'ok': False, 'error': error} + response: dict[str, Union[bool, int, str]] = {'ok': False, 'error': error} else: response = {'ok': True, 'data': data} if peer_id > 0: @@ -513,7 +507,7 @@ def get_password(opts: RCOptions) -> str: return ans -def get_pubkey() -> Tuple[str, bytes]: +def get_pubkey() -> tuple[str, bytes]: raw = os.environ.get('KITTY_PUBLIC_KEY', '') if not raw: raise SystemExit('Password usage requested but KITTY_PUBLIC_KEY environment variable is not available') diff --git a/kitty/render_cache.py b/kitty/render_cache.py index 8c3c1f9f8..17748bb17 100644 --- a/kitty/render_cache.py +++ b/kitty/render_cache.py @@ -3,9 +3,9 @@ import os import time +from collections.abc import Iterator from contextlib import closing, suppress from functools import partial -from typing import Iterator, Tuple from .constants import cache_dir, kitten_exe from .utils import lock_file, unlock_file @@ -72,7 +72,7 @@ class ImageRenderCache: if cp.returncode != 0: raise ValueError(f'Failed to convert path to RGBA data with error: {cp.stderr.decode("utf-8", "replace")}') - def read_metadata(self, output_path: str) -> Tuple[int, int, int]: + def read_metadata(self, output_path: str) -> tuple[int, int, int]: with open(output_path, 'rb') as f: header = f.read(8) import struct @@ -95,7 +95,7 @@ class ImageRenderCache: self.prune_entries() return output_path - def __call__(self, src: str) -> Tuple[int, int, int]: + def __call__(self, src: str) -> tuple[int, int, int]: return self.read_metadata(self.render(src)) diff --git a/kitty/search_query_parser.py b/kitty/search_query_parser.py index b08399b54..d83c2755c 100644 --- a/kitty/search_query_parser.py +++ b/kitty/search_query_parser.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import re +from collections.abc import Iterator, Sequence from enum import Enum from functools import lru_cache from gettext import gettext as _ -from typing import Callable, Iterator, List, NamedTuple, Optional, Sequence, Set, Tuple, TypeVar, Union +from typing import Callable, NamedTuple, Optional, TypeVar, Union from .types import run_once @@ -35,7 +36,7 @@ class TokenType(Enum): T = TypeVar('T') -GetMatches = Callable[[str, str, Set[T]], Set[T]] +GetMatches = Callable[[str, str, set[T]], set[T]] class SearchTreeNode: @@ -44,10 +45,10 @@ class SearchTreeNode: def __init__(self, type: ExpressionType) -> None: self.type = type - def search(self, universal_set: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def search(self, universal_set: set[T], get_matches: GetMatches[T]) -> set[T]: return self(universal_set, get_matches) - def __call__(self, candidates: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return set() def iter_token_nodes(self) -> Iterator['TokenNode']: @@ -60,7 +61,7 @@ class OrNode(SearchTreeNode): self.lhs = lhs self.rhs = rhs - def __call__(self, candidates: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: lhs = self.lhs(candidates, get_matches) return lhs.union(self.rhs(candidates.difference(lhs), get_matches)) @@ -76,7 +77,7 @@ class AndNode(SearchTreeNode): self.lhs = lhs self.rhs = rhs - def __call__(self, candidates: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: lhs = self.lhs(candidates, get_matches) return self.rhs(lhs, get_matches) @@ -91,7 +92,7 @@ class NotNode(SearchTreeNode): def __init__(self, rhs: SearchTreeNode) -> None: self.rhs = rhs - def __call__(self, candidates: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return candidates.difference(self.rhs(candidates, get_matches)) def iter_token_nodes(self) -> Iterator['TokenNode']: @@ -105,7 +106,7 @@ class TokenNode(SearchTreeNode): self.location = location self.query = query - def __call__(self, candidates: Set[T], get_matches: GetMatches[T]) -> Set[T]: + def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return get_matches(self.location, self.query, candidates) def iter_token_nodes(self) -> Iterator['TokenNode']: @@ -118,7 +119,7 @@ class Token(NamedTuple): @run_once -def lex_scanner() -> Callable[[str], Tuple[List[Token], str]]: +def lex_scanner() -> Callable[[str], tuple[list[Token], str]]: return getattr(re, 'Scanner')([ # type: ignore (r'[()]', lambda x, t: Token(TokenType.OPCODE, t)), (r'@.+?:[^")\s]+', lambda x, t: Token(TokenType.WORD, str(t))), @@ -129,7 +130,7 @@ def lex_scanner() -> Callable[[str], Tuple[List[Token], str]]: @run_once -def replacements() -> Tuple[Tuple[str, str], ...]: +def replacements() -> tuple[tuple[str, str], ...]: return tuple(('\\' + x, chr(i + 1)) for i, x in enumerate('\\"()')) @@ -147,7 +148,7 @@ class Parser: def __init__(self, allow_no_location: bool = False) -> None: self.current_token = 0 - self.tokens: List[Token] = [] + self.tokens: list[Token] = [] self.allow_no_location = allow_no_location def token(self, advance: bool = False) -> Optional[str]: @@ -177,7 +178,7 @@ class Parser: def advance(self) -> None: self.current_token += 1 - def tokenize(self, expr: str) -> List[Token]: + def tokenize(self, expr: str) -> list[Token]: # Strip out escaped backslashes, quotes and parens so that the # lex scanner doesn't get confused. We put them back later. for k, v in replacements(): @@ -279,7 +280,7 @@ class Parser: @lru_cache(maxsize=64) -def build_tree(query: str, locations: Union[str, Tuple[str, ...]], allow_no_location: bool = False) -> SearchTreeNode: +def build_tree(query: str, locations: Union[str, tuple[str, ...]], allow_no_location: bool = False) -> SearchTreeNode: if isinstance(locations, str): locations = tuple(locations.split()) p = Parser(allow_no_location) @@ -290,7 +291,7 @@ def build_tree(query: str, locations: Union[str, Tuple[str, ...]], allow_no_loca def search( - query: str, locations: Union[str, Tuple[str, ...]], universal_set: Set[T], get_matches: GetMatches[T], + query: str, locations: Union[str, tuple[str, ...]], universal_set: set[T], get_matches: GetMatches[T], allow_no_location: bool = False, -) -> Set[T]: +) -> set[T]: return build_tree(query, locations, allow_no_location).search(universal_set, get_matches) diff --git a/kitty/session.py b/kitty/session.py index 588046991..1cf7180f2 100644 --- a/kitty/session.py +++ b/kitty/session.py @@ -4,9 +4,10 @@ import os import shlex import sys +from collections.abc import Generator, Iterator, Mapping from contextlib import suppress from functools import partial -from typing import TYPE_CHECKING, Callable, Generator, Iterator, List, Mapping, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable, Optional, Union from .cli_stub import CLIOptions from .layout.interface import all_layouts @@ -31,7 +32,7 @@ def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None opts.single_window_padding_width, opts.window_padding_width) -ResizeSpec = Tuple[str, int] +ResizeSpec = tuple[str, int] class WindowSpec: @@ -50,7 +51,7 @@ class WindowSpec: class Tab: def __init__(self, opts: Options, name: str): - self.windows: List[WindowSpec] = [] + self.windows: list[WindowSpec] = [] self.pending_resize_spec: Optional[ResizeSpec] = None self.pending_focus_matching_window: str = '' self.name = name.strip() @@ -71,7 +72,7 @@ class Tab: class Session: def __init__(self, default_title: Optional[str] = None): - self.tabs: List[Tab] = [] + self.tabs: list[Tab] = [] self.active_tab_idx = 0 self.default_title = default_title self.os_window_size: Optional[WindowSizes] = None @@ -99,7 +100,7 @@ class Session: raise ValueError(f'{val} is not a valid layout') self.tabs[-1].layout = val - def add_window(self, cmd: Union[None, str, List[str]], expand: Callable[[str], str] = lambda x: x) -> None: + def add_window(self, cmd: Union[None, str, list[str]], expand: Callable[[str], str] = lambda x: x) -> None: from .launch import parse_launch_args needs_expandvars = False if isinstance(cmd, str): @@ -128,7 +129,7 @@ class Session: t.windows[-1].focus_matching_window_spec = t.pending_focus_matching_window t.pending_focus_matching_window = '' - def resize_window(self, args: List[str]) -> None: + def resize_window(self, args: list[str]) -> None: s = resize_window('resize_window', shlex.join(args))[1] spec: ResizeSpec = s[0], s[1] t = self.tabs[-1] diff --git a/kitty/shaders.py b/kitty/shaders.py index a99617262..fef40cde1 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -2,9 +2,10 @@ # License: GPLv3 Copyright: 2023, Kovid Goyal import re +from collections.abc import Iterator from functools import lru_cache, partial from itertools import count -from typing import Any, Callable, Dict, Iterator, Optional, Set +from typing import Any, Callable, Optional from .constants import read_kitty_resource from .fast_data_types import ( @@ -48,7 +49,7 @@ class Program: def __init__(self, name: str, vertex_name: str = '', fragment_name: str = '') -> None: self.name = name self.filename_number_counter = count(self.filename_number_base + 1) - self.filename_map: Dict[str, int] = {} + self.filename_map: dict[str, int] = {} if Program.include_pat is None: Program.include_pat = re.compile(r'^#pragma\s+kitty_include_shader\s+<(.+?)>', re.MULTILINE) self.vertex_name = vertex_name or f'{name}_vertex.glsl' @@ -58,7 +59,7 @@ class Program: self.vertex_sources = self.original_vertex_sources self.fragment_sources = self.original_fragment_sources - def _load_sources(self, name: str, seen: Set[str], level: int = 0) -> Iterator[str]: + def _load_sources(self, name: str, seen: set[str], level: int = 0) -> Iterator[str]: if level == 0: yield f'#version {GLSL_VERSION}\n' if name in seen: diff --git a/kitty/shell_integration.py b/kitty/shell_integration.py index f1a8dc22a..c3cf1493b 100644 --- a/kitty/shell_integration.py +++ b/kitty/shell_integration.py @@ -5,7 +5,7 @@ import os import subprocess from contextlib import suppress -from typing import Callable, Dict, List, Optional +from typing import Callable, Optional from .constants import shell_integration_dir from .fast_data_types import get_options @@ -13,7 +13,7 @@ from .options.types import Options, defaults from .utils import log_error, which -def setup_fish_env(env: Dict[str, str], argv: List[str]) -> None: +def setup_fish_env(env: dict[str, str], argv: list[str]) -> None: val = env.get('XDG_DATA_DIRS') env['KITTY_FISH_XDG_DATA_DIR'] = shell_integration_dir if not val: @@ -24,7 +24,7 @@ def setup_fish_env(env: Dict[str, str], argv: List[str]) -> None: env['XDG_DATA_DIRS'] = os.pathsep.join(dirs) -def is_new_zsh_install(env: Dict[str, str], zdotdir: Optional[str]) -> bool: +def is_new_zsh_install(env: dict[str, str], zdotdir: Optional[str]) -> bool: # if ZDOTDIR is empty, zsh will read user rc files from / # if there aren't any, it'll run zsh-newuser-install # the latter will bail if there are rc files in $HOME @@ -39,14 +39,14 @@ def is_new_zsh_install(env: Dict[str, str], zdotdir: Optional[str]) -> bool: return True -def get_zsh_zdotdir_from_global_zshenv(env: Dict[str, str], argv: List[str]) -> Optional[str]: +def get_zsh_zdotdir_from_global_zshenv(env: dict[str, str], argv: list[str]) -> Optional[str]: exe = which(argv[0], only_system=True) or 'zsh' with suppress(Exception): return subprocess.check_output([exe, '--norcs', '--interactive', '-c', 'echo -n $ZDOTDIR'], env=env).decode('utf-8') return None -def setup_zsh_env(env: Dict[str, str], argv: List[str]) -> None: +def setup_zsh_env(env: dict[str, str], argv: list[str]) -> None: zdotdir = env.get('ZDOTDIR') if is_new_zsh_install(env, zdotdir): if zdotdir is None: @@ -67,7 +67,7 @@ def setup_zsh_env(env: Dict[str, str], argv: List[str]) -> None: env['ZDOTDIR'] = os.path.join(shell_integration_dir, 'zsh') -def setup_bash_env(env: Dict[str, str], argv: List[str]) -> None: +def setup_bash_env(env: dict[str, str], argv: list[str]) -> None: inject = {'1'} posix_env = rcfile = '' remove_args = set() @@ -156,14 +156,14 @@ def as_fish_str_literal(x: str) -> str: return f"'{x}'" -def posix_serialize_env(env: Dict[str, str], prefix: str = 'builtin export', sep: str = '=') -> str: +def posix_serialize_env(env: dict[str, str], prefix: str = 'builtin export', sep: str = '=') -> str: ans = [] for k, v in env.items(): ans.append(f'{prefix} {as_str_literal(k)}{sep}{as_str_literal(v)}') return '\n'.join(ans) -def fish_serialize_env(env: Dict[str, str]) -> str: +def fish_serialize_env(env: dict[str, str]) -> str: ans = [] for k, v in env.items(): ans.append(f'set -gx {as_fish_str_literal(k)} {as_fish_str_literal(v)}') @@ -176,7 +176,7 @@ ENV_MODIFIERS = { 'bash': setup_bash_env, } -ENV_SERIALIZERS: Dict[str, Callable[[Dict[str, str]], str]] = { +ENV_SERIALIZERS: dict[str, Callable[[dict[str, str]], str]] = { 'zsh': posix_serialize_env, 'bash': posix_serialize_env, 'fish': fish_serialize_env, @@ -196,7 +196,7 @@ def shell_integration_allows_rc_modification(opts: Options) -> bool: return not (opts.shell_integration & {'disabled', 'no-rc'}) -def serialize_env(path: str, env: Dict[str, str]) -> str: +def serialize_env(path: str, env: dict[str, str]) -> str: if not env: return '' name = get_supported_shell_name(path) @@ -215,7 +215,7 @@ def get_effective_ksi_env_var(opts: Optional[Options] = None) -> str: return ' '.join(opts.shell_integration) -def modify_shell_environ(opts: Options, env: Dict[str, str], argv: List[str]) -> None: +def modify_shell_environ(opts: Options, env: dict[str, str], argv: list[str]) -> None: shell = get_supported_shell_name(argv[0]) ksi = get_effective_ksi_env_var(opts) if shell is None or not ksi: diff --git a/kitty/short_uuid.py b/kitty/short_uuid.py index 3f5ed8ce7..355b8f18a 100644 --- a/kitty/short_uuid.py +++ b/kitty/short_uuid.py @@ -4,7 +4,8 @@ import math import string import uuid as _uuid -from typing import Dict, Optional, Sequence +from collections.abc import Sequence +from typing import Optional def num_to_string(number: int, alphabet: Sequence[str], alphabet_len: int, pad_to_length: Optional[int] = None) -> str: @@ -18,7 +19,7 @@ def num_to_string(number: int, alphabet: Sequence[str], alphabet_len: int, pad_t return ''.join(ans) -def string_to_num(string: str, alphabet_map: Dict[str, int], alphabet_len: int) -> int: +def string_to_num(string: str, alphabet_map: dict[str, int], alphabet_len: int) -> int: ans = 0 for char in reversed(string): ans = ans * alphabet_len + alphabet_map[char] diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index 17a55e0bc..c3aad401e 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -3,17 +3,14 @@ import os import re +from collections.abc import Sequence from functools import lru_cache, partial, wraps from string import Formatter as StringFormatter from typing import ( Any, Callable, - Dict, - List, NamedTuple, Optional, - Sequence, - Tuple, ) from .borders import Border, BorderColor @@ -93,12 +90,12 @@ def as_rgb(x: int) -> int: return (x << 8) | 2 -@lru_cache() +@lru_cache def report_template_failure(template: str, e: str) -> None: log_error(f'Invalid tab title template: "{template}" with error: {e}') -@lru_cache() +@lru_cache def compile_template(template: str) -> Any: try: return compile('f"""' + template + '"""', '