More typing work

Also use a mypy based linter when editing
This commit is contained in:
Kovid Goyal 2020-03-08 11:02:14 +05:30
parent 9b32f18109
commit cc1336a616
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
43 changed files with 333 additions and 196 deletions

View File

@ -7,28 +7,28 @@
# full list see the documentation:
# https://www.sphinx-doc.org/en/master/config
import importlib
import os
import re
import subprocess
import sys
import time
from functools import partial
from typing import Optional, Dict, Tuple
from docutils import nodes
from docutils.parsers.rst.roles import set_classes
from pygments.lexer import RegexLexer, bygroups
from pygments.token import (
from pygments.lexer import RegexLexer, bygroups # type: ignore
from pygments.token import ( # type: ignore
Comment, Keyword, Literal, Name, Number, String, Whitespace
)
from sphinx import addnodes
from sphinx.environment.adapters.toctree import TocTree
from sphinx.util.logging import getLogger
from sphinx import addnodes # type: ignore
from sphinx.environment.adapters.toctree import TocTree # type: ignore
from sphinx.util.logging import getLogger # type: ignore
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if kitty_src not in sys.path:
sys.path.insert(0, kitty_src)
str_version = importlib.import_module('kitty.constants').str_version
from kitty.constants import str_version # noqa
# config {{{
# -- Project information -----------------------------------------------------
@ -77,7 +77,7 @@ master_doc = 'index'
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language: Optional[str] = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@ -205,7 +205,8 @@ def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[]
def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
' Link to a github commit '
try:
commit_id = subprocess.check_output(f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip()
commit_id = subprocess.check_output(
f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip()
except Exception:
msg = inliner.reporter.error(
f'GitHub commit id "{text}" not recognized.', line=lineno)
@ -213,7 +214,8 @@ def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
return [prb], [msg]
url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}'
set_classes(options)
short_id = subprocess.check_output(f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()
short_id = subprocess.check_output(
f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()
node = nodes.reference(rawtext, f'commit: {short_id}', refuri=url, **options)
return [node], []
# }}}
@ -409,8 +411,8 @@ def expand_opt_references(conf_name, text):
return re.sub(r':opt:`(.+?)`', expand, text)
opt_aliases = {}
shortcut_slugs = {}
opt_aliases: Dict[str, str] = {}
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
def parse_opt_node(env, sig, signode):

View File

@ -10,6 +10,7 @@ from contextlib import suppress
from functools import partial
from gettext import gettext as _
from weakref import WeakValueDictionary
from typing import Optional
from .child import cached_process_data, cwd_of_process
from .cli import create_opts, parse_args
@ -43,8 +44,8 @@ from .window import Window
def notification_activated(identifier: str) -> None:
if identifier == 'new-version':
from .update_check import notification_activated
notification_activated()
from .update_check import notification_activated as do
do()
def listen_on(spec: str) -> int:
@ -57,7 +58,7 @@ def listen_on(spec: str) -> int:
return s.fileno()
def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> str:
def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> Optional[str]:
def as_text(**kw) -> str:
kw['add_wrap_markers'] = add_wrap_markers
return w.as_text(**kw)
@ -80,6 +81,7 @@ def data_for_at(w: Window, arg: str, add_wrap_markers: bool = False) -> str:
return as_text(as_ansi=True, alternate_screen=True)
if arg == '@ansi_alternate_scrollback':
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
return None
class DumpCommands: # {{{
@ -935,7 +937,7 @@ class Boss:
stdin = stdin.encode('utf-8')
return env, stdin
def data_for_at(self, which, window=None, add_wrap_markers=False):
def data_for_at(self, which, window=None, add_wrap_markers=False) -> Optional[str]:
return data_for_at(window or self.active_window, which, add_wrap_markers=add_wrap_markers)
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):

View File

@ -7,7 +7,7 @@ import os
import sys
from collections import defaultdict
from contextlib import contextmanager, suppress
from typing import DefaultDict, List
from typing import DefaultDict, List, Optional
import kitty.fast_data_types as fast_data_types
@ -158,7 +158,8 @@ def openpty():
class Child:
child_fd = pid = None
child_fd: Optional[int] = None
pid: Optional[int] = None
forked = False
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None, allow_remote_control=False):

View File

@ -7,13 +7,13 @@ import re
import sys
from collections import deque
from typing import (
Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Tuple, Type,
TypeVar, Union, cast
Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Sequence, Tuple,
Type, TypeVar, Union, cast
)
from .cli_stub import CLIOptions
from .conf.utils import resolve_config
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .cli_stub import CLIOptions
from .options_stub import Options as OptionsStub
try:
@ -135,7 +135,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
disabled: OptionSpecSeq = []
mpat = re.compile('([a-z]+)=(.+)')
current_cmd: OptionDict = {
'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(), 'type': '', 'condition': False, 'default': None
'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(),
'type': '', 'condition': False, 'default': None
}
empty_cmd = current_cmd
@ -150,7 +151,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
if line.startswith('--'):
parts = line.split(' ')
current_cmd = {
'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '', 'choices': frozenset(), 'type': '',
'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '',
'choices': frozenset(), 'type': '',
'default': None, 'condition': True
}
state = METADATA
@ -335,7 +337,13 @@ class PrintHelpForSeq:
print_help_for_seq = PrintHelpForSeq()
def seq_as_rst(seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str], heading_char: str = '-') -> str:
def seq_as_rst(
seq: OptionSpecSeq,
usage: Optional[str],
message: Optional[str],
appname: Optional[str],
heading_char: str = '-'
) -> str:
import textwrap
blocks: List[str] = []
a = blocks.append
@ -382,7 +390,7 @@ def seq_as_rst(seq: OptionSpecSeq, usage: Optional[str], message: Optional[str],
return text
def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str) -> str:
def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, extra_fields: Sequence[str] = ()) -> str:
from itertools import chain
ans: List[str] = ['class {}:'.format(class_name)]
for opt in chain(seq, disabled):
@ -401,6 +409,8 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str) -
else:
raise ValueError('Unknown CLI option type: {}'.format(otype))
ans.append(' {}: {}'.format(name, t))
for x in extra_fields:
ans.append(' {}'.format(x))
return '\n'.join(ans) + '\n\n\n'

