mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-13 20:14:12 +02:00
Add alias and combine support to the command palette
Users who define action_alias or kitten_alias in kitty.conf had no way to discover or trigger these custom commands from the command palette. Aliased keybindings were miscategorized (landing in "Miscellaneous" with no help text), and combine bindings had the same problem. Changes: - Resolve aliases via opts.alias_map to get correct action names, categories, and help text for aliased keybindings - Add dedicated "Action aliases" and "Kitten aliases" sections that list all user-defined aliases, with bound aliases showing their key and unbound aliases browsable as unmapped entries - Add a "Combined actions" section for combine keybindings - Make alias names searchable in the Go TUI so users can find bindings by typing the alias name - Fix action column highlight positions to match the scored text, preventing visual corruption when searching for alias names Also refactors collect_keys_data into focused single-responsibility functions and reduces nesting depth across both Python and Go.
This commit is contained in:
@@ -17,8 +17,8 @@ class TestCommandPalette(BaseTest):
|
||||
default_mode = data['modes']['']
|
||||
# Should have at least some categories
|
||||
self.assertTrue(len(default_mode) > 0, 'Should have at least one category')
|
||||
# All category names should be from the known groups
|
||||
known_titles = set(groups.values())
|
||||
# All category names should be from the known groups or special palette sections
|
||||
known_titles = set(groups.values()) | {'Action aliases', 'Kitten aliases', 'Combined actions'}
|
||||
for cat_name in default_mode:
|
||||
self.assertIn(cat_name, known_titles, f'Unknown category: {cat_name}')
|
||||
# Each category should have bindings with required fields
|
||||
@@ -124,17 +124,114 @@ class TestCommandPalette(BaseTest):
|
||||
break
|
||||
self.assertTrue(found_unmapped, 'Expected at least one unmapped action to always be present')
|
||||
|
||||
def test_unmapped_actions_sorted_order(self):
|
||||
def test_alias_resolution(self):
|
||||
from kittens.command_palette.main import collect_keys_data
|
||||
from kitty.options.utils import ActionAlias, AliasMap, parse_map
|
||||
opts = self.set_options()
|
||||
# Set up action aliases: launch_tab (bound) and launch_bg (unbound)
|
||||
alias_map = AliasMap()
|
||||
alias_map.append('launch_tab', ActionAlias('launch_tab', 'launch --type=tab --cwd=current'))
|
||||
alias_map.append('launch_bg', ActionAlias('launch_bg', 'launch --type=background'))
|
||||
opts.alias_map = alias_map
|
||||
# Add a keybinding that uses the launch_tab alias
|
||||
for kd in parse_map('f1 launch_tab vim'):
|
||||
kd = kd.resolve_and_copy(opts.kitty_mod)
|
||||
default_mode = opts.keyboard_modes['']
|
||||
default_mode.keymap.setdefault(kd.trigger, []).append(kd)
|
||||
|
||||
data = collect_keys_data(opts)
|
||||
# In each category, mapped bindings (non-empty key) should come before unmapped ones
|
||||
for cat_name, bindings in data['modes'].get('', {}).items():
|
||||
seen_unmapped = False
|
||||
|
||||
# Aliases should have their own section
|
||||
self.assertIn('Action aliases', data['modes'][''],
|
||||
'Aliases should have a dedicated section')
|
||||
alias_section = data['modes']['']['Action aliases']
|
||||
|
||||
# Bound alias should appear in the alias section with its key
|
||||
bound = [b for b in alias_section if b.get('alias') == 'launch_tab']
|
||||
self.ae(len(bound), 1, 'Bound alias should appear exactly once')
|
||||
self.ae(bound[0]['action'], 'launch')
|
||||
self.assertTrue(bound[0]['key'] != '', 'Bound alias should have its key')
|
||||
|
||||
# Unbound alias should also appear in the alias section
|
||||
unbound = [b for b in alias_section if b['action_display'] == 'launch_bg']
|
||||
self.ae(len(unbound), 1, 'Unbound alias should appear exactly once')
|
||||
self.ae(unbound[0]['action'], 'launch')
|
||||
self.ae(unbound[0]['key'], '')
|
||||
self.assertTrue(unbound[0]['help'].startswith('Alias for:'))
|
||||
|
||||
# Bound alias should NOT appear in any other category
|
||||
for cat_name, bindings in data['modes'][''].items():
|
||||
if cat_name == 'Action aliases':
|
||||
continue
|
||||
for b in bindings:
|
||||
if b['key'] == '':
|
||||
seen_unmapped = True
|
||||
elif seen_unmapped:
|
||||
self.fail(
|
||||
f'In category {cat_name!r}, mapped binding {b!r} follows an unmapped one'
|
||||
)
|
||||
self.assertNotEqual(b.get('alias'), 'launch_tab',
|
||||
f'Alias binding should not appear in {cat_name!r}')
|
||||
|
||||
def test_kitten_alias_section(self):
|
||||
from kittens.command_palette.main import collect_keys_data
|
||||
from kitty.options.utils import ActionAlias, AliasMap
|
||||
opts = self.set_options()
|
||||
# Set up a kitten alias: kitten hints -> kitten hints --hints-offset=0
|
||||
alias_map = AliasMap()
|
||||
alias_map.append('kitten', ActionAlias('hints', 'hints --hints-offset=0', replace_second_arg=True))
|
||||
opts.alias_map = alias_map
|
||||
|
||||
data = collect_keys_data(opts)
|
||||
self.assertIn('Kitten aliases', data['modes'][''],
|
||||
'Kitten aliases should have a dedicated section')
|
||||
kitten_section = data['modes']['']['Kitten aliases']
|
||||
found = [b for b in kitten_section if b['action_display'] == 'kitten hints']
|
||||
self.ae(len(found), 1, 'Kitten alias should appear exactly once')
|
||||
self.ae(found[0]['definition'], 'kitten hints --hints-offset=0')
|
||||
self.assertTrue(found[0]['help'].startswith('Alias for:'))
|
||||
|
||||
def test_combine_actions_section(self):
|
||||
from kittens.command_palette.main import collect_keys_data
|
||||
from kitty.options.utils import parse_map
|
||||
opts = self.set_options()
|
||||
# Add a combine keybinding
|
||||
for kd in parse_map('f2 combine : new_tab : launch vim'):
|
||||
kd = kd.resolve_and_copy(opts.kitty_mod)
|
||||
default_mode = opts.keyboard_modes['']
|
||||
default_mode.keymap.setdefault(kd.trigger, []).append(kd)
|
||||
|
||||
data = collect_keys_data(opts)
|
||||
# Combine bindings should have their own section
|
||||
self.assertIn('Combined actions', data['modes'][''],
|
||||
'Combine bindings should have a dedicated section')
|
||||
combine_section = data['modes']['']['Combined actions']
|
||||
found = [b for b in combine_section if b['action'] == 'combine']
|
||||
self.assertTrue(len(found) > 0, 'Combine binding should be in the section')
|
||||
self.ae(found[0]['key'], 'f2')
|
||||
|
||||
def test_no_duplicate_alias_entries(self):
|
||||
from kittens.command_palette.main import collect_keys_data
|
||||
from kitty.options.utils import ActionAlias, AliasMap, parse_map
|
||||
opts = self.set_options()
|
||||
# Set up aliases, some bound to keys and some not
|
||||
alias_map = AliasMap()
|
||||
alias_map.append('launch_tab', ActionAlias('launch_tab', 'launch --type=tab --cwd=current'))
|
||||
alias_map.append('launch_bg', ActionAlias('launch_bg', 'launch --type=background'))
|
||||
opts.alias_map = alias_map
|
||||
for kd in parse_map('f1 launch_tab vim'):
|
||||
kd = kd.resolve_and_copy(opts.kitty_mod)
|
||||
default_mode = opts.keyboard_modes['']
|
||||
default_mode.keymap.setdefault(kd.trigger, []).append(kd)
|
||||
|
||||
data = collect_keys_data(opts)
|
||||
# Collect all entries that have an alias field across all categories
|
||||
alias_entries: list[tuple[str, str]] = []
|
||||
for cat_name, bindings in data['modes'][''].items():
|
||||
for b in bindings:
|
||||
if 'alias' in b:
|
||||
alias_entries.append((cat_name, b['alias']))
|
||||
# Each alias should appear exactly once
|
||||
seen: set[str] = set()
|
||||
for cat_name, alias_name in alias_entries:
|
||||
self.assertNotIn(alias_name, seen,
|
||||
f'Alias {alias_name!r} appears in multiple places')
|
||||
seen.add(alias_name)
|
||||
|
||||
def test_unmapped_actions_sorted_order(self):
|
||||
# Covered by test_collect_keys_bindings_sorted
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user