more typing work

This commit is contained in:
Kovid Goyal 2020-03-10 15:48:51 +05:30
parent 8803eeb890
commit fc0adfd965
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 122 additions and 114 deletions

View File

@ -7,8 +7,8 @@ import re
import sys import sys
from collections import deque from collections import deque
from typing import ( from typing import (
Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Sequence, Tuple, TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List,
Type, TypeVar, Union, cast Match, Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast
) )
from .cli_stub import CLIOptions from .cli_stub import CLIOptions
@ -30,6 +30,8 @@ try:
except ImportError: except ImportError:
OptionDict = Dict[str, Any] # type: ignore OptionDict = Dict[str, Any] # type: ignore
if TYPE_CHECKING:
from .config import BadLine, KeyAction, KeySpec, SequenceMap # noqa
CONFIG_HELP = '''\ CONFIG_HELP = '''\
Specify a path to the configuration file(s) to use. All configuration files are Specify a path to the configuration file(s) to use. All configuration files are
@ -197,9 +199,9 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
def prettify(text: str) -> str: def prettify(text: str) -> str:
role_map = globals() role_map = globals()
def sub(m): def sub(m: Match) -> str:
role, text = m.group(1, 2) role, text = m.group(1, 2)
return role_map[role](text) return str(role_map[role](text))
text = re.sub(r':([a-z]+):`([^`]+)`', sub, text) text = re.sub(r':([a-z]+):`([^`]+)`', sub, text)
return text return text
@ -280,7 +282,7 @@ class PrintHelpForSeq:
blocks: List[str] = [] blocks: List[str] = []
a = blocks.append a = blocks.append
def wa(text, indent=0, leading_indent=None): def wa(text: str, indent: int = 0, leading_indent: Optional[int] = None) -> None:
if leading_indent is None: if leading_indent is None:
leading_indent = indent leading_indent = indent
j = '\n' + (' ' * indent) j = '\n' + (' ' * indent)
@ -698,7 +700,7 @@ def options_for_completion() -> OptionSpecSeq:
def option_spec_as_rst( def option_spec_as_rst(
ospec: Callable[[], str] = options_spec, ospec: Callable[[], str] = options_spec,
usage: Optional[str] = None, message: Optional[str] = None, appname: Optional[str] = None, usage: Optional[str] = None, message: Optional[str] = None, appname: Optional[str] = None,
heading_char='-' heading_char: str = '-'
) -> str: ) -> str:
options = parse_option_spec(ospec()) options = parse_option_spec(ospec())
seq, disabled = options seq, disabled = options
@ -728,9 +730,10 @@ def parse_args(
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf' SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
ShortcutMap = Dict[Tuple['KeySpec', ...], 'KeyAction']
def print_shortcut(key_sequence, action): def print_shortcut(key_sequence: Iterable['KeySpec'], action: 'KeyAction') -> None:
if not getattr(print_shortcut, 'maps', None): if not getattr(print_shortcut, 'maps', None):
from kitty.keys import defines from kitty.keys import defines
v = vars(defines) v = vars(defines)
@ -740,9 +743,9 @@ def print_shortcut(key_sequence, action):
setattr(print_shortcut, 'maps', (mmap, krmap)) setattr(print_shortcut, 'maps', (mmap, krmap))
mmap, krmap = getattr(print_shortcut, 'maps') mmap, krmap = getattr(print_shortcut, 'maps')
keys = [] keys = []
for key in key_sequence: for key_spec in key_sequence:
names = [] names = []
mods, is_native, key = key mods, is_native, key = key_spec
for name, val in mmap.items(): for name, val in mmap.items():
if mods & val: if mods & val:
names.append(name) names.append(name)
@ -758,7 +761,7 @@ def print_shortcut(key_sequence, action):
print('\t', ' > '.join(keys), action) print('\t', ' > '.join(keys), action)
def print_shortcut_changes(defns, text, changes): def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple['KeySpec', ...]]) -> None:
if changes: if changes:
print(title(text)) print(title(text))
@ -766,7 +769,7 @@ def print_shortcut_changes(defns, text, changes):
print_shortcut(k, defns[k]) print_shortcut(k, defns[k])
def compare_keymaps(final, initial): def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None:
added = set(final) - set(initial) added = set(final) - set(initial)
removed = set(initial) - set(final) removed = set(initial) - set(final)
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]} changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
@ -775,11 +778,11 @@ def compare_keymaps(final, initial):
print_shortcut_changes(final, 'Changed shortcuts:', changed) print_shortcut_changes(final, 'Changed shortcuts:', changed)
def flatten_sequence_map(m): def flatten_sequence_map(m: 'SequenceMap') -> ShortcutMap:
ans = {} ans: Dict[Tuple['KeySpec', ...], 'KeyAction'] = {}
for k, rest_map in m.items(): for key_spec, rest_map in m.items():
for r, action in rest_map.items(): for r, action in rest_map.items():
ans[(k,) + (r)] = action ans[(key_spec,) + (r)] = action
return ans return ans
@ -797,15 +800,15 @@ def compare_opts(opts: OptionsStub) -> None:
print(title(fmt.format(f)), getattr(opts, f)) print(title(fmt.format(f)), getattr(opts, f))
final_, initial_ = opts.keymap, default_opts.keymap final_, initial_ = opts.keymap, default_opts.keymap
final = {(k,): v for k, v in final_.items()} final: ShortcutMap = {(k,): v for k, v in final_.items()}
initial = {(k,): v for k, v in initial_.items()} initial: ShortcutMap = {(k,): v for k, v in initial_.items()}
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map)) final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
final.update(final_s) final.update(final_s)
initial.update(initial_s) initial.update(initial_s)
compare_keymaps(final, initial) compare_keymaps(final, initial)
def create_opts(args: CLIOptions, debug_config=False, accumulate_bad_lines=None) -> OptionsStub: def create_opts(args: CLIOptions, debug_config: bool = False, accumulate_bad_lines: Optional[List['BadLine']] = None) -> OptionsStub:
from .config import load_config from .config import load_config
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config)) config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
if debug_config: if debug_config:

