mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 10:41:58 +02:00
Refactor remote control commands into individual modules
Also add type information
This commit is contained in:
0
kitty/rc/__init__.py
Normal file
0
kitty/rc/__init__.py
Normal file
179
kitty/rc/base.py
Normal file
179
kitty/rc/base.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from contextlib import suppress
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Generator, List, NoReturn,
|
||||
Optional, Tuple, Union
|
||||
)
|
||||
|
||||
from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec
|
||||
from kitty.cli_stub import RCOptions
|
||||
from kitty.constants import appname, running_in_kitty
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.boss import Boss
|
||||
from kitty.window import Window
|
||||
Boss, Window
|
||||
else:
|
||||
Boss = Window = None
|
||||
|
||||
|
||||
class NoResponse:
|
||||
pass
|
||||
|
||||
|
||||
class MatchError(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
def __init__(self, expression, target='windows'):
|
||||
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
|
||||
|
||||
|
||||
class OpacityError(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
class UnknownLayout(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
no_response = NoResponse()
|
||||
ResponseType = Optional[Union[bool, str]]
|
||||
CmdReturnType = Union[Dict[str, Any], List, Tuple, str, int, float, bool]
|
||||
CmdGenerator = Generator[CmdReturnType, None, None]
|
||||
PayloadType = Optional[Union[CmdReturnType, CmdGenerator]]
|
||||
PayloadGetType = Callable[[str, str], Any]
|
||||
ArgsType = List[str]
|
||||
|
||||
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
|
||||
You can use the :italic:`ls` command to get a list of windows. Note that for
|
||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||
not a regular expression. The field num refers to the window position in the current tab,
|
||||
starting from zero and counting clockwise (this is the same as the order in which the
|
||||
windows are reported by the :italic:`ls` command). The window id of the current window
|
||||
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
|
||||
to match on environment variables you can specify only the environment variable name or a name
|
||||
and value, for example, :italic:`env:MY_ENV_VAR=2`
|
||||
'''
|
||||
MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
The tab to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, env, cmdline.
|
||||
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
||||
numeric fields such as id and pid the expression is interpreted as a number,
|
||||
not a regular expression. When using title or id, first a matching tab is
|
||||
looked for and if not found a matching window is looked for, and the tab
|
||||
for that window is used.
|
||||
'''
|
||||
|
||||
|
||||
def windows_for_payload(boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> Tuple['Window', ...]:
|
||||
if payload_get('all'):
|
||||
windows = tuple(boss.all_windows)
|
||||
else:
|
||||
windows = (window or boss.active_window,)
|
||||
if payload_get('match_window'):
|
||||
windows = tuple(boss.match_windows(payload_get('match_window')))
|
||||
if not windows:
|
||||
raise MatchError(payload_get('match_window'))
|
||||
if payload_get('match_tab'):
|
||||
tabs = tuple(boss.match_tabs(payload_get('match_tab')))
|
||||
if not tabs:
|
||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
return windows
|
||||
|
||||
|
||||
class RemoteCommand:
|
||||
|
||||
name: str = ''
|
||||
short_desc: str = ''
|
||||
desc: str = ''
|
||||
argspec: str = '...'
|
||||
options_spec: Optional[str] = None
|
||||
no_response: bool = False
|
||||
string_return_is_error: bool = False
|
||||
args_count: Optional[int] = None
|
||||
args_completion: Optional[Dict[str, Tuple[str, Tuple[str, ...]]]] = None
|
||||
defaults: Optional[Dict[str, Any]] = None
|
||||
|
||||
def __init__(self):
|
||||
self.desc = self.desc or self.short_desc
|
||||
self.name = self.__class__.__module__.split('.')[-1].replace('_', '-')
|
||||
self.args_count = 0 if not self.argspec else self.args_count
|
||||
|
||||
def fatal(self, msg: str) -> NoReturn:
|
||||
if running_in_kitty():
|
||||
raise Exception(msg)
|
||||
raise SystemExit(msg)
|
||||
|
||||
def get_default(self, name: str, missing: Any = None) -> Any:
|
||||
if self.options_spec:
|
||||
if self.defaults is None:
|
||||
self.defaults = get_defaults_from_seq(parse_option_spec(self.options_spec)[0])
|
||||
return self.defaults.get(name, missing)
|
||||
return missing
|
||||
|
||||
def payload_get(self, payload: Dict[str, Any], key: str, opt_name: Optional[str] = None) -> Any:
|
||||
payload_get = object()
|
||||
ans = payload.get(key, payload_get)
|
||||
if ans is not payload_get:
|
||||
return ans
|
||||
return self.get_default(opt_name or key)
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
|
||||
raise NotImplementedError()
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def cli_params_for(command: RemoteCommand) -> Tuple[Callable[[], str], str, str, str]:
|
||||
return (command.options_spec or '\n').format, command.argspec, command.desc, '{} @ {}'.format(appname, command.name)
|
||||
|
||||
|
||||
def parse_subcommand_cli(command: RemoteCommand, args: ArgsType) -> Tuple[Any, ArgsType]:
|
||||
opts, items = parse_args(args[1:], *cli_params_for(command))
|
||||
if command.args_count is not None and command.args_count != len(items):
|
||||
if command.args_count == 0:
|
||||
raise SystemExit('Unknown extra argument(s) supplied to {}'.format(command.name))
|
||||
raise SystemExit('Must specify exactly {} argument(s) for {}'.format(command.args_count, command.name))
|
||||
return opts, items
|
||||
|
||||
|
||||
def display_subcommand_help(func):
|
||||
with suppress(SystemExit):
|
||||
parse_args(['--help'], (func.options_spec or '\n').format, func.argspec, func.desc, func.name)
|
||||
|
||||
|
||||
def command_for_name(cmd_name: str) -> RemoteCommand:
|
||||
from importlib import import_module
|
||||
cmd_name = cmd_name.replace('-', '_')
|
||||
try:
|
||||
m = import_module(f'kitty.rc.{cmd_name}')
|
||||
except ImportError:
|
||||
raise KeyError(f'{cmd_name} is not a known kitty remote control command')
|
||||
return getattr(m, cmd_name)
|
||||
|
||||
|
||||
def all_command_names() -> FrozenSet[str]:
|
||||
try:
|
||||
from importlib.resources import contents
|
||||
except ImportError:
|
||||
from importlib_resources import contents
|
||||
|
||||
def ok(name: str) -> bool:
|
||||
root, _, ext = name.rpartition('.')
|
||||
return ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__')
|
||||
|
||||
return frozenset({x.rpartition('.')[0] for x in filter(ok, contents('kitty.rc'))})
|
||||
49
kitty/rc/close_tab.py
Normal file
49
kitty/rc/close_tab.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import CloseTabRCOptions as CLIOptions
|
||||
|
||||
|
||||
class CloseTab(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: Which tab to close
|
||||
self: Boolean indicating whether to close the window the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Close the specified tab(s)'
|
||||
options_spec = MATCH_TAB_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the tab this command is run in, rather than the active tab.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window and payload_get('self') else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if window:
|
||||
if tab:
|
||||
boss.close_tab(tab)
|
||||
|
||||
|
||||
close_tab = CloseTab()
|
||||
47
kitty/rc/close_window.py
Normal file
47
kitty/rc/close_window.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import CloseWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class CloseWindow(RemoteCommand):
|
||||
'''
|
||||
match: Which window to close
|
||||
self: Boolean indicating whether to close the window the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Close the specified window(s)'
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.close_window(window)
|
||||
|
||||
|
||||
close_window = CloseWindow()
|
||||
58
kitty/rc/create_marker.py
Normal file
58
kitty/rc/create_marker.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.config import parse_marker_spec
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import CreateMarkerRCOptions as CLIOptions
|
||||
|
||||
|
||||
class CreateMarker(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: Which window to detach
|
||||
self: Boolean indicating whether to detach the window the command is run in
|
||||
marker_spec: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR']
|
||||
'''
|
||||
|
||||
short_desc = 'Create a marker that highlights specified text'
|
||||
desc = (
|
||||
'Create a marker which can highlight text in the specified window. For example: '
|
||||
'create_marker text 1 ERROR. For full details see: https://sw.kovidgoyal.net/kitty/marks.html'
|
||||
)
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified apply marker to the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = 'MARKER SPECIFICATION'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if len(args) < 2:
|
||||
self.fatal('Invalid marker specification: {}'.format(' '.join(args)))
|
||||
parse_marker_spec(args[0], args[1:])
|
||||
return {'match': opts.match, 'self': opts.self, 'marker_spec': args}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
args = payload_get('marker_spec')
|
||||
|
||||
for window in windows:
|
||||
window.set_marker(args)
|
||||
|
||||
|
||||
create_marker = CreateMarker()
|
||||
59
kitty/rc/detach_tab.py
Normal file
59
kitty/rc/detach_tab.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import DetachTabRCOptions as CLIOptions
|
||||
|
||||
|
||||
class DetachTab(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: Which tab to detach
|
||||
target: Which OS Window to move the detached tab to
|
||||
self: Boolean indicating whether to detach the tab the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Detach a tab and place it in a different/new OS Window'
|
||||
desc = (
|
||||
'Detach the specified tab and either move it into a new OS window'
|
||||
' or add it to the OS Window containing the tab specified by --target-tab'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified detach the tab this command is run in, rather than the active tab.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
tabs = [window.tabref() if payload_get('self') and window and window.tabref() else boss.active_tab]
|
||||
match = payload_get('target_tab')
|
||||
kwargs = {}
|
||||
if match:
|
||||
targets = tuple(boss.match_tabs(match))
|
||||
if not targets:
|
||||
raise MatchError(match, 'tabs')
|
||||
kwargs['target_os_window_id'] = targets[0].os_window_id
|
||||
|
||||
for tab in tabs:
|
||||
boss._move_tab_to(tab=tab, **kwargs)
|
||||
|
||||
|
||||
detach_tab = DetachTab()
|
||||
66
kitty/rc/detach_window.py
Normal file
66
kitty/rc/detach_window.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
|
||||
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType,
|
||||
Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import DetachWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class DetachWindow(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: Which window to detach
|
||||
target: Which tab to move the detached window to
|
||||
self: Boolean indicating whether to detach the window the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Detach a window and place it in a different/new tab'
|
||||
desc = (
|
||||
'Detach the specified window and either move it into a new tab, a new OS window'
|
||||
' or add it to the specified tab. Use the special value :code:`new` for --target-tab'
|
||||
' to move to a new tab. If no target tab is specified the window is moved to a new OS window.'
|
||||
)
|
||||
options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified detach the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
match = payload_get('target_tab')
|
||||
kwargs = {}
|
||||
if match:
|
||||
if match == 'new':
|
||||
kwargs['target_tab_id'] = 'new'
|
||||
else:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
kwargs['target_tab_id'] = tabs[0].id
|
||||
if not kwargs:
|
||||
kwargs['target_os_window_id'] = 'new'
|
||||
|
||||
for window in windows:
|
||||
boss._move_window_to(window=window, **kwargs)
|
||||
|
||||
|
||||
detach_window = DetachWindow()
|
||||
60
kitty/rc/disable_ligatures.py
Normal file
60
kitty/rc/disable_ligatures.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window,
|
||||
windows_for_payload
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import DisableLigaturesRCOptions as CLIOptions
|
||||
|
||||
|
||||
class DisableLigatures(RemoteCommand):
|
||||
|
||||
'''
|
||||
strategy+: One of :code:`never`, :code:`always` or :code:`cursor`
|
||||
match_window: Window to change opacity in
|
||||
match_tab: Tab to change opacity in
|
||||
all: Boolean indicating operate on all windows
|
||||
'''
|
||||
|
||||
short_desc = 'Control ligature rendering'
|
||||
desc = (
|
||||
'Control ligature rendering for the specified windows/tabs (defaults to active window). The STRATEGY'
|
||||
' can be one of: never, always, cursor'
|
||||
)
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, ligatures are only affected in the active window. This option will
|
||||
cause ligatures to be changed in all windows.
|
||||
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'STRATEGY'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if not args:
|
||||
self.fatal(
|
||||
'You must specify the STRATEGY for disabling ligatures, must be one of'
|
||||
' never, always or cursor')
|
||||
strategy = args[0]
|
||||
if strategy not in ('never', 'always', 'cursor'):
|
||||
self.fatal('{} is not a valid disable_ligatures strategy'.format('strategy'))
|
||||
return {
|
||||
'strategy': strategy, 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all,
|
||||
}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = windows_for_payload(boss, window, payload_get)
|
||||
boss.disable_ligatures_in(windows, payload_get('strategy'))
|
||||
# }}}
|
||||
|
||||
|
||||
disable_ligatures = DisableLigatures()
|
||||
52
kitty/rc/focus_tab.py
Normal file
52
kitty/rc/focus_tab.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import FocusTabRCOptions as CLIOptions
|
||||
|
||||
|
||||
class FocusTab(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: The tab to focus
|
||||
'''
|
||||
|
||||
short_desc = 'Focus the specified tab'
|
||||
desc = 'The active window in the specified tab will be focused.'
|
||||
options_spec = MATCH_TAB_OPTION + '''
|
||||
|
||||
--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.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
tab = tabs[0]
|
||||
boss.set_active_tab(tab)
|
||||
|
||||
|
||||
focus_tab = FocusTab()
|
||||
55
kitty/rc/focus_window.py
Normal file
55
kitty/rc/focus_window.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.fast_data_types import focus_os_window
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import FocusWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class FocusWindow(RemoteCommand):
|
||||
'''
|
||||
match: The window to focus
|
||||
'''
|
||||
|
||||
short_desc = 'Focus the specified window'
|
||||
desc = 'Focus the specified window, if no window is specified, focus the window this command is run inside.'
|
||||
argspec = ''
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n\n
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response from kitty. This means that even if no matching window is found,
|
||||
the command will exit with a success code.
|
||||
'''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'no_response': opts.no_response}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [window or boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
os_window_id = boss.set_active_window(window)
|
||||
if os_window_id:
|
||||
focus_os_window(os_window_id, True)
|
||||
break
|
||||
|
||||
|
||||
focus_window = FocusWindow()
|
||||
57
kitty/rc/get_colors.py
Normal file
57
kitty/rc/get_colors.py
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.rgb import Color, color_as_sharp, color_from_int
|
||||
from kitty.utils import natsort_ints
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import GetColorsRCOptions as CLIOptions
|
||||
|
||||
|
||||
class GetColors(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: The window to get the colors for
|
||||
configured: Boolean indicating whether to get configured or current colors
|
||||
'''
|
||||
|
||||
short_desc = 'Get terminal colors'
|
||||
desc = (
|
||||
'Get the terminal colors for the specified window (defaults to active window). '
|
||||
'Colors will be output to stdout in the same syntax as used for kitty.conf'
|
||||
)
|
||||
options_spec = '''\
|
||||
--configured -c
|
||||
type=bool-set
|
||||
Instead of outputting the colors for the specified window, output the currently
|
||||
configured colors.
|
||||
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'configured': opts.configured, 'match': opts.match}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
ans = {k: getattr(boss.opts, k) for k in boss.opts if isinstance(getattr(boss.opts, k), Color)}
|
||||
if not payload_get('configured'):
|
||||
windows = (window or boss.active_window,)
|
||||
if payload_get('match'):
|
||||
windows = tuple(boss.match_windows(payload_get('match')))
|
||||
if not windows:
|
||||
raise MatchError(payload_get('match'))
|
||||
ans.update({k: color_from_int(v) for k, v in windows[0].current_colors.items()})
|
||||
all_keys = natsort_ints(ans)
|
||||
maxlen = max(map(len, all_keys))
|
||||
return '\n'.join(('{:%ds} {}' % maxlen).format(key, color_as_sharp(ans[key])) for key in all_keys)
|
||||
# }}}
|
||||
|
||||
|
||||
get_colors = GetColors()
|
||||
67
kitty/rc/get_text.py
Normal file
67
kitty/rc/get_text.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import GetTextRCOptions as CLIOptions
|
||||
|
||||
|
||||
class GetText(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: The tab to focus
|
||||
extent: One of :code:`screen`, :code:`all`, or :code:`selection`
|
||||
ansi: Boolean, if True send ANSI formatting codes
|
||||
self: Boolean, if True use window command was run in
|
||||
'''
|
||||
|
||||
short_desc = 'Get text from the specified window'
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--extent
|
||||
default=screen
|
||||
choices=screen, all, selection
|
||||
What text to get. The default of screen means all text currently on the screen. all means
|
||||
all the screen+scrollback and selection means currently selected text.
|
||||
|
||||
|
||||
--ansi
|
||||
type=bool-set
|
||||
By default, only plain text is returned. If you specify this flag, the text will
|
||||
include the formatting escape codes for colors/bold/italic/etc. Note that when
|
||||
getting the current selection, the result is always plain text.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified get text from the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
window = windows[0]
|
||||
if payload_get('extent') == 'selection':
|
||||
ans = window.text_for_selection()
|
||||
else:
|
||||
ans = window.as_text(as_ansi=bool(payload_get('ansi')), add_history=payload_get('extent') == 'all')
|
||||
return ans
|
||||
|
||||
|
||||
get_text = GetText()
|
||||
56
kitty/rc/goto_layout.py
Normal file
56
kitty/rc/goto_layout.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType,
|
||||
RCOptions, RemoteCommand, ResponseType, UnknownLayout, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import GotoLayoutRCOptions as CLIOptions
|
||||
|
||||
|
||||
class GotoLayout(RemoteCommand):
|
||||
|
||||
'''
|
||||
layout+: The new layout name
|
||||
match: Which tab to change the layout of
|
||||
'''
|
||||
|
||||
short_desc = 'Set the window layout'
|
||||
desc = (
|
||||
'Set the window layout in the specified tab (or the active tab if not specified).'
|
||||
' You can use special match value :italic:`all` to set the layout in all tabs.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION
|
||||
argspec = 'LAYOUT_NAME'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
try:
|
||||
return {'layout': args[0], 'match': opts.match}
|
||||
except IndexError:
|
||||
raise self.fatal('No layout specified')
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
if match == 'all':
|
||||
tabs = tuple(boss.all_tabs)
|
||||
else:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
try:
|
||||
tab.goto_layout(payload_get('layout'), raise_exception=True)
|
||||
except ValueError:
|
||||
raise UnknownLayout('The layout {} is unknown or disabled'.format(payload_get('layout')))
|
||||
|
||||
|
||||
goto_layout = GotoLayout()
|
||||
52
kitty/rc/kitten.py
Normal file
52
kitty/rc/kitten.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import KittenRCOptions as CLIOptions
|
||||
|
||||
|
||||
class Kitten(RemoteCommand):
|
||||
|
||||
'''
|
||||
kitten+: The name of the kitten to run
|
||||
args: Arguments to pass to the kitten as a list
|
||||
match: The window to run the kitten over
|
||||
'''
|
||||
|
||||
short_desc = 'Run a kitten'
|
||||
desc = (
|
||||
'Run a kitten over the specified window (active window by default).'
|
||||
' The :italic:`kitten_name` can be either the name of a builtin kitten'
|
||||
' or the path to a python file containing a custom kitten. If a relative path'
|
||||
' is used it is searched for in the kitty config directory.'
|
||||
)
|
||||
options_spec = MATCH_WINDOW_OPTION
|
||||
argspec = 'kitten_name'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if len(args) < 1:
|
||||
self.fatal('Must specify kitten name')
|
||||
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [window or boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss._run_kitten(payload_get('kitten'), args=tuple(payload_get('args') or ()), window=window)
|
||||
break
|
||||
|
||||
|
||||
kitten = Kitten()
|
||||
48
kitty/rc/last_used_layout.py
Normal file
48
kitty/rc/last_used_layout.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import LastUsedLayoutRCOptions as CLIOptions
|
||||
|
||||
|
||||
class LastUsedLayout(RemoteCommand):
|
||||
'''
|
||||
match: Which tab to change the layout of
|
||||
'''
|
||||
|
||||
short_desc = 'Switch to the last used layout'
|
||||
desc = (
|
||||
'Switch to the last used window layout in the specified tab (or the active tab if not specified).'
|
||||
' You can use special match value :italic:`all` to set the layout in all tabs.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
if match == 'all':
|
||||
tabs = tuple(boss.all_tabs)
|
||||
else:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
tab.last_used_layout()
|
||||
|
||||
|
||||
last_used_layout = LastUsedLayout()
|
||||
95
kitty/rc/launch.py
Normal file
95
kitty/rc/launch.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.launch import (
|
||||
LaunchCLIOptions, launch as do_launch, options_spec as launch_options_spec,
|
||||
parse_launch_args
|
||||
)
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType,
|
||||
RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions
|
||||
|
||||
|
||||
class Launch(RemoteCommand):
|
||||
|
||||
'''
|
||||
args+: The command line to run in the new window, as a list, use an empty list to run the default shell
|
||||
match: The tab to open the new window in
|
||||
window_title: Title for the new window
|
||||
cwd: Working directory for the new window
|
||||
env: List of environment variables of the form NAME=VALUE
|
||||
tab_title: Title for the new tab
|
||||
type: The type of window to open
|
||||
keep_focus: Boolean indicating whether the current window should retain focus or not
|
||||
copy_colors: Boolean indicating whether to copy the colors from the current window
|
||||
copy_cmdline: Boolean indicating whether to copy the cmdline from the current window
|
||||
copy_env: Boolean indicating whether to copy the environ from the current window
|
||||
location: Where in the tab to open the new window
|
||||
allow_remote_control: Boolean indicating whether to allow remote control from the new window
|
||||
stdin_source: Where to get stdin for thew process from
|
||||
stdin_add_formatting: Boolean indicating whether to add formatting codes to stdin
|
||||
stdin_add_line_wrap_markers: Boolean indicating whether to add line wrap markers to stdin
|
||||
no_response: Boolean indicating whether to send back the window id
|
||||
marker: Specification for marker for new window, for example: "text 1 ERROR"
|
||||
'''
|
||||
|
||||
short_desc = 'Run an arbitrary process in a new window/tab'
|
||||
desc = (
|
||||
' Prints out the id of the newly opened window. Any command line arguments'
|
||||
' are assumed to be the command line used to run in the new window, if none'
|
||||
' are provided, the default shell is run. For example:'
|
||||
' :italic:`kitty @ launch --title Email mutt`.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION + '\n\n' + '''\
|
||||
--no-response
|
||||
type=bool-set
|
||||
Do not print out the id of the newly created window.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified the tab containing the window this command is run in is used
|
||||
instead of the active tab
|
||||
''' + '\n\n' + launch_options_spec().replace(':option:`launch', ':option:`kitty @ launch')
|
||||
argspec = '[CMD ...]'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
ans = {'args': args or []}
|
||||
for attr, val in opts.__dict__.items():
|
||||
ans[attr] = val
|
||||
return ans
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
default_opts = parse_launch_args()[0]
|
||||
opts = LaunchCLIOptions()
|
||||
for key, default_value in default_opts.__dict__.items():
|
||||
val = payload_get(key)
|
||||
if val is None:
|
||||
val = default_value
|
||||
setattr(opts, key, val)
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.active_tab]
|
||||
if payload_get('self') and window and window.tabref():
|
||||
tabs = [window.tabref()]
|
||||
tab = tabs[0]
|
||||
w = do_launch(boss, opts, payload_get('args') or None, target_tab=tab)
|
||||
return None if payload_get('no_response') else str(getattr(w, 'id', 0))
|
||||
|
||||
|
||||
launch = Launch()
|
||||
40
kitty/rc/ls.py
Normal file
40
kitty/rc/ls.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from kitty.constants import appname
|
||||
|
||||
from .base import (
|
||||
ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand,
|
||||
ResponseType, Window
|
||||
)
|
||||
|
||||
|
||||
class LS(RemoteCommand):
|
||||
'''
|
||||
No payload
|
||||
'''
|
||||
|
||||
short_desc = 'List all tabs/windows'
|
||||
desc = (
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
f' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
|
||||
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
|
||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`, '
|
||||
' :italic:`command-line` and :italic:`environment` of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'
|
||||
)
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
|
||||
pass
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
data = list(boss.list_os_windows())
|
||||
return json.dumps(data, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
ls = LS()
|
||||
124
kitty/rc/new_window.py
Normal file
124
kitty/rc/new_window.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.fast_data_types import focus_os_window
|
||||
from kitty.tabs import SpecialWindow
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType,
|
||||
RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import NewWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class NewWindow(RemoteCommand):
|
||||
|
||||
'''
|
||||
args+: The command line to run in the new window, as a list, use an empty list to run the default shell
|
||||
match: The tab to open the new window in
|
||||
title: Title for the new window
|
||||
cwd: Working directory for the new window
|
||||
tab_title: Title for the new tab
|
||||
window_type: One of :code:`kitty` or :code:`os`
|
||||
keep_focus: Boolean indicating whether the current window should retain focus or not
|
||||
'''
|
||||
|
||||
short_desc = 'Open new window'
|
||||
desc = (
|
||||
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
|
||||
' the first matching tab is used. Otherwise the currently active tab is used.'
|
||||
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
|
||||
' are assumed to be the command line used to run in the new window, if none'
|
||||
' are provided, the default shell is run. For example:\n'
|
||||
':italic:`kitty @ new-window --title Email mutt`'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION + '''\n
|
||||
--title
|
||||
The title for the new window. By default it will use the title set by the
|
||||
program running in it.
|
||||
|
||||
|
||||
--cwd
|
||||
The initial working directory for the new window. Defaults to whatever
|
||||
the working directory for the kitty process you are talking to is.
|
||||
|
||||
|
||||
--keep-focus
|
||||
type=bool-set
|
||||
Keep the current window focused instead of switching to the newly opened window
|
||||
|
||||
|
||||
--window-type
|
||||
default=kitty
|
||||
choices=kitty,os
|
||||
What kind of window to open. A kitty window or a top-level OS window.
|
||||
|
||||
|
||||
--new-tab
|
||||
type=bool-set
|
||||
Open a new tab
|
||||
|
||||
|
||||
--tab-title
|
||||
When using --new-tab set the title of the tab.
|
||||
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response giving the id of the newly opened window. Note that
|
||||
using this option means that you will not be notified of failures and that
|
||||
the id of the new window will not be printed out.
|
||||
'''
|
||||
argspec = '[CMD ...]'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
||||
'window_type': opts.window_type, 'no_response': opts.no_response,
|
||||
'keep_focus': opts.keep_focus, 'args': args or []}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
w = SpecialWindow(cmd=payload_get('args') or None, override_title=payload_get('title'), cwd=payload_get('cwd'))
|
||||
old_window = boss.active_window
|
||||
if payload_get('new_tab'):
|
||||
boss._new_tab(w)
|
||||
tab = boss.active_tab
|
||||
if payload_get('tab_title'):
|
||||
tab.set_title(payload_get('tab_title'))
|
||||
wid = boss.active_window.id
|
||||
if payload_get('keep_focus') and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return None if payload_get('no_response') else str(wid)
|
||||
|
||||
if payload_get('window_type') == 'os':
|
||||
boss._new_os_window(w)
|
||||
wid = boss.active_window.id
|
||||
if payload_get('keep_focus') and old_window:
|
||||
os_window_id = boss.set_active_window(old_window)
|
||||
if os_window_id:
|
||||
focus_os_window(os_window_id)
|
||||
return None if payload_get('no_response') else str(wid)
|
||||
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.active_tab]
|
||||
tab = tabs[0]
|
||||
w = tab.new_special_window(w)
|
||||
if payload_get('keep_focus') and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return None if payload_get('no_response') else str(w.id)
|
||||
|
||||
|
||||
new_window = NewWindow()
|
||||
48
kitty/rc/remove_marker.py
Normal file
48
kitty/rc/remove_marker.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import RemoveMarkerRCOptions as CLIOptions
|
||||
|
||||
|
||||
class RemoveMarker(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: Which window to remove the marker from
|
||||
self: Boolean indicating whether to detach the window the command is run in
|
||||
'''
|
||||
|
||||
short_desc = 'Remove the currently set marker, if any.'
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified apply marker to the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
|
||||
for window in windows:
|
||||
window.remove_marker()
|
||||
|
||||
|
||||
remove_marker = RemoveMarker()
|
||||
69
kitty/rc/resize_window.py
Normal file
69
kitty/rc/resize_window.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import ResizeWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class ResizeWindow(RemoteCommand):
|
||||
'''
|
||||
match: Which window to resize
|
||||
self: Boolean indicating whether to close the window the command is run in
|
||||
increment: Integer specifying the resize increment
|
||||
axis: One of :code:`horizontal, vertical` or :code:`reset`
|
||||
'''
|
||||
|
||||
short_desc = 'Resize the specified window'
|
||||
desc = 'Resize the specified window in the current layout. Note that not all layouts can resize all windows in all directions.'
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--increment -i
|
||||
type=int
|
||||
default=2
|
||||
The number of cells to change the size by, can be negative to decrease the size.
|
||||
|
||||
|
||||
--axis -a
|
||||
type=choices
|
||||
choices=horizontal,vertical,reset
|
||||
default=horizontal
|
||||
The axis along which to resize. If :italic:`horizontal`, it will make the window wider or narrower by the specified increment.
|
||||
If :italic:`vertical`, it will make the window taller or shorter by the specified increment. The special value :italic:`reset` will
|
||||
reset the layout to its default configuration.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified resize the window this command is run in, rather than the active window.
|
||||
'''
|
||||
argspec = ''
|
||||
string_return_is_error = True
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'increment': opts.increment, 'axis': opts.axis, 'self': opts.self}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload_get('self') else boss.active_window]
|
||||
resized = False
|
||||
if windows and windows[0]:
|
||||
resized = boss.resize_layout_window(
|
||||
windows[0], increment=payload_get('increment'), is_horizontal=payload_get('axis') == 'horizontal',
|
||||
reset=payload_get('axis') == 'reset'
|
||||
)
|
||||
return resized
|
||||
|
||||
|
||||
resize_window = ResizeWindow()
|
||||
70
kitty/rc/scroll_window.py
Normal file
70
kitty/rc/scroll_window.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import ScrollWindowRCOptions as CLIOptions
|
||||
|
||||
|
||||
class ScrollWindow(RemoteCommand):
|
||||
|
||||
'''
|
||||
amount+: The amount to scroll, a two item list with the first item being \
|
||||
either a number or the keywords, start and end. \
|
||||
And the second item being either 'p' for pages or 'l' for lines.
|
||||
match: The window to scroll
|
||||
'''
|
||||
|
||||
short_desc = 'Scroll the specified window'
|
||||
desc = (
|
||||
'Scroll the specified window, if no window is specified, scroll the window this command is run inside.'
|
||||
' SCROLL_AMOUNT can be either the keywords :code:`start` or :code:`end` or an'
|
||||
' argument of the form <number>[unit][+-]. For example, 30 will scroll down 30 lines and 2p- will'
|
||||
' scroll up 2 pages.'
|
||||
)
|
||||
argspec = 'SCROLL_AMOUNT'
|
||||
options_spec = MATCH_WINDOW_OPTION
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
amt = args[0]
|
||||
ans = {'match': opts.match}
|
||||
if amt in ('start', 'end'):
|
||||
ans['amount'] = amt, None
|
||||
else:
|
||||
pages = 'p' in amt
|
||||
amt = amt.replace('p', '')
|
||||
mult = -1 if amt.endswith('-') else 1
|
||||
amt = int(amt.replace('-', ''))
|
||||
ans['amount'] = [amt * mult, 'p' if pages else 'l']
|
||||
return ans
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [window or boss.active_window]
|
||||
match = payload_get('match')
|
||||
amt = payload_get('amount')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
if amt[0] in ('start', 'end'):
|
||||
getattr(window, {'start': 'scroll_home'}.get(amt[0], 'scroll_end'))()
|
||||
else:
|
||||
amt, unit = amt
|
||||
unit = 'page' if unit == 'p' else 'line'
|
||||
direction = 'up' if amt < 0 else 'down'
|
||||
func = getattr(window, 'scroll_{}_{}'.format(unit, direction))
|
||||
for i in range(abs(amt)):
|
||||
func()
|
||||
|
||||
|
||||
scroll_window = ScrollWindow()
|
||||
132
kitty/rc/send_text.py
Normal file
132
kitty/rc/send_text.py
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.config import parse_send_text_bytes
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
|
||||
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType,
|
||||
Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SendTextRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SendText(RemoteCommand):
|
||||
'''
|
||||
text+: The text being sent
|
||||
is_binary+: If False text is interpreted as a python string literal instead of plain text
|
||||
match: A string indicating the window to send text to
|
||||
match_tab: A string indicating the tab to send text to
|
||||
'''
|
||||
short_desc = 'Send arbitrary text to specified windows'
|
||||
desc = (
|
||||
'Send arbitrary text to specified windows. The text follows Python'
|
||||
' escaping rules. So you can use escapes like :italic:`\\x1b` to send control codes'
|
||||
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --match` option'
|
||||
' the text will be sent to all matched windows. By default, text is sent to'
|
||||
' only the currently active window.'
|
||||
)
|
||||
options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
||||
--stdin
|
||||
type=bool-set
|
||||
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
||||
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
|
||||
|
||||
|
||||
--from-file
|
||||
Path to a file whose contents you wish to send. Note that in this case the file contents
|
||||
are sent as is, not interpreted for escapes.
|
||||
'''
|
||||
no_response = True
|
||||
argspec = '[TEXT TO SEND]'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
limit = 1024
|
||||
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
|
||||
|
||||
def pipe():
|
||||
ret['is_binary'] = True
|
||||
if sys.stdin.isatty():
|
||||
import select
|
||||
fd = sys.stdin.fileno()
|
||||
keep_going = True
|
||||
while keep_going:
|
||||
rd = select.select([fd], [], [])[0]
|
||||
if not rd:
|
||||
break
|
||||
data = os.read(fd, limit)
|
||||
if not data:
|
||||
break # eof
|
||||
data = data.decode('utf-8')
|
||||
if '\x04' in data:
|
||||
data = data[:data.index('\x04')]
|
||||
keep_going = False
|
||||
ret['text'] = data
|
||||
yield ret
|
||||
else:
|
||||
while True:
|
||||
data = sys.stdin.read(limit)
|
||||
if not data:
|
||||
break
|
||||
ret['text'] = data[:limit]
|
||||
yield ret
|
||||
|
||||
def chunks(text):
|
||||
ret['is_binary'] = False
|
||||
while text:
|
||||
ret['text'] = text[:limit]
|
||||
yield ret
|
||||
text = text[limit:]
|
||||
|
||||
def file_pipe(path):
|
||||
ret['is_binary'] = True
|
||||
with open(path, encoding='utf-8') as f:
|
||||
while True:
|
||||
data = f.read(limit)
|
||||
if not data:
|
||||
break
|
||||
ret['text'] = data
|
||||
yield ret
|
||||
|
||||
sources = []
|
||||
if opts.stdin:
|
||||
sources.append(pipe())
|
||||
|
||||
if opts.from_file:
|
||||
sources.append(file_pipe(opts.from_file))
|
||||
|
||||
text = ' '.join(args)
|
||||
sources.append(chunks(text))
|
||||
|
||||
def chain():
|
||||
for src in sources:
|
||||
yield from src
|
||||
return chain()
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
mt = payload_get('match_tab')
|
||||
if mt:
|
||||
windows = []
|
||||
tabs = tuple(boss.match_tabs(mt))
|
||||
if not tabs:
|
||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
data = payload_get('text').encode('utf-8') if payload_get('is_binary') else parse_send_text_bytes(payload_get('text'))
|
||||
for window in windows:
|
||||
if window is not None:
|
||||
window.write_to_child(data)
|
||||
|
||||
|
||||
send_text = SendText()
|
||||
116
kitty/rc/set_background_image.py
Normal file
116
kitty/rc/set_background_image.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import imghdr
|
||||
import tempfile
|
||||
from base64 import standard_b64decode, standard_b64encode
|
||||
from typing import TYPE_CHECKING, BinaryIO, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType,
|
||||
RCOptions, RemoteCommand, ResponseType, Window, no_response,
|
||||
windows_for_payload
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetBackgroundImageRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetBackgroundImage(RemoteCommand):
|
||||
|
||||
'''
|
||||
data+: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \
|
||||
Or the special value - to indicate image must be removed.
|
||||
img_id+: Unique uuid (as string) used for chunking
|
||||
match: Window to change opacity in
|
||||
layout: The image layout
|
||||
all: Boolean indicating operate on all windows
|
||||
configured: Boolean indicating if the configured value should be changed
|
||||
'''
|
||||
|
||||
short_desc = 'Set the background_image'
|
||||
desc = (
|
||||
'Set the background image for the specified OS windows. You must specify the path to a PNG image that'
|
||||
' will be used as the background. If you specify the special value "none" then any existing image will'
|
||||
' be removed.'
|
||||
)
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, background image is only changed for the currently active OS window. This option will
|
||||
cause the image to be changed in all windows.
|
||||
|
||||
|
||||
--configured -c
|
||||
type=bool-set
|
||||
Change the configured background image which is used for new OS windows.
|
||||
|
||||
|
||||
--layout
|
||||
type=choices
|
||||
choices=tiled,scaled,mirror-tiled,configured
|
||||
How the image should be displayed. The value of configured will use the configured value.
|
||||
|
||||
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION
|
||||
argspec = 'PATH_TO_PNG_IMAGE'
|
||||
args_count = 1
|
||||
args_completion = {'files': ('PNG Images', ('*.png',))}
|
||||
current_img_id: Optional[str] = None
|
||||
current_file_obj: Optional[BinaryIO] = None
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if not args:
|
||||
self.fatal('Must specify path to PNG image')
|
||||
path = args[0]
|
||||
ret = {'match': opts.match, 'configured': opts.configured, 'layout': opts.layout, 'all': opts.all, 'img_id': str(uuid4())}
|
||||
if path.lower() == 'none':
|
||||
ret['data'] = '-'
|
||||
return ret
|
||||
if imghdr.what(path) != 'png':
|
||||
self.fatal('{} is not a PNG image'.format(path))
|
||||
|
||||
def file_pipe(path):
|
||||
with open(path, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(512)
|
||||
if not data:
|
||||
break
|
||||
ret['data'] = standard_b64encode(data).decode('ascii')
|
||||
yield ret
|
||||
ret['data'] = ''
|
||||
yield ret
|
||||
return file_pipe(path)
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
data = payload_get('data')
|
||||
if data != '-':
|
||||
img_id = payload_get('img_id')
|
||||
if img_id != set_background_image.current_img_id:
|
||||
set_background_image.current_img_id = img_id
|
||||
set_background_image.current_file_obj = tempfile.NamedTemporaryFile()
|
||||
if data:
|
||||
set_background_image.current_file_obj.write(standard_b64decode(data))
|
||||
return no_response
|
||||
|
||||
windows = windows_for_payload(boss, window, payload_get)
|
||||
os_windows = tuple({w.os_window_id for w in windows})
|
||||
layout = payload_get('layout')
|
||||
if data == '-':
|
||||
path = None
|
||||
else:
|
||||
f = set_background_image.current_file_obj
|
||||
path = f.name
|
||||
set_background_image.current_file_obj = None
|
||||
f.flush()
|
||||
|
||||
try:
|
||||
boss.set_background_image(path, os_windows, payload_get('configured'), layout)
|
||||
except ValueError as err:
|
||||
err.hide_traceback = True
|
||||
raise
|
||||
|
||||
|
||||
set_background_image = SetBackgroundImage()
|
||||
58
kitty/rc/set_background_opacity.py
Normal file
58
kitty/rc/set_background_opacity.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, OpacityError,
|
||||
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType,
|
||||
Window, windows_for_payload
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetBackgroundOpacityRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetBackgroundOpacity(RemoteCommand):
|
||||
|
||||
'''
|
||||
opacity+: A number between 0.1 and 1
|
||||
match_window: Window to change opacity in
|
||||
match_tab: Tab to change opacity in
|
||||
all: Boolean indicating operate on all windows
|
||||
'''
|
||||
|
||||
short_desc = 'Set the background_opacity'
|
||||
desc = (
|
||||
'Set the background opacity for the specified windows. This will only work if you have turned on'
|
||||
' :opt:`dynamic_background_opacity` in :file:`kitty.conf`. The background opacity affects all kitty windows in a'
|
||||
' single os_window. For example: kitty @ set-background-opacity 0.5'
|
||||
)
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, colors are only changed for the currently active window. This option will
|
||||
cause colors to be changed in all windows.
|
||||
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'OPACITY'
|
||||
args_count = 1
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
opacity = max(0.1, min(float(args[0]), 1.0))
|
||||
return {
|
||||
'opacity': opacity, 'match_window': opts.match,
|
||||
'all': opts.all, 'match_tab': opts.match_tab
|
||||
}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
if not boss.opts.dynamic_background_opacity:
|
||||
raise OpacityError('You must turn on the dynamic_background_opacity option in kitty.conf to be able to set background opacity')
|
||||
windows = windows_for_payload(boss, window, payload_get)
|
||||
for os_window_id in {w.os_window_id for w in windows}:
|
||||
boss._set_os_window_background_opacity(os_window_id, payload_get('opacity'))
|
||||
|
||||
|
||||
set_background_opacity = SetBackgroundOpacity()
|
||||
97
kitty/rc/set_colors.py
Normal file
97
kitty/rc/set_colors.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kitty.config import parse_config
|
||||
from kitty.fast_data_types import patch_color_profiles
|
||||
from kitty.rgb import Color, color_as_int
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window,
|
||||
windows_for_payload
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetColorsRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetColors(RemoteCommand):
|
||||
|
||||
'''
|
||||
colors+: An object mapping names to colors as 24-bit RGB integers
|
||||
cursor_text_color: A 24-bit color for text under the cursor
|
||||
match_window: Window to change colors in
|
||||
match_tab: Tab to change colors in
|
||||
all: Boolean indicating change colors everywhere or not
|
||||
configured: Boolean indicating whether to change the configured colors. Must be True if reset is True
|
||||
reset: Boolean indicating colors should be reset to startup values
|
||||
'''
|
||||
|
||||
short_desc = 'Set terminal colors'
|
||||
desc = (
|
||||
'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file'
|
||||
' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,'
|
||||
' for example: kitty @ set-colors foreground=red background=white'
|
||||
)
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, colors are only changed for the currently active window. This option will
|
||||
cause colors to be changed in all windows.
|
||||
|
||||
|
||||
--configured -c
|
||||
type=bool-set
|
||||
Also change the configured colors (i.e. the colors kitty will use for new
|
||||
windows or after a reset).
|
||||
|
||||
|
||||
--reset
|
||||
type=bool-set
|
||||
Restore all colors to the values they had at kitty startup. Note that if you specify
|
||||
this option, any color arguments are ignored and --configured and --all are implied.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')
|
||||
argspec = 'COLOR_OR_FILE ...'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
colors, cursor_text_color = {}, False
|
||||
if not opts.reset:
|
||||
for spec in args:
|
||||
if '=' in spec:
|
||||
colors.update(parse_config((spec.replace('=', ' '),)))
|
||||
else:
|
||||
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||
colors.update(parse_config(f))
|
||||
cursor_text_color = colors.pop('cursor_text_color', False)
|
||||
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||
return {
|
||||
'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
|
||||
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
|
||||
}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = windows_for_payload(boss, window, payload_get)
|
||||
colors = payload_get('colors')
|
||||
cursor_text_color = payload_get('cursor_text_color') or False
|
||||
if payload_get('reset'):
|
||||
colors = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
||||
cursor_text_color = boss.startup_cursor_text_color
|
||||
profiles = tuple(w.screen.color_profile for w in windows)
|
||||
if isinstance(cursor_text_color, (tuple, list, Color)):
|
||||
cursor_text_color = color_as_int(Color(*cursor_text_color))
|
||||
patch_color_profiles(colors, cursor_text_color, profiles, payload_get('configured'))
|
||||
boss.patch_colors(colors, cursor_text_color, payload_get('configured'))
|
||||
default_bg_changed = 'background' in colors
|
||||
for w in windows:
|
||||
if default_bg_changed:
|
||||
boss.default_bg_changed_for(w.id)
|
||||
w.refresh()
|
||||
|
||||
|
||||
set_colors = SetColors()
|
||||
54
kitty/rc/set_font_size.py
Normal file
54
kitty/rc/set_font_size.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand,
|
||||
ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetFontSizeRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetFontSize(RemoteCommand):
|
||||
'''
|
||||
size+: The new font size in pts (a positive number)
|
||||
all: Boolean whether to change font size in the current window or all windows
|
||||
increment_op: The string ``+`` or ``-`` to interpret size as an increment
|
||||
'''
|
||||
|
||||
short_desc = 'Set the font size in the active top-level OS window'
|
||||
desc = (
|
||||
'Sets the font size to the specified size, in pts. Note'
|
||||
' that in kitty all sub-windows in the same OS window'
|
||||
' must have the same font size. A value of zero'
|
||||
' resets the font size to default. Prefixing the value'
|
||||
' with a + or - increments the font size by the specified'
|
||||
' amount.'
|
||||
)
|
||||
argspec = 'FONT_SIZE'
|
||||
args_count = 1
|
||||
options_spec = '''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, the font size is only changed in the active OS window,
|
||||
this option will cause it to be changed in all OS windows.
|
||||
'''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
if not args:
|
||||
self.fatal('No font size specified')
|
||||
fs = args[0]
|
||||
inc = fs[0] if fs and fs[0] in '+-' else None
|
||||
return {'size': abs(float(fs)), 'all': opts.all, 'increment_op': inc}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
boss.change_font_size(
|
||||
payload_get('all'),
|
||||
payload_get('increment_op'), payload_get('size'))
|
||||
|
||||
|
||||
set_font_size = SetFontSize()
|
||||
50
kitty/rc/set_tab_title.py
Normal file
50
kitty/rc/set_tab_title.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetTabTitleRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetTabTitle(RemoteCommand):
|
||||
|
||||
'''
|
||||
title+: The new title
|
||||
match: Which tab to change the title of
|
||||
'''
|
||||
|
||||
short_desc = 'Set the tab title'
|
||||
desc = (
|
||||
'Set the title for the specified tab(s). If you use the :option:`kitty @ set-tab-title --match` option'
|
||||
' the title will be set for all matched tabs. By default, only the tab'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' title of the currently active window in the tab is used.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION
|
||||
argspec = 'TITLE ...'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
tab.set_title(payload_get('title'))
|
||||
|
||||
|
||||
set_tab_title = SetTabTitle()
|
||||
59
kitty/rc/set_window_title.py
Normal file
59
kitty/rc/set_window_title.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import (
|
||||
MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType,
|
||||
PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import SetWindowTitleRCOptions as CLIOptions
|
||||
|
||||
|
||||
class SetWindowTitle(RemoteCommand):
|
||||
|
||||
'''
|
||||
title+: The new title
|
||||
match: Which windows to change the title in
|
||||
temporary: Boolean indicating if the change is temporary or permanent
|
||||
'''
|
||||
|
||||
short_desc = 'Set the window title'
|
||||
desc = (
|
||||
'Set the title for the specified window(s). If you use the :option:`kitty @ set-window-title --match` option'
|
||||
' the title will be set for all matched windows. By default, only the window'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' last title set by the child process running in the window will be used.'
|
||||
)
|
||||
options_spec = '''\
|
||||
--temporary
|
||||
type=bool-set
|
||||
By default, if you use :italic:`set-window-title` the title will be permanently changed
|
||||
and programs running in the window will not be able to change it again. If you
|
||||
want to allow other programs to change it afterwards, use this option.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION
|
||||
argspec = 'TITLE ...'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'title': ' '.join(args), 'match': opts.match, 'temporary': opts.temporary}
|
||||
|
||||
def response_from_kitty(self, boss: 'Boss', window: 'Window', payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [window or boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
if payload_get('temporary'):
|
||||
window.override_title = None
|
||||
window.title_changed(payload_get('title'))
|
||||
else:
|
||||
window.set_title(payload_get('title'))
|
||||
|
||||
|
||||
set_window_title = SetWindowTitle()
|
||||
Reference in New Issue
Block a user