Cleanup previous PR

Actually respect the fallback order when finding matching shortcuts
This commit is contained in:
Kovid Goyal 2026-03-26 09:30:14 +05:30
parent 3e5b3eb55d
commit 88ee80b327
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 130 additions and 90 deletions

View file

@ -168,6 +168,8 @@ Detailed list of changes
0.47.0 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- For builtin key mappings automatically fallback to matching ASCII key when the pressed key has no matches and is a non-English character (:pull:`9671`)
- :doc:`Remote control <remote-control>`: Expose :code:`session_name` in the output of ``kitten @ ls`` for each window (:iss:`9732`)
- Fix thickness of diagonal lines in box drawing characters not the same as horizontal/vertical lines (:iss:`9719`)
@ -184,6 +186,7 @@ Detailed list of changes
- The :opt:`show_hyperlink_targets` option now allows specifying a keyboard modifier so that target URLs are only shown on hover when the modifier is pressed (:pull:`9741`)
0.46.2 [2026-03-21]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -173,6 +173,8 @@ def generate_class(defn: Definition, loc: str) -> tuple[str, str]:
fmod = f'{loc}.options.utils'
imports.add((fmod, ftype))
return ftype
if loc == 'kitty':
imports.add(('kitty.options.utils', 'KeyFallbackType'))
for aname, action in defn.actions.items():
option_names.add(aname)
@ -620,7 +622,7 @@ def gen_go_code(defn: Definition) -> str:
a('KeyboardShortcuts: []*config.KeyAction{')
for sc in keyboard_shortcuts:
options, leftover = parse_options_for_map(sc.parseable_text)
allow_fallback = options.allow_fallback
allow_fallback = ','.join(x.value for x in options.allow_fallback)
key_spec, action = leftover.split(None, 1)
aname, _, aargs = action.partition(' ')
aname = serialize_as_go_string(aname)

View file

@ -445,7 +445,7 @@ class ShortcutMapping(Mapping):
from kitty.options.utils import parse_options_for_map
_, remainder = parse_options_for_map(raw_definition)
parts = remainder.split(maxsplit=1)
self.key = parts[0] if parts else ''
self.key = parts[0]
self.action_def = parts[1] if len(parts) > 1 else ''
@property

View file

@ -23,14 +23,14 @@ from .fast_data_types import (
set_ignore_os_keyboard_processing,
)
from .options.types import Options
from .options.utils import KeyboardMode, KeyDefinition, KeyMap, KeyMapOptions
from .options.utils import KeyboardMode, KeyDefinition, KeyFallbackType, KeyMap, KeyMapOptions
from .typing_compat import ScreenType
if TYPE_CHECKING:
from .window import Window
mod_mask = GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER | GLFW_MOD_META | GLFW_MOD_HYPER
_global_shortcut_options = KeyMapOptions(allow_fallback='shifted,ascii')
_global_shortcut_options = KeyMapOptions(allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate))
def keyboard_mode_name(screen: ScreenType) -> str:
@ -43,24 +43,37 @@ def keyboard_mode_name(screen: ScreenType) -> str:
def get_shortcut(keymap: KeyMap, ev: KeyEvent) -> list[KeyDefinition] | None:
mods = ev.mods & mod_mask
ans = keymap.get(SingleKey(mods, False, ev.key))
if ans is None and ev.shifted_key and mods & GLFW_MOD_SHIFT:
candidate = keymap.get(SingleKey(mods & (~GLFW_MOD_SHIFT), False, ev.shifted_key))
if candidate:
filtered = [d for d in candidate if 'shifted' in d.options.allow_fallback]
if filtered:
ans = filtered
if ans is None and ev.alternate_key and 127 < ev.key < 0xE000:
candidate = keymap.get(SingleKey(mods, False, ev.alternate_key))
if candidate:
filtered = [d for d in candidate if 'ascii' in d.options.allow_fallback]
if filtered:
ans = filtered
if ans is None:
priority_map: dict[int, int] = {}
items: list[KeyDefinition] = []
def add(q: list[KeyDefinition] | None, ft: KeyFallbackType) -> None:
if q:
for d in q:
prio = -1
for i, x in enumerate(d.options.allow_fallback):
if x is ft:
prio = i
break
key = id(d)
if -1 < prio < priority_map.get(key, 100000):
if key not in priority_map:
items.append(d)
priority_map[key] = prio
if ev.shifted_key and mods & GLFW_MOD_SHIFT:
add(keymap.get(SingleKey(mods & (~GLFW_MOD_SHIFT), False, ev.shifted_key)), KeyFallbackType.shifted)
if ev.alternate_key and 127 < ev.key < 0xE000:
add(keymap.get(SingleKey(mods, False, ev.alternate_key)), KeyFallbackType.alternate)
if items:
ans = sorted(items, key=lambda x: priority_map[id(x)])
if ans is None:
ans = keymap.get(SingleKey(mods, True, ev.native_key))
return ans
def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool:
' used only for testing '
mods = ev.mods & mod_mask
smods = s.mods & mod_mask
if s.is_native:

