diff --git a/kittens/tui/handler.py b/kittens/tui/handler.py index 79092488c..1b3d66537 100644 --- a/kittens/tui/handler.py +++ b/kittens/tui/handler.py @@ -10,7 +10,7 @@ from .operations import commander class Handler: - image_manager_class = None # type: Type[ImageManagerBase] + image_manager_class: Optional[Type['ImageManagerBase']] = None def _initialize(self, screen_size, term_manager, schedule_write, tui_loop, debug, image_manager=None): self.screen_size = screen_size diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index 7e6d93477..5e711b344 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -36,12 +36,12 @@ MODES = dict( ) -def set_mode(which, private=True) -> str: +def set_mode(which: str, private=True) -> str: num, private = MODES[which] return '\033[{}{}h'.format(private, num) -def reset_mode(which) -> str: +def reset_mode(which: str) -> str: num, private = MODES[which] return '\033[{}{}l'.format(private, num) @@ -66,12 +66,12 @@ def set_window_title(value) -> str: return ('\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\') -def set_line_wrapping(yes_or_no) -> str: - return (set_mode if yes_or_no else reset_mode)('DECAWM') +def set_line_wrapping(yes_or_no: bool) -> str: + return set_mode('DECAWM') if yes_or_no else reset_mode('DECAWM') -def set_cursor_visible(yes_or_no) -> str: - return (set_mode if yes_or_no else reset_mode)('DECTCEM') +def set_cursor_visible(yes_or_no: bool) -> str: + return set_mode('DECTEM') if yes_or_no else reset_mode('DECTCEM') def set_cursor_position(x, y) -> str: # (0, 0) is top left @@ -151,13 +151,16 @@ def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=No end.append('4:0') if italic is not None: s, e = (start, end) if italic else (end, start) - s.append('3'), e.append('23') + s.append('3') + e.append('23') if bold is not None: s, e = (start, end) if bold else (end, start) - s.append('1'), e.append('22') + s.append('1') + e.append('22') if reverse is not None: s, e = (start, end) if reverse else (end, start) - s.append('7'), e.append('27') + s.append('7') + e.append('27') if not start: return text return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end)) diff --git a/kitty/child.py b/kitty/child.py index ecddddec2..ba7239007 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -7,6 +7,7 @@ import os import sys from collections import defaultdict from contextlib import contextmanager, suppress +from typing import DefaultDict, List import kitty.fast_data_types as fast_data_types @@ -18,31 +19,31 @@ if is_macos: process_group_map as _process_group_map ) - def cwd_of_process(pid): + def cwd_of_process(pid: int) -> str: return os.path.realpath(_cwd(pid)) - def process_group_map(): - ans = defaultdict(list) + def process_group_map() -> DefaultDict[int, List[int]]: + ans: DefaultDict[int, List] = defaultdict(list) for pid, pgid in _process_group_map(): ans[pgid].append(pid) return ans else: - def cmdline_of_process(pid): + def cmdline_of_process(pid: int) -> List[str]: with open('/proc/{}/cmdline'.format(pid), 'rb') as f: return list(filter(None, f.read().decode('utf-8').split('\0'))) - def cwd_of_process(pid): + def cwd_of_process(pid: int) -> str: ans = '/proc/{}/cwd'.format(pid) return os.path.realpath(ans) - def _environ_of_process(pid): + def _environ_of_process(pid: int) -> str: with open('/proc/{}/environ'.format(pid), 'rb') as f: return f.read().decode('utf-8') - def process_group_map(): - ans = 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) diff --git a/kitty/cli.py b/kitty/cli.py index 9eda6269d..8c0f1fa94 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -228,71 +228,75 @@ For example: {appname} sh -c "echo hello, world. Press ENTER to quit; read" For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname) -def print_help_for_seq(seq, usage, message, appname): - from kitty.utils import screen_size_function - screen_size = screen_size_function() - try: - linesz = min(screen_size().cols, 76) - except OSError: - linesz = 76 - blocks = [] - a = blocks.append +class PrintHelpForSeq: - def wa(text, indent=0, leading_indent=None): - if leading_indent is None: - leading_indent = indent - j = '\n' + (' ' * indent) - lines = [] - for l in text.splitlines(): - if l: - lines.extend(wrap(l, limit=linesz - indent)) - else: - lines.append('') - a((' ' * leading_indent) + j.join(lines)) + allow_pager = True - usage = '[program-to-run ...]' if usage is None else usage - optstring = '[options] ' if seq else '' - a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage)) - a('') - message = message or default_msg - wa(prettify(message)) - a('') - if seq: - a('{}:'.format(title('Options'))) - for opt in seq: - if isinstance(opt, str): - a('{}:'.format(title(opt))) - continue - help_text = opt['help'] - if help_text == '!': - continue # hidden option - a(' ' + ', '.join(map(green, sorted(opt['aliases'])))) - if not opt.get('type', '').startswith('bool-'): - blocks[-1] += '={}'.format(italic(opt['dest'].upper())) - if opt.get('help'): - defval = opt.get('default') - t = help_text.replace('%default', str(defval)) - wa(prettify(t.strip()), indent=4) - if defval is not None: - wa('Default: {}'.format(defval), indent=4) - if 'choices' in opt: - wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4) - a('') - - text = '\n'.join(blocks) + '\n\n' + version() - if print_help_for_seq.allow_pager and sys.stdout.isatty(): - import subprocess - p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE) + def __call__(self, seq, usage, message, appname): + from kitty.utils import screen_size_function + screen_size = screen_size_function() try: - p.communicate(text.encode('utf-8')) - except KeyboardInterrupt: - raise SystemExit(1) - raise SystemExit(p.wait()) - else: - print(text) + linesz = min(screen_size().cols, 76) + except OSError: + linesz = 76 + blocks = [] + a = blocks.append + + def wa(text, indent=0, leading_indent=None): + if leading_indent is None: + leading_indent = indent + j = '\n' + (' ' * indent) + lines = [] + for l in text.splitlines(): + if l: + lines.extend(wrap(l, limit=linesz - indent)) + else: + lines.append('') + a((' ' * leading_indent) + j.join(lines)) + + usage = '[program-to-run ...]' if usage is None else usage + optstring = '[options] ' if seq else '' + a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage)) + a('') + message = message or default_msg + wa(prettify(message)) + a('') + if seq: + a('{}:'.format(title('Options'))) + for opt in seq: + if isinstance(opt, str): + a('{}:'.format(title(opt))) + continue + help_text = opt['help'] + if help_text == '!': + continue # hidden option + a(' ' + ', '.join(map(green, sorted(opt['aliases'])))) + if not opt.get('type', '').startswith('bool-'): + blocks[-1] += '={}'.format(italic(opt['dest'].upper())) + if opt.get('help'): + defval = opt.get('default') + t = help_text.replace('%default', str(defval)) + wa(prettify(t.strip()), indent=4) + if defval is not None: + wa('Default: {}'.format(defval), indent=4) + if 'choices' in opt: + wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4) + a('') + + text = '\n'.join(blocks) + '\n\n' + version() + if print_help_for_seq.allow_pager and sys.stdout.isatty(): + import subprocess + p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE) + try: + p.communicate(text.encode('utf-8')) + except KeyboardInterrupt: + raise SystemExit(1) + raise SystemExit(p.wait()) + else: + print(text) -print_help_for_seq.allow_pager = True +print_help_for_seq = PrintHelpForSeq() def seq_as_rst(seq, usage, message, appname, heading_char='-'): diff --git a/kitty/config_data.py b/kitty/config_data.py index 1b3def206..30ecb55fa 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -5,7 +5,7 @@ # Utils {{{ import os from gettext import gettext as _ -from typing import Mapping, Union +from typing import Dict, Union from . import fast_data_types as defines from .conf.definition import Option, Shortcut, option_func @@ -55,7 +55,7 @@ def uniq(vals, result_type=list): # Groups {{{ -all_options: Mapping[str, Union[Option, Shortcut]] = {} +all_options: Dict[str, Union[Option, Shortcut]] = {} o, k, g, all_groups = option_func(all_options, { diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 97fb0572e..4c02b5990 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1,10 +1,10 @@ -from collections import namedtuple from typing import ( Any, Callable, List, Dict, NewType, Optional, Tuple, Union ) from kitty.cli import Namespace +# Constants {{{ GLFW_IBEAM_CURSOR: int GLFW_KEY_UNKNOWN: int GLFW_KEY_SPACE: int @@ -319,6 +319,23 @@ FC_WEIGHT_BOLD: int FC_SLANT_ROMAN: int FC_SLANT_ITALIC: int BORDERS_PROGRAM: int +# }}} + + +def process_group_map() -> Tuple[Tuple[int, int], ...]: + pass + + +def environ_of_process(pid: int) -> str: + pass + + +def cmdline_of_process(pid: int) -> List[str]: + pass + + +def cwd_of_process(pid: int) -> str: + pass def default_color_table() -> Tuple[int, ...]: @@ -545,10 +562,18 @@ def swap_tabs(os_window_id: int, a: int, b: int) -> None: pass +def swap_windows(os_window_id: int, tab_id: int, a: int, b: int) -> None: + pass + + def set_active_tab(os_window_id: int, a: int) -> None: pass +def set_active_window(os_window_id: int, tab_id: int, window_idx: int) -> None: + pass + + def ring_bell() -> None: pass @@ -589,7 +614,16 @@ def cell_size_for_window(os_window_id: int) -> Tuple[int, int]: pass -Region = namedtuple('Region', 'left top right bottom width height') +class Region: + left: int + top: int + right: int + bottom: int + width: int + height: int + + def __init__(self, x: Tuple[int, int, int, int, int, int]): + pass def viewport_for_window(os_window_id: int) -> Tuple[Region, Region, int, int, int, int]: @@ -690,7 +724,7 @@ class ChildMonitor: def __init__( self, death_notify: Callable[[int], None], - dump_callback: Optional[callable], + dump_callback: Optional[Callable], talk_fd: int = -1, listen_fd: int = -1 ): pass diff --git a/kitty/layout.py b/kitty/layout.py index 8c5d4a56e..fd69dc19c 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -5,6 +5,7 @@ from collections import namedtuple from functools import lru_cache, partial from itertools import islice, repeat +from typing import Callable, Dict, Generator, List, Optional, Tuple, Union from .constants import WindowGeometry from .fast_data_types import ( @@ -19,6 +20,11 @@ no_borders = False, False, False, False draw_minimal_borders = False draw_active_borders = True align_top_left = False +LayoutDimension = Generator[Tuple[int, int], None, None] +XOrYLayout = Union[ + Callable[['Layout', int, Optional[List[float]], Optional[int], Optional[int]], LayoutDimension], + Callable[['Layout', int, bool, Optional[List[float]], Optional[int], Optional[int]], LayoutDimension] +] def idx_for_id(win_id, windows): @@ -34,7 +40,10 @@ def set_layout_options(opts): align_top_left = opts.placement_strategy == 'top-left' -def layout_dimension(start_at, length, cell_length, decoration_pairs, left_align=False, bias=None): +def layout_dimension( + start_at, length, cell_length, decoration_pairs, + left_align=False, bias: Optional[List[float]] = None +) -> LayoutDimension: number_of_windows = len(decoration_pairs) number_of_cells = length // cell_length space_needed_for_decorations = sum(map(sum, decoration_pairs)) @@ -159,7 +168,7 @@ def variable_bias(num_windows, candidate): class Layout: # {{{ - name = None + name: Optional[str] = None needs_window_borders = True needs_all_windows = False only_active_window_visible = False @@ -405,7 +414,7 @@ class Layout: # {{{ w.set_geometry(0, wg) self.blank_rects = blank_rects_for_window(w) - def xlayout(self, num, bias=None, left=None, width=None): + def xlayout(self, num: int, bias: Optional[List[float]] = None, left: Optional[int] = None, width: Optional[int] = None) -> LayoutDimension: decoration = self.margin_width + self.border_width + self.padding_width decoration_pairs = tuple(repeat((decoration, decoration), num)) if left is None: @@ -414,7 +423,9 @@ class Layout: # {{{ width = central.width return layout_dimension(left, width, cell_width, decoration_pairs, bias=bias, left_align=align_top_left) - def ylayout(self, num, left_align=True, bias=None, top=None, height=None): + def ylayout( + self, num: int, left_align: bool = True, bias: Optional[List[float]] = None, top: Optional[int] = None, height: Optional[int] = None + ) -> LayoutDimension: decoration = self.margin_width + self.border_width + self.padding_width decoration_pairs = tuple(repeat((decoration, decoration), num)) if top is None: @@ -511,7 +522,7 @@ def neighbors_for_tall_window(num_full_size_windows, window, windows): class Tall(Layout): name = 'tall' - vlayout = Layout.ylayout + vlayout: XOrYLayout = Layout.ylayout main_is_horizontal = True only_between_border = False, False, False, True only_main_border = False, False, True, False @@ -892,7 +903,7 @@ class Vertical(Layout): # {{{ name = 'vertical' main_is_horizontal = False - vlayout = Layout.ylayout + vlayout: XOrYLayout = Layout.ylayout only_between_border = False, False, False, True def variable_layout(self, num_windows, biased_map): @@ -1408,17 +1419,20 @@ class Splits(Layout): all_layouts = {o.name: o for o in globals().values() if isinstance(o, type) and issubclass(o, Layout) and o is not Layout} -def create_layout_object_for(name, os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts=''): - key = name, os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts - ans = create_layout_object_for.cache.get(key) - if ans is None: - name, layout_opts = name.partition(':')[::2] - ans = create_layout_object_for.cache[key] = all_layouts[name]( - os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts) - return ans +class CreateLayoutObjectFor: + cache: Dict[Tuple, Layout] = {} + + def __call__(self, name, os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts=''): + key = name, os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts + ans = create_layout_object_for.cache.get(key) + if ans is None: + name, layout_opts = name.partition(':')[::2] + ans = create_layout_object_for.cache[key] = all_layouts[name]( + os_window_id, tab_id, margin_width, single_window_margin_width, padding_width, border_width, layout_opts) + return ans -create_layout_object_for.cache = {} +create_layout_object_for = CreateLayoutObjectFor() def evict_cached_layouts(tab_id): diff --git a/kitty/notify.py b/kitty/notify.py index e33bb132f..f4829dede 100644 --- a/kitty/notify.py +++ b/kitty/notify.py @@ -2,7 +2,7 @@ # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2019, Kovid Goyal -from typing import Mapping +from typing import Dict from .constants import is_macos, logo_png_file @@ -26,8 +26,8 @@ else: from .fast_data_types import dbus_send_notification from .constants import get_boss - alloc_map: Mapping[int, str] = {} - identifier_map: Mapping[str, int] = {} + alloc_map: Dict[int, str] = {} + identifier_map: Dict[str, int] = {} def dbus_notification_created(alloc_id, notification_id): identifier = alloc_map.pop(alloc_id, None) diff --git a/kitty/terminfo.py b/kitty/terminfo.py index 7fa1f069b..81de4ae40 100644 --- a/kitty/terminfo.py +++ b/kitty/terminfo.py @@ -4,6 +4,7 @@ import re from binascii import hexlify, unhexlify +from typing import cast, Dict def modify_key_bytes(keybytes, amt): @@ -406,7 +407,7 @@ termcap_aliases.update({ 'FV FW FX FY FZ Fa Fb Fc Fd Fe Ff Fg Fh Fi Fj Fk Fl Fm Fn Fo ' 'Fp Fq Fr'.split(), 1)}) -queryable_capabilities = numeric_capabilities.copy() +queryable_capabilities = cast(Dict[str, str], numeric_capabilities.copy()) queryable_capabilities.update(string_capabilities) extra = (bool_capabilities | numeric_capabilities.keys() | string_capabilities.keys()) - set(termcap_aliases.values()) no_termcap_for = frozenset(