mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-11 02:59:40 +02:00
Merge branch 'master' of https://github.com/theAnuragMishra/kitty
This commit is contained in:
@@ -167,6 +167,33 @@ the program running in the terminal, map it to :ac:`discard_event`::
|
||||
|
||||
.. _conditional_mappings:
|
||||
|
||||
Configuring a timeout
|
||||
----------------------
|
||||
|
||||
You can also set a timeout for keyboard modes and multi-key mappings. If a
|
||||
timeout is set and you don't complete the key sequence or exit the mode within
|
||||
the specified time, the mode will be automatically cancelled. This is useful
|
||||
for multi-key mappings where you might accidentally press the first key and
|
||||
then change your mind. The timeout is specified in seconds and can be set
|
||||
globally using the :opt:`map_timeout` option or per-mode using ``--timeout``::
|
||||
|
||||
# Set a global 2 second timeout for all multi-key and modal mappings
|
||||
map_timeout 2.0
|
||||
|
||||
# This mode will have a 5 second timeout (overrides global setting)
|
||||
map --new-mode resize --timeout 5.0 kitty_mod+r
|
||||
map --mode resize h resize_window narrower
|
||||
map --mode resize l resize_window wider
|
||||
# ... more mappings
|
||||
|
||||
# Multi-key mapping with the global timeout
|
||||
map ctrl+a>h new_window
|
||||
|
||||
When a timeout occurs, the mode is exited and any buffered keys are discarded.
|
||||
A timeout value of zero disables the timeout. For multi-key sequences, the
|
||||
timeout is restarted after each valid key press in the sequence.
|
||||
|
||||
|
||||
Conditional mappings depending on the state of the focused window
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ def finalize_keys(opts: Options, accumulate_bad_lines: list[BadLine] | None = No
|
||||
modes[defn.options.new_mode] = nm = KeyboardMode(defn.options.new_mode)
|
||||
nm.on_unknown = defn.options.on_unknown
|
||||
nm.on_action = defn.options.on_action
|
||||
nm.timeout = defn.options.timeout if defn.options.timeout is not None else opts.map_timeout
|
||||
defn.definition = f'push_keyboard_mode {defn.options.new_mode}'
|
||||
try:
|
||||
m = modes[defn.options.mode]
|
||||
|
||||
@@ -62,11 +62,11 @@ def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool:
|
||||
|
||||
|
||||
class Mappings:
|
||||
"Manage all keyboard mappings"
|
||||
|
||||
' Manage all keyboard mappings '
|
||||
|
||||
def __init__(self, global_shortcuts:dict[str, SingleKey] | None = None, callback_on_mode_change: Callable[[], Any] = lambda: None) -> None:
|
||||
def __init__(self, global_shortcuts: dict[str, SingleKey] | None = None, callback_on_mode_change: Callable[[], Any] = lambda: None) -> None:
|
||||
self.keyboard_mode_stack: list[KeyboardMode] = []
|
||||
self.mode_timeout_timer_id: int | None = None
|
||||
self.update_keymap(global_shortcuts)
|
||||
self.callback_on_mode_change = callback_on_mode_change
|
||||
|
||||
@@ -87,6 +87,7 @@ class Mappings:
|
||||
|
||||
def clear_keyboard_modes(self) -> None:
|
||||
had_mode = bool(self.keyboard_mode_stack)
|
||||
self._cancel_mode_timeout()
|
||||
self.keyboard_mode_stack = []
|
||||
self.set_ignore_os_keyboard_processing(False)
|
||||
if had_mode:
|
||||
@@ -95,6 +96,7 @@ class Mappings:
|
||||
def pop_keyboard_mode(self) -> bool:
|
||||
passthrough = True
|
||||
if self.keyboard_mode_stack:
|
||||
self._cancel_mode_timeout()
|
||||
self.keyboard_mode_stack.pop()
|
||||
if not self.keyboard_mode_stack:
|
||||
self.set_ignore_os_keyboard_processing(False)
|
||||
@@ -110,12 +112,38 @@ class Mappings:
|
||||
def _push_keyboard_mode(self, mode: KeyboardMode) -> None:
|
||||
self.keyboard_mode_stack.append(mode)
|
||||
self.set_ignore_os_keyboard_processing(True)
|
||||
self._start_mode_timeout(mode)
|
||||
self.callback_on_mode_change()
|
||||
|
||||
def push_keyboard_mode(self, new_mode: str) -> None:
|
||||
mode = self.keyboard_modes[new_mode]
|
||||
self._push_keyboard_mode(mode)
|
||||
|
||||
def _start_mode_timeout(self, mode: KeyboardMode) -> None:
|
||||
if mode.timeout > 0:
|
||||
from .fast_data_types import add_timer
|
||||
|
||||
self._cancel_mode_timeout()
|
||||
self.mode_timeout_timer_id = add_timer(self._on_mode_timeout, mode.timeout, False)
|
||||
mode.timeout_timer_id = self.mode_timeout_timer_id
|
||||
|
||||
def _cancel_mode_timeout(self) -> None:
|
||||
if self.mode_timeout_timer_id is not None:
|
||||
from .fast_data_types import remove_timer
|
||||
|
||||
remove_timer(self.mode_timeout_timer_id)
|
||||
self.mode_timeout_timer_id = None
|
||||
|
||||
def _on_mode_timeout(self, timer_id: int | None) -> None:
|
||||
self.mode_timeout_timer_id = None
|
||||
if self.keyboard_mode_stack:
|
||||
self.pop_keyboard_mode()
|
||||
|
||||
def _get_effective_timeout(self, key_def: KeyDefinition) -> float:
|
||||
if key_def.options.timeout is not None:
|
||||
return key_def.options.timeout
|
||||
return self.get_options().map_timeout
|
||||
|
||||
def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> list[KeyDefinition]:
|
||||
w = self.get_active_window()
|
||||
matches = []
|
||||
@@ -128,8 +156,9 @@ class Mappings:
|
||||
is_applicable = True
|
||||
except Exception:
|
||||
self.clear_keyboard_modes()
|
||||
self.show_error(_('Invalid key mapping'), _(
|
||||
'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on'))
|
||||
self.show_error(
|
||||
_('Invalid key mapping'), _('The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on')
|
||||
)
|
||||
return []
|
||||
else:
|
||||
is_applicable = True
|
||||
@@ -143,10 +172,10 @@ class Mappings:
|
||||
if not x.rest:
|
||||
last_terminal_idx = i
|
||||
if last_terminal_idx > -1:
|
||||
if last_terminal_idx == len(matches) -1:
|
||||
if last_terminal_idx == len(matches) - 1:
|
||||
matches = matches[last_terminal_idx:]
|
||||
else:
|
||||
matches = matches[last_terminal_idx+1:]
|
||||
matches = matches[last_terminal_idx + 1 :]
|
||||
q = matches[-1].options.when_focus_on
|
||||
matches = [x for x in matches if x.options.when_focus_on == q]
|
||||
elif matches:
|
||||
@@ -192,6 +221,7 @@ class Mappings:
|
||||
sm = KeyboardMode('__sequence__')
|
||||
sm.on_action = 'end'
|
||||
sm.sequence_keys = [ev]
|
||||
sm.timeout = self._get_effective_timeout(final_actions[0])
|
||||
for fa in final_actions:
|
||||
sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
|
||||
self._push_keyboard_mode(sm)
|
||||
@@ -206,6 +236,7 @@ class Mappings:
|
||||
w.send_key_sequence(*mode.sequence_keys)
|
||||
return consumed
|
||||
mode.sequence_keys.append(ev)
|
||||
self._start_mode_timeout(mode)
|
||||
self.debug_print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='')
|
||||
mode.keymap.clear()
|
||||
for fa in final_actions:
|
||||
@@ -219,6 +250,8 @@ class Mappings:
|
||||
self.callback_on_mode_change()
|
||||
if not self.keyboard_mode_stack:
|
||||
self.set_ignore_os_keyboard_processing(False)
|
||||
elif not is_root_mode and mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode:
|
||||
self._start_mode_timeout(mode)
|
||||
return consumed
|
||||
return False
|
||||
|
||||
@@ -252,5 +285,7 @@ class Mappings:
|
||||
|
||||
def set_cocoa_global_shortcuts(self, opts: Options) -> dict[str, SingleKey]:
|
||||
from .main import set_cocoa_global_shortcuts
|
||||
|
||||
return set_cocoa_global_shortcuts(opts)
|
||||
|
||||
# }}}
|
||||
|
||||
@@ -3847,6 +3847,24 @@ remove the default shortcuts.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('map_timeout', '0.0',
|
||||
option_type='positive_float', ctype='time',
|
||||
long_text='''
|
||||
The default timeout (in seconds) for multi-key mappings and modal keyboard modes.
|
||||
If you press the first key(s) of a multi-key mapping and don't press the next
|
||||
key within this timeout, the mapping is cancelled and the mode is exited. A value
|
||||
of zero disables the timeout. This can be overridden for specific modes using the
|
||||
:code:`--timeout` option when creating a keyboard mode with :code:`--new-mode`.
|
||||
For example::
|
||||
|
||||
# 2 second timeout for all mappings
|
||||
map_timeout 2.0
|
||||
|
||||
# This mode will have a 5 second timeout (overrides the global 2 second timeout)
|
||||
map --new-mode resize --timeout 5.0 kitty_mod+r
|
||||
'''
|
||||
)
|
||||
|
||||
opt('+action_alias', 'launch_tab launch --type=tab --cwd=current',
|
||||
option_type='action_alias',
|
||||
add_to_default=False,
|
||||
|
||||
3
kitty/options/parse.py
generated
3
kitty/options/parse.py
generated
@@ -1060,6 +1060,9 @@ class Parser:
|
||||
def kitty_mod(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['kitty_mod'] = to_modifiers(val)
|
||||
|
||||
def map_timeout(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['map_timeout'] = positive_float(val)
|
||||
|
||||
def linux_bell_theme(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['linux_bell_theme'] = str(val)
|
||||
|
||||
|
||||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
@@ -390,6 +390,7 @@ option_names = (
|
||||
'macos_traditional_fullscreen',
|
||||
'macos_window_resizable',
|
||||
'map',
|
||||
'map_timeout',
|
||||
'mark1_background',
|
||||
'mark1_foreground',
|
||||
'mark2_background',
|
||||
@@ -572,6 +573,7 @@ class Options:
|
||||
input_delay: int = 3
|
||||
italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto')
|
||||
kitty_mod: int = 5
|
||||
map_timeout: float = 0.0
|
||||
linux_bell_theme: str = '__custom'
|
||||
linux_display_server: choices_for_linux_display_server = 'auto'
|
||||
listen_on: str = 'none'
|
||||
|
||||
@@ -1290,6 +1290,7 @@ class KeyMapOptions:
|
||||
mode: str = ''
|
||||
on_unknown: LiteralField[OnUnknown] = LiteralField[OnUnknown](get_args(OnUnknown))
|
||||
on_action: LiteralField[OnAction] = LiteralField[OnAction](get_args(OnAction))
|
||||
timeout: float | None = None
|
||||
|
||||
|
||||
default_key_map_options = KeyMapOptions()
|
||||
@@ -1349,6 +1350,8 @@ class KeyboardMode:
|
||||
on_unknown: OnUnknown = get_args(OnUnknown)[0]
|
||||
on_action : OnAction = get_args(OnAction)[0]
|
||||
sequence_keys: list[defines.KeyEvent] | None = None
|
||||
timeout: float = 0.0
|
||||
timeout_timer_id: int | None = None
|
||||
|
||||
def __init__(self, name: str = '') -> None:
|
||||
self.name = name
|
||||
@@ -1361,11 +1364,15 @@ KeyboardModeMap = dict[str, KeyboardMode]
|
||||
def parse_options_for_map(val: str) -> tuple[KeyMapOptions, str]:
|
||||
expecting_arg = ''
|
||||
ans = KeyMapOptions()
|
||||
key_map_option_converters: dict[str, Callable[[str], object]] = {
|
||||
'timeout': float,
|
||||
}
|
||||
s = Shlex(val)
|
||||
while (tok := s.next_word())[0] > -1:
|
||||
x = tok[1]
|
||||
if expecting_arg:
|
||||
object.__setattr__(ans, expecting_arg, x)
|
||||
convert = key_map_option_converters.get(expecting_arg)
|
||||
object.__setattr__(ans, expecting_arg, convert(x) if convert else x)
|
||||
expecting_arg = ''
|
||||
elif x.startswith('--'):
|
||||
expecting_arg = x[2:]
|
||||
@@ -1375,7 +1382,8 @@ def parse_options_for_map(val: str) -> tuple[KeyMapOptions, str]:
|
||||
if expecting_arg not in allowed_key_map_options:
|
||||
raise KeyError(f'The map option {x} is unknown. Allowed options: {", ".join(allowed_key_map_options)}')
|
||||
if sep == '=':
|
||||
object.__setattr__(ans, k, v)
|
||||
convert = key_map_option_converters.get(k)
|
||||
object.__setattr__(ans, k, convert(v) if convert else v)
|
||||
expecting_arg = ''
|
||||
else:
|
||||
return ans, val[tok[0]:]
|
||||
|
||||
Reference in New Issue
Block a user