96
kitty/options/types.py generated
View file

@ -11,8 +11,8 @@ import kitty.fast_data_types
from kitty.fonts import FontSpec
import kitty.fonts
from kitty.options.utils import (
AliasMap, KeyDefinition, KeyMapOptions, KeyboardModeMap, MouseHideWait, MouseMap, MouseMapping,
NotifyOnCmdFinish, TabBarMarginHeight
AliasMap, KeyDefinition, KeyFallbackType, KeyMapOptions, KeyboardModeMap, MouseHideWait, MouseMap,
MouseMapping, NotifyOnCmdFinish, TabBarMarginHeight
)
import kitty.options.utils
from kitty.types import FloatEdges
@ -848,23 +848,23 @@ defaults.watcher = {}
defaults.map = [
# copy_to_clipboard
KeyDefinition(trigger=SingleKey(mods=256, key=99), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='copy_to_clipboard'),
KeyDefinition(trigger=SingleKey(mods=256, key=99), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='copy_to_clipboard'),
# paste_from_clipboard
KeyDefinition(trigger=SingleKey(mods=256, key=118), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='paste_from_clipboard'),
KeyDefinition(trigger=SingleKey(mods=256, key=118), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='paste_from_clipboard'),
# paste_from_selection
KeyDefinition(trigger=SingleKey(mods=256, key=115), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='paste_from_selection'),
KeyDefinition(trigger=SingleKey(mods=256, key=115), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='paste_from_selection'),
# paste_from_selection
KeyDefinition(trigger=SingleKey(mods=1, key=57348), definition='paste_from_selection'),
# pass_selection_to_program
KeyDefinition(trigger=SingleKey(mods=256, key=111), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='pass_selection_to_program'),
KeyDefinition(trigger=SingleKey(mods=256, key=111), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='pass_selection_to_program'),
# scroll_line_up
KeyDefinition(trigger=SingleKey(mods=256, key=57352), definition='scroll_line_up'),
# scroll_line_up
KeyDefinition(trigger=SingleKey(mods=256, key=107), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='scroll_line_up'),
KeyDefinition(trigger=SingleKey(mods=256, key=107), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_up'),
# scroll_line_down
KeyDefinition(trigger=SingleKey(mods=256, key=57353), definition='scroll_line_down'),
# scroll_line_down
KeyDefinition(trigger=SingleKey(mods=256, key=106), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='scroll_line_down'),
KeyDefinition(trigger=SingleKey(mods=256, key=106), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_down'),
# scroll_page_up
KeyDefinition(trigger=SingleKey(mods=256, key=57354), definition='scroll_page_up'),
# scroll_page_down
@ -874,33 +874,33 @@ defaults.map = [
# scroll_end
KeyDefinition(trigger=SingleKey(mods=256, key=57357), definition='scroll_end'),
# scroll_to_previous_prompt
KeyDefinition(trigger=SingleKey(mods=256, key=122), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='scroll_to_prompt -1'),
KeyDefinition(trigger=SingleKey(mods=256, key=122), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_to_prompt -1'),
# scroll_to_next_prompt
KeyDefinition(trigger=SingleKey(mods=256, key=120), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='scroll_to_prompt 1'),
KeyDefinition(trigger=SingleKey(mods=256, key=120), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_to_prompt 1'),
# show_scrollback
KeyDefinition(trigger=SingleKey(mods=256, key=104), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='show_scrollback'),
KeyDefinition(trigger=SingleKey(mods=256, key=104), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='show_scrollback'),
# show_last_command_output
KeyDefinition(trigger=SingleKey(mods=256, key=103), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='show_last_command_output'),
KeyDefinition(trigger=SingleKey(mods=256, key=103), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='show_last_command_output'),
# search_scrollback
KeyDefinition(trigger=SingleKey(mods=256, key=47), definition='search_scrollback'),
# new_window
KeyDefinition(trigger=SingleKey(mods=256, key=57345), definition='new_window'),
# new_os_window
KeyDefinition(trigger=SingleKey(mods=256, key=110), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='new_os_window'),
KeyDefinition(trigger=SingleKey(mods=256, key=110), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='new_os_window'),
# close_window
KeyDefinition(trigger=SingleKey(mods=256, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='close_window'),
KeyDefinition(trigger=SingleKey(mods=256, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='close_window'),
# next_window
KeyDefinition(trigger=SingleKey(mods=256, key=93), definition='next_window'),
# previous_window
KeyDefinition(trigger=SingleKey(mods=256, key=91), definition='previous_window'),
# move_window_forward
KeyDefinition(trigger=SingleKey(mods=256, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='move_window_forward'),
KeyDefinition(trigger=SingleKey(mods=256, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='move_window_forward'),
# move_window_backward
KeyDefinition(trigger=SingleKey(mods=256, key=98), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='move_window_backward'),
KeyDefinition(trigger=SingleKey(mods=256, key=98), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='move_window_backward'),
# move_window_to_top
KeyDefinition(trigger=SingleKey(mods=256, key=96), definition='move_window_to_top'),
# start_resizing_window
KeyDefinition(trigger=SingleKey(mods=256, key=114), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='start_resizing_window'),
KeyDefinition(trigger=SingleKey(mods=256, key=114), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='start_resizing_window'),
# first_window
KeyDefinition(trigger=SingleKey(mods=256, key=49), definition='first_window'),
# second_window
@ -934,17 +934,17 @@ defaults.map = [
# previous_tab
KeyDefinition(trigger=SingleKey(mods=5, key=57346), definition='previous_tab'),
# new_tab
KeyDefinition(trigger=SingleKey(mods=256, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='new_tab'),
KeyDefinition(trigger=SingleKey(mods=256, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='new_tab'),
# close_tab
KeyDefinition(trigger=SingleKey(mods=256, key=113), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='close_tab'),
KeyDefinition(trigger=SingleKey(mods=256, key=113), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='close_tab'),
# move_tab_forward
KeyDefinition(trigger=SingleKey(mods=256, key=46), definition='move_tab_forward'),
# move_tab_backward
KeyDefinition(trigger=SingleKey(mods=256, key=44), definition='move_tab_backward'),
# set_tab_title
KeyDefinition(trigger=SingleKey(mods=258, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_tab_title'),
KeyDefinition(trigger=SingleKey(mods=258, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_tab_title'),
# next_layout
KeyDefinition(trigger=SingleKey(mods=256, key=108), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='next_layout'),
KeyDefinition(trigger=SingleKey(mods=256, key=108), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='next_layout'),
# increase_font_size
KeyDefinition(trigger=SingleKey(mods=256, key=61), definition='change_font_size all +2.0'),
# increase_font_size
@ -958,25 +958,25 @@ defaults.map = [
# reset_font_size
KeyDefinition(trigger=SingleKey(mods=256, key=57347), definition='change_font_size all 0'),
# open_url
KeyDefinition(trigger=SingleKey(mods=256, key=101), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='open_url_with_hints'),
KeyDefinition(trigger=SingleKey(mods=256, key=101), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='open_url_with_hints'),
# insert_selected_path
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=102),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type path --program -'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=102),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type path --program -'),
# open_selected_path
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(mods=1, key=102),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type path'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(mods=1, key=102),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type path'),
# insert_chosen_file
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=99),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten choose-files'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=99),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten choose-files'),
# insert_chosen_directory
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=100),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten choose-files --mode=dir'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=100),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten choose-files --mode=dir'),
# insert_selected_line
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=108),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type line --program -'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=108),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type line --program -'),
# insert_selected_word
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=119),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type word --program -'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=119),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type word --program -'),
# insert_selected_hash
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=104),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type hash --program -'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=104),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type hash --program -'),
# goto_file_line
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=110),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type linenum'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=110),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type linenum'),
# open_selected_hyperlink
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=121),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten hints --type hyperlink'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=121),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten hints --type hyperlink'),
# show_kitty_doc
KeyDefinition(trigger=SingleKey(mods=256, key=57364), definition='show_kitty_doc overview'),
# command_palette
@ -986,19 +986,19 @@ defaults.map = [
# toggle_maximized
KeyDefinition(trigger=SingleKey(mods=256, key=57373), definition='toggle_maximized'),
# input_unicode_character
KeyDefinition(trigger=SingleKey(mods=256, key=117), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='kitten unicode_input'),
KeyDefinition(trigger=SingleKey(mods=256, key=117), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='kitten unicode_input'),
# edit_config_file
KeyDefinition(trigger=SingleKey(mods=256, key=57365), definition='edit_config_file'),
# kitty_shell
KeyDefinition(trigger=SingleKey(mods=256, key=57344), definition='kitty_shell window'),
# increase_background_opacity
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=109),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_background_opacity +0.1'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=109),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_background_opacity +0.1'),
# decrease_background_opacity
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=108),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_background_opacity -0.1'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=108),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_background_opacity -0.1'),
# full_background_opacity
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=49),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_background_opacity 1'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=49),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_background_opacity 1'),
# reset_background_opacity
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=100),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_background_opacity default'),
KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=100),), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_background_opacity default'),
# reset_terminal
KeyDefinition(trigger=SingleKey(mods=256, key=57349), definition='clear_terminal reset active'),
# reload_config_file
@ -1008,8 +1008,8 @@ defaults.map = [
]
if is_macos:
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='copy_or_noop'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=118), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='paste_from_clipboard'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='copy_or_noop'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=118), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='paste_from_clipboard'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57354), definition='scroll_line_up'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57352), definition='scroll_line_up'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57355), definition='scroll_line_down'))
@ -1018,11 +1018,11 @@ if is_macos:
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57355), definition='scroll_page_down'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57356), definition='scroll_home'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57357), definition='scroll_end'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='search_scrollback'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='search_scrollback'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57345), definition='new_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=110), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='new_os_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=100), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='close_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=114), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='start_resizing_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=110), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='new_os_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=100), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='close_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=114), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='start_resizing_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=49), definition='first_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=50), definition='second_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=51), definition='third_window'))
@ -1034,18 +1034,18 @@ if is_macos:
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57), definition='ninth_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=93), definition='next_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=91), definition='previous_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='new_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='close_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='close_os_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=105), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='set_tab_title'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=116), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='new_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='close_tab'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=119), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='close_os_window'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=105), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='set_tab_title'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=43), definition='change_font_size all +2.0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=61), definition='change_font_size all +2.0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=61), definition='change_font_size all +2.0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=45), definition='change_font_size all -2.0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=45), definition='change_font_size all -2.0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=48), definition='change_font_size all 0'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='toggle_fullscreen'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=115), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback='ascii,shifted'), definition='toggle_macos_secure_keyboard_entry'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=102), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='toggle_fullscreen'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=115), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='toggle_macos_secure_keyboard_entry'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=96), definition='macos_cycle_through_os_windows'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=96), definition='macos_cycle_through_os_windows_backwards'))
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=32), definition='kitten unicode_input'))