View File

@ -3,6 +3,9 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Sequence
class CLIOptions:
pass
@ -17,9 +20,9 @@ def generate_stub() -> None:
from .conf.definition import save_type_stub
text = 'import typing\n\n\n'
def do(otext=None, cls: str = 'CLIOptions'):
def do(otext=None, cls: str = 'CLIOptions', extra_fields: Sequence[str] = ()):
nonlocal text
text += as_type_stub(*parse_option_spec(otext), class_name=cls)
text += as_type_stub(*parse_option_spec(otext), class_name=cls, extra_fields=extra_fields)
do()
@ -27,7 +30,7 @@ def generate_stub() -> None:
do(options_spec(), 'LaunchCLIOptions')
from .remote_control import global_options_spec
do(global_options_spec(), 'RCOptions')
do(global_options_spec(), 'RCOptions', extra_fields=['no_command_response: typing.Optional[bool]'])
from kittens.ask.main import option_text
do(option_text(), 'AskCLIOptions')

View File

@ -6,7 +6,7 @@ import os
import shlex
import sys
from functools import lru_cache
from typing import Tuple
from typing import Callable, Dict, Tuple
from kittens.runner import all_kitten_names, get_kitten_cli_docs
@ -33,7 +33,8 @@ taking the results from kitty's completion system and converting
them into something your shell will understand.
'''
parsers, serializers = {}, {}
parsers: Dict[str, Callable] = {}
serializers: Dict[str, Callable] = {}
def debug(*a, **kw):

View File

@ -7,7 +7,7 @@ import re
import shlex
from collections import namedtuple
from typing import (
Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Sequence, Tuple,
Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Tuple,
Type, Union
)
@ -118,7 +118,7 @@ def parse_line(
def _parse(
lines: Iterator[str],
lines: Iterable[str],
type_convert: Callable[[str, Any], Any],
special_handling: Callable,
ans: Dict[str, Any],
@ -144,7 +144,7 @@ def _parse(
def parse_config_base(
lines: Iterator[str],
lines: Iterable[str],
defaults: Any,
type_convert: Callable[[str, Any], Any],
special_handling: Callable,
@ -158,7 +158,7 @@ def parse_config_base(
)
def create_options_class(all_keys: Iterator[str]) -> Type:
def create_options_class(all_keys: Iterable[str]) -> Type:
keys = tuple(sorted(all_keys))
slots = keys + ('_fields', )
@ -223,10 +223,10 @@ def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Seq
def load_config(
Options: Type,
defaults: Any,
parse_config: Callable[[Iterator[str]], Dict[str, Any]],
parse_config: Callable[[Iterable[str]], Dict[str, Any]],
merge_configs: Callable[[Dict, Dict], Dict],
*paths: str,
overrides: Optional[Iterator[str]] = None
overrides: Optional[Iterable[str]] = None
):
ans: Dict = defaults._asdict()
for path in paths:
@ -244,7 +244,7 @@ def load_config(
return Options(ans)
def init_config(default_config_lines: Iterator[str], parse_config: Callable):
def init_config(default_config_lines: Iterable[str], parse_config: Callable):
defaults = parse_config(default_config_lines, check_keys=False)
Options = create_options_class(defaults.keys())
defaults = Options(defaults)

View File

@ -10,8 +10,8 @@ from collections import namedtuple
from contextlib import contextmanager, suppress
from functools import partial
from typing import (
TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Sequence, Tuple, Type,
cast
TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional,
Sequence, Set, Tuple, Type, cast
)
from . import fast_data_types as defines
@ -310,7 +310,7 @@ def scroll_to_mark(func, rest):
return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))]
def parse_key_action(action):
def parse_key_action(action: str) -> Optional[KeyAction]:
parts = action.strip().split(maxsplit=1)
func = parts[0]
if len(parts) == 1:
@ -324,9 +324,10 @@ def parse_key_action(action):
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
else:
return KeyAction(func, args)
return None
all_key_actions = set()
all_key_actions: Set[str] = set()
sequence_sep = '>'
@ -454,16 +455,17 @@ def parse_send_text(val, key_definitions):
return parse_key(key_str, key_definitions)
special_handlers = {}
SpecialHandlerFunc = Callable[[str, str, Dict[str, Any]], None]
special_handlers: Dict[str, SpecialHandlerFunc] = {}
def special_handler(func):
def special_handler(func: SpecialHandlerFunc) -> SpecialHandlerFunc:
special_handlers[func.__name__.partition('_')[2]] = func
return func
def deprecated_handler(*names):
def special_handler(func):
def deprecated_handler(*names: str) -> Callable[[SpecialHandlerFunc], SpecialHandlerFunc]:
def special_handler(func: SpecialHandlerFunc) -> SpecialHandlerFunc:
for name in names:
special_handlers[name] = func
return func
@ -588,7 +590,7 @@ def option_names_for_completion():
yield from special_handlers
def parse_config(lines: Iterator[str], check_keys=True, accumulate_bad_lines: Optional[List[BadLine]] = None):
def parse_config(lines: Iterable[str], check_keys=True, accumulate_bad_lines: Optional[List[BadLine]] = None):
ans: Dict[str, Any] = {
'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [],
'env': {}, 'kitten_aliases': {}, 'font_features': {}
@ -777,7 +779,7 @@ def finalize_keys(opts: OptionsStub) -> None:
opts.sequence_map = sequence_map
def load_config(*paths: str, overrides: Optional[Iterator[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> OptionsStub:
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[List[BadLine]] = None) -> OptionsStub:
parser = parse_config
if accumulate_bad_lines is not None:
parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines)

View File

@ -175,4 +175,4 @@ supports_primary_selection = not is_macos
def running_in_kitty(set_val: Optional[bool] = None) -> bool:
if set_val is not None:
setattr(running_in_kitty, 'ans', set_val)
return getattr(running_in_kitty, 'ans', False)
return bool(getattr(running_in_kitty, 'ans', False))

View File

@ -348,7 +348,11 @@ StartupCtx = NewType('StartupCtx', int)
Display = NewType('Display', int)
def init_x11_startup_notification(display: Display, window_id: int, startup_id: Optional[str] = None) -> StartupCtx:
def init_x11_startup_notification(
display: Display,
window_id: int,
startup_id: Optional[str] = None
) -> StartupCtx:
pass
@ -387,7 +391,10 @@ def default_color_table() -> Tuple[int, ...]:
FontConfigPattern = Dict[str, Union[str, int, bool, float]]
def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False) -> Tuple[FontConfigPattern, ...]:
def fc_list(
spacing: int = -1,
allow_bitmapped_fonts: bool = False
) -> Tuple[FontConfigPattern, ...]:
pass
@ -407,7 +414,11 @@ def coretext_all_fonts() -> Tuple[Dict[str, Any], ...]:
pass
def add_timer(callback: Callable[[Optional[int]], bool], interval: float, repeats: bool = True) -> int:
def add_timer(
callback: Callable[[Optional[int]], bool],
interval: float,
repeats: bool = True
) -> int:
pass
@ -419,7 +430,9 @@ def add_window(os_window_id: int, tab_id: int, title: str) -> int:
pass
def compile_program(which: int, vertex_shader: str, fragment_shader: str) -> int:
def compile_program(
which: int, vertex_shader: str, fragment_shader: str
) -> int:
pass
@ -431,7 +444,10 @@ def set_titlebar_color(os_window_id: int, color: int) -> bool:
pass
def add_borders_rect(os_window_id: int, tab_id: int, left: int, top: int, right: int, bottom: int, color: int) -> None:
def add_borders_rect(
os_window_id: int, tab_id: int, left: int, top: int, right: int,
bottom: int, color: int
) -> None:
pass
@ -443,16 +459,30 @@ def os_window_has_background_image(os_window_id: int) -> bool:
pass
def dbus_send_notification(app_name: str, icon: str, summary: str, body: str, action_name: str, timeout: int = -1) -> int:
def dbus_send_notification(
app_name: str,
icon: str,
summary: str,
body: str,
action_name: str,
timeout: int = -1
) -> int:
pass
def cocoa_send_notification(identifier: Optional[str], title: str, informative_text: str, path_to_img: Optional[str], subtitle: Optional[str] = None) -> None:
def cocoa_send_notification(
identifier: Optional[str],
title: str,
informative_text: str,
path_to_img: Optional[str],
subtitle: Optional[str] = None
) -> None:
pass
def create_os_window(
get_window_size: Callable[[int, int, int, int, float, float], Tuple[int, int]],
get_window_size: Callable[[int, int, int, int, float, float], Tuple[int,
int]],
pre_show_callback: Callable[[object], None],
title: str,
wm_class_name: str,
@ -464,11 +494,16 @@ def create_os_window(
pass
def update_window_title(os_window_id: int, tab_id: int, window_id: int, title: str) -> None:
def update_window_title(
os_window_id: int, tab_id: int, window_id: int, title: str
) -> None:
pass
def update_window_visibility(os_window_id: int, tab_id: int, window_id: int, window_idx: int, visible: bool) -> None:
def update_window_visibility(
os_window_id: int, tab_id: int, window_id: int, window_idx: int,
visible: bool
) -> None:
pass
@ -501,7 +536,12 @@ def set_default_window_icon(data: bytes, width: int, height: int) -> None:
pass
def set_custom_cursor(cursor_type: int, images: Tuple[Tuple[bytes, int, int], ...], x: int = 0, y: int = 0) -> None:
def set_custom_cursor(
cursor_type: int,
images: Tuple[Tuple[bytes, int, int], ...],
x: int = 0,
y: int = 0
) -> None:
pass
@ -542,8 +582,10 @@ def set_clipboard_string(data: bytes) -> None:
def set_background_image(
path: Optional[str], os_window_ids: Tuple[int, ...],
configured: bool = True, layout_name: Optional[str] = None
path: Optional[str],
os_window_ids: Tuple[int, ...],
configured: bool = True,
layout_name: Optional[str] = None
) -> None:
pass
@ -564,7 +606,20 @@ def patch_global_colors(spec: Dict[str, int], configured: bool) -> None:
pass
def os_window_font_size(os_window_id: int, new_sz: float = -1., force: bool = False) -> float:
class ColorProfile:
pass
def patch_color_profiles(
spec: Dict[str, int], cursor_text_color: Optional[Union[bool, int]],
profiles: Tuple[ColorProfile, ...], change_configured: bool
) -> None:
pass
def os_window_font_size(
os_window_id: int, new_sz: float = -1., force: bool = False
) -> float:
pass
@ -692,7 +747,9 @@ class Region:
pass
def viewport_for_window(os_window_id: int) -> Tuple[Region, Region, int, int, int, int]:
def viewport_for_window(
os_window_id: int
) -> Tuple[Region, Region, int, int, int, int]:
pass
@ -716,14 +773,10 @@ def open_tty(read_with_timeout: bool = False) -> Tuple[int, TermiosPtr]:
def parse_input_from_terminal(
text_callback: Callable[[str], None],
dcs_callback: Callable[[str], None],
csi_callback: Callable[[str], None],
osc_callback: Callable[[str], None],
pm_callback: Callable[[str], None],
apc_callback: Callable[[str], None],
data: str,
in_bracketed_paste: bool
text_callback: Callable[[str], None], dcs_callback: Callable[[str], None],
csi_callback: Callable[[str], None], osc_callback: Callable[[str], None],
pm_callback: Callable[[str], None], apc_callback: Callable[[str], None],
data: str, in_bracketed_paste: bool
):
pass
@ -732,7 +785,9 @@ class Line:
pass
def test_shape(line: Line, path: Optional[str] = None, index: int = 0) -> List[Tuple[int, int, int, Tuple[int, ...]]]:
def test_shape(line: Line,
path: Optional[str] = None,
index: int = 0) -> List[Tuple[int, int, int, Tuple[int, ...]]]:
pass
@ -744,17 +799,21 @@ def sprite_map_set_limits(w: int, h: int) -> None:
pass
def set_send_sprite_to_gpu(func: Callable[[int, int, int, bytes], None]) -> None:
def set_send_sprite_to_gpu(
func: Callable[[int, int, int, bytes], None]
) -> None:
pass
def set_font_data(
box_drawing_func: Callable[[int, int, int, float], Tuple[int, Union[bytearray, bytes]]],
prerender_func: Callable[[int, int, int, int, int, float, float, float, float], Tuple[int, ...]],
descriptor_for_idx: Callable[[int], Tuple[dict, bool, bool]],
box_drawing_func: Callable[[int, int, int, float],
Tuple[int, Union[bytearray, bytes]]],
prerender_func: Callable[
[int, int, int, int, int, float, float, float, float],
Tuple[int, ...]], descriptor_for_idx: Callable[[int], Tuple[dict, bool,
bool]],
bold: int, italic: int, bold_italic: int, num_symbol_fonts: int,
symbol_maps: Tuple[Tuple[int, int, int], ...],
font_sz_in_pts: float,
symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float,
font_feature_settings: Dict[str, Tuple[bytes, ...]]
):
pass
@ -764,7 +823,8 @@ def get_fallback_font(text: str, bold: bool, italic: bool):
pass
def create_test_font_group(sz: float, dpix: float, dpiy: float) -> Tuple[int, int]:
def create_test_font_group(sz: float, dpix: float,
dpiy: float) -> Tuple[int, int]:
pass
@ -772,30 +832,35 @@ class Screen:
pass
def set_tab_bar_render_data(os_window_id: int, xstart: float, ystart: float, dx: float, dy: float, screen: Screen) -> None:
def set_tab_bar_render_data(
os_window_id: int, xstart: float, ystart: float, dx: float, dy: float,
screen: Screen
) -> None:
pass
def set_window_render_data(
os_window_id: int, tab_id: int, window_id: int, window_idx: int,
xstart: float, ystart: float, dx: float, dy: float,
screen: Screen,
left: int, top: int, right: int, bottom: int
os_window_id: int, tab_id: int, window_id: int, window_idx: int,
xstart: float, ystart: float, dx: float, dy: float, screen: Screen,
left: int, top: int, right: int, bottom: int
):
pass
def truncate_point_for_length(text: str, num_cells: int, start_pos: int = 0) -> int:
def truncate_point_for_length(
text: str, num_cells: int, start_pos: int = 0
) -> int:
pass
class ChildMonitor:
def __init__(
self,
death_notify: Callable[[int], None],
dump_callback: Optional[Callable],
talk_fd: int = -1, listen_fd: int = -1
self,
death_notify: Callable[[int], None],
dump_callback: Optional[Callable],
talk_fd: int = -1,
listen_fd: int = -1
):
pass

View File

@ -45,10 +45,14 @@ def all_fonts_map(monospaced=True):
def list_fonts() -> Generator[ListedFont, None, None]:
for fd in fc_list():
f = fd.get('family')
if f:
fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip()
if f and isinstance(f, str):
fn_ = fd.get('full_name')
if fn_:
fn = str(fn_)
else:
fn = (f + ' ' + str(fd.get('style', ''))).strip()
is_mono = fd.get('spacing') in ('MONO', 'DUAL')
yield {'family': f, 'full_name': fn, 'postscript_name': fd.get('postscript_name', ''), 'is_monospace': is_mono}
yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono}
def family_name_to_key(family):

View File

@ -5,7 +5,8 @@
import ctypes
import sys
from functools import partial
from math import ceil, pi, cos, floor
from math import ceil, cos, floor, pi
from typing import Any, List, Tuple
from kitty.config import defaults
from kitty.constants import is_macos
@ -22,7 +23,7 @@ if is_macos:
else:
from .fontconfig import get_font_files, font_for_family
current_faces = None
current_faces: List[Tuple[Any, bool, bool]] = []
def coalesce_symbol_maps(maps):

View File

@ -11,10 +11,9 @@ from .terminfo import key_as_bytes, modify_key_bytes
from .utils import base64_encode
def modify_complex_key(name, amt):
if not isinstance(name, bytes):
name = key_as_bytes(name)
return modify_key_bytes(name, amt)
def modify_complex_key(name: Union[str, bytes], amt: int) -> bytes:
q = name if isinstance(name, bytes) else key_as_bytes(name)
return modify_key_bytes(q, amt)
control_codes: Dict[int, Union[bytes, Tuple[int, ...]]] = {
@ -48,7 +47,7 @@ SHIFTED_KEYS = {
control_alt_codes = {
defines.GLFW_KEY_SPACE: b'\x1b\0',
}
control_alt_shift_codes = {}
control_alt_shift_codes: Dict[int, bytes] = {}
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',

View File

@ -5,7 +5,7 @@
from contextlib import suppress
from typing import (
TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Generator, List, NoReturn,
Optional, Tuple, Union
Optional, Tuple, Type, Union, cast
)
from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec
@ -42,12 +42,26 @@ class UnknownLayout(ValueError):
hide_traceback = True
class PayloadGetter:
def __init__(self, cmd: 'RemoteCommand', payload: Dict[str, Any]):
self.payload = payload
self.cmd = cmd
def __call__(self, key: str, opt_name: Optional[str] = None, missing: Any = None):
ans = self.payload.get(key, payload_get)
if ans is not payload_get:
return ans
return self.cmd.get_default(opt_name or key, missing=missing)
no_response = NoResponse()
payload_get = object()
ResponseType = Optional[Union[bool, str]]
CmdReturnType = Union[Dict[str, Any], List, Tuple, str, int, float, bool]
CmdGenerator = Generator[CmdReturnType, None, None]
PayloadType = Optional[Union[CmdReturnType, CmdGenerator]]
PayloadGetType = Callable[[str, str], Any]
PayloadGetType = PayloadGetter
ArgsType = List[str]
@ -106,6 +120,7 @@ class RemoteCommand:
args_count: Optional[int] = None
args_completion: Optional[Dict[str, Tuple[str, Tuple[str, ...]]]] = None
defaults: Optional[Dict[str, Any]] = None
options_class: Type = RCOptions
def __init__(self):
self.desc = self.desc or self.short_desc
@ -124,13 +139,6 @@ class RemoteCommand:
return self.defaults.get(name, missing)
return missing
def payload_get(self, payload: Dict[str, Any], key: str, opt_name: Optional[str] = None) -> Any:
payload_get = object()
ans = payload.get(key, payload_get)
if ans is not payload_get:
return ans
return self.get_default(opt_name or key)
def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
raise NotImplementedError()
@ -143,7 +151,7 @@ def cli_params_for(command: RemoteCommand) -> Tuple[Callable[[], str], str, str,
def parse_subcommand_cli(command: RemoteCommand, args: ArgsType) -> Tuple[Any, ArgsType]:
opts, items = parse_args(args[1:], *cli_params_for(command))
opts, items = parse_args(args[1:], *cli_params_for(command), result_class=command.options_class)
if command.args_count is not None and command.args_count != len(items):
if command.args_count == 0:
raise SystemExit('Unknown extra argument(s) supplied to {}'.format(command.name))
@ -163,17 +171,17 @@ def command_for_name(cmd_name: str) -> RemoteCommand:
m = import_module(f'kitty.rc.{cmd_name}')
except ImportError:
raise KeyError(f'{cmd_name} is not a known kitty remote control command')
return getattr(m, cmd_name)
return cast(RemoteCommand, getattr(m, cmd_name))
def all_command_names() -> FrozenSet[str]:
try:
from importlib.resources import contents
except ImportError:
from importlib_resources import contents
from importlib_resources import contents # type:ignore
def ok(name: str) -> bool:
root, _, ext = name.rpartition('.')
return ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__')
return bool(ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__'))
return frozenset({x.rpartition('.')[0] for x in filter(ok, contents('kitty.rc'))})

View File

@ -39,7 +39,7 @@ If specified close the tab this command is run in, rather than the active tab.
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window and payload_get('self') else boss.active_tab]
tabs = tuple(boss.tab_for_window(window) if window and payload_get('self') else boss.active_tab)
for tab in tabs:
if window:
if tab:

View File

@ -38,7 +38,7 @@ If specified close the window this command is run in, rather than the active win
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
for window in windows:
if window:
boss.close_window(window)

View File

@ -48,7 +48,7 @@ If specified apply marker to the window this command is run in, rather than the
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
args = payload_get('marker_spec')
for window in windows:

View File

@ -43,7 +43,7 @@ If specified detach the tab this command is run in, rather than the active tab.
if not tabs:
raise MatchError(match)
else:
tabs = [window.tabref() if payload_get('self') and window and window.tabref() else boss.active_tab]
tabs = tuple(window.tabref() if payload_get('self') and window and window.tabref() else boss.active_tab)
match = payload_get('target_tab')
kwargs = {}
if match:

View File

@ -45,7 +45,7 @@ If specified detach the window this command is run in, rather than the active wi
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
match = payload_get('target_tab')
kwargs = {}
if match:

View File

@ -42,7 +42,7 @@ using this option means that you will not be notified of failures.
if match:
tabs = tuple(boss.match_tabs(match))
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab)
if not tabs:
raise MatchError(match, 'tabs')
tab = tabs[0]

View File

@ -41,7 +41,7 @@ the command will exit with a success code.
windows = [window or boss.active_window]
match = payload_get('match')
if match:
windows = tuple(boss.match_windows(match))
windows = [boss.match_windows(match)]
if not windows:
raise MatchError(match)
for window in windows:

View File

@ -42,9 +42,9 @@ configured colors.
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
ans = {k: getattr(boss.opts, k) for k in boss.opts if isinstance(getattr(boss.opts, k), Color)}
if not payload_get('configured'):
windows = (window or boss.active_window,)
windows = [window or boss.active_window]
if payload_get('match'):
windows = tuple(boss.match_windows(payload_get('match')))
windows = list(boss.match_windows(payload_get('match')))
if not windows:
raise MatchError(payload_get('match'))
ans.update({k: color_from_int(v) for k, v in windows[0].current_colors.items()})

View File

@ -55,7 +55,7 @@ If specified get text from the window this command is run in, rather than the ac
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
window = windows[0]
if payload_get('extent') == 'selection':
ans = window.text_for_selection()

View File

@ -44,7 +44,7 @@ class GotoLayout(RemoteCommand):
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab)
for tab in tabs:
if tab:
try:

View File

@ -40,7 +40,7 @@ class Kitten(RemoteCommand):
windows = [window or boss.active_window]
match = payload_get('match')
if match:
windows = tuple(boss.match_windows(match))
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:

View File

@ -39,7 +39,7 @@ class LastUsedLayout(RemoteCommand):
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab)
for tab in tabs:
if tab:
tab.last_used_layout()

View File

@ -80,7 +80,7 @@ instead of the active tab
setattr(opts, key, val)
match = payload_get('match')
if match:
tabs = tuple(boss.match_tabs(match))
tabs = list(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
@ -88,7 +88,7 @@ instead of the active tab
if payload_get('self') and window and window.tabref():
tabs = [window.tabref()]
tab = tabs[0]
w = do_launch(boss, opts, payload_get('args') or None, target_tab=tab)
w = do_launch(boss, opts, payload_get('args') or [], target_tab=tab)
return None if payload_get('no_response') else str(getattr(w, 'id', 0))

View File

@ -32,7 +32,8 @@ class NewWindow(RemoteCommand):
desc = (
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
' Prints out the id of the newly opened window'
' (unless :option:`--no-response` is used). Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
':italic:`kitty @ new-window --title Email mutt`'
@ -109,7 +110,7 @@ the id of the new window will not be printed out.
match = payload_get('match')
if match:
tabs = tuple(boss.match_tabs(match))
tabs = list(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:

View File

@ -39,7 +39,7 @@ If specified apply marker to the window this command is run in, rather than the
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
for window in windows:
window.remove_marker()

View File

@ -22,7 +22,10 @@ class ResizeWindow(RemoteCommand):
'''
short_desc = 'Resize the specified window'
desc = 'Resize the specified window in the current layout. Note that not all layouts can resize all windows in all directions.'
desc = (
'Resize the specified window in the current layout.'
' Note that not all layouts can resize all windows in all directions.'
)
options_spec = MATCH_WINDOW_OPTION + '''\n
--increment -i
type=int
@ -34,9 +37,10 @@ The number of cells to change the size by, can be negative to decrease the size.
type=choices
choices=horizontal,vertical,reset
default=horizontal
The axis along which to resize. If :italic:`horizontal`, it will make the window wider or narrower by the specified increment.
If :italic:`vertical`, it will make the window taller or shorter by the specified increment. The special value :italic:`reset` will
reset the layout to its default configuration.
The axis along which to resize. If :italic:`horizontal`,
it will make the window wider or narrower by the specified increment.
If :italic:`vertical`, it will make the window taller or shorter by the specified increment.
The special value :italic:`reset` will reset the layout to its default configuration.
--self
@ -56,7 +60,7 @@ If specified resize the window this command is run in, rather than the active wi
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload_get('self') else boss.active_window]
windows = tuple(window if window and payload_get('self') else boss.active_window)
resized = False
if windows and windows[0]:
resized = boss.resize_layout_window(

View File

@ -3,7 +3,7 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional, Tuple, Union
from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
@ -35,23 +35,22 @@ class ScrollWindow(RemoteCommand):
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
amt = args[0]
ans = {'match': opts.match}
if amt in ('start', 'end'):
ans['amount'] = amt, None
else:
amount: Tuple[Union[str, int], Optional[str]] = amt, None
if amt not in ('start', 'end'):
pages = 'p' in amt
amt = amt.replace('p', '')
mult = -1 if amt.endswith('-') else 1
amt = int(amt.replace('-', ''))
ans['amount'] = [amt * mult, 'p' if pages else 'l']
return ans
q = int(amt.replace('-', ''))
amount = q * mult, 'p' if pages else 'l'
return {'match': opts.match, 'amount': amount}
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
windows = [window or boss.active_window]
match = payload_get('match')
amt = payload_get('amount')
if match:
windows = tuple(boss.match_windows(match))
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:

View File

@ -4,7 +4,7 @@
import os
import sys
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Generator, Dict
from kitty.config import parse_send_text_bytes
@ -51,8 +51,7 @@ are sent as is, not interpreted for escapes.
limit = 1024
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
def pipe():
ret['is_binary'] = True
def pipe() -> Generator[Dict, None, None]:
if sys.stdin.isatty():
import select
fd = sys.stdin.fileno()
@ -64,28 +63,29 @@ are sent as is, not interpreted for escapes.
data = os.read(fd, limit)
if not data:
break # eof
data = data.decode('utf-8')
if '\x04' in data:
data = data[:data.index('\x04')]
decoded_data = data.decode('utf-8')
if '\x04' in decoded_data:
decoded_data = decoded_data[:decoded_data.index('\x04')]
keep_going = False
ret['text'] = data
ret['text'] = decoded_data
yield ret
else:
ret['is_binary'] = True
while True:
data = sys.stdin.read(limit)
data = sys.stdin.buffer.read(limit)
if not data:
break
ret['text'] = data[:limit]
yield ret
def chunks(text):
def chunks(text: str) -> Generator[Dict, None, None]:
ret['is_binary'] = False
while text:
ret['text'] = text[:limit]
yield ret
text = text[limit:]
def file_pipe(path):
def file_pipe(path: str) -> Generator[Dict, None, None]:
ret['is_binary'] = True
with open(path, encoding='utf-8') as f:
while True:
@ -105,7 +105,7 @@ are sent as is, not interpreted for escapes.
text = ' '.join(args)
sources.append(chunks(text))
def chain():
def chain() -> Generator[Dict, None, None]:
for src in sources:
yield from src
return chain()
@ -114,7 +114,7 @@ are sent as is, not interpreted for escapes.
windows = [boss.active_window]
match = payload_get('match')
if match:
windows = tuple(boss.match_windows(match))
windows = list(boss.match_windows(match))
mt = payload_get('match_tab')
if mt:
windows = []
@ -123,7 +123,8 @@ are sent as is, not interpreted for escapes.
raise MatchError(payload_get('match_tab'), 'tabs')
for tab in tabs:
windows += tuple(tab)
data = payload_get('text').encode('utf-8') if payload_get('is_binary') else parse_send_text_bytes(payload_get('text'))
data = payload_get('text').encode('utf-8') if payload_get('is_binary') else parse_send_text_bytes(
payload_get('text'))
for window in windows:
if window is not None:
window.write_to_child(data)

View File

@ -5,12 +5,12 @@
import imghdr
import tempfile
from base64 import standard_b64decode, standard_b64encode
from typing import TYPE_CHECKING, BinaryIO, Optional
from typing import IO, TYPE_CHECKING, Dict, Generator, Optional
from uuid import uuid4
from .base import (
MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType,
RCOptions, RemoteCommand, ResponseType, Window, no_response,
RCOptions, RemoteCommand, ResponseType, Window,
windows_for_payload
)
@ -59,20 +59,26 @@ How the image should be displayed. The value of configured will use the configur
args_count = 1
args_completion = {'files': ('PNG Images', ('*.png',))}
current_img_id: Optional[str] = None
current_file_obj: Optional[BinaryIO] = None
current_file_obj: Optional[IO[bytes]] = None
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
if not args:
self.fatal('Must specify path to PNG image')
path = args[0]
ret = {'match': opts.match, 'configured': opts.configured, 'layout': opts.layout, 'all': opts.all, 'img_id': str(uuid4())}
ret = {
'match': opts.match,
'configured': opts.configured,
'layout': opts.layout,
'all': opts.all,
'img_id': str(uuid4())
}
if path.lower() == 'none':
ret['data'] = '-'
return ret
if imghdr.what(path) != 'png':
self.fatal('{} is not a PNG image'.format(path))
def file_pipe(path):
def file_pipe(path) -> Generator[Dict, None, None]:
with open(path, 'rb') as f:
while True:
data = f.read(512)
@ -92,8 +98,9 @@ How the image should be displayed. The value of configured will use the configur
set_background_image.current_img_id = img_id
set_background_image.current_file_obj = tempfile.NamedTemporaryFile()
if data:
assert set_background_image.current_file_obj is not None
set_background_image.current_file_obj.write(standard_b64decode(data))
return no_response
return None
windows = windows_for_payload(boss, window, payload_get)
os_windows = tuple({w.os_window_id for w in windows})
@ -101,6 +108,7 @@ How the image should be displayed. The value of configured will use the configur
if data == '-':
path = None
else:
assert set_background_image.current_file_obj is not None
f = set_background_image.current_file_obj
path = f.name
set_background_image.current_file_obj = None
@ -109,7 +117,7 @@ How the image should be displayed. The value of configured will use the configur
try:
boss.set_background_image(path, os_windows, payload_get('configured'), layout)
except ValueError as err:
err.hide_traceback = True
err.hide_traceback = True # type: ignore
raise

View File

@ -4,7 +4,7 @@
import os
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Dict, Optional
from kitty.config import parse_config
from kitty.fast_data_types import patch_color_profiles
@ -34,7 +34,8 @@ class SetColors(RemoteCommand):
short_desc = 'Set terminal colors'
desc = (
'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file'
'Set the terminal colors for the specified windows/tabs (defaults to active window).'
' You can either specify the path to a conf file'
' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,'
' for example: kitty @ set-colors foreground=red background=white'
)
@ -59,21 +60,28 @@ this option, any color arguments are ignored and --configured and --all are impl
argspec = 'COLOR_OR_FILE ...'
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
colors, cursor_text_color = {}, False
final_colors: Dict[str, int] = {}
cursor_text_color: Optional[int] = None
if not opts.reset:
colors: Dict[str, Optional[Color]] = {}
for spec in args:
if '=' in spec:
colors.update(parse_config((spec.replace('=', ' '),)))
else:
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
colors.update(parse_config(f))
cursor_text_color = colors.pop('cursor_text_color', False)
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
return {
ctc = colors.pop('cursor_text_color')
if isinstance(ctc, Color):
cursor_text_color = color_as_int(ctc)
final_colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
ans = {
'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
'colors': final_colors, 'reset': opts.reset
}
if cursor_text_color is not None:
ans['cursor_text_color'] = cursor_text_color
return ans
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
windows = windows_for_payload(boss, window, payload_get)

View File

@ -41,7 +41,7 @@ class SetTabTitle(RemoteCommand):
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
tabs = tuple(boss.tab_for_window(window) if window else boss.active_tab)
for tab in tabs:
if tab:
tab.set_title(payload_get('title'))

View File

@ -44,7 +44,7 @@ want to allow other programs to change it afterwards, use this option.
windows = [window or boss.active_window]
match = payload_get('match')
if match:
windows = tuple(boss.match_windows(match))
windows = list(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:

View File

@ -9,15 +9,15 @@ import sys
import types
from contextlib import suppress
from functools import partial
from typing import Any, List, Optional
from typing import List
from .cli import emph, parse_args
from .cli_stub import RCOptions
from .constants import appname, version
from .fast_data_types import read_command_response
from .rc.base import (
all_command_names, command_for_name, no_response as no_response_sentinel,
parse_subcommand_cli
PayloadGetter, all_command_names, command_for_name,
no_response as no_response_sentinel, parse_subcommand_cli
)
from .utils import TTYIO, parse_address_spec
@ -33,11 +33,8 @@ def handle_cmd(boss, window, cmd):
c = command_for_name(cmd['cmd'])
payload = cmd.get('payload') or {}
def payload_get(key: str, opt_name: Optional[str] = None) -> Any:
return c.payload_get(payload, key, opt_name)
try:
ans = c.response_from_kitty(boss, window, payload_get)
ans = c.response_from_kitty(boss, window, PayloadGetter(c, payload))
except Exception:
if no_response: # don't report errors if --no-response was used
return

View File

@ -48,7 +48,7 @@ def cmd_names_matching(prefix):
@lru_cache()
def options_for_cmd(cmd: str) -> Tuple[Tuple[str, ...], Dict[str, OptionDict]]:
alias_map = {}
alias_map: Dict[str, OptionDict] = {}
try:
func = command_for_name(cmd)
except KeyError:

View File

@ -7,15 +7,15 @@ from binascii import hexlify, unhexlify
from typing import cast, Dict
def modify_key_bytes(keybytes, amt):
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
if amt == 0:
return keybytes
ans = bytearray(keybytes)
amt = str(amt).encode('ascii')
samt = str(amt).encode('ascii')
if ans[-1] == ord('~'):
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
return bytes(ans[:-1] + bytearray(b';' + samt + b'~'))
if ans[1] == ord('O'):
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
return bytes(ans[:1] + bytearray(b'[1;' + samt) + ans[-1:])
raise ValueError('Unknown key type in key: {!r}'.format(keybytes))
@ -433,7 +433,7 @@ octal_escape = re.compile(r'\\([0-7]{3})')
escape_escape = re.compile(r'\\[eE]')
def key_as_bytes(name):
def key_as_bytes(name: str) -> bytes:
ans = string_capabilities[name]
ans = octal_escape.sub(lambda m: chr(int(m.group(1), 8)), ans)
ans = escape_escape.sub('\033', ans)

View File

@ -9,6 +9,7 @@ import weakref
from collections import deque
from enum import IntEnum
from itertools import chain
from typing import List
from .config import build_ansi_color_table
from .constants import ScreenGeometry, WindowGeometry, appname, wakeup
@ -483,7 +484,7 @@ class Window:
self.title_stack.append(self.child_title)
# }}}
def text_for_selection(self):
def text_for_selection(self) -> str:
lines = self.screen.text_for_selection()
if self.opts.strip_trailing_spaces == 'always' or (
self.opts.strip_trailing_spaces == 'smart' and not self.screen.is_rectangle_select()):
@ -497,8 +498,8 @@ class Window:
self.screen.reset_callbacks()
self.screen = None
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False):
lines = []
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False) -> str:
lines: List[str] = []
add_history = add_history and not (self.screen.is_using_alternate_linebuf() ^ alternate_screen)
if alternate_screen:
f = self.screen.as_text_alternate
@ -506,7 +507,7 @@ class Window:
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h = []
h: List[str] = []
self.screen.historybuf.pagerhist_as_text(h.append)
if h and (not as_ansi or not add_wrap_markers):
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
@ -517,7 +518,7 @@ class Window:
h[-1] += '\n'
if as_ansi:
h[-1] += '\x1b[m'
lines = chain(h, lines)
return ''.join(chain(h, lines))
return ''.join(lines)
@property

View File

@ -1,6 +1,6 @@
" Scan the following dirs recursively for tags
let g:project_tags_dirs = ['kitty']
let g:syntastic_python_flake8_exec = 'flake8'
let g:project_tags_dirs = ['kitty', 'kittens']
let g:syntastic_python_checkers = ['pylama']
let g:ycm_python_binary_path = 'python3'
set wildignore+==template.py
set wildignore+=tags

View File

@ -13,8 +13,15 @@ blank_line_before_nested_class_or_def = True
combine_as_imports = True
multi_line_output = 5
[mypy]
files = kitty,kittens,glfw,*.py
[pylama]
linters=mypy,pycodestyle,pyflakes
[pylama:pycodestyle]
max_line_length = 120
exclude==template.py,linux-package
[pylama:mypy]
files = kitty,kittens,glfw,*.py,docs/conf.py
no_implicit_optional = True
sqlite_cache = True
cache_fine_grained = True
@ -22,4 +29,17 @@ warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
warn_unreachable = True
warn_no_return = False
check_untyped_defs = True
[mypy]
files = kitty,kittens,glfw,*.py,docs/conf.py
no_implicit_optional = True
sqlite_cache = True
cache_fine_grained = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
warn_unreachable = True
warn_no_return = False
# check_untyped_defs = True

View File

@ -741,7 +741,7 @@ def compile_python(base_path):
kw = {}
compileall.compile_dir(
base_path, ddir='', force=True, optimize=optimize, quiet=1,
workers=num_workers, **kw # type: ignore
workers=num_workers, **kw
)