diff --git a/docs/changelog.rst b/docs/changelog.rst index fb6bca90a..8ac3aff00 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -114,6 +114,9 @@ To update |kitty|, :doc:`follow the instructions `. - Use negative values for :opt:`mouse_hide_wait` to hide the mouse cursor immediately when pressing a key (:iss:`1534`) +- When encountering errors in :file:`kitty.conf` report them to the user + instead of failing to start. + 0.13.3 [2019-01-19] ------------------------------ diff --git a/kitty/boss.py b/kitty/boss.py index 539b08100..d44f5f92a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -742,7 +742,7 @@ class Boss: self._run_kitten('ask', args) def show_error(self, title, msg): - self._run_kitten('show_error', ['--title', title], input_data=msg) + self._run_kitten('show_error', args=['--title', title], input_data=msg) def do_set_tab_title(self, title, tab_id): tm = self.active_tab_manager @@ -1068,3 +1068,11 @@ class Boss: dbus_notification_activated(*args) else: dbus_notification_created(*args) + + def show_bad_config_lines(self, bad_lines): + + def format_bad_line(bad_line): + return '{}:{} in line: {}\n'.format(bad_line.number, bad_line.exception, bad_line.line) + + msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip() + self.show_error(_('Errors in kitty.conf'), msg) diff --git a/kitty/cli.py b/kitty/cli.py index d09be597d..f361180d9 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -710,7 +710,7 @@ def compare_opts(opts): compare_keymaps(final, initial) -def create_opts(args, debug_config=False): +def create_opts(args, debug_config=False, accumulate_bad_lines=None): from .config import load_config config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config)) if debug_config: @@ -729,7 +729,7 @@ def create_opts(args, debug_config=False): if config: print(green('Loaded config files:'), ', '.join(config)) overrides = (a.replace('=', ' ', 1) for a in args.override or ()) - opts = load_config(*config, overrides=overrides) + opts = load_config(*config, overrides=overrides, accumulate_bad_lines=accumulate_bad_lines) if debug_config: compare_opts(opts) return opts diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index f7838fb57..af3e4ea70 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -5,11 +5,13 @@ import os import re import shlex +from collections import namedtuple from ..rgb import to_color as as_color from ..utils import log_error key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') +BadLine = namedtuple('BadLine', 'number line exception') def to_color(x): @@ -90,22 +92,28 @@ def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_in ans[key] = val -def _parse(lines, type_map, special_handling, ans, all_keys): +def _parse(lines, type_map, special_handling, ans, all_keys, accumulate_bad_lines=None): name = getattr(lines, 'name', None) if name: base_path_for_includes = os.path.dirname(os.path.abspath(name)) else: from ..constants import config_dir base_path_for_includes = config_dir - for line in lines: - parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes) + for i, line in enumerate(lines): + try: + parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes) + except Exception as e: + if accumulate_bad_lines is None: + raise + accumulate_bad_lines.append(BadLine(i + 1, line.rstrip(), e)) def parse_config_base( - lines, defaults, type_map, special_handling, ans, check_keys=True + lines, defaults, type_map, special_handling, ans, check_keys=True, + accumulate_bad_lines=None ): all_keys = defaults._asdict() if check_keys else None - _parse(lines, type_map, special_handling, ans, all_keys) + _parse(lines, type_map, special_handling, ans, all_keys, accumulate_bad_lines) def create_options_class(keys): diff --git a/kitty/config.py b/kitty/config.py index d1c41fdfb..2eddf2096 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -8,6 +8,7 @@ import re import sys from collections import namedtuple from contextlib import contextmanager +from functools import partial from . import fast_data_types as defines from .conf.definition import as_conf_file, config_lines @@ -439,7 +440,7 @@ def option_names_for_completion(): yield from special_handlers -def parse_config(lines, check_keys=True): +def parse_config(lines, check_keys=True, accumulate_bad_lines=None): ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}} parse_config_base( lines, @@ -447,7 +448,8 @@ def parse_config(lines, check_keys=True): type_map, special_handling, ans, - check_keys=check_keys + check_keys=check_keys, + accumulate_bad_lines=accumulate_bad_lines ) return ans @@ -617,8 +619,11 @@ def finalize_keys(opts): opts.sequence_map = sequence_map -def load_config(*paths, overrides=None): - opts = _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides) +def load_config(*paths, overrides=None, accumulate_bad_lines=None): + parser = parse_config + if accumulate_bad_lines is not None: + parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines) + opts = _load_config(Options, defaults, parser, merge_configs, *paths, overrides=overrides) finalize_keys(opts) if opts.background_opacity < 1.0 and opts.macos_titlebar_color: log_error('Cannot use both macos_titlebar_color and background_opacity') diff --git a/kitty/main.py b/kitty/main.py index ce2928cc6..835d1fda0 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -115,7 +115,7 @@ def get_new_os_window_trigger(opts): return new_os_window_trigger -def _run_app(opts, args): +def _run_app(opts, args, bad_lines=()): new_os_window_trigger = get_new_os_window_trigger(opts) if is_macos and opts.macos_custom_beam_cursor: set_custom_ibeam_cursor() @@ -132,18 +132,20 @@ def _run_app(opts, args): args.cls or appname, load_all_shaders) boss = Boss(window_id, opts, args, cached_values, new_os_window_trigger) boss.start() + if bad_lines: + boss.show_bad_config_lines(bad_lines) try: boss.child_monitor.main_loop() finally: boss.destroy() -def run_app(opts, args): +def run_app(opts, args, bad_lines=()): set_scale(opts.box_drawing_scale) set_options(opts, is_wayland, args.debug_gl, args.debug_font_fallback) set_font_family(opts, debug_font_matching=args.debug_font_fallback) try: - _run_app(opts, args) + _run_app(opts, args, bad_lines) finally: free_font_data() # must free font data before glfw/freetype/fontconfig/opengl etc are finalized @@ -262,12 +264,13 @@ def _main(): talk_to_instance(args) return init_glfw(args.debug_keyboard) # needed for parsing native keysyms - opts = create_opts(args) + bad_lines = [] + opts = create_opts(args, accumulate_bad_lines=bad_lines) setup_environment(opts, args) try: with setup_profiling(args): # Avoid needing to launch threads to reap zombies - run_app(opts, args) + run_app(opts, args, bad_lines) finally: glfw_terminate()