parent
c127476c4e
commit
4b259dd719
@ -5,8 +5,9 @@
|
||||
import os
|
||||
|
||||
from kitty.config_utils import (
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
python_string, resolve_config, to_color
|
||||
init_config, key_func, load_config as _load_config, merge_dicts,
|
||||
parse_config_base, parse_kittens_key, python_string, resolve_config,
|
||||
to_color
|
||||
)
|
||||
from kitty.constants import config_dir
|
||||
from kitty.rgb import color_as_sgr
|
||||
@ -58,14 +59,46 @@ for name in (
|
||||
' highlight_removed_bg highlight_added_bg'
|
||||
).split():
|
||||
type_map[name] = to_color
|
||||
func_with_args, args_funcs = key_func()
|
||||
|
||||
|
||||
def special_handling(*a):
|
||||
pass
|
||||
@func_with_args('scroll_by')
|
||||
def parse_scroll_by(func, rest):
|
||||
try:
|
||||
return func, int(rest)
|
||||
except Exception:
|
||||
return func, 1
|
||||
|
||||
|
||||
@func_with_args('scroll_to')
|
||||
def parse_scroll_to(func, rest):
|
||||
rest = rest.lower()
|
||||
if rest not in {'start', 'end', 'next-change', 'prev-change'}:
|
||||
rest = 'start'
|
||||
return func, rest
|
||||
|
||||
|
||||
@func_with_args('change_context')
|
||||
def parse_change_context(func, rest):
|
||||
rest = rest.lower()
|
||||
if rest in {'all', 'default'}:
|
||||
return func, rest
|
||||
try:
|
||||
amount = int(rest)
|
||||
except Exception:
|
||||
amount = 5
|
||||
return func, amount
|
||||
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
action, *key_def = parse_kittens_key(val, args_funcs)
|
||||
ans['key_definitions'][tuple(key_def)] = action
|
||||
return True
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {}
|
||||
ans = {'key_definitions': {}}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
|
||||
@ -36,3 +36,20 @@ added_margin_bg #cdffd8
|
||||
filler_bg #fafbfc
|
||||
hunk_margin_bg #dbedff
|
||||
hunk_bg #f1f8ff
|
||||
|
||||
|
||||
# Keyboard shortcuts
|
||||
map q quit
|
||||
map esc quit
|
||||
map j scroll_by 1
|
||||
map k scroll_by -1
|
||||
map down scroll_by 1
|
||||
map up scroll_by -1
|
||||
map home scroll_to start
|
||||
map end scroll_to end
|
||||
map n scroll_to next-change
|
||||
map p scroll_to prev-change
|
||||
map a change_context all
|
||||
map = change_context default
|
||||
map + change_context 5
|
||||
map - change_context -5
|
||||
|
||||
@ -11,9 +11,7 @@ from gettext import gettext as _
|
||||
|
||||
from kitty.cli import CONFIG_HELP, appname, parse_args
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import (
|
||||
DOWN, END, ESCAPE, HOME, PAGE_DOWN, PAGE_UP, RELEASE, UP
|
||||
)
|
||||
from kitty.key_encoding import RELEASE
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.images import ImageManager
|
||||
@ -63,6 +61,33 @@ class DiffHandler(Handler):
|
||||
self.current_context_count = self.original_context_count = self.opts.num_context_lines
|
||||
self.highlighting_done = False
|
||||
self.restore_position = None
|
||||
for key_def, action in self.opts.key_definitions.items():
|
||||
self.add_shortcut(action, *key_def)
|
||||
|
||||
def perform_action(self, action):
|
||||
func, args = action
|
||||
if func == 'quit':
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state <= DIFFED:
|
||||
if func == 'scroll_by':
|
||||
return self.scroll_lines(*args)
|
||||
if func == 'scroll_to':
|
||||
where = args[0]
|
||||
if 'change' in where:
|
||||
return self.scroll_to_next_change(backwards='prev' in where)
|
||||
amt = len(self.diff_lines) * (1 if 'end' in where else -1)
|
||||
return self.scroll_lines(amt)
|
||||
if func == 'change_context':
|
||||
new_ctx = self.current_context_count
|
||||
to = args[0]
|
||||
if to == 'all':
|
||||
new_ctx = 100000
|
||||
elif to == 'default':
|
||||
new_ctx = self.original_context_count
|
||||
else:
|
||||
new_ctx += to
|
||||
return self.change_context_count(new_ctx)
|
||||
|
||||
def create_collection(self):
|
||||
self.start_job('collect', create_collection, self.left, self.right)
|
||||
@ -258,46 +283,16 @@ class DiffHandler(Handler):
|
||||
self.draw_screen()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
if text == 'q':
|
||||
if self.state <= DIFFED:
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state is DIFFED:
|
||||
if text in 'jk':
|
||||
self.scroll_lines(1 if text == 'j' else -1)
|
||||
return
|
||||
if text in 'a+-=':
|
||||
new_ctx = self.current_context_count
|
||||
if text == 'a':
|
||||
new_ctx = 100000
|
||||
elif text == '=':
|
||||
new_ctx = self.original_context_count
|
||||
else:
|
||||
new_ctx += (-1 if text == '-' else 1) * 5
|
||||
self.change_context_count(new_ctx)
|
||||
if text in 'np':
|
||||
self.scroll_to_next_change(backwards=text == 'p')
|
||||
return
|
||||
action = self.shortcut_action(text)
|
||||
if action is not None:
|
||||
return self.perform_action(action)
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event.type is RELEASE:
|
||||
return
|
||||
if key_event.key is ESCAPE:
|
||||
if self.state <= DIFFED:
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state is DIFFED:
|
||||
if key_event.key is UP or key_event.key is DOWN:
|
||||
self.scroll_lines(1 if key_event.key is DOWN else -1)
|
||||
return
|
||||
if key_event.key is PAGE_UP or key_event.key is PAGE_DOWN:
|
||||
amt = self.num_lines * (1 if key_event.key is PAGE_DOWN else -1)
|
||||
self.scroll_lines(amt)
|
||||
return
|
||||
if key_event.key is HOME or key_event.key is END:
|
||||
amt = len(self.diff_lines) * (1 if key_event.key is END else -1)
|
||||
self.scroll_lines(amt)
|
||||
return
|
||||
action = self.shortcut_action(key_event)
|
||||
if action is not None:
|
||||
return self.perform_action(action)
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
|
||||
@ -18,6 +18,19 @@ class Handler:
|
||||
self.cmd = commander(self)
|
||||
self.image_manager = image_manager
|
||||
|
||||
def add_shortcut(self, action, key, mods=None, is_text=False):
|
||||
if not hasattr(self, '_text_shortcuts'):
|
||||
self._text_shortcuts, self._key_shortcuts = {}, {}
|
||||
if is_text:
|
||||
self._text_shortcuts[key] = action
|
||||
else:
|
||||
self._key_shortcuts[(key, mods or 0)] = action
|
||||
|
||||
def shortcut_action(self, key_event_or_text):
|
||||
if isinstance(key_event_or_text, str):
|
||||
return self._text_shortcuts.get(key_event_or_text)
|
||||
return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods))
|
||||
|
||||
def __enter__(self):
|
||||
if self.image_manager is not None:
|
||||
self.image_manager.__enter__()
|
||||
|
||||
@ -11,9 +11,9 @@ from contextlib import contextmanager
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .config_utils import (
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
positive_float, positive_int, python_string, to_bool, to_cmdline, to_color,
|
||||
unit_float
|
||||
init_config, key_func, load_config as _load_config, merge_dicts,
|
||||
parse_config_base, positive_float, positive_int, python_string, to_bool,
|
||||
to_cmdline, to_color, unit_float
|
||||
)
|
||||
from .constants import cache_dir, defconf
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
@ -92,18 +92,7 @@ def parse_shortcut(sc):
|
||||
|
||||
|
||||
KeyAction = namedtuple('KeyAction', 'func args')
|
||||
args_funcs = {}
|
||||
|
||||
|
||||
def func_with_args(*names):
|
||||
|
||||
def w(f):
|
||||
for name in names:
|
||||
if args_funcs.setdefault(name, f) is not f:
|
||||
raise ValueError('the args_func {} is being redefined'.format(name))
|
||||
return f
|
||||
|
||||
return w
|
||||
func_with_args, args_funcs = key_func()
|
||||
|
||||
|
||||
@func_with_args(
|
||||
|
||||
@ -170,3 +170,71 @@ def init_config(defaults_path, parse_config):
|
||||
Options = create_options_class(defaults.keys())
|
||||
defaults = Options(defaults)
|
||||
return Options, defaults
|
||||
|
||||
|
||||
def key_func():
|
||||
ans = {}
|
||||
|
||||
def func_with_args(*names):
|
||||
|
||||
def w(f):
|
||||
for name in names:
|
||||
if ans.setdefault(name, f) is not f:
|
||||
raise ValueError('the args_func {} is being redefined'.format(name))
|
||||
return f
|
||||
|
||||
return w
|
||||
return func_with_args, ans
|
||||
|
||||
|
||||
def parse_kittens_shortcut(sc):
|
||||
from kitty.key_encoding import config_key_map, config_mod_map, text_match
|
||||
if sc.endswith('+'):
|
||||
parts = list(filter(None, sc.rstrip('+').split('+') + ['+']))
|
||||
else:
|
||||
parts = sc.split('+')
|
||||
mods = parts[:-1] or None
|
||||
if mods is not None:
|
||||
resolved_mods = 0
|
||||
for mod in mods:
|
||||
m = config_mod_map.get(mod.upper())
|
||||
if m is None:
|
||||
raise ValueError('Unknown shortcut modifiers: {}'.format(sc))
|
||||
resolved_mods |= m
|
||||
mods = resolved_mods
|
||||
is_text = False
|
||||
rkey = parts[-1]
|
||||
if text_match(rkey) is None:
|
||||
rkey = rkey.upper()
|
||||
rkey = config_key_map.get(rkey)
|
||||
if rkey is None:
|
||||
raise ValueError('Unknown shortcut key: {}'.format(sc))
|
||||
else:
|
||||
is_text = True
|
||||
return mods, rkey, is_text
|
||||
|
||||
|
||||
def parse_kittens_func_args(action, args_funcs):
|
||||
parts = action.split(' ', 1)
|
||||
func = parts[0]
|
||||
if len(parts) == 1:
|
||||
return func, ()
|
||||
rest = parts[1]
|
||||
parser = args_funcs.get(func)
|
||||
if parser is not None:
|
||||
try:
|
||||
func, args = parser(func, rest)
|
||||
except Exception:
|
||||
raise ValueError('Unknown key action: {}'.format(action))
|
||||
if not isinstance(args, (list, tuple)):
|
||||
args = (args,)
|
||||
return func, tuple(args)
|
||||
|
||||
|
||||
def parse_kittens_key(val, funcs_with_args):
|
||||
sc, action = val.partition(' ')[::2]
|
||||
if not sc or not action:
|
||||
return
|
||||
mods, key, is_text = parse_kittens_shortcut(sc)
|
||||
action = parse_kittens_func_args(action, funcs_with_args)
|
||||
return action, key, mods, is_text
|
||||
|
||||
32
kitty/key_encoding.py
generated
32
kitty/key_encoding.py
generated
@ -254,6 +254,15 @@ KEY_MAP = {
|
||||
# END_ENCODING }}}
|
||||
|
||||
|
||||
text_keys = string.ascii_uppercase + string.ascii_lowercase + string.digits + '`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
|
||||
|
||||
|
||||
def text_match(key):
|
||||
if key not in text_keys:
|
||||
return
|
||||
return key
|
||||
|
||||
|
||||
def encode(
|
||||
integer,
|
||||
chars=string.ascii_uppercase + string.ascii_lowercase + string.digits +
|
||||
@ -312,10 +321,31 @@ type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
|
||||
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
|
||||
key_rmap = {}
|
||||
g = globals()
|
||||
config_key_map = {}
|
||||
config_mod_map = {'SHIFT': SHIFT, 'ALT': ALT, 'OPTION': ALT, '⌥': ALT, '⌘': SUPER, 'CMD': SUPER, 'SUPER': SUPER, 'CTRL': CTRL, 'CONTROL': CTRL}
|
||||
for key_name, enc in ENCODING.items():
|
||||
key_name = key_name.replace(' ', '_')
|
||||
g[key_name] = key_name
|
||||
g[key_name] = config_key_map[key_name] = key_name
|
||||
key_rmap[enc] = key_name
|
||||
config_key_map.update({
|
||||
'`': g['GRAVE_ACCENT'],
|
||||
'-': g['MINUS'],
|
||||
'=': g['EQUAL'],
|
||||
|
||||
'[': g['LEFT_BRACKET'],
|
||||
']': g['RIGHT_BRACKET'],
|
||||
'\\': g['BACKSLASH'],
|
||||
|
||||
';': g['SEMICOLON'],
|
||||
"'": g['APOSTROPHE'],
|
||||
|
||||
',': g['COMMA'],
|
||||
'.': g['PERIOD'],
|
||||
'/': g['SLASH'],
|
||||
|
||||
'ESC': g['ESCAPE'],
|
||||
})
|
||||
|
||||
del key_name, enc, g
|
||||
enter_key = KeyEvent(PRESS, 0, ENCODING['ENTER'])
|
||||
backspace_key = KeyEvent(PRESS, 0, ENCODING['BACKSPACE'])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user