From f178dff4e08a4e6abfcc7150859f46ee4442e39b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 May 2021 11:25:39 +0530 Subject: [PATCH] Remove the special_types kludge for is_multiple options --- kitty/conf/definition.py | 9 +++--- kitty/config.py | 64 +++++++--------------------------------- kitty/config_data.py | 18 +++++------ kitty/options_stub.py | 4 --- kitty/options_types.py | 57 ++++++++++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/kitty/conf/definition.py b/kitty/conf/definition.py index cd9140f47..68b6863e1 100644 --- a/kitty/conf/definition.py +++ b/kitty/conf/definition.py @@ -40,8 +40,6 @@ class Option: ans = x.__name__ if x.__module__ and x.__module__ != 'builtins': imports.add((x.__module__, x.__name__)) - if self.is_multiple: - ans = 'typing.Dict[str, str]' return ans def option_type_as_str(x: Any) -> str: @@ -49,6 +47,9 @@ class Option: return type_name(x) ans = repr(x) ans = ans.replace('NoneType', 'None') + if self.is_multiple: + ans = ans[ans.index('[') + 1:-1] + ans = ans.replace('Tuple', 'Dict', 1) return ans if type(self.option_type) is type: @@ -354,18 +355,16 @@ def config_lines( def as_type_stub( all_options: Dict[str, OptionOrAction], - special_types: Optional[Dict[str, str]] = None, preamble_lines: Union[Tuple[str, ...], List[str], Iterable[str]] = (), extra_fields: Union[Tuple[Tuple[str, str], ...], List[Tuple[str, str]], Iterable[Tuple[str, str]]] = (), class_name: str = 'Options' ) -> str: ans = ['import typing\n'] + list(preamble_lines) + ['', 'class {}:'.format(class_name)] imports: Set[Tuple[str, str]] = set() - overrides = special_types or {} for name, val in all_options.items(): if isinstance(val, Option): field_name = name.partition(' ')[0] - ans.append(' {}: {}'.format(field_name, overrides.get(field_name, val.type_definition(imports)))) + ans.append(' {}: {}'.format(field_name, val.type_definition(imports))) for mod, name in imports: ans.insert(0, 'from {} import {}'.format(mod, name)) ans.insert(0, 'import {}'.format(mod)) diff --git a/kitty/config.py b/kitty/config.py index a9d7baa41..3c55fb69a 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -5,7 +5,6 @@ import json import os import re -import sys from contextlib import contextmanager, suppress from functools import partial from typing import ( @@ -21,12 +20,13 @@ from .conf.utils import ( ) from .config_data import all_options from .constants import cache_dir, defconf, is_macos -from .fonts import FontFeature from .options_stub import Options as OptionsStub -from .options_types import InvalidMods, parse_mods, parse_shortcut +from .options_types import ( + InvalidMods, env, font_features, parse_mods, parse_shortcut, symbol_map +) from .types import MouseEvent, SingleKey from .typing import TypedDict -from .utils import expandvars, log_error +from .utils import log_error KeyMap = Dict[SingleKey, 'KeyAction'] MouseMap = Dict[MouseEvent, 'KeyAction'] @@ -511,37 +511,6 @@ def parse_mouse_action(val: str, mouse_mappings: List[MouseMapping]) -> None: mouse_mappings.append(MouseMapping(button, mods, count, mode == 'grabbed', paction)) -def parse_symbol_map(val: str) -> Dict[Tuple[int, int], str]: - parts = val.split() - symbol_map: Dict[Tuple[int, int], str] = {} - - def abort() -> Dict[Tuple[int, int], str]: - log_error('Symbol map: {} is invalid, ignoring'.format( - val)) - return {} - - if len(parts) < 2: - return abort() - family = ' '.join(parts[1:]) - - def to_chr(x: str) -> int: - if not x.startswith('U+'): - raise ValueError() - return int(x[2:], 16) - - for x in parts[0].split(','): - a_, b_ = x.partition('-')[::2] - b_ = b_ or a_ - try: - a, b = map(to_chr, (a_, b_)) - except Exception: - return abort() - if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1: - return abort() - symbol_map[(a, b)] = family - return symbol_map - - def parse_send_text_bytes(text: str) -> bytes: return python_string(text).encode('utf-8') @@ -590,26 +559,14 @@ def handle_mouse_map(key: str, val: str, ans: Dict[str, Any]) -> None: @special_handler def handle_symbol_map(key: str, val: str, ans: Dict[str, Any]) -> None: - ans['symbol_map'].update(parse_symbol_map(val)) + for k, v in symbol_map(val): + ans['symbol_map'][k] = v @special_handler def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> None: - if val != 'none': - parts = val.split() - if len(parts) < 2: - log_error("Ignoring invalid font_features {}".format(val)) - else: - features = [] - for feat in parts[1:]: - try: - parsed = defines.parse_font_feature(feat) - except ValueError: - log_error('Ignoring invalid font feature: {}'.format(feat)) - else: - features.append(FontFeature(feat, parsed)) - if features: - ans['font_features'][parts[0]] = tuple(features) + for key, features in font_features(val): + ans['font_features'][key] = features @special_handler @@ -662,9 +619,8 @@ def handle_deprecated_macos_show_window_title_in_menubar_alias(key: str, val: st @special_handler def handle_env(key: str, val: str, ans: Dict[str, Any]) -> None: - key, val = val.partition('=')[::2] - key, val = key.strip(), val.strip() - ans['env'][key] = expandvars(val, ans['env']) + for key, val in env(val, ans['env']): + ans['env'][key] = val def special_handling(key: str, val: str, ans: Dict[str, Any]) -> bool: diff --git a/kitty/config_data.py b/kitty/config_data.py index bc9b0b508..128eec3ad 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -17,17 +17,16 @@ from .options_types import ( active_tab_title_template, adjust_line_height, allow_hyperlinks, allow_remote_control, box_drawing_scale, clipboard_control, config_or_absolute_path, copy_on_select, cursor_text_color, - default_tab_separator, disable_ligatures, edge_width, + default_tab_separator, disable_ligatures, edge_width, env, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, optional_edge_width, resize_draw_strategy, scrollback_lines, - scrollback_pager_history_size, tab_activity_symbol, tab_bar_edge, - tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, + scrollback_pager_history_size, symbol_map, tab_activity_symbol, + tab_bar_edge, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, to_cursor_shape, to_font_size, to_layout_names, - url_prefixes, url_style, window_border_width, window_size, to_modifiers + to_modifiers, url_prefixes, url_style, window_border_width, window_size ) from .rgb import color_as_sharp, color_from_int - # }}} # Groups {{{ @@ -263,7 +262,7 @@ o('adjust_column_width', 0, option_type=adjust_line_height) o( '+symbol_map', 'U+E0A0-U+E0A3,U+E0C0-U+E0C7 PowerlineSymbols', - add_to_default=False, + add_to_default=False, option_type=symbol_map, long_text=_(''' Map the specified unicode codepoints to a particular font. Useful if you need special rendering for some symbols, such as for Powerline. Avoids the need for @@ -294,7 +293,7 @@ Note that this refers to programming ligatures, typically implemented using the :opt:`font_features` setting. ''')) -o('+font_features', 'none', add_to_default=False, long_text=_(''' +o('+font_features', 'none', add_to_default=False, option_type=font_features, long_text=_(''' Choose exactly which OpenType features to enable or disable. This is useful as some fonts might have features worthwhile in a terminal. For example, Fira Code Retina includes a discretionary feature, :code:`zero`, which in that font @@ -920,10 +919,7 @@ you also set :opt:`allow_remote_control` to enable remote control. See the help for :option:`kitty --listen-on` for more details. ''')) -o( - '+env', '', - add_to_default=False, - long_text=_(''' +o('+env', '', add_to_default=False, option_type=env, long_text=_(''' Specify environment variables to set in all child processes. Note that environment variables are expanded recursively, so if you use:: diff --git a/kitty/options_stub.py b/kitty/options_stub.py index 5e9f40c46..886cb5922 100644 --- a/kitty/options_stub.py +++ b/kitty/options_stub.py @@ -15,10 +15,6 @@ def generate_stub(): from .conf.definition import as_type_stub, save_type_stub text = as_type_stub( all_options, - special_types={ - 'symbol_map': 'typing.Dict[typing.Tuple[int, int], str]', - 'font_features': 'typing.Dict[str, typing.Tuple[FontFeature, ...]]' - }, preamble_lines=( 'from kitty.types import SingleKey', 'from kitty.config import KeyAction, KeyMap, SequenceMap, MouseMap', diff --git a/kitty/options_types.py b/kitty/options_types.py index 77b60ff53..e62de5f85 100644 --- a/kitty/options_types.py +++ b/kitty/options_types.py @@ -4,6 +4,7 @@ import os +import sys from typing import ( Callable, Dict, FrozenSet, Iterable, List, Optional, Tuple, Union ) @@ -15,6 +16,7 @@ from .conf.utils import ( positive_float, positive_int, to_bool, to_color, uniq, unit_float ) from .constants import config_dir +from .fonts import FontFeature from .key_names import ( character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup @@ -22,7 +24,7 @@ from .key_names import ( from .layout.interface import all_layouts from .rgb import Color, color_as_int from .types import FloatEdges, SingleKey -from .utils import log_error +from .utils import expandvars, log_error MINIMUM_FONT_SIZE = 4 default_tab_separator = ' ┇' @@ -345,3 +347,56 @@ def macos_option_as_alt(x: str) -> int: if to_bool(x): return 0b11 return 0 + + +def font_features(val: str) -> Iterable[Tuple[str, Tuple[FontFeature, ...]]]: + if val == 'none': + return + parts = val.split() + if len(parts) < 2: + log_error("Ignoring invalid font_features {}".format(val)) + return + if parts[0]: + features = [] + for feat in parts[1:]: + try: + parsed = defines.parse_font_feature(feat) + except ValueError: + log_error('Ignoring invalid font feature: {}'.format(feat)) + else: + features.append(FontFeature(feat, parsed)) + yield parts[0], tuple(features) + + +def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]: + key, val = val.partition('=')[::2] + key, val = key.strip(), val.strip() + if key: + yield key, expandvars(val, current_val) + + +def symbol_map(val: str) -> Iterable[Tuple[Tuple[int, int], str]]: + parts = val.split() + + def abort() -> Dict[Tuple[int, int], str]: + log_error(f'Symbol map: {val} is invalid, ignoring') + + if len(parts) < 2: + return abort() + family = ' '.join(parts[1:]) + + def to_chr(x: str) -> int: + if not x.startswith('U+'): + raise ValueError() + return int(x[2:], 16) + + for x in parts[0].split(','): + a_, b_ = x.partition('-')[::2] + b_ = b_ or a_ + try: + a, b = map(to_chr, (a_, b_)) + except Exception: + return abort() + if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1: + return abort() + yield (a, b), family