From 4a71afaf963a40921445f11b068e3f7ef2bd5821 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2021 14:27:24 +0530 Subject: [PATCH] Get rid of --debug-config Instead have a keybind that shows the configuration used by the currently running kitty instance --- .github/ISSUE_TEMPLATE/bug_report.md | 7 +- docs/basic.rst | 1 + kitty/boss.py | 39 ++++++-- kitty/cli.py | 142 ++------------------------ kitty/debug_config.py | 143 +++++++++++++++++++++++++++ kitty/main.py | 3 - kitty/options/definition.py | 3 + kitty/options/types.py | 2 + kitty/window.py | 25 +---- 9 files changed, 195 insertions(+), 170 deletions(-) create mode 100644 kitty/debug_config.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 39207828b..27e9726e3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,10 +21,11 @@ Steps to reproduce the behavior: If applicable, add screenshots to help explain your problem. **Environment details** -OS: Name and version of operating system(s) - ``` -Output of kitty --debug-config +Press Ctrl+Shift+F6 in kitty, to copy debug output about kitty and its +configuration to the clipboard and paste it here. + +On older versions of kitty, run kitty --debug-config instead ``` **Additional context** diff --git a/docs/basic.rst b/docs/basic.rst index 2a34548b7..e7b5acab4 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -116,6 +116,7 @@ Toggle maximized :sc:`toggle_maximized` Input unicode character :sc:`input_unicode_character` (also :kbd:`^+⌘+space` on macOS) Click URL using the keyboard :sc:`open_url` Reset the terminal :sc:`reset_terminal` +Debug :file:`kitty.conf` :sc:`debug_config` Pass current selection to program :sc:`pass_selection_to_program` Edit |kitty| config file :sc:`edit_config_file` Open a |kitty| shell :sc:`kitty_shell` diff --git a/kitty/boss.py b/kitty/boss.py index baba94c5c..57dd10b57 100755 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -50,8 +50,8 @@ from .typing import PopenType, TypedDict from .utils import ( func_name, get_editor, get_primary_selection, is_path_in_temp_dir, log_error, open_url, parse_address_spec, parse_uri_list, - platform_window_id, remove_socket_file, safe_print, set_primary_selection, - single_instance, startup_notification_handler + platform_window_id, read_shell_environment, remove_socket_file, safe_print, + set_primary_selection, single_instance, startup_notification_handler ) from .window import MatchPatternType, Window @@ -874,11 +874,29 @@ class Boss: s.shutdown(socket.SHUT_RDWR) s.close() - def display_scrollback(self, window: Window, data: Optional[bytes], cmd: List[str]) -> None: + def display_scrollback(self, window: Window, data: Union[bytes, str], input_line_number: int = 0, title: str = '') -> None: + def prepare_arg(x: str) -> str: + x = x.replace('INPUT_LINE_NUMBER', str(input_line_number)) + x = x.replace('CURSOR_LINE', str(window.screen.cursor.y + 1)) + x = x.replace('CURSOR_COLUMN', str(window.screen.cursor.x + 1)) + return x + + cmd = list(map(prepare_arg, get_options().scrollback_pager)) + if not os.path.isabs(cmd[0]): + import shutil + exe = shutil.which(cmd[0]) + if not exe: + env = read_shell_environment(get_options()) + if env and 'PATH' in env: + exe = shutil.which(cmd[0], path=env['PATH']) + if exe: + cmd[0] = exe + tab = self.active_tab if tab is not None: + bdata = data.encode('utf-8') if isinstance(data, str) else data tab.new_special_window( - SpecialWindow(cmd, data, _('History'), overlay_for=window.id, cwd=window.cwd_of_child), + SpecialWindow(cmd, bdata, title or _('History'), overlay_for=window.id, cwd=window.cwd_of_child), copy_colors_from=self.active_window ) @@ -1677,8 +1695,8 @@ class Boss: def show_kitty_env_vars(self) -> None: w = self.active_window if w: - output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()).encode('utf-8') - self.display_scrollback(w, output, ['less']) + output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()) + self.display_scrollback(w, output, title=_('Current kitty env vars')) def open_file(self, path: str) -> None: if path == ":cocoa::application launched::": @@ -1697,3 +1715,12 @@ class Boss: self.new_window(path) if w is not None: tab.remove_window(w) + + def debug_config(self) -> None: + from .debug_config import debug_config + w = self.active_window + if w is not None: + output = debug_config(get_options()) + set_clipboard_string(re.sub(r'\x1b.+?m', '', output)) + output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m' + self.display_scrollback(w, output, title=_('Current kitty options')) diff --git a/kitty/cli.py b/kitty/cli.py index c63c59af7..af565ed06 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -2,22 +2,19 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal -import os import re import sys from collections import deque from typing import ( - Any, Callable, Dict, FrozenSet, Generator, Iterable, Iterator, List, Match, - Optional, Sequence, Set, Tuple, Type, TypeVar, Union, cast + Any, Callable, Dict, FrozenSet, Iterator, List, Match, Optional, Sequence, + Tuple, Type, TypeVar, Union, cast ) from .cli_stub import CLIOptions -from .conf.utils import KeyAction, resolve_config -from .constants import appname, defconf, is_macos, is_wayland, str_version -from .options.types import Options as KittyOpts, defaults -from .options.utils import MouseMap -from .types import MouseEvent, SingleKey -from .typing import BadLineType, SequenceMap, TypedDict +from .conf.utils import resolve_config +from .constants import appname, defconf, is_macos, str_version +from .options.types import Options as KittyOpts +from .typing import BadLineType, TypedDict class OptionDict(TypedDict): @@ -701,13 +698,6 @@ type=bool-set Print out information about the selection of fallback fonts for characters not present in the main font. ---debug-config -type=bool-set -Print out information about the system and kitty configuration. Note that this only -reads the standard kitty.conf not any extra configuration or alternative conf files -that were specified on the command line. - - --execute -e type=bool-set ! @@ -759,131 +749,13 @@ def parse_args( SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf' -ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction] -def mod_to_names(mods: int) -> Generator[str, None, None]: - from .fast_data_types import ( - GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER, - GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER - ) - modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER, - 'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK} - for name, val in modmap.items(): - if mods & val: - yield name - - -def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None: - from .fast_data_types import glfw_get_key_name - keys = [] - for key_spec in key_sequence: - names = [] - mods, is_native, key = key_spec - names = list(mod_to_names(mods)) - if key: - kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0) - names.append(kname or f'{key}') - keys.append('+'.join(names)) - - print('\t', ' > '.join(keys), action) - - -def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]]) -> None: - if changes: - print(title(text)) - - for k in sorted(changes): - print_shortcut(k, defns[k]) - - -def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None: - added = set(final) - set(initial) - removed = set(initial) - set(final) - changed = {k for k in set(final) & set(initial) if final[k] != initial[k]} - print_shortcut_changes(final, 'Added shortcuts:', added) - print_shortcut_changes(initial, 'Removed shortcuts:', removed) - print_shortcut_changes(final, 'Changed shortcuts:', changed) - - -def flatten_sequence_map(m: SequenceMap) -> ShortcutMap: - ans: Dict[Tuple[SingleKey, ...], KeyAction] = {} - for key_spec, rest_map in m.items(): - for r, action in rest_map.items(): - ans[(key_spec,) + (r)] = action - return ans - - -def compare_mousemaps(final: MouseMap, initial: MouseMap) -> None: - added = set(final) - set(initial) - removed = set(initial) - set(final) - changed = {k for k in set(final) & set(initial) if final[k] != initial[k]} - - def print_mouse_action(trigger: MouseEvent, action: KeyAction) -> None: - names = list(mod_to_names(trigger.mods)) + [f'b{trigger.button+1}'] - when = {-1: 'repeat', 1: 'press', 2: 'doublepress', 3: 'triplepress'}.get(trigger.repeat_count, trigger.repeat_count) - grabbed = 'grabbed' if trigger.grabbed else 'ungrabbed' - print('\t', '+'.join(names), when, grabbed, action) - - def print_changes(defns: MouseMap, changes: Set[MouseEvent], text: str) -> None: - if changes: - print(title(text)) - for k in sorted(changes): - print_mouse_action(k, defns[k]) - - print_changes(final, added, 'Added mouse actions:') - print_changes(initial, removed, 'Removed mouse actions:') - print_changes(final, changed, 'Changed mouse actions:') - - -def compare_opts(opts: KittyOpts) -> None: - from .config import load_config - print('\nConfig options different from defaults:') - default_opts = load_config() - ignored = ('keymap', 'sequence_map', 'mousemap', 'map', 'mouse_map') - changed_opts = [ - f for f in sorted(defaults._fields) - if f not in ignored and getattr(opts, f) != getattr(defaults, f) - ] - field_len = max(map(len, changed_opts)) if changed_opts else 20 - fmt = '{{:{:d}s}}'.format(field_len) - for f in changed_opts: - print(title(fmt.format(f)), getattr(opts, f)) - - compare_mousemaps(opts.mousemap, default_opts.mousemap) - final_, initial_ = opts.keymap, default_opts.keymap - final: ShortcutMap = {(k,): v for k, v in final_.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.update(final_s) - initial.update(initial_s) - compare_keymaps(final, initial) - - -def create_opts(args: CLIOptions, debug_config: bool = False, accumulate_bad_lines: Optional[List[BadLineType]] = None) -> KittyOpts: +def create_opts(args: CLIOptions, accumulate_bad_lines: Optional[List[BadLineType]] = None) -> KittyOpts: from .config import load_config config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config)) - if debug_config: - print(version(add_rev=True)) - print(' '.join(os.uname())) - if is_macos: - import subprocess - print(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip()) - if os.path.exists('/etc/issue'): - with open('/etc/issue', encoding='utf-8', errors='replace') as f: - print(f.read().strip()) - if os.path.exists('/etc/lsb-release'): - with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f: - print(f.read().strip()) - config = tuple(x for x in config if os.path.exists(x)) - 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, accumulate_bad_lines=accumulate_bad_lines) - if debug_config: - if not is_macos: - print('Running under:', green('Wayland' if is_wayland(opts) else 'X11')) - compare_opts(opts) return opts diff --git a/kitty/debug_config.py b/kitty/debug_config.py new file mode 100644 index 000000000..3e45d41ca --- /dev/null +++ b/kitty/debug_config.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2021, Kovid Goyal + +import os +from functools import partial +from typing import Callable, Dict, Generator, Iterable, Set, Tuple + +from .cli import green, title, version +from .conf.utils import KeyAction +from .constants import is_macos, is_wayland +from .options.types import Options as KittyOpts, defaults +from .options.utils import MouseMap +from .types import MouseEvent, SingleKey +from .typing import SequenceMap + +ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction] + + +def mod_to_names(mods: int) -> Generator[str, None, None]: + from .fast_data_types import ( + GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER, + GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER + ) + modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER, + 'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK} + for name, val in modmap.items(): + if mods & val: + yield name + + +def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction, print: Callable) -> None: + from .fast_data_types import glfw_get_key_name + keys = [] + for key_spec in key_sequence: + names = [] + mods, is_native, key = key_spec + names = list(mod_to_names(mods)) + if key: + kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0) + names.append(kname or f'{key}') + keys.append('+'.join(names)) + + print('\t', ' > '.join(keys), action) + + +def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]], print: Callable) -> None: + if changes: + print(title(text)) + + for k in sorted(changes): + print_shortcut(k, defns[k], print) + + +def compare_keymaps(final: ShortcutMap, initial: ShortcutMap, print: Callable) -> None: + added = set(final) - set(initial) + removed = set(initial) - set(final) + changed = {k for k in set(final) & set(initial) if final[k] != initial[k]} + print_shortcut_changes(final, 'Added shortcuts:', added, print) + print_shortcut_changes(initial, 'Removed shortcuts:', removed, print) + print_shortcut_changes(final, 'Changed shortcuts:', changed, print) + + +def flatten_sequence_map(m: SequenceMap) -> ShortcutMap: + ans: Dict[Tuple[SingleKey, ...], KeyAction] = {} + for key_spec, rest_map in m.items(): + for r, action in rest_map.items(): + ans[(key_spec,) + (r)] = action + return ans + + +def compare_mousemaps(final: MouseMap, initial: MouseMap, print: Callable) -> None: + added = set(final) - set(initial) + removed = set(initial) - set(final) + changed = {k for k in set(final) & set(initial) if final[k] != initial[k]} + + def print_mouse_action(trigger: MouseEvent, action: KeyAction) -> None: + names = list(mod_to_names(trigger.mods)) + [f'b{trigger.button+1}'] + when = {-1: 'repeat', 1: 'press', 2: 'doublepress', 3: 'triplepress'}.get(trigger.repeat_count, trigger.repeat_count) + grabbed = 'grabbed' if trigger.grabbed else 'ungrabbed' + print('\t', '+'.join(names), when, grabbed, action) + + def print_changes(defns: MouseMap, changes: Set[MouseEvent], text: str) -> None: + if changes: + print(title(text)) + for k in sorted(changes): + print_mouse_action(k, defns[k]) + + print_changes(final, added, 'Added mouse actions:') + print_changes(initial, removed, 'Removed mouse actions:') + print_changes(final, changed, 'Changed mouse actions:') + + +def compare_opts(opts: KittyOpts, print: Callable) -> None: + from .config import load_config + print() + print('Config options different from defaults:') + default_opts = load_config() + ignored = ('keymap', 'sequence_map', 'mousemap', 'map', 'mouse_map') + changed_opts = [ + f for f in sorted(defaults._fields) + if f not in ignored and getattr(opts, f) != getattr(defaults, f) + ] + field_len = max(map(len, changed_opts)) if changed_opts else 20 + fmt = '{{:{:d}s}}'.format(field_len) + for f in changed_opts: + print(title(fmt.format(f)), str(getattr(opts, f))) + + compare_mousemaps(opts.mousemap, default_opts.mousemap, print) + final_, initial_ = opts.keymap, default_opts.keymap + final: ShortcutMap = {(k,): v for k, v in final_.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.update(final_s) + initial.update(initial_s) + compare_keymaps(final, initial, print) + + +def debug_config(opts: KittyOpts) -> str: + from io import StringIO + out = StringIO() + p = partial(print, file=out) + p(version(add_rev=True)) + p(' '.join(os.uname())) + if is_macos: + import subprocess + p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip()) + if os.path.exists('/etc/issue'): + with open('/etc/issue', encoding='utf-8', errors='replace') as f: + p(f.read().strip()) + if os.path.exists('/etc/lsb-release'): + with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f: + p(f.read().strip()) + if not is_macos: + p('Running under:' + green('Wayland' if is_wayland() else 'X11')) + if opts.config_paths: + p(green('Loaded config files:')) + p(' ', '\n '.join(opts.config_paths)) + if opts.config_overrides: + p(green('Loaded config overrides:')) + p(' ', '\n '.join(opts.config_overrides)) + compare_opts(opts, p) + return out.getvalue() diff --git a/kitty/main.py b/kitty/main.py index 36ed93b0e..c8151a8a1 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -310,9 +310,6 @@ def _main() -> None: os.chdir(os.path.expanduser('~')) cli_opts, rest = parse_args(args=args, result_class=CLIOptions) cli_opts.args = rest - if cli_opts.debug_config: - create_opts(cli_opts, debug_config=True) - return if cli_opts.detach: if cli_opts.session == '-': from .session import PreReadSession diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 3a4eb0d99..13b822bdd 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -3280,6 +3280,9 @@ map('Reset the terminal', only="macos" ) +map('Debug kitty configuration', + 'debug_config kitty_mod+f6 debug_config' + ) map('Send arbitrary text on key presses', 'send_text ctrl+shift+alt+h send_text all Hello World', diff --git a/kitty/options/types.py b/kitty/options/types.py index 234106ccc..97a78ea80 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -804,6 +804,8 @@ defaults.map = [ KeyDefinition(True, KeyAction('set_background_opacity', ('default',)), 1024, False, 97, (SingleKey(mods=0, is_native=False, key=100),)), # reset_terminal KeyDefinition(False, KeyAction('clear_terminal', ('reset', True)), 1024, False, 57349, ()), + # debug_config + KeyDefinition(False, KeyAction('debug_config'), 1024, False, 57369, ()), ] if is_macos: defaults.map.append(KeyDefinition(False, KeyAction('copy_to_clipboard'), 8, False, 99, ())) diff --git a/kitty/window.py b/kitty/window.py index db36c597d..1c21c8fc6 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -42,8 +42,7 @@ from .types import MouseEvent, ScreenGeometry, WindowGeometry from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .utils import ( color_as_int, get_primary_selection, load_shaders, log_error, open_cmd, - open_url, parse_color_set, read_shell_environment, sanitize_title, - set_primary_selection + open_url, parse_color_set, sanitize_title, set_primary_selection ) MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]] @@ -902,27 +901,7 @@ class Window: def show_scrollback(self) -> None: text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True) data = self.pipe_data(text, has_wrap_markers=True) - - def prepare_arg(x: str) -> str: - x = x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) - x = x.replace('CURSOR_LINE', str(data['cursor_y'])) - x = x.replace('CURSOR_COLUMN', str(data['cursor_x'])) - return x - - cmd = list(map(prepare_arg, get_options().scrollback_pager)) - if not os.path.isabs(cmd[0]): - import shutil - exe = shutil.which(cmd[0]) - if not exe: - env = read_shell_environment(get_options()) - if env and 'PATH' in env: - exe = shutil.which(cmd[0], path=env['PATH']) - if exe: - cmd[0] = exe - bdata: Union[str, bytes, None] = data['text'] - if isinstance(bdata, str): - bdata = bdata.encode('utf-8') - get_boss().display_scrollback(self, bdata, cmd) + get_boss().display_scrollback(self, data['text'], data['input_line_number']) def paste_bytes(self, text: Union[str, bytes]) -> None: # paste raw bytes without any processing