Allow running mappable actions via remote control
Saves me having to define a special remote control wrapper for every mappable action.
This commit is contained in:
parent
ac7b6870a8
commit
54548931b5
7 changed files with 100 additions and 4 deletions
|
|
@ -48,6 +48,8 @@ Detailed list of changes
|
|||
|
||||
- kitten @ load-config: Allow (re)loading kitty.conf via remote control
|
||||
|
||||
- Remote control: Allow running mappable actions via remote control (`kitten @ action`)
|
||||
|
||||
- kitten @ send-text: Add a new option to automatically wrap the sent text in
|
||||
bracketed paste escape codes if the program in the destination window has
|
||||
turned on bracketed paste.
|
||||
|
|
|
|||
|
|
@ -1550,7 +1550,7 @@ def user_menu_action(self, defn: str) -> None:
|
|||
|
||||
map kitty_mod+e combine : new_window : next_layout
|
||||
''')
|
||||
def combine(self, action_definition: str, window_for_dispatch: Optional[Window] = None, dispatch_type: str = 'KeyPress') -> bool:
|
||||
def combine(self, action_definition: str, window_for_dispatch: Optional[Window] = None, dispatch_type: str = 'KeyPress', raise_error: bool = False) -> bool:
|
||||
consumed = False
|
||||
if action_definition:
|
||||
try:
|
||||
|
|
@ -1565,6 +1565,8 @@ def combine(self, action_definition: str, window_for_dispatch: Optional[Window]
|
|||
if len(actions) > 1:
|
||||
self.drain_actions(list(actions[1:]), window_for_dispatch, dispatch_type)
|
||||
except Exception as e:
|
||||
if raise_error:
|
||||
raise
|
||||
self.show_error('Key action failed', f'{actions[0].pretty()}\n{e}')
|
||||
consumed = True
|
||||
return consumed
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class CLIOptions:
|
|||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
|
||||
ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = CLIOptions
|
||||
ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions
|
||||
|
||||
|
||||
def generate_stub() -> None:
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ def parse_launch_args(args: Optional[Sequence[str]] = None) -> LaunchSpec:
|
|||
try:
|
||||
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
|
||||
except SystemExit as e:
|
||||
raise ValueError from e
|
||||
raise ValueError(str(e)) from e
|
||||
return LaunchSpec(opts, args)
|
||||
|
||||
|
||||
|
|
|
|||
77
kitty/rc/action.py
Normal file
77
kitty/rc/action.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION,
|
||||
ArgsType,
|
||||
Boss,
|
||||
PayloadGetType,
|
||||
PayloadType,
|
||||
RCOptions,
|
||||
RemoteCommand,
|
||||
ResponseType,
|
||||
Window,
|
||||
)
|
||||
from .base import (
|
||||
RemoteControlErrorWithoutTraceback as RemoteControlError,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import ActionRCOptions as CLIOptions
|
||||
|
||||
|
||||
class Action(RemoteCommand):
|
||||
|
||||
protocol_spec = __doc__ = '''
|
||||
action+/str: The action to perform. Of the form: action [optional args...]
|
||||
match_window/str: Window to run the action on
|
||||
self/bool: Whether to use the window this command is run in as the active window
|
||||
'''
|
||||
|
||||
short_desc = 'Run the specified mappable action'
|
||||
desc = (
|
||||
'Run the specified mappable action. For a list of all available mappable actions, see :doc:`actions`.'
|
||||
' Any arguments for ACTION should follow the action. Note that parsing of arguments is action dependent'
|
||||
' so for best results specify all arguments as single string on the command line in the same format as you would'
|
||||
' use for that action in kitty.conf.'
|
||||
)
|
||||
options_spec = '''\
|
||||
--self
|
||||
type=bool-set
|
||||
Run the action on the window this command is run in instead of the active window.
|
||||
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response indicating the success of the action. Note that
|
||||
using this option means that you will not be notified of failures.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION
|
||||
|
||||
args = RemoteCommand.Args(spec='ACTION [ARGS FOR ACTION...]', json_field='action', minimum_count=1)
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'action': ' '.join(args), 'self': opts.self, 'match_window': opts.match}
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
w = self.windows_for_match_payload(boss, window, payload_get)
|
||||
if w:
|
||||
window = w[0]
|
||||
ac = payload_get('action')
|
||||
if not ac:
|
||||
raise RemoteControlError('Must specify an action')
|
||||
|
||||
try:
|
||||
consumed = boss.combine(str(ac), window, raise_error=True)
|
||||
except (Exception, SystemExit) as e:
|
||||
raise RemoteControlError(str(e))
|
||||
|
||||
if not consumed:
|
||||
raise RemoteControlError(f'Unknown action: {ac}')
|
||||
return None
|
||||
|
||||
|
||||
action = Action()
|
||||
|
|
@ -31,6 +31,11 @@ class RemoteControlError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class RemoteControlErrorWithoutTraceback(Exception):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
class MatchError(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
|
@ -219,7 +224,10 @@ def as_go_code(self, cmd_name: str, field_types: Dict[str, str], handled_fields:
|
|||
yield f'args = append(args, "{x}")'
|
||||
yield '}'
|
||||
if self.minimum_count > -1:
|
||||
yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", "Must specify at least {self.minimum_count} arguments to {cmd_name}") }}'
|
||||
if self.minimum_count == 1:
|
||||
yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", "Must specify at least one argument to {cmd_name}") }}'
|
||||
else:
|
||||
yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", "Must specify at least {self.minimum_count} arguments to {cmd_name}") }}'
|
||||
if self.args_choices:
|
||||
achoices = tuple(self.args_choices())
|
||||
yield 'achoices := map[string]bool{' + ' '.join(f'"{x}":true,' for x in achoices) + '}'
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ class LoadConfig(RemoteCommand):
|
|||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: :italic:`name=value`. For example: :option:`{appname} -o` font_size=20
|
||||
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response indicating the success of the action. Note that
|
||||
using this option means that you will not be notified of failures.
|
||||
'''
|
||||
|
||||
args = RemoteCommand.Args(spec='CONF_FILE ...', json_field='paths',
|
||||
|
|
|
|||
Loading…
Reference in a new issue