mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-18 06:25:13 +02:00
Refactor configuration file parsing
Now the time for importing the kitty.config module has been halved, from 16ms from 32ms on my machine. Also, the new architecture will eventually allow for auto generating a bunch of python-to-C boilerplate code.
This commit is contained in:
@@ -3,21 +3,16 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
from typing import Any, Dict, FrozenSet, Iterable, Optional, Tuple, Type, Union
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.conf.definition import config_lines
|
||||
from kitty.conf.utils import (
|
||||
init_config as _init_config, key_func, load_config as _load_config,
|
||||
merge_dicts, parse_config_base, parse_kittens_key, resolve_config
|
||||
load_config as _load_config, parse_config_base, resolve_config
|
||||
)
|
||||
from kitty.constants import config_dir
|
||||
from kitty.options_stub import DiffOptions
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
from .config_data import all_options
|
||||
|
||||
defaults: Optional[DiffOptions] = None
|
||||
from .options.types import Options as DiffOptions, defaults
|
||||
|
||||
formats: Dict[str, str] = {
|
||||
'title': '',
|
||||
@@ -42,97 +37,27 @@ def set_formats(opts: DiffOptions) -> None:
|
||||
formats['added_highlight'] = '48' + color_as_sgr(opts.highlight_added_bg)
|
||||
|
||||
|
||||
func_with_args, args_funcs = key_func()
|
||||
|
||||
|
||||
@func_with_args('scroll_by')
|
||||
def parse_scroll_by(func: str, rest: str) -> Tuple[str, int]:
|
||||
try:
|
||||
return func, int(rest)
|
||||
except Exception:
|
||||
return func, 1
|
||||
|
||||
|
||||
@func_with_args('scroll_to')
|
||||
def parse_scroll_to(func: str, rest: str) -> Tuple[str, str]:
|
||||
rest = rest.lower()
|
||||
if rest not in {'start', 'end', 'next-change', 'prev-change', 'next-page', 'prev-page', 'next-match', 'prev-match'}:
|
||||
rest = 'start'
|
||||
return func, rest
|
||||
|
||||
|
||||
@func_with_args('change_context')
|
||||
def parse_change_context(func: str, rest: str) -> Tuple[str, Union[int, str]]:
|
||||
rest = rest.lower()
|
||||
if rest in {'all', 'default'}:
|
||||
return func, rest
|
||||
try:
|
||||
amount = int(rest)
|
||||
except Exception:
|
||||
amount = 5
|
||||
return func, amount
|
||||
|
||||
|
||||
@func_with_args('start_search')
|
||||
def parse_start_search(func: str, rest: str) -> Tuple[str, Tuple[bool, bool]]:
|
||||
rest_ = rest.lower().split()
|
||||
is_regex = bool(rest_ and rest_[0] == 'regex')
|
||||
is_backward = bool(len(rest_) > 1 and rest_[1] == 'backward')
|
||||
return func, (is_regex, is_backward)
|
||||
|
||||
|
||||
def special_handling(key: str, val: str, ans: Dict) -> bool:
|
||||
if key == 'map':
|
||||
x = parse_kittens_key(val, args_funcs)
|
||||
if x is not None:
|
||||
action, key_def = x
|
||||
ans['key_definitions'][key_def] = action
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def parse_config(lines: Iterable[str], check_keys: bool = True) -> Dict[str, Any]:
|
||||
ans: Dict[str, Any] = {'key_definitions': {}}
|
||||
defs: Optional[FrozenSet] = None
|
||||
if check_keys:
|
||||
defs = frozenset(defaults._fields) # type: ignore
|
||||
|
||||
parse_config_base(
|
||||
lines,
|
||||
defs,
|
||||
all_options,
|
||||
special_handling,
|
||||
ans,
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults: Dict, vals: Dict) -> Dict:
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
newvals = vals.get(k, {})
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
def parse_defaults(lines: Iterable[str], check_keys: bool = False) -> Dict[str, Any]:
|
||||
return parse_config(lines, check_keys)
|
||||
|
||||
|
||||
x = _init_config(config_lines(all_options), parse_defaults)
|
||||
Options: Type[DiffOptions] = x[0]
|
||||
defaults = x[1]
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
|
||||
defconf = os.path.join(config_dir, 'diff.conf')
|
||||
|
||||
|
||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> DiffOptions:
|
||||
return _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
from .options.parse import (
|
||||
create_result_dict, merge_result_dicts, parse_conf_item
|
||||
)
|
||||
|
||||
def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
|
||||
ans: Dict[str, Any] = create_result_dict()
|
||||
parse_config_base(
|
||||
lines,
|
||||
parse_conf_item,
|
||||
ans,
|
||||
)
|
||||
return ans
|
||||
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
|
||||
defconf = os.path.join(config_dir, 'diff.conf')
|
||||
opts_dict = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
|
||||
opts = DiffOptions(opts_dict)
|
||||
return opts
|
||||
|
||||
|
||||
def init_config(args: DiffCLIOptions) -> DiffOptions:
|
||||
@@ -140,4 +65,6 @@ def init_config(args: DiffCLIOptions) -> DiffOptions:
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
set_formats(opts)
|
||||
for (sc, action) in opts.map:
|
||||
opts.key_definitions[sc] = action
|
||||
return opts
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
# Utils {{{
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from typing import Dict
|
||||
|
||||
from kitty.conf.definition import OptionOrAction, option_func
|
||||
from kitty.conf.utils import (
|
||||
positive_int, python_string, to_color, to_color_or_none
|
||||
)
|
||||
|
||||
# }}}
|
||||
|
||||
all_options: Dict[str, OptionOrAction] = {}
|
||||
o, k, m, g, all_groups = option_func(all_options, {
|
||||
'colors': [_('Colors')],
|
||||
'diff': [_('Diffing'), ],
|
||||
'shortcuts': [_('Keyboard shortcuts')],
|
||||
})
|
||||
|
||||
|
||||
g('diff')
|
||||
|
||||
|
||||
def syntax_aliases(raw: str) -> Dict[str, str]:
|
||||
ans = {}
|
||||
for x in raw.split():
|
||||
a, b = x.partition(':')[::2]
|
||||
if a and b:
|
||||
ans[a.lower()] = b
|
||||
return ans
|
||||
|
||||
|
||||
o('syntax_aliases', 'pyj:py pyi:py recipe:py', option_type=syntax_aliases, long_text=_('''
|
||||
File extension aliases for syntax highlight
|
||||
For example, to syntax highlight :file:`file.xyz` as
|
||||
:file:`file.abc` use a setting of :code:`xyz:abc`
|
||||
'''))
|
||||
|
||||
o('num_context_lines', 3, option_type=positive_int, long_text=_('''
|
||||
The number of lines of context to show around each change.'''))
|
||||
|
||||
o('diff_cmd', 'auto', long_text=_('''
|
||||
The diff command to use. Must contain the placeholder :code:`_CONTEXT_`
|
||||
which will be replaced by the number of lines of context. The default
|
||||
is to search the system for either git or diff and use that, if found.
|
||||
'''))
|
||||
|
||||
o('replace_tab_by', r'\x20\x20\x20\x20', option_type=python_string, long_text=_('''
|
||||
The string to replace tabs with. Default is to use four spaces.'''))
|
||||
|
||||
|
||||
g('colors')
|
||||
|
||||
o('pygments_style', 'default', long_text=_('''
|
||||
The pygments color scheme to use for syntax highlighting.
|
||||
See :link:`pygments colors schemes <https://help.farbox.com/pygments.html>` for a list of schemes.'''))
|
||||
|
||||
|
||||
c = partial(o, option_type=to_color)
|
||||
c('foreground', 'black', long_text=_('Basic colors'))
|
||||
c('background', 'white')
|
||||
|
||||
c('title_fg', 'black', long_text=_('Title colors'))
|
||||
c('title_bg', 'white')
|
||||
|
||||
c('margin_bg', '#fafbfc', long_text=_('Margin colors'))
|
||||
c('margin_fg', '#aaaaaa')
|
||||
|
||||
c('removed_bg', '#ffeef0', long_text=_('Removed text backgrounds'))
|
||||
c('highlight_removed_bg', '#fdb8c0')
|
||||
c('removed_margin_bg', '#ffdce0')
|
||||
|
||||
c('added_bg', '#e6ffed', long_text=_('Added text backgrounds'))
|
||||
c('highlight_added_bg', '#acf2bd')
|
||||
c('added_margin_bg', '#cdffd8')
|
||||
|
||||
c('filler_bg', '#fafbfc', long_text=_('Filler (empty) line background'))
|
||||
c('margin_filler_bg', 'none', option_type=to_color_or_none, long_text=_(
|
||||
'Filler (empty) line background in margins, defaults to the filler background'))
|
||||
|
||||
c('hunk_margin_bg', '#dbedff', long_text=_('Hunk header colors'))
|
||||
c('hunk_bg', '#f1f8ff')
|
||||
|
||||
c('search_bg', '#444', long_text=_('Highlighting'))
|
||||
c('search_fg', 'white')
|
||||
c('select_bg', '#b4d5fe')
|
||||
o('select_fg', 'black', option_type=to_color_or_none)
|
||||
|
||||
g('shortcuts')
|
||||
k('quit', 'q', 'quit', _('Quit'))
|
||||
k('quit', 'esc', 'quit', _('Quit'))
|
||||
|
||||
k('scroll_down', 'j', 'scroll_by 1', _('Scroll down'))
|
||||
k('scroll_down', 'down', 'scroll_by 1', _('Scroll down'))
|
||||
k('scroll_up', 'k', 'scroll_by -1', _('Scroll up'))
|
||||
k('scroll_up', 'up', 'scroll_by -1', _('Scroll up'))
|
||||
|
||||
k('scroll_top', 'home', 'scroll_to start', _('Scroll to top'))
|
||||
k('scroll_bottom', 'end', 'scroll_to end', _('Scroll to bottom'))
|
||||
|
||||
k('scroll_page_down', 'page_down', 'scroll_to next-page', _('Scroll to next page'))
|
||||
k('scroll_page_down', 'space', 'scroll_to next-page', _('Scroll to next page'))
|
||||
k('scroll_page_up', 'page_up', 'scroll_to prev-page', _('Scroll to previous page'))
|
||||
|
||||
k('next_change', 'n', 'scroll_to next-change', _('Scroll to next change'))
|
||||
k('prev_change', 'p', 'scroll_to prev-change', _('Scroll to previous change'))
|
||||
|
||||
k('all_context', 'a', 'change_context all', _('Show all context'))
|
||||
k('default_context', '=', 'change_context default', _('Show default context'))
|
||||
k('increase_context', '+', 'change_context 5', _('Increase context'))
|
||||
k('decrease_context', '-', 'change_context -5', _('Decrease context'))
|
||||
|
||||
k('search_forward', '/', 'start_search regex forward', _('Search forward'))
|
||||
k('search_backward', '?', 'start_search regex backward', _('Search backward'))
|
||||
k('next_match', '.', 'scroll_to next-match', _('Scroll to next search match'))
|
||||
k('prev_match', ',', 'scroll_to prev-match', _('Scroll to previous search match'))
|
||||
k('next_match', '>', 'scroll_to next-match', _('Scroll to next search match'))
|
||||
k('prev_match', '<', 'scroll_to prev-match', _('Scroll to previous search match'))
|
||||
k('search_forward_simple', 'f', 'start_search substring forward', _('Search forward (no regex)'))
|
||||
k('search_backward_simple', 'b', 'start_search substring backward', _('Search backward (no regex)'))
|
||||
@@ -159,13 +159,12 @@ def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str
|
||||
|
||||
|
||||
def main() -> None:
|
||||
from .config import defaults
|
||||
# kitty +runpy "from kittens.diff.highlight import main; main()" file
|
||||
from .options.types import defaults
|
||||
import sys
|
||||
initialize_highlighter()
|
||||
if defaults is not None:
|
||||
with open(sys.argv[-1]) as f:
|
||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||
if highlighted is None:
|
||||
raise SystemExit('Unknown filetype: {}'.format(sys.argv[-1]))
|
||||
print(highlighted)
|
||||
with open(sys.argv[-1]) as f:
|
||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||
if highlighted is None:
|
||||
raise SystemExit('Unknown filetype: {}'.format(sys.argv[-1]))
|
||||
print(highlighted)
|
||||
|
||||
@@ -19,11 +19,10 @@ from typing import (
|
||||
|
||||
from kitty.cli import CONFIG_HELP, parse_args
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.conf.utils import KittensKeyAction
|
||||
from kitty.conf.utils import KeyAction
|
||||
from kitty.constants import appname
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import EventType, KeyEvent
|
||||
from kitty.options_stub import DiffOptions
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.handler import Handler
|
||||
@@ -33,10 +32,11 @@ from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
from . import global_data
|
||||
from .collect import (
|
||||
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
||||
set_highlight_data, add_remote_dir
|
||||
Collection, add_remote_dir, create_collection, data_for_path,
|
||||
lines_for_path, sanitize, set_highlight_data
|
||||
)
|
||||
from .config import init_config
|
||||
from .options.types import Options as DiffOptions
|
||||
from .patch import Differ, Patch, set_diff_command, worker_processes
|
||||
from .render import (
|
||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||
@@ -95,14 +95,14 @@ class DiffHandler(Handler):
|
||||
for key_def, action in self.opts.key_definitions.items():
|
||||
self.add_shortcut(action, key_def)
|
||||
|
||||
def perform_action(self, action: KittensKeyAction) -> None:
|
||||
def perform_action(self, action: KeyAction) -> None:
|
||||
func, args = action
|
||||
if func == 'quit':
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state <= DIFFED:
|
||||
if func == 'scroll_by':
|
||||
return self.scroll_lines(int(args[0]))
|
||||
return self.scroll_lines(int(args[0] or 0))
|
||||
if func == 'scroll_to':
|
||||
where = str(args[0])
|
||||
if 'change' in where:
|
||||
@@ -122,7 +122,7 @@ class DiffHandler(Handler):
|
||||
elif to == 'default':
|
||||
new_ctx = self.original_context_count
|
||||
else:
|
||||
new_ctx += int(to)
|
||||
new_ctx += int(to or 0)
|
||||
return self.change_context_count(new_ctx)
|
||||
if func == 'start_search':
|
||||
self.start_search(bool(args[0]), bool(args[1]))
|
||||
@@ -658,5 +658,5 @@ elif __name__ == '__doc__':
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
elif __name__ == '__conf__':
|
||||
from .config_data import all_options
|
||||
sys.all_options = all_options # type: ignore
|
||||
from .options.definition import definition
|
||||
sys.options_definition = definition # type: ignore
|
||||
|
||||
0
kittens/diff/options/__init__.py
Normal file
0
kittens/diff/options/__init__.py
Normal file
241
kittens/diff/options/definition.py
Normal file
241
kittens/diff/options/definition.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from kitty.conf.types import Action, Definition
|
||||
|
||||
|
||||
definition = Definition(
|
||||
'kittens.diff',
|
||||
Action('map', 'parse_map', {'key_definitions': 'kitty.conf.utils.KittensKeyMap'}, ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']),
|
||||
)
|
||||
|
||||
agr = definition.add_group
|
||||
egr = definition.end_group
|
||||
opt = definition.add_option
|
||||
map = definition.add_map
|
||||
mma = definition.add_mouse_map
|
||||
|
||||
# diff {{{
|
||||
agr('diff', 'Diffing')
|
||||
|
||||
opt('syntax_aliases', 'pyj:py pyi:py recipe:py',
|
||||
option_type='syntax_aliases',
|
||||
long_text='''
|
||||
File extension aliases for syntax highlight For example, to syntax highlight
|
||||
:file:`file.xyz` as :file:`file.abc` use a setting of :code:`xyz:abc`
|
||||
'''
|
||||
)
|
||||
|
||||
opt('num_context_lines', '3',
|
||||
option_type='positive_int',
|
||||
long_text='The number of lines of context to show around each change.'
|
||||
)
|
||||
|
||||
opt('diff_cmd', 'auto',
|
||||
long_text='''
|
||||
The diff command to use. Must contain the placeholder :code:`_CONTEXT_` which
|
||||
will be replaced by the number of lines of context. The default is to search the
|
||||
system for either git or diff and use that, if found.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('replace_tab_by', '\\x20\\x20\\x20\\x20',
|
||||
option_type='python_string',
|
||||
long_text='The string to replace tabs with. Default is to use four spaces.'
|
||||
)
|
||||
egr() # }}}
|
||||
|
||||
# colors {{{
|
||||
agr('colors', 'Colors')
|
||||
|
||||
opt('pygments_style', 'default',
|
||||
long_text='''
|
||||
The pygments color scheme to use for syntax highlighting. See :link:`pygments
|
||||
colors schemes <https://help.farbox.com/pygments.html>` for a list of schemes.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('foreground', 'black',
|
||||
option_type='to_color',
|
||||
long_text='Basic colors'
|
||||
)
|
||||
|
||||
opt('background', 'white',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('title_fg', 'black',
|
||||
option_type='to_color',
|
||||
long_text='Title colors'
|
||||
)
|
||||
|
||||
opt('title_bg', 'white',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('margin_bg', '#fafbfc',
|
||||
option_type='to_color',
|
||||
long_text='Margin colors'
|
||||
)
|
||||
|
||||
opt('margin_fg', '#aaaaaa',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('removed_bg', '#ffeef0',
|
||||
option_type='to_color',
|
||||
long_text='Removed text backgrounds'
|
||||
)
|
||||
|
||||
opt('highlight_removed_bg', '#fdb8c0',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('removed_margin_bg', '#ffdce0',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('added_bg', '#e6ffed',
|
||||
option_type='to_color',
|
||||
long_text='Added text backgrounds'
|
||||
)
|
||||
|
||||
opt('highlight_added_bg', '#acf2bd',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('added_margin_bg', '#cdffd8',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('filler_bg', '#fafbfc',
|
||||
option_type='to_color',
|
||||
long_text='Filler (empty) line background'
|
||||
)
|
||||
|
||||
opt('margin_filler_bg', 'none',
|
||||
option_type='to_color_or_none',
|
||||
long_text='Filler (empty) line background in margins, defaults to the filler background'
|
||||
)
|
||||
|
||||
opt('hunk_margin_bg', '#dbedff',
|
||||
option_type='to_color',
|
||||
long_text='Hunk header colors'
|
||||
)
|
||||
|
||||
opt('hunk_bg', '#f1f8ff',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('search_bg', '#444',
|
||||
option_type='to_color',
|
||||
long_text='Highlighting'
|
||||
)
|
||||
|
||||
opt('search_fg', 'white',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('select_bg', '#b4d5fe',
|
||||
option_type='to_color',
|
||||
)
|
||||
|
||||
opt('select_fg', 'black',
|
||||
option_type='to_color_or_none',
|
||||
)
|
||||
egr() # }}}
|
||||
|
||||
# shortcuts {{{
|
||||
agr('shortcuts', 'Keyboard shortcuts')
|
||||
|
||||
map('Quit',
|
||||
'quit q quit',
|
||||
)
|
||||
map('Quit',
|
||||
'quit esc quit',
|
||||
)
|
||||
|
||||
map('Scroll down',
|
||||
'scroll_down j scroll_by 1',
|
||||
)
|
||||
map('Scroll down',
|
||||
'scroll_down down scroll_by 1',
|
||||
)
|
||||
|
||||
map('Scroll up',
|
||||
'scroll_up k scroll_by -1',
|
||||
)
|
||||
map('Scroll up',
|
||||
'scroll_up up scroll_by -1',
|
||||
)
|
||||
|
||||
map('Scroll to top',
|
||||
'scroll_top home scroll_to start',
|
||||
)
|
||||
|
||||
map('Scroll to bottom',
|
||||
'scroll_bottom end scroll_to end',
|
||||
)
|
||||
|
||||
map('Scroll to next page',
|
||||
'scroll_page_down page_down scroll_to next-page',
|
||||
)
|
||||
map('Scroll to next page',
|
||||
'scroll_page_down space scroll_to next-page',
|
||||
)
|
||||
|
||||
map('Scroll to previous page',
|
||||
'scroll_page_up page_up scroll_to prev-page',
|
||||
)
|
||||
|
||||
map('Scroll to next change',
|
||||
'next_change n scroll_to next-change',
|
||||
)
|
||||
|
||||
map('Scroll to previous change',
|
||||
'prev_change p scroll_to prev-change',
|
||||
)
|
||||
|
||||
map('Show all context',
|
||||
'all_context a change_context all',
|
||||
)
|
||||
|
||||
map('Show default context',
|
||||
'default_context = change_context default',
|
||||
)
|
||||
|
||||
map('Increase context',
|
||||
'increase_context + change_context 5',
|
||||
)
|
||||
|
||||
map('Decrease context',
|
||||
'decrease_context - change_context -5',
|
||||
)
|
||||
|
||||
map('Search forward',
|
||||
'search_forward / start_search regex forward',
|
||||
)
|
||||
|
||||
map('Search backward',
|
||||
'search_backward ? start_search regex backward',
|
||||
)
|
||||
|
||||
map('Scroll to next search match',
|
||||
'next_match . scroll_to next-match',
|
||||
)
|
||||
map('Scroll to next search match',
|
||||
'next_match > scroll_to next-match',
|
||||
)
|
||||
|
||||
map('Scroll to previous search match',
|
||||
'prev_match , scroll_to prev-match',
|
||||
)
|
||||
map('Scroll to previous search match',
|
||||
'prev_match < scroll_to prev-match',
|
||||
)
|
||||
|
||||
map('Search forward (no regex)',
|
||||
'search_forward_simple f start_search substring forward',
|
||||
)
|
||||
|
||||
map('Search backward (no regex)',
|
||||
'search_backward_simple b start_search substring backward',
|
||||
)
|
||||
egr() # }}}
|
||||
120
kittens/diff/options/parse.py
generated
Normal file
120
kittens/diff/options/parse.py
generated
Normal file
@@ -0,0 +1,120 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
# vim:fileencoding=utf-8
|
||||
|
||||
import typing
|
||||
from kitty.conf.utils import merge_dicts, positive_int, python_string, to_color, to_color_or_none
|
||||
from kittens.diff.options.utils import parse_map, syntax_aliases
|
||||
|
||||
|
||||
class Parser:
|
||||
|
||||
def added_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['added_bg'] = to_color(val)
|
||||
|
||||
def added_margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['added_margin_bg'] = to_color(val)
|
||||
|
||||
def background(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['background'] = to_color(val)
|
||||
|
||||
def diff_cmd(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['diff_cmd'] = str(val)
|
||||
|
||||
def filler_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['filler_bg'] = to_color(val)
|
||||
|
||||
def foreground(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['foreground'] = to_color(val)
|
||||
|
||||
def highlight_added_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['highlight_added_bg'] = to_color(val)
|
||||
|
||||
def highlight_removed_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['highlight_removed_bg'] = to_color(val)
|
||||
|
||||
def hunk_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['hunk_bg'] = to_color(val)
|
||||
|
||||
def hunk_margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['hunk_margin_bg'] = to_color(val)
|
||||
|
||||
def margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['margin_bg'] = to_color(val)
|
||||
|
||||
def margin_fg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['margin_fg'] = to_color(val)
|
||||
|
||||
def margin_filler_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['margin_filler_bg'] = to_color_or_none(val)
|
||||
|
||||
def num_context_lines(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['num_context_lines'] = positive_int(val)
|
||||
|
||||
def pygments_style(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['pygments_style'] = str(val)
|
||||
|
||||
def removed_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['removed_bg'] = to_color(val)
|
||||
|
||||
def removed_margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['removed_margin_bg'] = to_color(val)
|
||||
|
||||
def replace_tab_by(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['replace_tab_by'] = python_string(val)
|
||||
|
||||
def search_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['search_bg'] = to_color(val)
|
||||
|
||||
def search_fg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['search_fg'] = to_color(val)
|
||||
|
||||
def select_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['select_bg'] = to_color(val)
|
||||
|
||||
def select_fg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['select_fg'] = to_color_or_none(val)
|
||||
|
||||
def syntax_aliases(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['syntax_aliases'] = syntax_aliases(val)
|
||||
|
||||
def title_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['title_bg'] = to_color(val)
|
||||
|
||||
def title_fg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['title_fg'] = to_color(val)
|
||||
|
||||
def map(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
for k in parse_map(val):
|
||||
ans['map'].append(k)
|
||||
|
||||
|
||||
def create_result_dict() -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'map': [],
|
||||
}
|
||||
|
||||
|
||||
actions = frozenset(('map',))
|
||||
|
||||
|
||||
def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
ans[k] = merge_dicts(v, vals.get(k, {}))
|
||||
elif k in actions:
|
||||
ans[k] = v + vals.get(k, [])
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
parser = Parser()
|
||||
|
||||
|
||||
def parse_conf_item(key: str, val: str, ans: typing.Dict[str, typing.Any]) -> bool:
|
||||
func = getattr(parser, key, None)
|
||||
if func is not None:
|
||||
func(val, ans)
|
||||
return True
|
||||
return False
|
||||
141
kittens/diff/options/types.py
generated
Normal file
141
kittens/diff/options/types.py
generated
Normal file
@@ -0,0 +1,141 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
# vim:fileencoding=utf-8
|
||||
|
||||
import typing
|
||||
from kitty.types import ParsedShortcut
|
||||
import kitty.types
|
||||
from kitty.conf.utils import KeyAction, KittensKeyMap
|
||||
import kitty.conf.utils
|
||||
from kitty.rgb import Color
|
||||
import kitty.rgb
|
||||
|
||||
|
||||
option_names = ( # {{{
|
||||
'added_bg',
|
||||
'added_margin_bg',
|
||||
'background',
|
||||
'diff_cmd',
|
||||
'filler_bg',
|
||||
'foreground',
|
||||
'highlight_added_bg',
|
||||
'highlight_removed_bg',
|
||||
'hunk_bg',
|
||||
'hunk_margin_bg',
|
||||
'map',
|
||||
'margin_bg',
|
||||
'margin_fg',
|
||||
'margin_filler_bg',
|
||||
'num_context_lines',
|
||||
'pygments_style',
|
||||
'removed_bg',
|
||||
'removed_margin_bg',
|
||||
'replace_tab_by',
|
||||
'search_bg',
|
||||
'search_fg',
|
||||
'select_bg',
|
||||
'select_fg',
|
||||
'syntax_aliases',
|
||||
'title_bg',
|
||||
'title_fg') # }}}
|
||||
|
||||
|
||||
class Options:
|
||||
added_bg: Color = Color(red=230, green=255, blue=237)
|
||||
added_margin_bg: Color = Color(red=205, green=255, blue=216)
|
||||
background: Color = Color(red=255, green=255, blue=255)
|
||||
diff_cmd: str = 'auto'
|
||||
filler_bg: Color = Color(red=250, green=251, blue=252)
|
||||
foreground: Color = Color(red=0, green=0, blue=0)
|
||||
highlight_added_bg: Color = Color(red=172, green=242, blue=189)
|
||||
highlight_removed_bg: Color = Color(red=253, green=184, blue=192)
|
||||
hunk_bg: Color = Color(red=241, green=248, blue=255)
|
||||
hunk_margin_bg: Color = Color(red=219, green=237, blue=255)
|
||||
margin_bg: Color = Color(red=250, green=251, blue=252)
|
||||
margin_fg: Color = Color(red=170, green=170, blue=170)
|
||||
margin_filler_bg: typing.Optional[kitty.rgb.Color] = None
|
||||
num_context_lines: int = 3
|
||||
pygments_style: str = 'default'
|
||||
removed_bg: Color = Color(red=255, green=238, blue=240)
|
||||
removed_margin_bg: Color = Color(red=255, green=220, blue=224)
|
||||
replace_tab_by: str = ' '
|
||||
search_bg: Color = Color(red=68, green=68, blue=68)
|
||||
search_fg: Color = Color(red=255, green=255, blue=255)
|
||||
select_bg: Color = Color(red=180, green=213, blue=254)
|
||||
select_fg: typing.Optional[kitty.rgb.Color] = Color(red=0, green=0, blue=0)
|
||||
syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'}
|
||||
title_bg: Color = Color(red=255, green=255, blue=255)
|
||||
title_fg: Color = Color(red=0, green=0, blue=0)
|
||||
map: typing.List[typing.Tuple[kitty.types.ParsedShortcut, kitty.conf.utils.KeyAction]] = []
|
||||
key_definitions: KittensKeyMap = {}
|
||||
|
||||
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
|
||||
if options_dict is not None:
|
||||
for key in option_names:
|
||||
setattr(self, key, options_dict[key])
|
||||
|
||||
@property
|
||||
def _fields(self) -> typing.Tuple[str, ...]:
|
||||
return option_names
|
||||
|
||||
def __iter__(self) -> typing.Iterator[str]:
|
||||
return iter(self._fields)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._fields)
|
||||
|
||||
def _copy_of_val(self, name: str) -> typing.Any:
|
||||
ans = getattr(self, name)
|
||||
if isinstance(ans, dict):
|
||||
ans = ans.copy()
|
||||
elif isinstance(ans, list):
|
||||
ans = ans[:]
|
||||
return ans
|
||||
|
||||
def _asdict(self) -> typing.Dict[str, typing.Any]:
|
||||
return {k: self._copy_of_val(k) for k in self}
|
||||
|
||||
def _replace(self, **kw: typing.Any) -> "Options":
|
||||
ans = Options()
|
||||
for name in self:
|
||||
setattr(ans, name, self._copy_of_val(name))
|
||||
for name, val in kw.items():
|
||||
setattr(ans, name, val)
|
||||
return ans
|
||||
|
||||
def __getitem__(self, key: typing.Union[int, str]) -> typing.Any:
|
||||
k = option_names[key] if isinstance(key, int) else key
|
||||
try:
|
||||
return getattr(self, k)
|
||||
except AttributeError:
|
||||
pass
|
||||
raise KeyError(f"No option named: {k}")
|
||||
|
||||
|
||||
defaults = Options()
|
||||
defaults.map = [
|
||||
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')),
|
||||
(ParsedShortcut(mods=0, key_name='ESCAPE'), KeyAction('quit')),
|
||||
(ParsedShortcut(mods=0, key_name='j'), KeyAction('scroll_by', (1,))),
|
||||
(ParsedShortcut(mods=0, key_name='DOWN'), KeyAction('scroll_by', (1,))),
|
||||
(ParsedShortcut(mods=0, key_name='k'), KeyAction('scroll_by', (-1,))),
|
||||
(ParsedShortcut(mods=0, key_name='UP'), KeyAction('scroll_by', (-1,))),
|
||||
(ParsedShortcut(mods=0, key_name='HOME'), KeyAction('scroll_to', ('start',))),
|
||||
(ParsedShortcut(mods=0, key_name='END'), KeyAction('scroll_to', ('end',))),
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_DOWN'), KeyAction('scroll_to', ('next-page',))),
|
||||
(ParsedShortcut(mods=0, key_name=' '), KeyAction('scroll_to', ('next-page',))),
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_UP'), KeyAction('scroll_to', ('prev-page',))),
|
||||
(ParsedShortcut(mods=0, key_name='n'), KeyAction('scroll_to', ('next-change',))),
|
||||
(ParsedShortcut(mods=0, key_name='p'), KeyAction('scroll_to', ('prev-change',))),
|
||||
(ParsedShortcut(mods=0, key_name='a'), KeyAction('change_context', ('all',))),
|
||||
(ParsedShortcut(mods=0, key_name='='), KeyAction('change_context', ('default',))),
|
||||
(ParsedShortcut(mods=0, key_name='+'), KeyAction('change_context', (5,))),
|
||||
(ParsedShortcut(mods=0, key_name='-'), KeyAction('change_context', (-5,))),
|
||||
(ParsedShortcut(mods=0, key_name='/'), KeyAction('start_search', (True, False))),
|
||||
(ParsedShortcut(mods=0, key_name='?'), KeyAction('start_search', (True, True))),
|
||||
(ParsedShortcut(mods=0, key_name='.'), KeyAction('scroll_to', ('next-match',))),
|
||||
(ParsedShortcut(mods=0, key_name='>'), KeyAction('scroll_to', ('next-match',))),
|
||||
(ParsedShortcut(mods=0, key_name=','), KeyAction('scroll_to', ('prev-match',))),
|
||||
(ParsedShortcut(mods=0, key_name='<'), KeyAction('scroll_to', ('prev-match',))),
|
||||
(ParsedShortcut(mods=0, key_name='f'), KeyAction('start_search', (False, False))),
|
||||
(ParsedShortcut(mods=0, key_name='b'), KeyAction('start_search', (False, True))),
|
||||
]
|
||||
62
kittens/diff/options/utils.py
Normal file
62
kittens/diff/options/utils.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import Any, Dict, Iterable, Sequence, Tuple, Union
|
||||
|
||||
from kitty.conf.utils import KittensKeyDefinition, key_func, parse_kittens_key
|
||||
|
||||
func_with_args, args_funcs = key_func()
|
||||
FuncArgsType = Tuple[str, Sequence[Any]]
|
||||
|
||||
|
||||
@func_with_args('scroll_by')
|
||||
def parse_scroll_by(func: str, rest: str) -> Tuple[str, int]:
|
||||
try:
|
||||
return func, int(rest)
|
||||
except Exception:
|
||||
return func, 1
|
||||
|
||||
|
||||
@func_with_args('scroll_to')
|
||||
def parse_scroll_to(func: str, rest: str) -> Tuple[str, str]:
|
||||
rest = rest.lower()
|
||||
if rest not in {'start', 'end', 'next-change', 'prev-change', 'next-page', 'prev-page', 'next-match', 'prev-match'}:
|
||||
rest = 'start'
|
||||
return func, rest
|
||||
|
||||
|
||||
@func_with_args('change_context')
|
||||
def parse_change_context(func: str, rest: str) -> Tuple[str, Union[int, str]]:
|
||||
rest = rest.lower()
|
||||
if rest in {'all', 'default'}:
|
||||
return func, rest
|
||||
try:
|
||||
amount = int(rest)
|
||||
except Exception:
|
||||
amount = 5
|
||||
return func, amount
|
||||
|
||||
|
||||
@func_with_args('start_search')
|
||||
def parse_start_search(func: str, rest: str) -> Tuple[str, Tuple[bool, bool]]:
|
||||
rest_ = rest.lower().split()
|
||||
is_regex = bool(rest_ and rest_[0] == 'regex')
|
||||
is_backward = bool(len(rest_) > 1 and rest_[1] == 'backward')
|
||||
return func, (is_regex, is_backward)
|
||||
|
||||
|
||||
def syntax_aliases(raw: str) -> Dict[str, str]:
|
||||
ans = {}
|
||||
for x in raw.split():
|
||||
a, b = x.partition(':')[::2]
|
||||
if a and b:
|
||||
ans[a.lower()] = b
|
||||
return ans
|
||||
|
||||
|
||||
def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
|
||||
x = parse_kittens_key(val, args_funcs)
|
||||
if x is not None:
|
||||
yield x
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Tuple
|
||||
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.options_stub import DiffOptions
|
||||
from .options.types import Options as DiffOptions
|
||||
|
||||
from ..tui.operations import styled
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import (
|
||||
from kitty.types import ParsedShortcut
|
||||
from kitty.typing import (
|
||||
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
|
||||
KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
|
||||
KeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
|
||||
)
|
||||
|
||||
|
||||
@@ -46,15 +46,15 @@ class Handler:
|
||||
def asyncio_loop(self) -> AbstractEventLoop:
|
||||
return self._tui_loop.asycio_loop
|
||||
|
||||
def add_shortcut(self, action: KittensKeyActionType, spec: Union[str, ParsedShortcut]) -> None:
|
||||
def add_shortcut(self, action: KeyActionType, spec: Union[str, ParsedShortcut]) -> None:
|
||||
if not hasattr(self, '_key_shortcuts'):
|
||||
self._key_shortcuts: Dict[ParsedShortcut, KittensKeyActionType] = {}
|
||||
self._key_shortcuts: Dict[ParsedShortcut, KeyActionType] = {}
|
||||
if isinstance(spec, str):
|
||||
from kitty.key_encoding import parse_shortcut
|
||||
spec = parse_shortcut(spec)
|
||||
self._key_shortcuts[spec] = action
|
||||
|
||||
def shortcut_action(self, key_event: KeyEventType) -> Optional[KittensKeyActionType]:
|
||||
def shortcut_action(self, key_event: KeyEventType) -> Optional[KeyActionType]:
|
||||
for sc, action in self._key_shortcuts.items():
|
||||
if key_event.matches(sc):
|
||||
return action
|
||||
|
||||
@@ -9,6 +9,6 @@ class CMD:
|
||||
|
||||
def generate_stub() -> None:
|
||||
from kittens.tui.operations import as_type_stub
|
||||
from kitty.conf.definition import save_type_stub
|
||||
from kitty.conf.utils import save_type_stub
|
||||
text = as_type_stub()
|
||||
save_type_stub(text, __file__)
|
||||
|
||||
Reference in New Issue
Block a user