View File

@ -6,12 +6,11 @@ import json
import os import os
import re import re
import sys import sys
from collections import namedtuple
from contextlib import contextmanager, suppress from contextlib import contextmanager, suppress
from functools import partial from functools import partial
from typing import ( from typing import (
Any, Callable, Dict, Iterable, List, Optional, Any, Callable, Dict, Generator, Iterable, List, Match, NamedTuple,
Sequence, Set, Tuple, Type Optional, Sequence, Set, Tuple, Type, Union
) )
from . import fast_data_types as defines from . import fast_data_types as defines
@ -26,7 +25,6 @@ from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub from .options_stub import Options as OptionsStub
from .utils import log_error from .utils import log_error
KeySpec = Tuple[int, bool, int] KeySpec = Tuple[int, bool, int]
KeyMap = Dict[KeySpec, 'KeyAction'] KeyMap = Dict[KeySpec, 'KeyAction']
KeySequence = Tuple[KeySpec, ...] KeySequence = Tuple[KeySpec, ...]
@ -59,8 +57,13 @@ def parse_shortcut(sc: str) -> Tuple[int, bool, Optional[int]]:
return mods, is_native, key return mods, is_native, key
KeyAction = namedtuple('KeyAction', 'func args') class KeyAction(NamedTuple):
func: str
args: Sequence[str]
func_with_args, args_funcs = key_func() func_with_args, args_funcs = key_func()
FuncArgsType = Tuple[str, Sequence[Any]]
@func_with_args( @func_with_args(
@ -68,12 +71,12 @@ func_with_args, args_funcs = key_func()
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd',
'launch' 'launch'
) )
def shlex_parse(func, rest): def shlex_parse(func: str, rest: str) -> FuncArgsType:
return func, to_cmdline(rest) return func, to_cmdline(rest)
@func_with_args('combine') @func_with_args('combine')
def combine_parse(func, rest): def combine_parse(func: str, rest: str) -> FuncArgsType:
sep, rest = rest.split(maxsplit=1) sep, rest = rest.split(maxsplit=1)
parts = re.split(r'\s*' + re.escape(sep) + r'\s*', rest) parts = re.split(r'\s*' + re.escape(sep) + r'\s*', rest)
args = tuple(map(parse_key_action, filter(None, parts))) args = tuple(map(parse_key_action, filter(None, parts)))
@ -81,19 +84,21 @@ def combine_parse(func, rest):
@func_with_args('send_text') @func_with_args('send_text')
def send_text_parse(func, rest): def send_text_parse(func: str, rest: str) -> FuncArgsType:
args = rest.split(maxsplit=1) args = rest.split(maxsplit=1)
if len(args) > 0: mode = ''
data = b''
if len(args) > 1:
mode = args[0]
try: try:
args[1] = parse_send_text_bytes(args[1]) data = parse_send_text_bytes(args[1])
except Exception: except Exception:
log_error('Ignoring invalid send_text string: ' + args[1]) log_error('Ignoring invalid send_text string: ' + args[1])
args[1] = '' return func, [mode, data]
return func, args
@func_with_args('run_kitten', 'run_simple_kitten', 'kitten') @func_with_args('run_kitten', 'run_simple_kitten', 'kitten')
def kitten_parse(func, rest): def kitten_parse(func: str, rest: str) -> FuncArgsType:
if func == 'kitten': if func == 'kitten':
args = rest.split(maxsplit=1) args = rest.split(maxsplit=1)
else: else:
@ -103,13 +108,13 @@ def kitten_parse(func, rest):
@func_with_args('goto_tab') @func_with_args('goto_tab')
def goto_tab_parse(func, rest): def goto_tab_parse(func: str, rest: str) -> FuncArgsType:
args = (max(0, int(rest)), ) args = (max(0, int(rest)), )
return func, args return func, args
@func_with_args('detach_window') @func_with_args('detach_window')
def detach_window_parse(func, rest): def detach_window_parse(func: str, rest: str) -> FuncArgsType:
if rest not in ('new', 'new-tab', 'ask'): if rest not in ('new', 'new-tab', 'ask'):
log_error('Ignoring invalid detach_window argument: {}'.format(rest)) log_error('Ignoring invalid detach_window argument: {}'.format(rest))
rest = 'new' rest = 'new'
@ -117,7 +122,7 @@ def detach_window_parse(func, rest):
@func_with_args('detach_tab') @func_with_args('detach_tab')
def detach_tab_parse(func, rest): def detach_tab_parse(func: str, rest: str) -> FuncArgsType:
if rest not in ('new', 'ask'): if rest not in ('new', 'ask'):
log_error('Ignoring invalid detach_tab argument: {}'.format(rest)) log_error('Ignoring invalid detach_tab argument: {}'.format(rest))
rest = 'new' rest = 'new'
@ -125,12 +130,12 @@ def detach_tab_parse(func, rest):
@func_with_args('set_background_opacity', 'goto_layout', 'kitty_shell') @func_with_args('set_background_opacity', 'goto_layout', 'kitty_shell')
def simple_parse(func, rest): def simple_parse(func: str, rest: str) -> FuncArgsType:
return func, [rest] return func, [rest]
@func_with_args('set_font_size') @func_with_args('set_font_size')
def float_parse(func, rest): def float_parse(func: str, rest: str) -> FuncArgsType:
return func, (float(rest),) return func, (float(rest),)
@ -150,28 +155,28 @@ def parse_change_font_size(func: str, rest: str) -> Tuple[str, Tuple[bool, Optio
@func_with_args('clear_terminal') @func_with_args('clear_terminal')
def clear_terminal(func, rest): def clear_terminal(func: str, rest: str) -> FuncArgsType:
vals = rest.strip().split(maxsplit=1) vals = rest.strip().split(maxsplit=1)
if len(vals) != 2: if len(vals) != 2:
log_error('clear_terminal needs two arguments, using defaults') log_error('clear_terminal needs two arguments, using defaults')
args = ['reset', 'active'] args: List[Union[str, bool]] = ['reset', 'active']
else: else:
args = [vals[0].lower(), vals[1].lower() == 'active'] args = [vals[0].lower(), vals[1].lower() == 'active']
return func, args return func, args
@func_with_args('copy_to_buffer') @func_with_args('copy_to_buffer')
def copy_to_buffer(func, rest): def copy_to_buffer(func: str, rest: str) -> FuncArgsType:
return func, [rest] return func, [rest]
@func_with_args('paste_from_buffer') @func_with_args('paste_from_buffer')
def paste_from_buffer(func, rest): def paste_from_buffer(func: str, rest: str) -> FuncArgsType:
return func, [rest] return func, [rest]
@func_with_args('neighboring_window') @func_with_args('neighboring_window')
def neighboring_window(func, rest): def neighboring_window(func: str, rest: str) -> FuncArgsType:
rest = rest.lower() rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest) rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
if rest not in ('left', 'right', 'top', 'bottom'): if rest not in ('left', 'right', 'top', 'bottom'):
@ -181,7 +186,7 @@ def neighboring_window(func, rest):
@func_with_args('resize_window') @func_with_args('resize_window')
def resize_window(func, rest): def resize_window(func: str, rest: str) -> FuncArgsType:
vals = rest.strip().split(maxsplit=1) vals = rest.strip().split(maxsplit=1)
if len(vals) > 2: if len(vals) > 2:
log_error('resize_window needs one or two arguments, using defaults') log_error('resize_window needs one or two arguments, using defaults')
@ -202,39 +207,40 @@ def resize_window(func, rest):
@func_with_args('move_window') @func_with_args('move_window')
def move_window(func, rest): def move_window(func: str, rest: str) -> FuncArgsType:
rest = rest.lower() rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest) rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
prest: Union[int, str] = rest
try: try:
rest = int(rest) prest = int(prest)
except Exception: except Exception:
if rest not in ('left', 'right', 'top', 'bottom'): if prest not in ('left', 'right', 'top', 'bottom'):
log_error('Invalid move_window specification: {}'.format(rest)) log_error('Invalid move_window specification: {}'.format(rest))
rest = 0 prest = 0
return func, [rest] return func, [prest]
@func_with_args('pipe') @func_with_args('pipe')
def pipe(func, rest): def pipe(func: str, rest: str) -> FuncArgsType:
import shlex import shlex
rest = shlex.split(rest) r = shlex.split(rest)
if len(rest) < 3: if len(r) < 3:
log_error('Too few arguments to pipe function') log_error('Too few arguments to pipe function')
rest = ['none', 'none', 'true'] r = ['none', 'none', 'true']
return func, rest return func, r
@func_with_args('set_colors') @func_with_args('set_colors')
def set_colors(func, rest): def set_colors(func: str, rest: str) -> FuncArgsType:
import shlex import shlex
rest = shlex.split(rest) r = shlex.split(rest)
if len(rest) < 1: if len(r) < 1:
log_error('Too few arguments to set_colors function') log_error('Too few arguments to set_colors function')
return func, rest return func, r
@func_with_args('nth_window') @func_with_args('nth_window')
def nth_window(func, rest): def nth_window(func: str, rest: str) -> FuncArgsType:
try: try:
num = int(rest) num = int(rest)
except Exception: except Exception:
@ -244,7 +250,7 @@ def nth_window(func, rest):
@func_with_args('disable_ligatures_in') @func_with_args('disable_ligatures_in')
def disable_ligatures_in(func, rest): def disable_ligatures_in(func: str, rest: str) -> FuncArgsType:
parts = rest.split(maxsplit=1) parts = rest.split(maxsplit=1)
if len(parts) == 1: if len(parts) == 1:
where, strategy = 'active', parts[0] where, strategy = 'active', parts[0]
@ -258,14 +264,14 @@ def disable_ligatures_in(func, rest):
@func_with_args('layout_action') @func_with_args('layout_action')
def layout_action(func, rest): def layout_action(func: str, rest: str) -> FuncArgsType:
parts = rest.split(maxsplit=1) parts = rest.split(maxsplit=1)
if not parts: if not parts:
raise ValueError('layout_action must have at least one argument') raise ValueError('layout_action must have at least one argument')
return func, [parts[0], tuple(parts[1:])] return func, [parts[0], tuple(parts[1:])]
def parse_marker_spec(ftype, parts): def parse_marker_spec(ftype: str, parts: Sequence[str]) -> Tuple[str, Union[str, Tuple[Tuple[int, str], ...]], int]:
flags = re.UNICODE flags = re.UNICODE
if ftype in ('text', 'itext', 'regex', 'iregex'): if ftype in ('text', 'itext', 'regex', 'iregex'):
if ftype.startswith('i'): if ftype.startswith('i'):
@ -278,12 +284,12 @@ def parse_marker_spec(ftype, parts):
color = max(1, min(int(parts[i]), 3)) color = max(1, min(int(parts[i]), 3))
except Exception: except Exception:
raise ValueError('color {} in marker specification is not an integer'.format(parts[i])) raise ValueError('color {} in marker specification is not an integer'.format(parts[i]))
spec = parts[i + 1] sspec = parts[i + 1]
if 'regex' not in ftype: if 'regex' not in ftype:
spec = re.escape(spec) sspec = re.escape(sspec)
ans.append((color, spec)) ans.append((color, sspec))
ftype = 'regex' ftype = 'regex'
spec = tuple(ans) spec: Union[str, Tuple[Tuple[int, str], ...]] = tuple(ans)
elif ftype == 'function': elif ftype == 'function':
spec = ' '.join(parts) spec = ' '.join(parts)
else: else:
@ -292,7 +298,7 @@ def parse_marker_spec(ftype, parts):
@func_with_args('toggle_marker') @func_with_args('toggle_marker')
def toggle_marker(func, rest): def toggle_marker(func: str, rest: str) -> FuncArgsType:
parts = rest.split(maxsplit=1) parts = rest.split(maxsplit=1)
if len(parts) != 2: if len(parts) != 2:
raise ValueError('{} if not a valid marker specification'.format(rest)) raise ValueError('{} if not a valid marker specification'.format(rest))
@ -302,7 +308,7 @@ def toggle_marker(func, rest):
@func_with_args('scroll_to_mark') @func_with_args('scroll_to_mark')
def scroll_to_mark(func, rest): def scroll_to_mark(func: str, rest: str) -> FuncArgsType:
parts = rest.split() parts = rest.split()
if not parts or not rest: if not parts or not rest:
return func, [True, 0] return func, [True, 0]
@ -340,17 +346,17 @@ sequence_sep = '>'
class KeyDefinition: class KeyDefinition:
def __init__(self, is_sequence, action, mods, is_native, key, rest=()): def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[KeySpec, ...] = ()):
self.is_sequence = is_sequence self.is_sequence = is_sequence
self.action = action self.action = action
self.trigger = mods, is_native, key self.trigger = mods, is_native, key
self.rest = rest self.rest = rest
def resolve(self, kitty_mod): def resolve(self, kitty_mod: int) -> None:
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1], self.trigger[2] self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1], self.trigger[2]
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), is_native, key) for mods, is_native, key in self.rest) self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), is_native, key) for mods, is_native, key in self.rest)
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]): def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
if not self.action.args: if not self.action.args:
return return
kitten = self.action.args[0] kitten = self.action.args[0]
@ -366,7 +372,7 @@ class KeyDefinition:
self.action = self.action._replace(args=[kitten + (' ' + rest).rstrip()]) self.action = self.action._replace(args=[kitten + (' ' + rest).rstrip()])
def parse_key(val, key_definitions): def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
parts = val.split(maxsplit=1) parts = val.split(maxsplit=1)
if len(parts) != 2: if len(parts) != 2:
return return
@ -413,14 +419,15 @@ def parse_key(val, key_definitions):
if trigger is not None: if trigger is not None:
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], trigger[2], rest)) key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], trigger[2], rest))
else: else:
assert key is not None
key_definitions.append(KeyDefinition(False, paction, mods, is_native, key)) key_definitions.append(KeyDefinition(False, paction, mods, is_native, key))
def parse_symbol_map(val): def parse_symbol_map(val: str) -> Dict[Tuple[int, int], str]:
parts = val.split() parts = val.split()
symbol_map = {} symbol_map: Dict[Tuple[int, int], str] = {}
def abort(): def abort() -> Dict[Tuple[int, int], str]:
log_error('Symbol map: {} is invalid, ignoring'.format( log_error('Symbol map: {} is invalid, ignoring'.format(
val)) val))
return {} return {}
@ -429,17 +436,16 @@ def parse_symbol_map(val):
return abort() return abort()
family = ' '.join(parts[1:]) family = ' '.join(parts[1:])
def to_chr(x): def to_chr(x: str) -> int:
if not x.startswith('U+'): if not x.startswith('U+'):
raise ValueError() raise ValueError()
x = int(x[2:], 16) return int(x[2:], 16)
return x
for x in parts[0].split(','): for x in parts[0].split(','):
a, b = x.partition('-')[::2] a_, b_ = x.partition('-')[::2]
b = b or a b_ = b_ or a_
try: try:
a, b = map(to_chr, (a, b)) a, b = map(to_chr, (a_, b_))
except Exception: except Exception:
return abort() return abort()
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1: if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
@ -448,24 +454,23 @@ def parse_symbol_map(val):
return symbol_map return symbol_map
def parse_send_text_bytes(text): def parse_send_text_bytes(text: str) -> bytes:
return python_string(text).encode('utf-8') return python_string(text).encode('utf-8')
def parse_send_text(val, key_definitions): def parse_send_text(val: str, key_definitions: List[KeyDefinition]) -> None:
parts = val.split(' ') parts = val.split(' ')
def abort(msg): def abort(msg: str) -> None:
log_error('Send text: {} is invalid ({}), ignoring'.format( log_error('Send text: {} is invalid ({}), ignoring'.format(
val, msg)) val, msg))
return {}
if len(parts) < 3: if len(parts) < 3:
return abort('Incomplete') return abort('Incomplete')
mode, sc = parts[:2] mode, sc = parts[:2]
text = ' '.join(parts[2:]) text = ' '.join(parts[2:])
key_str = '{} send_text {} {}'.format(sc, mode, text) key_str = '{} send_text {} {}'.format(sc, mode, text)
return parse_key(key_str, key_definitions) parse_key(key_str, key_definitions)
SpecialHandlerFunc = Callable[[str, str, Dict[str, Any]], None] SpecialHandlerFunc = Callable[[str, str, Dict[str, Any]], None]
@ -486,25 +491,25 @@ def deprecated_handler(*names: str) -> Callable[[SpecialHandlerFunc], SpecialHan
@special_handler @special_handler
def handle_map(key, val, ans): def handle_map(key: str, val: str, ans: Dict[str, Any]) -> None:
parse_key(val, ans['key_definitions']) parse_key(val, ans['key_definitions'])
@special_handler @special_handler
def handle_symbol_map(key, val, ans): def handle_symbol_map(key: str, val: str, ans: Dict[str, Any]) -> None:
ans['symbol_map'].update(parse_symbol_map(val)) ans['symbol_map'].update(parse_symbol_map(val))
class FontFeature(str): class FontFeature(str):
def __new__(cls, name: str, parsed: bytes): def __new__(cls, name: str, parsed: bytes) -> 'FontFeature':
ans = str.__new__(cls, name) # type: ignore ans: FontFeature = str.__new__(cls, name) # type: ignore
ans.parsed = parsed ans.parsed = parsed # type: ignore
return ans return ans
@special_handler @special_handler
def handle_font_features(key, val, ans): def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> None:
if val != 'none': if val != 'none':
parts = val.split() parts = val.split()
if len(parts) < 2: if len(parts) < 2:
@ -523,26 +528,26 @@ def handle_font_features(key, val, ans):
@special_handler @special_handler
def handle_kitten_alias(key, val, ans): def handle_kitten_alias(key: str, val: str, ans: Dict[str, Any]) -> None:
parts = val.split(maxsplit=2) parts = val.split(maxsplit=2)
if len(parts) >= 2: if len(parts) >= 2:
ans['kitten_aliases'][parts[0]] = parts[1:] ans['kitten_aliases'][parts[0]] = parts[1:]
@special_handler @special_handler
def handle_send_text(key, val, ans): def handle_send_text(key: str, val: str, ans: Dict[str, Any]) -> None:
# For legacy compatibility # For legacy compatibility
parse_send_text(val, ans['key_definitions']) parse_send_text(val, ans['key_definitions'])
@special_handler @special_handler
def handle_clear_all_shortcuts(key, val, ans): def handle_clear_all_shortcuts(key: str, val: str, ans: Dict[str, Any]) -> None:
if to_bool(val): if to_bool(val):
ans['key_definitions'] = [None] ans['key_definitions'] = [None]
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar') @deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
def handle_deprecated_hide_window_decorations_aliases(key, val, ans): def handle_deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None:
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key): if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
setattr(handle_deprecated_hide_window_decorations_aliases, 'key', True) setattr(handle_deprecated_hide_window_decorations_aliases, 'key', True)
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key)) log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
@ -552,7 +557,7 @@ def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
@deprecated_handler('macos_show_window_title_in_menubar') @deprecated_handler('macos_show_window_title_in_menubar')
def handle_deprecated_macos_show_window_title_in_menubar_alias(key, val, ans): def handle_deprecated_macos_show_window_title_in_menubar_alias(key: str, val: str, ans: Dict[str, Any]) -> None:
if not hasattr(handle_deprecated_macos_show_window_title_in_menubar_alias, key): if not hasattr(handle_deprecated_macos_show_window_title_in_menubar_alias, key):
setattr(handle_deprecated_macos_show_window_title_in_menubar_alias, 'key', True) setattr(handle_deprecated_macos_show_window_title_in_menubar_alias, 'key', True)
log_error('The option {} is deprecated. Use macos_show_window_title_in menubar instead.'.format(key)) log_error('The option {} is deprecated. Use macos_show_window_title_in menubar instead.'.format(key))
@ -570,9 +575,9 @@ def handle_deprecated_macos_show_window_title_in_menubar_alias(key, val, ans):
ans['macos_show_window_title_in'] = macos_show_window_title_in ans['macos_show_window_title_in'] = macos_show_window_title_in
def expandvars(val, env): def expandvars(val: str, env: Dict[str, str]) -> str:
def sub(m): def sub(m: Match) -> str:
key = m.group(1) key = m.group(1)
result = env.get(key) result = env.get(key)
if result is None: if result is None:
@ -585,25 +590,25 @@ def expandvars(val, env):
@special_handler @special_handler
def handle_env(key, val, ans): def handle_env(key: str, val: str, ans: Dict[str, Any]) -> None:
key, val = val.partition('=')[::2] key, val = val.partition('=')[::2]
key, val = key.strip(), val.strip() key, val = key.strip(), val.strip()
ans['env'][key] = expandvars(val, ans['env']) ans['env'][key] = expandvars(val, ans['env'])
def special_handling(key, val, ans): def special_handling(key: str, val: str, ans: Dict[str, Any]) -> bool:
func = special_handlers.get(key) func = special_handlers.get(key)
if func is not None: if func is not None:
func(key, val, ans) func(key, val, ans)
return True return True
def option_names_for_completion(): def option_names_for_completion() -> Generator[str, None, None]:
yield from defaults yield from defaults
yield from special_handlers yield from special_handlers
def parse_config(lines: Iterable[str], check_keys=True, accumulate_bad_lines: Optional[List[BadLine]] = None): def parse_config(lines: Iterable[str], check_keys: bool = True, accumulate_bad_lines: Optional[List[BadLine]] = None) -> Dict[str, Any]:
ans: Dict[str, Any] = { ans: Dict[str, Any] = {
'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [],
'env': {}, 'kitten_aliases': {}, 'font_features': {} 'env': {}, 'kitten_aliases': {}, 'font_features': {}
@ -624,7 +629,7 @@ def parse_config(lines: Iterable[str], check_keys=True, accumulate_bad_lines: Op
return ans return ans
def parse_defaults(lines, check_keys=False): def parse_defaults(lines: Iterable[str], check_keys: bool = False) -> Dict[str, Any]:
ans = parse_config(lines, check_keys) ans = parse_config(lines, check_keys)
return ans return ans
@ -652,18 +657,18 @@ def merge_configs(defaults: Dict, vals: Dict) -> Dict:
return ans return ans
def build_ansi_color_table(opts=defaults): def build_ansi_color_table(opts: OptionsStub = defaults) -> List[int]:
def as_int(x): def as_int(x: Tuple[int, int, int]) -> int:
return (x[0] << 16) | (x[1] << 8) | x[2] return (x[0] << 16) | (x[1] << 8) | x[2]
def col(i): def col(i: int) -> int:
return as_int(getattr(opts, 'color{}'.format(i))) return as_int(getattr(opts, 'color{}'.format(i)))
return list(map(col, range(256))) return list(map(col, range(256)))
def atomic_save(data, path): def atomic_save(data: bytes, path: str) -> None:
import tempfile import tempfile
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp') fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
try: try:
@ -681,7 +686,7 @@ def atomic_save(data, path):
@contextmanager @contextmanager
def cached_values_for(name): def cached_values_for(name: str) -> Generator[Dict, None, None]:
cached_path = os.path.join(cache_dir(), name + '.json') cached_path = os.path.join(cache_dir(), name + '.json')
cached_values: Dict = {} cached_values: Dict = {}
try: try:
@ -703,14 +708,14 @@ def cached_values_for(name):
err)) err))
def initial_window_size_func(opts, cached_values): def initial_window_size_func(opts: OptionsStub, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
if 'window-size' in cached_values and opts.remember_window_size: if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size'] ws = cached_values['window-size']
try: try:
w, h = map(int, ws) w, h = map(int, ws)
def initial_window_size(*a): def initial_window_size(*a: Any) -> Tuple[int, int]:
return w, h return w, h
return initial_window_size return initial_window_size
except Exception: except Exception:
@ -719,7 +724,7 @@ def initial_window_size_func(opts, cached_values):
w, w_unit = opts.initial_window_width w, w_unit = opts.initial_window_width
h, h_unit = opts.initial_window_height h, h_unit = opts.initial_window_height
def get_window_size(cell_width, cell_height, dpi_x, dpi_y, xscale, yscale): 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: if not is_macos:
# scaling is not needed on Wayland, but is needed on macOS. Not # scaling is not needed on Wayland, but is needed on macOS. Not
# sure about X11. # sure about X11.
@ -732,12 +737,12 @@ def initial_window_size_func(opts, cached_values):
height = cell_height * h / yscale + (dpi_y / 72) * (opts.window_margin_width + opts.window_padding_width) + 1 height = cell_height * h / yscale + (dpi_y / 72) * (opts.window_margin_width + opts.window_padding_width) + 1
else: else:
height = h height = h
return width, height return int(width), int(height)
return get_window_size return get_window_size
def commented_out_default_config(): def commented_out_default_config() -> str:
ans = [] ans = []
for line in as_conf_file(all_options.values()): for line in as_conf_file(all_options.values()):
if line and line[0] != '#': if line and line[0] != '#':
@ -746,7 +751,7 @@ def commented_out_default_config():
return '\n'.join(ans) return '\n'.join(ans)
def prepare_config_file_for_editing(): def prepare_config_file_for_editing() -> str:
if not os.path.exists(defconf): if not os.path.exists(defconf):
d = os.path.dirname(defconf) d = os.path.dirname(defconf)
with suppress(FileExistsError): with suppress(FileExistsError):

View File

@ -27,7 +27,7 @@ warn_unused_configs = True
check_untyped_defs = True check_untyped_defs = True
# disallow_untyped_defs = True # disallow_untyped_defs = True
[mypy-kitty.rc.*,kitty.conf.*,kitty.fonts.*,kitty.launch,kitty.child] [mypy-kitty.rc.*,kitty.conf.*,kitty.fonts.*,kitty.launch,kitty.child,kitty.cli,kitty.config]
disallow_untyped_defs = True disallow_untyped_defs = True
[mypy-conf] [mypy-conf]