mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-22 20:17:45 +03:00
Refactor remote control commands into individual modules
Also add type information
This commit is contained in:
parent
a0321376d5
commit
9b32f18109
17
docs/conf.py
17
docs/conf.py
@ -258,16 +258,17 @@ def write_cli_docs(all_kitten_names):
|
||||
f.write(option_spec_as_rst(appname='kitty').replace(
|
||||
'kitty --to', 'kitty @ --to'))
|
||||
as_rst = partial(option_spec_as_rst, heading_char='_')
|
||||
from kitty.remote_control import global_options_spec, cli_msg, cmap, all_commands
|
||||
from kitty.rc.base import all_command_names, command_for_name
|
||||
from kitty.remote_control import global_options_spec, cli_msg
|
||||
with open('generated/cli-kitty-at.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('kitty @\n' + '-' * 80)
|
||||
p('.. program::', 'kitty @')
|
||||
p('\n\n' + as_rst(
|
||||
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
||||
from kitty.cmds import cli_params_for
|
||||
for cmd_name in all_commands:
|
||||
func = cmap[cmd_name]
|
||||
from kitty.rc.base import cli_params_for
|
||||
for cmd_name in all_command_names():
|
||||
func = command_for_name(cmd_name)
|
||||
p(f'.. _at_{func.name}:\n')
|
||||
p('kitty @', func.name + '\n' + '-' * 120)
|
||||
p('.. program::', 'kitty @', func.name)
|
||||
@ -286,8 +287,8 @@ def write_cli_docs(all_kitten_names):
|
||||
# }}}
|
||||
|
||||
|
||||
def write_remote_control_protocol_docs(): # {{{
|
||||
from kitty.cmds import cmap
|
||||
def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import all_command_names, command_for_name
|
||||
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
|
||||
|
||||
def format_cmd(p, name, cmd):
|
||||
@ -319,8 +320,8 @@ def format_cmd(p, name, cmd):
|
||||
|
||||
with open(f'generated/rc.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
for name in sorted(cmap):
|
||||
cmd = cmap[name]
|
||||
for name in sorted(all_command_names()):
|
||||
cmd = command_for_name(name)
|
||||
if not cmd.__doc__:
|
||||
continue
|
||||
name = name.replace('_', '-')
|
||||
|
@ -6,6 +6,7 @@
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
@ -43,19 +44,19 @@
|
||||
'''.format
|
||||
|
||||
|
||||
args = None
|
||||
args = PanelCLIOptions()
|
||||
help_text = 'Use a command line program to draw a GPU accelerated panel on your X11 desktop'
|
||||
usage = 'program-to-run'
|
||||
|
||||
|
||||
def parse_panel_args(args):
|
||||
def parse_panel_args(args: List[str]) -> Tuple[PanelCLIOptions, List[str]]:
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
|
||||
|
||||
|
||||
def call_xprop(*cmd, silent=False):
|
||||
cmd = ['xprop'] + list(cmd)
|
||||
def call_xprop(*cmd: str, silent=False):
|
||||
cmd_ = ['xprop'] + list(cmd)
|
||||
try:
|
||||
cp = subprocess.run(cmd, stdout=subprocess.DEVNULL if silent else None)
|
||||
cp = subprocess.run(cmd_, stdout=subprocess.DEVNULL if silent else None)
|
||||
except FileNotFoundError:
|
||||
raise SystemExit('You must have the xprop program installed')
|
||||
if cp.returncode != 0:
|
||||
@ -94,29 +95,33 @@ def create_right_strut(win_id, width, height):
|
||||
create_strut(win_id, right=width, right_end_y=height)
|
||||
|
||||
|
||||
window_width = window_height = 0
|
||||
|
||||
|
||||
def setup_x11_window(win_id):
|
||||
call_xprop(
|
||||
'-id', str(win_id), '-format', '_NET_WM_WINDOW_TYPE', '32a',
|
||||
'-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_DOCK'
|
||||
)
|
||||
func = globals()['create_{}_strut'.format(args.edge)]
|
||||
func(win_id, initial_window_size_func.width, initial_window_size_func.height)
|
||||
func(win_id, window_width, window_height)
|
||||
|
||||
|
||||
def initial_window_size_func(opts, *a):
|
||||
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
|
||||
|
||||
def initial_window_size(cell_width, cell_height, dpi_x, dpi_y, xscale, yscale):
|
||||
global window_width, window_height
|
||||
monitor_width, monitor_height = glfw_primary_monitor_size()
|
||||
if args.edge in {'top', 'bottom'}:
|
||||
h = initial_window_size_func.height = cell_height * args.lines + 1
|
||||
initial_window_size_func.width = monitor_width
|
||||
h = window_height = cell_height * args.lines + 1
|
||||
window_width = monitor_width
|
||||
set_smallest_allowed_resize(100, h)
|
||||
else:
|
||||
w = initial_window_size_func.width = cell_width * args.columns + 1
|
||||
initial_window_size_func.height = monitor_height
|
||||
w = window_width = cell_width * args.columns + 1
|
||||
window_height = monitor_height
|
||||
set_smallest_allowed_resize(w, 100)
|
||||
return initial_window_size_func.width, initial_window_size_func.height
|
||||
return window_width, window_height
|
||||
|
||||
return initial_window_size
|
||||
|
||||
|
@ -4,10 +4,11 @@
|
||||
|
||||
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ResizeCLIOptions
|
||||
from kitty.cmds import cmap, parse_subcommand_cli
|
||||
from kitty.cli_stub import RCOptions, ResizeCLIOptions
|
||||
from kitty.rc.base import parse_subcommand_cli, command_for_name
|
||||
from kitty.constants import version
|
||||
from kitty.key_encoding import CTRL, RELEASE, key_defs as K
|
||||
from kitty.remote_control import encode_send, parse_rc_args
|
||||
@ -16,7 +17,7 @@
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
|
||||
global_opts = None
|
||||
global_opts = RCOptions()
|
||||
ESCAPE = K['ESCAPE']
|
||||
N = K['N']
|
||||
S = K['S']
|
||||
@ -26,7 +27,7 @@
|
||||
|
||||
class Resize(Handler):
|
||||
|
||||
print_on_fail = None
|
||||
print_on_fail: Optional[str] = None
|
||||
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
@ -40,7 +41,7 @@ def initialize(self):
|
||||
self.draw_screen()
|
||||
|
||||
def do_window_resize(self, is_decrease=False, is_horizontal=True, reset=False, multiplier=1):
|
||||
resize_window = cmap['resize-window']
|
||||
resize_window = command_for_name('resize_window')
|
||||
increment = self.opts.horizontal_increment if is_horizontal else self.opts.vertical_increment
|
||||
increment *= multiplier
|
||||
if is_decrease:
|
||||
@ -48,7 +49,7 @@ def do_window_resize(self, is_decrease=False, is_horizontal=True, reset=False, m
|
||||
axis = 'reset' if reset else ('horizontal' if is_horizontal else 'vertical')
|
||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||
payload = resize_window(global_opts, opts, items)
|
||||
payload = resize_window.message_to_kitty(global_opts, opts, items)
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||
self.write(encode_send(send))
|
||||
|
||||
|
@ -1164,10 +1164,11 @@ def format_bad_line(bad_line):
|
||||
self.show_error(_('Errors in kitty.conf'), msg)
|
||||
|
||||
def set_colors(self, *args):
|
||||
from .cmds import parse_subcommand_cli, cmd_set_colors, set_colors
|
||||
opts, items = parse_subcommand_cli(cmd_set_colors, ['set-colors'] + list(args))
|
||||
payload = cmd_set_colors(None, opts, items)
|
||||
set_colors(self, self.active_window, payload)
|
||||
from kitty.rc.base import parse_subcommand_cli, command_for_name
|
||||
c = command_for_name('set_colors')
|
||||
opts, items = parse_subcommand_cli(c, ['set-colors'] + list(args))
|
||||
payload = c.message_to_kitty(None, opts, items)
|
||||
c.response_from_kitty(self, self.active_window, payload)
|
||||
|
||||
def _move_window_to(self, window=None, target_tab_id=None, target_os_window_id=None):
|
||||
window = window or self.active_window
|
||||
|
@ -9,7 +9,7 @@ class CLIOptions:
|
||||
|
||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = CLIOptions
|
||||
|
||||
|
||||
def generate_stub() -> None:
|
||||
@ -26,6 +26,9 @@ def do(otext=None, cls: str = 'CLIOptions'):
|
||||
from .launch import options_spec
|
||||
do(options_spec(), 'LaunchCLIOptions')
|
||||
|
||||
from .remote_control import global_options_spec
|
||||
do(global_options_spec(), 'RCOptions')
|
||||
|
||||
from kittens.ask.main import option_text
|
||||
do(option_text(), 'AskCLIOptions')
|
||||
|
||||
@ -38,8 +41,8 @@ def do(otext=None, cls: str = 'CLIOptions'):
|
||||
from kittens.hints.main import OPTIONS
|
||||
do(OPTIONS(), 'HintsCLIOptions')
|
||||
|
||||
from kittens.icat.main import OPTIONS
|
||||
do(OPTIONS, 'IcatCLIOptions')
|
||||
from kittens.icat.main import options_spec
|
||||
do(options_spec(), 'IcatCLIOptions')
|
||||
|
||||
from kittens.panel.main import OPTIONS
|
||||
do(OPTIONS(), 'PanelCLIOptions')
|
||||
@ -53,6 +56,12 @@ def do(otext=None, cls: str = 'CLIOptions'):
|
||||
from kittens.unicode_input.main import OPTIONS
|
||||
do(OPTIONS(), 'UnicodeCLIOptions')
|
||||
|
||||
from kitty.rc.base import all_command_names, command_for_name
|
||||
for cmd_name in all_command_names():
|
||||
cmd = command_for_name(cmd_name)
|
||||
if cmd.options_spec:
|
||||
do(cmd.options_spec, cmd.__class__.__name__ + 'RCOptions')
|
||||
|
||||
save_type_stub(text, __file__)
|
||||
|
||||
|
||||
|
1465
kitty/cmds.py
1465
kitty/cmds.py
File diff suppressed because it is too large
Load Diff
@ -5,11 +5,13 @@
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import Tuple
|
||||
|
||||
from kittens.runner import get_kitten_cli_docs, all_kitten_names
|
||||
from kittens.runner import all_kitten_names, get_kitten_cli_docs
|
||||
|
||||
from .cli import options_for_completion, parse_option_spec
|
||||
from .cmds import cmap
|
||||
from .rc.base import all_command_names, command_for_name
|
||||
from .shell import options_for_cmd
|
||||
|
||||
'''
|
||||
@ -47,6 +49,11 @@ def __init__(self):
|
||||
self.files_groups = set()
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def remote_control_command_names() -> Tuple[str, ...]:
|
||||
return tuple(sorted(x.replace('_', '-') for x in all_command_names()))
|
||||
|
||||
|
||||
# Shell specific code {{{
|
||||
|
||||
|
||||
@ -164,7 +171,7 @@ def fish_output_serializer(ans):
|
||||
|
||||
|
||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||
cmds = ['@' + c for c in cmap]
|
||||
cmds = ['@' + c for c in remote_control_command_names()]
|
||||
ans.match_groups['Entry points'] = {
|
||||
k: None for k in
|
||||
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
||||
@ -266,9 +273,9 @@ def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
aliases, alias_map = options_for_cmd(cmd_name)
|
||||
if not alias_map:
|
||||
return
|
||||
args_completion = cmap[cmd_name].args_completion
|
||||
args_completer = None
|
||||
if 'files' in args_completion:
|
||||
args_completion = command_for_name(cmd_name).args_completion
|
||||
if args_completion and 'files' in args_completion:
|
||||
args_completer = remote_files_completer(args_completion['files'])
|
||||
complete_alias_map(ans, words, new_word, alias_map, complete_args=args_completer)
|
||||
|
||||
@ -390,14 +397,14 @@ def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
if words[0] == '@':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Remote control commands'] = {c: None for c in cmap if c.startswith(prefix)}
|
||||
ans.match_groups['Remote control commands'] = {c: None for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[1], words[2:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('@'):
|
||||
if len(words) == 1 and not new_word:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: None for c in cmap if c.startswith(prefix)}
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: None for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||
if words[0] == '+':
|
||||
|
@ -336,7 +336,10 @@ def save_type_stub(text: str, fpath: str) -> None:
|
||||
import os
|
||||
fpath += 'i'
|
||||
preamble = '# Update this file by running: python {}\n\n'.format(os.path.relpath(os.path.abspath(fpath)))
|
||||
existing = open(fpath).read()
|
||||
try:
|
||||
existing = open(fpath).read()
|
||||
except FileNotFoundError:
|
||||
existing = ''
|
||||
current = preamble + text
|
||||
if existing != current:
|
||||
open(fpath, 'w').write(current)
|
||||
|
@ -2,14 +2,14 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
import sys
|
||||
import errno
|
||||
from collections import namedtuple
|
||||
from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
from typing import Set
|
||||
from typing import Optional, Set
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 16, 0)
|
||||
@ -170,3 +170,9 @@ def is_wayland(opts=None):
|
||||
|
||||
|
||||
supports_primary_selection = not is_macos
|
||||
|
||||
|
||||
def running_in_kitty(set_val: Optional[bool] = None) -> bool:
|
||||
if set_val is not None:
|
||||
setattr(running_in_kitty, 'ans', set_val)
|
||||
return getattr(running_in_kitty, 'ans', False)
|
||||
|
@ -489,6 +489,14 @@ def parse_font_feature(str) -> bytes:
|
||||
pass
|
||||
|
||||
|
||||
def glfw_primary_monitor_size() -> Tuple[int, int]:
|
||||
pass
|
||||
|
||||
|
||||
def set_smallest_allowed_resize(width: int, height: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def set_default_window_icon(data: bytes, width: int, height: int) -> None:
|
||||
pass
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
from .config import cached_values_for, initial_window_size_func
|
||||
from .constants import (
|
||||
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
|
||||
is_wayland, kitty_exe, logo_data_file
|
||||
is_wayland, kitty_exe, logo_data_file, running_in_kitty
|
||||
)
|
||||
from .fast_data_types import (
|
||||
GLFW_IBEAM_CURSOR, create_os_window, free_font_data, glfw_init,
|
||||
@ -254,6 +254,7 @@ def set_locale():
|
||||
|
||||
|
||||
def _main():
|
||||
running_in_kitty(True)
|
||||
with suppress(AttributeError): # python compiled without threading
|
||||
sys.setswitchinterval(1000.0) # we have only a single python thread
|
||||
|
||||
|
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()
|
@ -9,13 +9,16 @@
|
||||
import types
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from .cli import emph, parse_args
|
||||
from .cmds import (
|
||||
cmap, no_response as no_response_sentinel, parse_subcommand_cli
|
||||
)
|
||||
from .cli_stub import RCOptions
|
||||
from .constants import appname, version
|
||||
from .fast_data_types import read_command_response
|
||||
from .rc.base import (
|
||||
all_command_names, command_for_name, no_response as no_response_sentinel,
|
||||
parse_subcommand_cli
|
||||
)
|
||||
from .utils import TTYIO, parse_address_spec
|
||||
|
||||
|
||||
@ -27,11 +30,14 @@ def handle_cmd(boss, window, cmd):
|
||||
if no_response:
|
||||
return
|
||||
return {'ok': False, 'error': 'The kitty client you are using to send remote commands is newer than this kitty instance. This is not supported.'}
|
||||
c = cmap[cmd['cmd']]
|
||||
func = partial(c.impl(), boss, window)
|
||||
payload = cmd.get('payload')
|
||||
c = command_for_name(cmd['cmd'])
|
||||
payload = cmd.get('payload') or {}
|
||||
|
||||
def payload_get(key: str, opt_name: Optional[str] = None) -> Any:
|
||||
return c.payload_get(payload, key, opt_name)
|
||||
|
||||
try:
|
||||
ans = func() if payload is None else func(payload)
|
||||
ans = c.response_from_kitty(boss, window, payload_get)
|
||||
except Exception:
|
||||
if no_response: # don't report errors if --no-response was used
|
||||
return
|
||||
@ -130,7 +136,6 @@ def send_generator():
|
||||
return response
|
||||
|
||||
|
||||
all_commands = tuple(sorted(cmap))
|
||||
cli_msg = (
|
||||
'Control {appname} by sending it commands. Set the'
|
||||
' :opt:`allow_remote_control` option to yes in :file:`kitty.conf` for this'
|
||||
@ -138,12 +143,9 @@ def send_generator():
|
||||
).format(appname=appname)
|
||||
|
||||
|
||||
class RCOptions:
|
||||
pass
|
||||
|
||||
|
||||
def parse_rc_args(args):
|
||||
cmds = (' :green:`{}`\n {}'.format(cmap[c].name, cmap[c].short_desc) for c in all_commands)
|
||||
def parse_rc_args(args: List[str]):
|
||||
cmap = {name: command_for_name(name) for name in sorted(all_command_names())}
|
||||
cmds = (' :green:`{}`\n {}'.format(cmd.name, cmd.short_desc) for c, cmd in cmap.items())
|
||||
msg = cli_msg + (
|
||||
'\n\n:title:`Commands`:\n{cmds}\n\n'
|
||||
'You can get help for each individual command by using:\n'
|
||||
@ -161,12 +163,12 @@ def main(args):
|
||||
return
|
||||
cmd = items[0]
|
||||
try:
|
||||
func = cmap[cmd]
|
||||
c = command_for_name(cmd)
|
||||
except KeyError:
|
||||
raise SystemExit('{} is not a known command. Known commands are: {}'.format(
|
||||
emph(cmd), ', '.join(all_commands)))
|
||||
opts, items = parse_subcommand_cli(func, items)
|
||||
payload = func(global_opts, opts, items)
|
||||
emph(cmd), ', '.join(x.replace('_', '-') for x in all_command_names())))
|
||||
opts, items = parse_subcommand_cli(c, items)
|
||||
payload = c.message_to_kitty(global_opts, opts, items)
|
||||
send = {
|
||||
'cmd': cmd,
|
||||
'version': version,
|
||||
@ -176,7 +178,7 @@ def main(args):
|
||||
if global_opts.no_command_response is not None:
|
||||
no_response = global_opts.no_command_response
|
||||
else:
|
||||
no_response = func.no_response
|
||||
no_response = c.no_response
|
||||
send['no_response'] = no_response
|
||||
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||
@ -189,6 +191,6 @@ def main(args):
|
||||
raise SystemExit(response['error'])
|
||||
data = response.get('data')
|
||||
if data is not None:
|
||||
if func.string_return_is_error and isinstance(data, str):
|
||||
if c.string_return_is_error and isinstance(data, str):
|
||||
raise SystemExit(data)
|
||||
print(data)
|
||||
|
@ -7,17 +7,25 @@
|
||||
import shlex
|
||||
import sys
|
||||
import traceback
|
||||
from functools import lru_cache
|
||||
from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from .cli import (
|
||||
emph, green, italic, parse_option_spec, print_help_for_seq, title
|
||||
OptionDict, emph, green, italic, parse_option_spec, print_help_for_seq,
|
||||
title
|
||||
)
|
||||
from .cmds import cmap, display_subcommand_help, parse_subcommand_cli
|
||||
from .constants import cache_dir, is_macos, version
|
||||
from .rc.base import (
|
||||
all_command_names, command_for_name, display_subcommand_help,
|
||||
parse_subcommand_cli
|
||||
)
|
||||
|
||||
all_commands = tuple(sorted(cmap))
|
||||
match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def match_commands() -> Tuple[str, ...]:
|
||||
all_commands = tuple(sorted(all_command_names()))
|
||||
return tuple(sorted(all_commands + ('exit', 'help', 'quit')))
|
||||
|
||||
|
||||
def init_readline(readline):
|
||||
@ -33,16 +41,16 @@ def init_readline(readline):
|
||||
|
||||
|
||||
def cmd_names_matching(prefix):
|
||||
for cmd in match_commands:
|
||||
for cmd in match_commands():
|
||||
if not prefix or cmd.startswith(prefix):
|
||||
yield cmd + ' '
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def options_for_cmd(cmd):
|
||||
def options_for_cmd(cmd: str) -> Tuple[Tuple[str, ...], Dict[str, OptionDict]]:
|
||||
alias_map = {}
|
||||
try:
|
||||
func = cmap[cmd]
|
||||
func = command_for_name(cmd)
|
||||
except KeyError:
|
||||
return (), alias_map
|
||||
if not func.options_spec:
|
||||
@ -106,8 +114,8 @@ def print_help(which=None):
|
||||
print('Control kitty by sending it commands.')
|
||||
print()
|
||||
print(title('Commands') + ':')
|
||||
for cmd in all_commands:
|
||||
c = cmap[cmd]
|
||||
for cmd in all_command_names():
|
||||
c = command_for_name(cmd)
|
||||
print(' ', green(c.name))
|
||||
print(' ', c.short_desc)
|
||||
print(' ', green('exit'))
|
||||
@ -115,7 +123,7 @@ def print_help(which=None):
|
||||
print('\nUse help {} for help on individual commands'.format(italic('command')))
|
||||
else:
|
||||
try:
|
||||
func = cmap[which]
|
||||
func = command_for_name(which)
|
||||
except KeyError:
|
||||
if which == 'exit':
|
||||
print('Exit this shell')
|
||||
@ -170,7 +178,7 @@ def real_main(global_opts):
|
||||
cmd = cmdline[0].lower()
|
||||
|
||||
try:
|
||||
func = cmap[cmd]
|
||||
func = command_for_name(cmd)
|
||||
except KeyError:
|
||||
if cmd in ('exit', 'quit'):
|
||||
break
|
||||
|
Loading…
Reference in New Issue
Block a user