View file

@ -1292,7 +1292,7 @@ class LiteralField(Generic[T]):
val = obj.__dict__.get(self._name)
if val is None or isinstance(val, LiteralField):
return self._vals[0]
return val
return cast(T, val)
def __set__(self, obj: object, value: str) -> None:
if value not in self._vals:
@ -1304,6 +1304,14 @@ OnUnknown = Literal['beep', 'end', 'ignore', 'passthrough']
OnAction = Literal['keep', 'end']
class KeyFallbackType(enum.Enum):
shifted = 'shifted'
alternate = 'alternate'
def __repr__(self) -> str:
return f'KeyFallbackType.{self.value}'
@dataclass(frozen=True)
class KeyMapOptions:
when_focus_on: str = ''
@ -1312,7 +1320,7 @@ class KeyMapOptions:
on_unknown: LiteralField[OnUnknown] = LiteralField[OnUnknown](get_args(OnUnknown))
on_action: LiteralField[OnAction] = LiteralField[OnAction](get_args(OnAction))
timeout: float | None = None
allow_fallback: str = 'shifted'
allow_fallback: tuple[KeyFallbackType, ...] = (KeyFallbackType.shifted,)
default_key_map_options = KeyMapOptions()
@ -1385,17 +1393,19 @@ key_map_option_converters: defaultdict[str, Callable[[str], Any]] = defaultdict(
key_map_option_converters['timeout'] = float
_allowed_fallback_values = frozenset(('shifted', 'ascii'))
def _convert_allow_fallback(val: str) -> str:
if not val or val == 'none':
return ''
parts = tuple(x.strip() for x in val.split(','))
invalid = set(parts) - _allowed_fallback_values
if invalid:
raise ValueError(f'allow_fallback values must be a subset of {_allowed_fallback_values}, got: {invalid}')
return ','.join(parts)
def _convert_allow_fallback(val: str) -> tuple[KeyFallbackType, ...]:
match val:
case '' | 'none':
return ()
case 'shifted,ascii':
return (KeyFallbackType.shifted, KeyFallbackType.alternate)
case 'ascii,shifted':
return (KeyFallbackType.alternate, KeyFallbackType.shifted)
case 'shifted':
return (KeyFallbackType.shifted,)
case 'ascii':
return (KeyFallbackType.alternate,)
raise ValueError(f'allow_fallback values must be a subset of shifted, ascii, got: {val}')
key_map_option_converters['allow_fallback'] = _convert_allow_fallback

View file

@ -6,6 +6,7 @@ from functools import partial
import kitty.fast_data_types as defines
from kitty.key_encoding import EventType, KeyEvent, decode_key_event, encode_key_event
from kitty.keys import Mappings
from kitty.options.utils import KeyFallbackType
from . import BaseTest
@ -680,7 +681,7 @@ class TestKeys(BaseTest):
def test_get_shortcut_per_mapping_fallback(self):
from kitty.keys import get_shortcut
from kitty.options.utils import KeyDefinition, KeyMapOptions
from kitty.options.utils import KeyDefinition, KeyMapOptions, _convert_allow_fallback
ctrl = defines.GLFW_MOD_CONTROL
shift = defines.GLFW_MOD_SHIFT
@ -688,8 +689,7 @@ class TestKeys(BaseTest):
latin_c = ord('c')
def make_kd(definition='test_action', allow_fallback='shifted'):
opts = KeyMapOptions()
object.__setattr__(opts, 'allow_fallback', allow_fallback)
opts = KeyMapOptions(allow_fallback=_convert_allow_fallback(allow_fallback))
return KeyDefinition(definition=definition, options=opts)
# non-ASCII key + alternate_key + allow_fallback includes ascii → match
@ -700,6 +700,18 @@ class TestKeys(BaseTest):
self.assertIsNotNone(result)
self.assertIs(result[0], kd_ascii)
# sorting by fallback order
kd1 = make_kd('ascii', allow_fallback='ascii,shifted')
kd2 = make_kd('shifted', allow_fallback='shifted,ascii')
keymap = {defines.SingleKey(ctrl, False, latin_c): [kd1, kd2]}
ev = defines.KeyEvent(cyrillic_s, 0, latin_c, ctrl)
result = get_shortcut(keymap, ev)
self.assertEqual(result, [kd1, kd2])
keymap = {defines.SingleKey(0, False, latin_c): [kd1, kd2]}
ev = defines.KeyEvent(ord('C'), latin_c, 0, shift)
result = get_shortcut(keymap, ev)
self.assertEqual(result, [kd2, kd1])
# non-ASCII key + alternate_key + allow_fallback='shifted' (no ascii) → no ascii match
kd_shifted_only = make_kd('copy', allow_fallback='shifted')
keymap = {defines.SingleKey(ctrl, False, latin_c): [kd_shifted_only]}
@ -842,34 +854,34 @@ class TestKeys(BaseTest):
# default: no --allow-fallback → allow_fallback='shifted'
kd = first_kd('ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, 'shifted')
self.ae(kd.options.allow_fallback, (KeyFallbackType.shifted,))
# --allow-fallback=shifted,ascii
kd = first_kd('--allow-fallback=shifted,ascii ctrl+c copy_to_clipboard')
self.assertIn('shifted', kd.options.allow_fallback)
self.assertIn('ascii', kd.options.allow_fallback)
self.assertIn(KeyFallbackType.shifted, kd.options.allow_fallback)
self.assertIn(KeyFallbackType.alternate, kd.options.allow_fallback)
# --allow-fallback=ascii (only ascii, no shifted)
kd = first_kd('--allow-fallback=ascii ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, 'ascii')
self.assertNotIn('shifted', kd.options.allow_fallback)
self.ae(kd.options.allow_fallback, (KeyFallbackType.alternate,))
self.assertNotIn(KeyFallbackType.shifted, kd.options.allow_fallback)
# --allow-fallback=shifted (explicit, same as default)
kd = first_kd('--allow-fallback=shifted ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, 'shifted')
self.ae(kd.options.allow_fallback, (KeyFallbackType.shifted,))
# invalid value raises
self.assertRaises(ValueError, first_kd, '--allow-fallback=bogus ctrl+c copy_to_clipboard')
# order normalization: ascii,shifted → sorted as ascii,shifted
kd = first_kd('--allow-fallback=ascii,shifted ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, 'ascii,shifted')
self.ae(kd.options.allow_fallback, (KeyFallbackType.alternate, KeyFallbackType.shifted))
# --allow-fallback=none → empty string (no fallback)
kd = first_kd('--allow-fallback=none ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, '')
self.ae(kd.options.allow_fallback, ())
# combined with other options
kd = first_kd('--when-focus-on 1 --allow-fallback=ascii ctrl+c copy_to_clipboard')
self.ae(kd.options.allow_fallback, 'ascii')
self.ae(kd.options.allow_fallback, (KeyFallbackType.alternate,))
self.ae(kd.options.when_focus_on, '1')

View file

@ -259,7 +259,7 @@ func validateAllowFallback(value string) error {
if value == "" || value == "none" {
return nil
}
for _, part := range strings.Split(value, ",") {
for part := range strings.SplitSeq(value, ",") {
part = strings.TrimSpace(part)
if part != "shifted" && part != "ascii" {
return fmt.Errorf("Invalid allow-fallback value %#v, allowed values: shifted, ascii, none", part)