Make get_all_actions() work in binary builds
This commit is contained in:
parent
57ced9bc83
commit
276a82d1f7
@ -7,7 +7,7 @@ from typing import Dict, List, NamedTuple
|
||||
|
||||
from .boss import Boss
|
||||
from .tabs import Tab
|
||||
from .types import run_once
|
||||
from .types import run_once, ActionGroup, ActionSpec
|
||||
from .window import Window
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ class Action(NamedTuple):
|
||||
long_help: str
|
||||
|
||||
|
||||
groups = {
|
||||
groups: Dict[ActionGroup, str] = {
|
||||
'cp': 'Copy/paste',
|
||||
'sc': 'Scrolling',
|
||||
'win': 'Window management',
|
||||
@ -34,29 +34,20 @@ group_title = groups.__getitem__
|
||||
@run_once
|
||||
def get_all_actions() -> Dict[str, List[Action]]:
|
||||
' test docstring '
|
||||
if not get_all_actions.__doc__:
|
||||
raise RuntimeError(
|
||||
'This build of kitty does not have docstrings, which'
|
||||
' are needed for get_all_actions(). If you are using a'
|
||||
' kitty binary build, setup KITTY_DEVELOP_FROM'
|
||||
' as described here: https://sw.kovidgoyal.net/kitty/build/'
|
||||
)
|
||||
|
||||
ans: Dict[str, List[Action]] = {}
|
||||
|
||||
def is_action(x: object) -> bool:
|
||||
doc = getattr(x, '__doc__', '')
|
||||
return bool(doc and doc.strip().startswith('@ac:'))
|
||||
return isinstance(getattr(x, 'action_spec', None), ActionSpec)
|
||||
|
||||
def as_action(x: object) -> Action:
|
||||
doc = inspect.cleandoc(x.__doc__ or '')
|
||||
spec: ActionSpec = getattr(x, 'action_spec')
|
||||
doc = inspect.cleandoc(spec.doc)
|
||||
lines = doc.splitlines()
|
||||
first = lines.pop(0)
|
||||
parts = first.split(':', 2)
|
||||
grp = parts[1].strip()
|
||||
short_help = parts[2].strip()
|
||||
short_help = first
|
||||
long_help = '\n'.join(lines).strip()
|
||||
return Action(getattr(x, '__name__'), grp, short_help, long_help)
|
||||
return Action(getattr(x, '__name__'), spec.group, short_help, long_help)
|
||||
|
||||
seen = set()
|
||||
for cls in (Window, Tab, Boss):
|
||||
|
||||
178
kitty/boss.py
178
kitty/boss.py
@ -50,7 +50,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data
|
||||
from .tabs import (
|
||||
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
|
||||
)
|
||||
from .types import SingleKey
|
||||
from .types import SingleKey, ac
|
||||
from .typing import PopenType, TypedDict
|
||||
from .utils import (
|
||||
func_name, get_editor, get_new_os_window_size, get_primary_selection,
|
||||
@ -333,8 +333,8 @@ class Boss:
|
||||
startup_session = next(create_sessions(get_options(), special_window=sw, cwd_from=cwd_from))
|
||||
return self.add_os_window(startup_session)
|
||||
|
||||
@ac('win', 'New OS Window')
|
||||
def new_os_window(self, *args: str) -> None:
|
||||
'@ac:win: New OS Window'
|
||||
self._new_os_window(args)
|
||||
|
||||
@property
|
||||
@ -343,8 +343,8 @@ class Boss:
|
||||
if t is not None:
|
||||
return t.active_window_for_cwd
|
||||
|
||||
@ac('win', 'New OS Window with the same working directory as the currently active window')
|
||||
def new_os_window_with_cwd(self, *args: str) -> None:
|
||||
'@ac:win: New OS Window with the same working directory as the currently active window'
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._new_os_window(args, cwd_from)
|
||||
@ -380,16 +380,16 @@ class Boss:
|
||||
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
|
||||
return response
|
||||
|
||||
def remote_control(self, *args: str) -> None:
|
||||
'''
|
||||
@ac:misc: Run a remote control command
|
||||
@ac('misc', '''
|
||||
Run a remote control command
|
||||
|
||||
For example::
|
||||
|
||||
map F1 remote_control set-spacing margin=30
|
||||
|
||||
See :ref:`rc_mapping` for details.
|
||||
'''
|
||||
''')
|
||||
def remote_control(self, *args: str) -> None:
|
||||
from .rc.base import (
|
||||
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
)
|
||||
@ -501,8 +501,8 @@ class Boss:
|
||||
if window:
|
||||
self.child_monitor.mark_for_close(window.id)
|
||||
|
||||
@ac('tab', 'Close the current tab')
|
||||
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
||||
'@ac:tab: Close the current tab'
|
||||
tab = tab or self.active_tab
|
||||
if tab:
|
||||
self.confirm_tab_close(tab)
|
||||
@ -534,12 +534,12 @@ class Boss:
|
||||
for window in tab:
|
||||
self.close_window(window)
|
||||
|
||||
@ac('win', 'Toggle the fullscreen status of the active OS Window')
|
||||
def toggle_fullscreen(self, os_window_id: int = 0) -> None:
|
||||
'@ac:win: Toggle the fullscreen status of the active OS Window'
|
||||
toggle_fullscreen(os_window_id)
|
||||
|
||||
@ac('win', 'Toggle the maximized status of the active OS Window')
|
||||
def toggle_maximized(self, os_window_id: int = 0) -> None:
|
||||
'@ac:win: Toggle the maximized status of the active OS Window'
|
||||
toggle_maximized(os_window_id)
|
||||
|
||||
def start(self, first_os_window_id: int) -> None:
|
||||
@ -566,9 +566,8 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.resize()
|
||||
|
||||
def clear_terminal(self, action: str, only_active: bool) -> None:
|
||||
'''
|
||||
@ac:misc: Clear the terminal
|
||||
@ac('misc', '''
|
||||
Clear the terminal
|
||||
|
||||
See :sc:`reset_terminal` for details. For example::
|
||||
|
||||
@ -581,7 +580,8 @@ class Boss:
|
||||
# Scroll the contents of the screen into the scrollback
|
||||
map kitty_mod+f12 clear_terminal scroll active
|
||||
|
||||
'''
|
||||
''')
|
||||
def clear_terminal(self, action: str, only_active: bool) -> None:
|
||||
if only_active:
|
||||
windows = []
|
||||
w = self.active_window
|
||||
@ -615,12 +615,12 @@ class Boss:
|
||||
def set_font_size(self, new_size: float) -> None: # legacy
|
||||
self.change_font_size(True, None, new_size)
|
||||
|
||||
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None:
|
||||
'''
|
||||
@ac:win: Change the font size for the current or all OS Windows
|
||||
@ac('win', '''
|
||||
Change the font size for the current or all OS Windows
|
||||
|
||||
See :ref:`conf-kitty-shortcuts.fonts` for details.
|
||||
'''
|
||||
''')
|
||||
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None:
|
||||
def calc_new_size(old_size: float) -> float:
|
||||
new_size = old_size
|
||||
if amt == 0:
|
||||
@ -676,16 +676,16 @@ class Boss:
|
||||
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None:
|
||||
change_background_opacity(os_window_id, max(0.1, min(opacity, 1.0)))
|
||||
|
||||
def set_background_opacity(self, opacity: str) -> None:
|
||||
'''
|
||||
@ac:win: Set the background opacity for the active OS Window
|
||||
@ac('win', '''
|
||||
Set the background opacity for the active OS Window
|
||||
|
||||
For example::
|
||||
|
||||
map f1 set_background_opacity +0.1
|
||||
map f2 set_background_opacity -0.1
|
||||
map f3 set_background_opacity 0.5
|
||||
'''
|
||||
''')
|
||||
def set_background_opacity(self, opacity: str) -> None:
|
||||
window = self.active_window
|
||||
if window is None or not opacity:
|
||||
return
|
||||
@ -761,12 +761,12 @@ class Boss:
|
||||
if matched_action is not None:
|
||||
self.dispatch_action(matched_action)
|
||||
|
||||
def start_resizing_window(self) -> None:
|
||||
'''
|
||||
@ac:win: Resize the active window interactively
|
||||
@ac('win', '''
|
||||
Resize the active window interactively
|
||||
|
||||
See :ref:`window_resizing` for details.
|
||||
'''
|
||||
''')
|
||||
def start_resizing_window(self) -> None:
|
||||
w = self.active_window
|
||||
if w is None:
|
||||
return
|
||||
@ -843,9 +843,8 @@ class Boss:
|
||||
return True
|
||||
return False
|
||||
|
||||
def combine(self, *actions: KeyAction) -> None:
|
||||
'''
|
||||
@ac:misc: Combine multiple actions and map to a single keypress
|
||||
@ac('misc', '''
|
||||
Combine multiple actions and map to a single keypress
|
||||
|
||||
The syntax is::
|
||||
|
||||
@ -854,7 +853,8 @@ class Boss:
|
||||
For example::
|
||||
|
||||
map kitty_mod+e combine : new_window : next_layout
|
||||
'''
|
||||
''')
|
||||
def combine(self, *actions: KeyAction) -> None:
|
||||
for key_action in actions:
|
||||
self.dispatch_action(key_action)
|
||||
|
||||
@ -889,8 +889,8 @@ class Boss:
|
||||
text = '\n'.join(parse_uri_list(text))
|
||||
w.paste(text)
|
||||
|
||||
@ac('win', 'Close the currently active OS Window')
|
||||
def close_os_window(self) -> None:
|
||||
'@ac:win: Close the currently active OS Window'
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
self.confirm_os_window_close(tm.os_window_id)
|
||||
@ -929,8 +929,8 @@ class Boss:
|
||||
if action is not None:
|
||||
action()
|
||||
|
||||
@ac('win', 'Quit, closing all windows')
|
||||
def quit(self, *args: Any) -> None:
|
||||
'@ac:win: Quit, closing all windows'
|
||||
tm = self.active_tab
|
||||
num = 0
|
||||
for q in self.os_window_map.values():
|
||||
@ -990,8 +990,8 @@ class Boss:
|
||||
copy_colors_from=self.active_window
|
||||
)
|
||||
|
||||
@ac('misc', 'Edit the kitty.conf config file in your favorite text editor')
|
||||
def edit_config_file(self, *a: Any) -> None:
|
||||
'@ac:misc: Edit the kitty.conf config file in your favorite text editor'
|
||||
confpath = prepare_config_file_for_editing()
|
||||
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
|
||||
# small delay.
|
||||
@ -1081,8 +1081,8 @@ class Boss:
|
||||
overlay_window.action_on_removal = callback_wrapper
|
||||
return overlay_window
|
||||
|
||||
@ac('misc', 'Run the specified kitten. See :doc:`/kittens/custom` for details')
|
||||
def kitten(self, kitten: str, *args: str) -> None:
|
||||
'@ac:misc: Run the specified kitten. See :doc:`/kittens/custom` for details'
|
||||
import shlex
|
||||
cmdline = args[0] if args else ''
|
||||
kargs = shlex.split(cmdline) if cmdline else []
|
||||
@ -1098,12 +1098,12 @@ class Boss:
|
||||
if data is not None:
|
||||
end_kitten(data, target_window_id, self)
|
||||
|
||||
@ac('misc', 'Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.')
|
||||
def input_unicode_character(self) -> None:
|
||||
'@ac:misc: Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.'
|
||||
self._run_kitten('unicode_input')
|
||||
|
||||
@ac('tab', 'Change the title of the active tab')
|
||||
def set_tab_title(self) -> None:
|
||||
'@ac:tab: Change the title of the active tab'
|
||||
tab = self.active_tab
|
||||
if tab:
|
||||
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
|
||||
@ -1121,8 +1121,8 @@ class Boss:
|
||||
def show_error(self, title: str, msg: str) -> None:
|
||||
self._run_kitten('show_error', args=['--title', title], input_data=msg)
|
||||
|
||||
@ac('mk', 'Create a new marker')
|
||||
def create_marker(self) -> None:
|
||||
'@ac:mk: Create a new marker'
|
||||
w = self.active_window
|
||||
if w:
|
||||
spec = None
|
||||
@ -1145,8 +1145,8 @@ class Boss:
|
||||
],
|
||||
custom_callback=done, action_on_removal=done2)
|
||||
|
||||
@ac('misc', 'Run the kitty shell to control kitty with commands')
|
||||
def kitty_shell(self, window_type: str = 'window') -> None:
|
||||
'@ac:misc: Run the kitty shell to control kitty with commands'
|
||||
kw: Dict[str, Any] = {}
|
||||
cmd = [kitty_exe(), '@']
|
||||
aw = self.active_window
|
||||
@ -1194,8 +1194,8 @@ class Boss:
|
||||
if not found_action:
|
||||
open_url(url, program or get_options().open_url_with, cwd=cwd)
|
||||
|
||||
@ac('misc', 'Click a URL using the keyboard')
|
||||
def open_url_with_hints(self) -> None:
|
||||
'@ac:misc: Click a URL using the keyboard'
|
||||
self._run_kitten('hints')
|
||||
|
||||
def drain_actions(self, actions: List) -> None:
|
||||
@ -1223,8 +1223,8 @@ class Boss:
|
||||
if w is not None:
|
||||
w.paste(text)
|
||||
|
||||
@ac('cp', 'Paste from the clipboard to the active window')
|
||||
def paste_from_clipboard(self) -> None:
|
||||
'@ac:cp: Paste from the clipboard to the active window'
|
||||
text = get_clipboard_string()
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
@ -1234,8 +1234,8 @@ class Boss:
|
||||
def current_primary_selection_or_clipboard(self) -> str:
|
||||
return get_primary_selection() if supports_primary_selection else get_clipboard_string()
|
||||
|
||||
@ac('cp', 'Paste from the clipboard to the active window')
|
||||
def paste_from_selection(self) -> None:
|
||||
'@ac:cp: Paste from the clipboard to the active window'
|
||||
text = self.current_primary_selection_or_clipboard()
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
@ -1248,12 +1248,12 @@ class Boss:
|
||||
if get_options().copy_on_select:
|
||||
self.copy_to_buffer(get_options().copy_on_select)
|
||||
|
||||
def copy_to_buffer(self, buffer_name: str) -> None:
|
||||
'''
|
||||
@ac:cp: Copy the selection from the active window to the specified buffer
|
||||
@ac('cp', '''
|
||||
Copy the selection from the active window to the specified buffer
|
||||
|
||||
See :ref:`cpbuf` for details.
|
||||
'''
|
||||
''')
|
||||
def copy_to_buffer(self, buffer_name: str) -> None:
|
||||
w = self.active_window
|
||||
if w is not None and not w.destroyed:
|
||||
text = w.text_for_selection()
|
||||
@ -1265,12 +1265,12 @@ class Boss:
|
||||
else:
|
||||
self.clipboard_buffers[buffer_name] = text
|
||||
|
||||
def paste_from_buffer(self, buffer_name: str) -> None:
|
||||
'''
|
||||
@ac:cp: Paste from the specified buffer to the active window
|
||||
@ac('cp', '''
|
||||
Paste from the specified buffer to the active window
|
||||
|
||||
See :ref:`cpbuf` for details.
|
||||
'''
|
||||
''')
|
||||
def paste_from_buffer(self, buffer_name: str) -> None:
|
||||
if buffer_name == 'clipboard':
|
||||
text: Optional[str] = get_clipboard_string()
|
||||
elif buffer_name == 'primary':
|
||||
@ -1280,12 +1280,12 @@ class Boss:
|
||||
if text:
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
def goto_tab(self, tab_num: int) -> None:
|
||||
'''
|
||||
@ac:tab: Go to the specified tab, by number, starting with 1
|
||||
@ac('tab', '''
|
||||
Go to the specified tab, by number, starting with 1
|
||||
|
||||
Zero and negative numbers go to previously active tabs
|
||||
'''
|
||||
''')
|
||||
def goto_tab(self, tab_num: int) -> None:
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.goto_tab(tab_num - 1)
|
||||
@ -1296,14 +1296,14 @@ class Boss:
|
||||
return tm.set_active_tab(tab)
|
||||
return False
|
||||
|
||||
@ac('tab', 'Make the next tab active')
|
||||
def next_tab(self) -> None:
|
||||
'@ac:tab: Make the next tab active'
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.next_tab()
|
||||
|
||||
@ac('tab', 'Make the previous tab active')
|
||||
def previous_tab(self) -> None:
|
||||
'@ac:tab: Make the previous tab active'
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.next_tab(-1)
|
||||
@ -1462,12 +1462,12 @@ class Boss:
|
||||
args = args[1:]
|
||||
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
|
||||
|
||||
@ac('tab', 'Create a new tab')
|
||||
def new_tab(self, *args: str) -> None:
|
||||
'@ac:tab: Create a new tab'
|
||||
self._create_tab(list(args))
|
||||
|
||||
@ac('tab', 'Create a new tab with working directory for the window in it set to the same as the active window')
|
||||
def new_tab_with_cwd(self, *args: str) -> None:
|
||||
'@ac:tab: Create a new tab with working directory for the window in it set to the same as the active window'
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._create_tab(list(args), cwd_from=cwd_from)
|
||||
@ -1494,46 +1494,46 @@ class Boss:
|
||||
else:
|
||||
return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control)
|
||||
|
||||
@ac('win', 'Create a new window')
|
||||
def new_window(self, *args: str) -> None:
|
||||
'@ac:win: Create a new window'
|
||||
self._new_window(list(args))
|
||||
|
||||
@ac('win', 'Create a new window with working directory same as that of the active window')
|
||||
def new_window_with_cwd(self, *args: str) -> None:
|
||||
'@ac:win: Create a new window with working directory same as that of the active window'
|
||||
w = self.active_window_for_cwd
|
||||
if w is None:
|
||||
return self.new_window(*args)
|
||||
cwd_from = w.child.pid_for_cwd
|
||||
self._new_window(list(args), cwd_from=cwd_from)
|
||||
|
||||
def launch(self, *args: str) -> None:
|
||||
'''
|
||||
@ac:misc: Launch the specified program in a new window/tab/etc.
|
||||
@ac('misc', '''
|
||||
Launch the specified program in a new window/tab/etc.
|
||||
|
||||
See :doc:`launch` for details
|
||||
'''
|
||||
''')
|
||||
def launch(self, *args: str) -> None:
|
||||
from kitty.launch import launch, parse_launch_args
|
||||
opts, args_ = parse_launch_args(args)
|
||||
launch(self, opts, args_)
|
||||
|
||||
@ac('tab', 'Move the active tab forward')
|
||||
def move_tab_forward(self) -> None:
|
||||
'@ac:tab: Move the active tab forward'
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.move_tab(1)
|
||||
|
||||
@ac('tab', 'Move the active tab backward')
|
||||
def move_tab_backward(self) -> None:
|
||||
'@ac:tab: Move the active tab backward'
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.move_tab(-1)
|
||||
|
||||
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None:
|
||||
'''
|
||||
@ac:misc: Turn on/off ligatures in the specified window
|
||||
@ac('misc', '''
|
||||
Turn on/off ligatures in the specified window
|
||||
|
||||
See :opt:`disable_ligatures` for details
|
||||
'''
|
||||
''')
|
||||
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None:
|
||||
if isinstance(where, str):
|
||||
windows: List[Window] = []
|
||||
if where == 'active':
|
||||
@ -1590,16 +1590,16 @@ class Boss:
|
||||
self.default_bg_changed_for(w.id)
|
||||
w.refresh()
|
||||
|
||||
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
|
||||
'''
|
||||
@ac:misc: Reload the config file
|
||||
@ac('misc', '''
|
||||
Reload the config file
|
||||
|
||||
If mapped without arguments reloads the default config file, otherwise loads
|
||||
the specified config files, in order. Loading a config file *replaces* all
|
||||
config options. For example::
|
||||
|
||||
map f5 load_config_file /path/to/some/kitty.conf
|
||||
'''
|
||||
''')
|
||||
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
|
||||
from .config import load_config
|
||||
old_opts = get_options()
|
||||
paths = paths or old_opts.config_paths
|
||||
@ -1656,14 +1656,14 @@ class Boss:
|
||||
msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip()
|
||||
self.show_error(_('Errors in kitty.conf'), msg)
|
||||
|
||||
def set_colors(self, *args: str) -> None:
|
||||
'''
|
||||
@ac:misc: Change colors in the specified windows
|
||||
@ac('misc', '''
|
||||
Change colors in the specified windows
|
||||
|
||||
For details, see :ref:`at_set-colors`. For example::
|
||||
|
||||
map f5 set_colors --configured /path/to/some/config/file/colors.conf
|
||||
'''
|
||||
''')
|
||||
def set_colors(self, *args: str) -> None:
|
||||
from kitty.rc.base import (
|
||||
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
)
|
||||
@ -1728,8 +1728,8 @@ class Boss:
|
||||
self._cleanup_tab_after_window_removal(tab)
|
||||
target_tab.make_active()
|
||||
|
||||
@ac('tab', 'Interactively select a tab to switch to')
|
||||
def select_tab(self) -> None:
|
||||
'@ac:tab: Interactively select a tab to switch to'
|
||||
title = 'Choose a tab to switch to'
|
||||
lines = [title, '']
|
||||
fmt = ': {1}'
|
||||
@ -1761,12 +1761,12 @@ class Boss:
|
||||
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
||||
)
|
||||
|
||||
def detach_window(self, *args: str) -> None:
|
||||
'''
|
||||
@ac:win: Detach a window, moving it to another tab or OS Window
|
||||
@ac('win', '''
|
||||
Detach a window, moving it to another tab or OS Window
|
||||
|
||||
See :ref:`detaching windows <detach_window>` for details.
|
||||
'''
|
||||
''')
|
||||
def detach_window(self, *args: str) -> None:
|
||||
if not args or args[0] == 'new':
|
||||
return self._move_window_to(target_os_window_id='new')
|
||||
if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'):
|
||||
@ -1817,12 +1817,12 @@ class Boss:
|
||||
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
||||
)
|
||||
|
||||
def detach_tab(self, *args: str) -> None:
|
||||
'''
|
||||
@ac:tab: Detach a tab, moving it to another OS Window
|
||||
@ac('tab', '''
|
||||
Detach a tab, moving it to another OS Window
|
||||
|
||||
See :ref:`detaching windows <detach_window>` for details.
|
||||
'''
|
||||
''')
|
||||
def detach_tab(self, *args: str) -> None:
|
||||
if not args or args[0] == 'new':
|
||||
return self._move_tab_to()
|
||||
|
||||
@ -1894,8 +1894,8 @@ class Boss:
|
||||
if report:
|
||||
w.report_notification_activated(identifier)
|
||||
|
||||
@ac('misc', 'Show the environment variables that the kitty process sees')
|
||||
def show_kitty_env_vars(self) -> None:
|
||||
'@ac:misc: Show the environment variables that the kitty process sees'
|
||||
w = self.active_window
|
||||
if w:
|
||||
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
|
||||
@ -1919,8 +1919,8 @@ class Boss:
|
||||
if w is not None:
|
||||
tab.remove_window(w)
|
||||
|
||||
@ac('misc', 'Show the effective configuration kitty is running with')
|
||||
def debug_config(self) -> None:
|
||||
'@ac:misc: Show the effective configuration kitty is running with'
|
||||
from .debug_config import debug_config
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
@ -1929,7 +1929,7 @@ class Boss:
|
||||
output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m'
|
||||
self.display_scrollback(w, output, title=_('Current kitty options'))
|
||||
|
||||
@ac('misc', 'Discard this event completely ignoring it')
|
||||
def discard_event(self) -> None:
|
||||
'@ac:misc: Discard this event completely ignoring it'
|
||||
pass
|
||||
mouse_discard_event = discard_event
|
||||
|
||||
@ -26,6 +26,7 @@ from .fast_data_types import (
|
||||
from .layout.base import Layout, Rect
|
||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
||||
from .tab_bar import TabBar, TabBarData
|
||||
from .types import ac
|
||||
from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict
|
||||
from .utils import log_error, platform_window_id, resolved_shell
|
||||
from .window import Watchers, Window, WindowDict
|
||||
@ -222,8 +223,8 @@ class Tab: # {{{
|
||||
def create_layout_object(self, name: str) -> Layout:
|
||||
return create_layout_object_for(name, self.os_window_id, self.id)
|
||||
|
||||
@ac('lay', 'Go to the next enabled layout')
|
||||
def next_layout(self) -> None:
|
||||
'@ac:lay: Go to the next enabled layout'
|
||||
if len(self.enabled_layouts) > 1:
|
||||
for i, layout_name in enumerate(self.enabled_layouts):
|
||||
if layout_name == self.current_layout.full_name:
|
||||
@ -235,20 +236,20 @@ class Tab: # {{{
|
||||
self._set_current_layout(nl)
|
||||
self.relayout()
|
||||
|
||||
@ac('lay', 'Go to the previously used layout')
|
||||
def last_used_layout(self) -> None:
|
||||
'@ac:lay: Go to the previously used layout'
|
||||
if len(self.enabled_layouts) > 1 and self._last_used_layout and self._last_used_layout != self._current_layout_name:
|
||||
self._set_current_layout(self._last_used_layout)
|
||||
self.relayout()
|
||||
|
||||
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None:
|
||||
'''
|
||||
@ac:lay: Switch to the named layout
|
||||
@ac('lay', '''
|
||||
Switch to the named layout
|
||||
|
||||
For example::
|
||||
|
||||
map f1 goto_layout tall
|
||||
'''
|
||||
''')
|
||||
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None:
|
||||
layout_name = layout_name.lower()
|
||||
if layout_name not in self.enabled_layouts:
|
||||
if raise_exception:
|
||||
@ -258,16 +259,16 @@ class Tab: # {{{
|
||||
self._set_current_layout(layout_name)
|
||||
self.relayout()
|
||||
|
||||
def toggle_layout(self, layout_name: str) -> None:
|
||||
'''
|
||||
@ac:lay: Toggle the named layout
|
||||
@ac('lay', '''
|
||||
Toggle the named layout
|
||||
|
||||
Switches to the named layout if another layout is current, otherwise
|
||||
switches to the last used layout. Useful to "zoom" a window temporarily
|
||||
by switching to the stack layout. For example::
|
||||
|
||||
map f1 toggle_layout stack
|
||||
'''
|
||||
''')
|
||||
def toggle_layout(self, layout_name: str) -> None:
|
||||
if self._current_layout_name == layout_name:
|
||||
self.last_used_layout()
|
||||
else:
|
||||
@ -280,12 +281,12 @@ class Tab: # {{{
|
||||
return None
|
||||
return 'Could not resize'
|
||||
|
||||
def resize_window(self, quality: str, increment: int) -> None:
|
||||
'''
|
||||
@ac:win: Resize the active window by the specified amount
|
||||
@ac('win', '''
|
||||
Resize the active window by the specified amount
|
||||
|
||||
See :ref:`window_resizing` for details.
|
||||
'''
|
||||
''')
|
||||
def resize_window(self, quality: str, increment: int) -> None:
|
||||
if increment < 1:
|
||||
raise ValueError(increment)
|
||||
is_horizontal = quality in ('wider', 'narrower')
|
||||
@ -296,13 +297,13 @@ class Tab: # {{{
|
||||
if get_options().enable_audio_bell:
|
||||
ring_bell()
|
||||
|
||||
@ac('win', 'Reset window sizes undoing any dynamic resizing of windows')
|
||||
def reset_window_sizes(self) -> None:
|
||||
'@ac:win:Reset window sizes undoing any dynamic resizing of windows'
|
||||
if self.current_layout.remove_all_biases():
|
||||
self.relayout()
|
||||
|
||||
@ac('lay', 'Perform a layout specific action. See :doc:`layouts` for details')
|
||||
def layout_action(self, action_name: str, args: Sequence[str]) -> None:
|
||||
'@ac:lay: Perform a layout specific action. See :doc:`layouts` for details'
|
||||
ret = self.current_layout.layout_action(action_name, args, self.windows)
|
||||
if ret is None:
|
||||
ring_bell()
|
||||
@ -424,14 +425,14 @@ class Tab: # {{{
|
||||
allow_remote_control=allow_remote_control, watchers=special_window.watchers
|
||||
)
|
||||
|
||||
@ac('win', 'Close the currently active window')
|
||||
def close_window(self) -> None:
|
||||
'@ac:win: Close the currently active window'
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
self.remove_window(w)
|
||||
|
||||
@ac('win', 'Close all windows in the tab other than the currently active window')
|
||||
def close_other_windows_in_tab(self) -> None:
|
||||
'@ac:win: Close all windows in the tab other than the currently active window'
|
||||
if len(self.windows) > 1:
|
||||
active_window = self.active_window
|
||||
for window in tuple(self.windows):
|
||||
@ -469,14 +470,14 @@ class Tab: # {{{
|
||||
if self.windows:
|
||||
return self.current_layout.nth_window(self.windows, n)
|
||||
|
||||
def nth_window(self, num: int = 0) -> None:
|
||||
'''
|
||||
@ac:win: Focus the nth window if positive or the previously active windows if negative
|
||||
@ac('win', '''
|
||||
Focus the nth window if positive or the previously active windows if negative
|
||||
|
||||
For example, to ficus the previously active window::
|
||||
|
||||
map ctrl+p nth_window -1
|
||||
'''
|
||||
''')
|
||||
def nth_window(self, num: int = 0) -> None:
|
||||
if self.windows:
|
||||
if num < 0:
|
||||
self.windows.make_previous_group_active(-num)
|
||||
@ -489,12 +490,12 @@ class Tab: # {{{
|
||||
self.current_layout.next_window(self.windows, delta)
|
||||
self.relayout_borders()
|
||||
|
||||
@ac('win', 'Focus the next window in the current tab')
|
||||
def next_window(self) -> None:
|
||||
'@ac:win: Focus the next window in the current tab'
|
||||
self._next_window()
|
||||
|
||||
@ac('win', 'Focus the previous window in the current tab')
|
||||
def previous_window(self) -> None:
|
||||
'@ac:win: Focus the previous window in the current tab'
|
||||
self._next_window(-1)
|
||||
|
||||
prev_window = previous_window
|
||||
@ -516,28 +517,28 @@ class Tab: # {{{
|
||||
if candidates:
|
||||
return self.most_recent_group(candidates)
|
||||
|
||||
def neighboring_window(self, which: EdgeLiteral) -> None:
|
||||
'''
|
||||
@ac:win: Focus the neighboring window in the current tab
|
||||
@ac('win', '''
|
||||
Focus the neighboring window in the current tab
|
||||
|
||||
For example::
|
||||
|
||||
map ctrl+left neighboring_window left
|
||||
map ctrl+down neighboring_window bottom
|
||||
'''
|
||||
''')
|
||||
def neighboring_window(self, which: EdgeLiteral) -> None:
|
||||
neighbor = self.neighboring_group_id(which)
|
||||
if neighbor:
|
||||
self.windows.set_active_group(neighbor)
|
||||
|
||||
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
|
||||
'''
|
||||
@ac:win: Move the window in the specified direction
|
||||
@ac('win', '''
|
||||
Move the window in the specified direction
|
||||
|
||||
For example::
|
||||
|
||||
map ctrl+left move_window left
|
||||
map ctrl+down move_window bottom
|
||||
'''
|
||||
''')
|
||||
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
|
||||
if isinstance(delta, int):
|
||||
if self.current_layout.move_window(self.windows, delta):
|
||||
self.relayout()
|
||||
@ -547,18 +548,18 @@ class Tab: # {{{
|
||||
if self.current_layout.move_window_to_group(self.windows, neighbor):
|
||||
self.relayout()
|
||||
|
||||
@ac('win', 'Move active window to the top (make it the first window)')
|
||||
def move_window_to_top(self) -> None:
|
||||
'@ac:win: Move active window to the top (make it the first window)'
|
||||
n = self.windows.active_group_idx
|
||||
if n > 0:
|
||||
self.move_window(-n)
|
||||
|
||||
@ac('win', 'Move active window forward (swap it with the next window)')
|
||||
def move_window_forward(self) -> None:
|
||||
'@ac:win: Move active window forward (swap it with the next window)'
|
||||
self.move_window()
|
||||
|
||||
@ac('win', 'Move active window backward (swap it with the previous window)')
|
||||
def move_window_backward(self) -> None:
|
||||
'@ac:win: Move active window backward (swap it with the previous window)'
|
||||
self.move_window(-1)
|
||||
|
||||
def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
|
||||
|
||||
@ -93,3 +93,22 @@ else:
|
||||
|
||||
def run_once(f: Callable[[], _T]) -> RunOnce:
|
||||
return RunOnce(f)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc']
|
||||
else:
|
||||
ActionGroup = str
|
||||
|
||||
|
||||
class ActionSpec(NamedTuple):
|
||||
group: str
|
||||
doc: str
|
||||
|
||||
|
||||
def ac(group: ActionGroup, doc: str) -> Callable[[_T], _T]:
|
||||
def w(f: _T) -> _T:
|
||||
setattr(f, 'action_spec', ActionSpec(group, doc))
|
||||
return f
|
||||
return w
|
||||
|
||||
@ -38,7 +38,7 @@ from .notify import NotificationCommand, handle_notification_cmd
|
||||
from .options.types import Options
|
||||
from .rgb import to_color
|
||||
from .terminfo import get_capabilities
|
||||
from .types import MouseEvent, ScreenGeometry, WindowGeometry
|
||||
from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac
|
||||
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
|
||||
from .utils import (
|
||||
color_as_int, get_primary_selection, load_shaders, log_error, open_cmd,
|
||||
@ -529,12 +529,12 @@ class Window:
|
||||
def close(self) -> None:
|
||||
get_boss().close_window(self)
|
||||
|
||||
def send_text(self, *args: str) -> bool:
|
||||
'''
|
||||
@ac:misc: Send the specified text to the active window
|
||||
@ac('misc', '''
|
||||
Send the specified text to the active window
|
||||
|
||||
For details, see :sc:`send_text`.
|
||||
'''
|
||||
''')
|
||||
def send_text(self, *args: str) -> bool:
|
||||
mode = keyboard_mode_name(self.screen)
|
||||
required_mode_, text = args[-2:]
|
||||
required_mode = frozenset(required_mode_.split(','))
|
||||
@ -848,31 +848,31 @@ class Window:
|
||||
# }}}
|
||||
|
||||
# mouse actions {{{
|
||||
@ac('mouse', 'Click the URL under the mouse')
|
||||
def mouse_click_url(self) -> None:
|
||||
'@ac:mouse: Click the URL under the mouse'
|
||||
click_mouse_url(self.os_window_id, self.tab_id, self.id)
|
||||
|
||||
@ac('mouse', 'Click the URL under the mouse only if the screen has no selection')
|
||||
def mouse_click_url_or_select(self) -> None:
|
||||
'@ac:mouse: Click the URL under the mouse only if the screen has no selection'
|
||||
if not self.screen.has_selection():
|
||||
self.mouse_click_url()
|
||||
|
||||
def mouse_selection(self, code: int) -> None:
|
||||
'''
|
||||
@ac:mouse: Manipulate the selection based on the current mouse position
|
||||
@ac('mouse', '''
|
||||
Manipulate the selection based on the current mouse position
|
||||
|
||||
For examples, see :ref:`conf-kitty-mouse.mousemap`
|
||||
'''
|
||||
''')
|
||||
def mouse_selection(self, code: int) -> None:
|
||||
mouse_selection(self.os_window_id, self.tab_id, self.id, code, self.current_mouse_event_button)
|
||||
|
||||
@ac('mouse', 'Paste the current primary selection')
|
||||
def paste_selection(self) -> None:
|
||||
'@ac:mouse: Paste the current primary selection'
|
||||
txt = get_boss().current_primary_selection()
|
||||
if txt:
|
||||
self.paste(txt)
|
||||
|
||||
@ac('mouse', 'Paste the current primary selection or the clipboard if no selection is present')
|
||||
def paste_selection_or_clipboard(self) -> None:
|
||||
'@ac:mouse: Paste the current primary selection or the clipboard if no selection is present'
|
||||
txt = get_boss().current_primary_selection_or_clipboard()
|
||||
if txt:
|
||||
self.paste(txt)
|
||||
@ -935,8 +935,8 @@ class Window:
|
||||
|
||||
# actions {{{
|
||||
|
||||
@ac('cp', 'Show scrollback in a pager like less')
|
||||
def show_scrollback(self) -> None:
|
||||
'@ac:cp: Show scrollback in a pager like less'
|
||||
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
|
||||
data = self.pipe_data(text, has_wrap_markers=True)
|
||||
get_boss().display_scrollback(self, data['text'], data['input_line_number'])
|
||||
@ -947,8 +947,8 @@ class Window:
|
||||
text = text.encode('utf-8')
|
||||
self.screen.paste_bytes(text)
|
||||
|
||||
@ac('cp', 'Paste the specified text into the current window')
|
||||
def paste(self, text: Union[str, bytes]) -> None:
|
||||
'@ac:cp: Paste the specified text into the current window'
|
||||
if text and not self.destroyed:
|
||||
if isinstance(text, str):
|
||||
text = text.encode('utf-8')
|
||||
@ -964,8 +964,8 @@ class Window:
|
||||
text = text.replace(b'\r\n', b'\n').replace(b'\n', b'\r')
|
||||
self.screen.paste(text)
|
||||
|
||||
@ac('cp', 'Copy the selected text from the active window to the clipboard')
|
||||
def copy_to_clipboard(self) -> None:
|
||||
'@ac:cp: Copy the selected text from the active window to the clipboard'
|
||||
text = self.text_for_selection()
|
||||
if text:
|
||||
set_clipboard_string(text)
|
||||
@ -978,21 +978,21 @@ class Window:
|
||||
cursor_key_mode=self.screen.cursor_key_mode,
|
||||
).encode('ascii')
|
||||
|
||||
@ac('cp', 'Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C')
|
||||
def copy_or_interrupt(self) -> None:
|
||||
'@ac:cp: Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C'
|
||||
text = self.text_for_selection()
|
||||
if text:
|
||||
set_clipboard_string(text)
|
||||
else:
|
||||
self.write_to_child(self.encoded_key(KeyEvent(key=ord('c'), mods=GLFW_MOD_CONTROL)))
|
||||
|
||||
@ac('cp', 'Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C')
|
||||
def copy_and_clear_or_interrupt(self) -> None:
|
||||
'@ac:cp: Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C'
|
||||
self.copy_or_interrupt()
|
||||
self.screen.clear_selection()
|
||||
|
||||
@ac('cp', 'Pass the selected text from the active window to the specified program')
|
||||
def pass_selection_to_program(self, *args: str) -> None:
|
||||
'@ac:cp: Pass the selected text from the active window to the specified program'
|
||||
cwd = self.cwd_of_child
|
||||
text = self.text_for_selection()
|
||||
if text:
|
||||
@ -1001,38 +1001,38 @@ class Window:
|
||||
else:
|
||||
open_url(text, cwd=cwd)
|
||||
|
||||
@ac('sc', 'Scroll up by one line')
|
||||
def scroll_line_up(self) -> None:
|
||||
'@ac:sc: Scroll up by one line'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_LINE, True)
|
||||
|
||||
@ac('sc', 'Scroll down by one line')
|
||||
def scroll_line_down(self) -> None:
|
||||
'@ac:sc: Scroll down by one line'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_LINE, False)
|
||||
|
||||
@ac('sc', 'Scroll up by one page')
|
||||
def scroll_page_up(self) -> None:
|
||||
'@ac:sc: Scroll up by one page'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_PAGE, True)
|
||||
|
||||
@ac('sc', 'Scroll down by one page')
|
||||
def scroll_page_down(self) -> None:
|
||||
'@ac:sc: Scroll down by one page'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_PAGE, False)
|
||||
|
||||
@ac('sc', 'Scroll to the top of the scrollback buffer')
|
||||
def scroll_home(self) -> None:
|
||||
'@ac:sc: Scroll to the top of the scrollback buffer'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_FULL, True)
|
||||
|
||||
@ac('sc', 'Scroll to the bottom of the scrollback buffer')
|
||||
def scroll_end(self) -> None:
|
||||
'@ac:sc: Scroll to the bottom of the scrollback buffer'
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_FULL, False)
|
||||
|
||||
@ac('mk', 'Toggle the current marker on/off')
|
||||
def toggle_marker(self, ftype: str, spec: Union[str, Tuple[Tuple[int, str], ...]], flags: int) -> None:
|
||||
'@ac:mk: Toggle the current marker on/off'
|
||||
from .marks import marker_from_spec
|
||||
key = ftype, spec
|
||||
if key == self.current_marker_spec:
|
||||
@ -1052,24 +1052,24 @@ class Window:
|
||||
self.screen.set_marker(marker_from_spec(ftype, spec_, flags))
|
||||
self.current_marker_spec = key
|
||||
|
||||
@ac('mk', 'Remove a previously created marker')
|
||||
def remove_marker(self) -> None:
|
||||
'@ac:mk: Remove a previously created marker'
|
||||
if self.current_marker_spec is not None:
|
||||
self.screen.set_marker()
|
||||
self.current_marker_spec = None
|
||||
|
||||
@ac('mk', 'Scroll to the next or previous mark of the specified type')
|
||||
def scroll_to_mark(self, prev: bool = True, mark: int = 0) -> None:
|
||||
'@ac:mk: Scroll to the next or previous mark of the specified type'
|
||||
self.screen.scroll_to_next_mark(mark, prev)
|
||||
|
||||
def signal_child(self, *signals: int) -> None:
|
||||
'''
|
||||
@ac:misc: Send the specified SIGNAL to the foreground process in the active window
|
||||
@ac('misc', '''
|
||||
Send the specified SIGNAL to the foreground process in the active window
|
||||
|
||||
For example::
|
||||
|
||||
map F1 signal_child SIGTERM
|
||||
'''
|
||||
''')
|
||||
def signal_child(self, *signals: int) -> None:
|
||||
pid = self.child.pid_for_cwd
|
||||
if pid is not None:
|
||||
for sig in signals:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user