795 lines
25 KiB
Python
795 lines
25 KiB
Python
#!/usr/bin/env python3
|
|
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import re
|
|
import sys
|
|
from collections import deque
|
|
from typing import (
|
|
Any, Callable, Dict, FrozenSet, Iterator, List, Match, Optional, Sequence,
|
|
Tuple, Type, TypeVar, Union, cast
|
|
)
|
|
|
|
from .cli_stub import CLIOptions
|
|
from .conf.utils import resolve_config
|
|
from .constants import appname, defconf, is_macos, str_version
|
|
from .options.types import Options as KittyOpts
|
|
from .typing import BadLineType, TypedDict
|
|
|
|
|
|
class OptionDict(TypedDict):
|
|
dest: str
|
|
aliases: FrozenSet[str]
|
|
help: str
|
|
choices: FrozenSet[str]
|
|
type: str
|
|
default: Optional[str]
|
|
condition: bool
|
|
|
|
|
|
CONFIG_HELP = '''\
|
|
Specify a path to the configuration file(s) to use. All configuration files are
|
|
merged onto the builtin {conf_name}.conf, overriding the builtin values. This option
|
|
can be specified multiple times to read multiple configuration files in
|
|
sequence, which are merged. Use the special value NONE to not load a config
|
|
file.
|
|
|
|
If this option is not specified, config files are searched for in the order:
|
|
:file:`$XDG_CONFIG_HOME/{appname}/{conf_name}.conf`, :file:`~/.config/{appname}/{conf_name}.conf`, {macos_confpath}
|
|
:file:`$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf`. The first one that exists is used as the
|
|
config file.
|
|
|
|
If the environment variable :envvar:`KITTY_CONFIG_DIRECTORY` is specified, that
|
|
directory is always used and the above searching does not happen.
|
|
|
|
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
|
|
priority) than any user config files. It can be used to specify system-wide
|
|
defaults for all users.
|
|
'''.replace(
|
|
'{macos_confpath}',
|
|
(':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
|
|
)
|
|
|
|
|
|
def surround(x: str, start: int, end: int) -> str:
|
|
if sys.stdout.isatty():
|
|
x = '\033[{}m{}\033[{}m'.format(start, x, end)
|
|
return x
|
|
|
|
|
|
role_map: Dict[str, Callable[[str], str]] = {}
|
|
|
|
|
|
def role(func: Callable[[str], str]) -> Callable[[str], str]:
|
|
role_map[func.__name__] = func
|
|
return func
|
|
|
|
|
|
@role
|
|
def emph(x: str) -> str:
|
|
return surround(x, 91, 39)
|
|
|
|
|
|
@role
|
|
def cyan(x: str) -> str:
|
|
return surround(x, 96, 39)
|
|
|
|
|
|
@role
|
|
def green(x: str) -> str:
|
|
return surround(x, 32, 39)
|
|
|
|
|
|
@role
|
|
def blue(x: str) -> str:
|
|
return surround(x, 34, 39)
|
|
|
|
|
|
@role
|
|
def yellow(x: str) -> str:
|
|
return surround(x, 93, 39)
|
|
|
|
|
|
@role
|
|
def italic(x: str) -> str:
|
|
return surround(x, 3, 23)
|
|
|
|
|
|
@role
|
|
def bold(x: str) -> str:
|
|
return surround(x, 1, 22)
|
|
|
|
|
|
@role
|
|
def title(x: str) -> str:
|
|
return blue(bold(x))
|
|
|
|
|
|
@role
|
|
def opt(text: str) -> str:
|
|
return text
|
|
|
|
|
|
@role
|
|
def option(x: str) -> str:
|
|
idx = x.rfind('--')
|
|
if idx < 0:
|
|
idx = x.find('-')
|
|
if idx > -1:
|
|
x = x[idx:]
|
|
parts = map(bold, x.split())
|
|
return ' '.join(parts)
|
|
|
|
|
|
@role
|
|
def code(x: str) -> str:
|
|
return x
|
|
|
|
|
|
@role
|
|
def kbd(x: str) -> str:
|
|
return x
|
|
|
|
|
|
@role
|
|
def env(x: str) -> str:
|
|
return italic(x)
|
|
|
|
|
|
role_map['envvar'] = role_map['env']
|
|
|
|
|
|
@role
|
|
def file(x: str) -> str:
|
|
return italic(x)
|
|
|
|
|
|
@role
|
|
def doc(x: str) -> str:
|
|
return f'https://sw.kovidgoyal.net/kitty/{x}/'
|
|
|
|
|
|
OptionSpecSeq = List[Union[str, OptionDict]]
|
|
|
|
|
|
def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, OptionSpecSeq]:
|
|
if spec is None:
|
|
spec = options_spec()
|
|
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
|
state = NORMAL
|
|
lines = spec.splitlines()
|
|
prev_line = ''
|
|
seq: OptionSpecSeq = []
|
|
disabled: OptionSpecSeq = []
|
|
mpat = re.compile('([a-z]+)=(.+)')
|
|
current_cmd: OptionDict = {
|
|
'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(),
|
|
'type': '', 'condition': False, 'default': None
|
|
}
|
|
empty_cmd = current_cmd
|
|
|
|
for line in lines:
|
|
line = line.rstrip()
|
|
if state is NORMAL:
|
|
if not line:
|
|
continue
|
|
if line.startswith('# '):
|
|
seq.append(line[2:])
|
|
continue
|
|
if line.startswith('--'):
|
|
parts = line.split(' ')
|
|
current_cmd = {
|
|
'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '',
|
|
'choices': frozenset(), 'type': '',
|
|
'default': None, 'condition': True
|
|
}
|
|
state = METADATA
|
|
continue
|
|
raise ValueError('Invalid option spec, unexpected line: {}'.format(line))
|
|
elif state is METADATA:
|
|
m = mpat.match(line)
|
|
if m is None:
|
|
state = HELP
|
|
current_cmd['help'] += line
|
|
else:
|
|
k, v = m.group(1), m.group(2)
|
|
if k == 'choices':
|
|
current_cmd['choices'] = frozenset(x.strip() for x in v.split(','))
|
|
else:
|
|
if k == 'default':
|
|
current_cmd['default'] = v
|
|
elif k == 'type':
|
|
current_cmd['type'] = v
|
|
elif k == 'dest':
|
|
current_cmd['dest'] = v
|
|
elif k == 'condition':
|
|
current_cmd['condition'] = bool(eval(v))
|
|
elif state is HELP:
|
|
if line:
|
|
spc = '' if current_cmd['help'].endswith('\n') else ' '
|
|
current_cmd['help'] += spc + line
|
|
else:
|
|
if prev_line:
|
|
current_cmd['help'] += '\n\n'
|
|
else:
|
|
state = NORMAL
|
|
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
|
|
current_cmd = empty_cmd
|
|
prev_line = line
|
|
if current_cmd is not empty_cmd:
|
|
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
|
|
|
|
return seq, disabled
|
|
|
|
|
|
def prettify(text: str) -> str:
|
|
|
|
def sub(m: Match) -> str:
|
|
role, text = m.group(1, 2)
|
|
try:
|
|
return str(role_map[role](text))
|
|
except KeyError:
|
|
return str(text)
|
|
|
|
text = re.sub(r':([a-z]+):`([^`]+)`', sub, text)
|
|
return text
|
|
|
|
|
|
def prettify_rst(text: str) -> str:
|
|
return re.sub(r':([a-z]+):`([^`]+)`(=[^\s.]+)', r':\1:`\2`:code:`\3`', text)
|
|
|
|
|
|
def version(add_rev: bool = False) -> str:
|
|
rev = ''
|
|
from . import fast_data_types
|
|
if add_rev and hasattr(fast_data_types, 'KITTY_VCS_REV'):
|
|
rev = ' ({})'.format(fast_data_types.KITTY_VCS_REV[:10])
|
|
return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal'))
|
|
|
|
|
|
def wrap(text: str, limit: int = 80) -> Iterator[str]:
|
|
NORMAL, IN_FORMAT = 'NORMAL', 'IN_FORMAT'
|
|
state = NORMAL
|
|
last_space_at = None
|
|
chars_in_line = 0
|
|
breaks = []
|
|
for i, ch in enumerate(text):
|
|
if state is IN_FORMAT:
|
|
if ch == 'm':
|
|
state = NORMAL
|
|
continue
|
|
if ch == '\033':
|
|
state = IN_FORMAT
|
|
continue
|
|
if ch == ' ':
|
|
last_space_at = i
|
|
if chars_in_line < limit:
|
|
chars_in_line += 1
|
|
continue
|
|
if last_space_at is not None:
|
|
breaks.append(last_space_at)
|
|
last_space_at = None
|
|
chars_in_line = i - breaks[-1]
|
|
|
|
lines: List[str] = []
|
|
for b in reversed(breaks):
|
|
lines.append(text[b:].lstrip())
|
|
text = text[:b]
|
|
if text:
|
|
lines.append(text)
|
|
return reversed(lines)
|
|
|
|
|
|
def get_defaults_from_seq(seq: OptionSpecSeq) -> Dict[str, Any]:
|
|
ans: Dict[str, Any] = {}
|
|
for opt in seq:
|
|
if not isinstance(opt, str):
|
|
ans[opt['dest']] = defval_for_opt(opt)
|
|
return ans
|
|
|
|
|
|
default_msg = ('''\
|
|
Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:`program`
|
|
to run inside :italic:`{appname}` as normal arguments following the :italic:`options`.
|
|
For example: {appname} sh -c "echo hello, world. Press ENTER to quit; read"
|
|
|
|
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname)
|
|
|
|
|
|
class PrintHelpForSeq:
|
|
|
|
allow_pager = True
|
|
|
|
def __call__(self, seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: str) -> None:
|
|
from kitty.utils import screen_size_function
|
|
screen_size = screen_size_function()
|
|
try:
|
|
linesz = min(screen_size().cols, 76)
|
|
except OSError:
|
|
linesz = 76
|
|
blocks: List[str] = []
|
|
a = blocks.append
|
|
|
|
def wa(text: str, indent: int = 0, leading_indent: Optional[int] = None) -> None:
|
|
if leading_indent is None:
|
|
leading_indent = indent
|
|
j = '\n' + (' ' * indent)
|
|
lines: List[str] = []
|
|
for ln in text.splitlines():
|
|
if ln:
|
|
lines.extend(wrap(ln, limit=linesz - indent))
|
|
else:
|
|
lines.append('')
|
|
a((' ' * leading_indent) + j.join(lines))
|
|
|
|
usage = '[program-to-run ...]' if usage is None else usage
|
|
optstring = '[options] ' if seq else ''
|
|
a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage))
|
|
a('')
|
|
message = message or default_msg
|
|
wa(prettify(message))
|
|
a('')
|
|
if seq:
|
|
a('{}:'.format(title('Options')))
|
|
for opt in seq:
|
|
if isinstance(opt, str):
|
|
a('{}:'.format(title(opt)))
|
|
continue
|
|
help_text = opt['help']
|
|
if help_text == '!':
|
|
continue # hidden option
|
|
a(' ' + ', '.join(map(green, sorted(opt['aliases']))))
|
|
if not opt.get('type', '').startswith('bool-'):
|
|
blocks[-1] += '={}'.format(italic(opt['dest'].upper()))
|
|
if opt.get('help'):
|
|
defval = opt.get('default')
|
|
t = help_text.replace('%default', str(defval))
|
|
wa(prettify(t.strip()), indent=4)
|
|
if defval is not None:
|
|
wa('Default: {}'.format(defval), indent=4)
|
|
if opt.get('choices'):
|
|
wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4)
|
|
a('')
|
|
|
|
text = '\n'.join(blocks) + '\n\n' + version()
|
|
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
|
import subprocess
|
|
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
|
try:
|
|
p.communicate(text.encode('utf-8'))
|
|
except KeyboardInterrupt:
|
|
raise SystemExit(1)
|
|
raise SystemExit(p.wait())
|
|
else:
|
|
print(text)
|
|
|
|
|
|
print_help_for_seq = PrintHelpForSeq()
|
|
|
|
|
|
def seq_as_rst(
|
|
seq: OptionSpecSeq,
|
|
usage: Optional[str],
|
|
message: Optional[str],
|
|
appname: Optional[str],
|
|
heading_char: str = '-'
|
|
) -> str:
|
|
import textwrap
|
|
blocks: List[str] = []
|
|
a = blocks.append
|
|
|
|
usage = '[program-to-run ...]' if usage is None else usage
|
|
optstring = '[options] ' if seq else ''
|
|
a('.. highlight:: sh')
|
|
a('.. code-block:: sh')
|
|
a('')
|
|
a(' {} {}{}'.format(appname, optstring, usage))
|
|
a('')
|
|
message = message or default_msg
|
|
a(prettify_rst(message))
|
|
a('')
|
|
if seq:
|
|
a('Options')
|
|
a(heading_char * 30)
|
|
for opt in seq:
|
|
if isinstance(opt, str):
|
|
a(opt)
|
|
a('~' * (len(opt) + 10))
|
|
continue
|
|
help_text = opt['help']
|
|
if help_text == '!':
|
|
continue # hidden option
|
|
defn = '.. option:: '
|
|
if not opt.get('type', '').startswith('bool-'):
|
|
val_name = ' <{}>'.format(opt['dest'].upper())
|
|
else:
|
|
val_name = ''
|
|
a(defn + ', '.join(o + val_name for o in sorted(opt['aliases'])))
|
|
if opt.get('help'):
|
|
defval = opt.get('default')
|
|
t = help_text.replace('%default', str(defval)).strip()
|
|
a('')
|
|
a(textwrap.indent(prettify_rst(t), ' ' * 4))
|
|
if defval is not None:
|
|
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
|
|
if opt.get('choices'):
|
|
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
|
|
a('')
|
|
|
|
text = '\n'.join(blocks)
|
|
return text
|
|
|
|
|
|
def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, extra_fields: Sequence[str] = ()) -> str:
|
|
from itertools import chain
|
|
ans: List[str] = ['class {}:'.format(class_name)]
|
|
for opt in chain(seq, disabled):
|
|
if isinstance(opt, str):
|
|
continue
|
|
name = opt['dest']
|
|
otype = opt['type'] or 'str'
|
|
if otype in ('str', 'int', 'float'):
|
|
t = otype
|
|
if t == 'str' and defval_for_opt(opt) is None:
|
|
t = 'typing.Optional[str]'
|
|
elif otype == 'list':
|
|
t = 'typing.Sequence[str]'
|
|
elif otype in ('choice', 'choices'):
|
|
if opt['choices']:
|
|
t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt['choices']))
|
|
else:
|
|
t = 'str'
|
|
elif otype.startswith('bool-'):
|
|
t = 'bool'
|
|
else:
|
|
raise ValueError('Unknown CLI option type: {}'.format(otype))
|
|
ans.append(' {}: {}'.format(name, t))
|
|
for x in extra_fields:
|
|
ans.append(' {}'.format(x))
|
|
return '\n'.join(ans) + '\n\n\n'
|
|
|
|
|
|
def defval_for_opt(opt: OptionDict) -> Any:
|
|
dv: Any = opt.get('default')
|
|
typ = opt.get('type', '')
|
|
if typ.startswith('bool-'):
|
|
if dv is None:
|
|
dv = False if typ == 'bool-set' else True
|
|
else:
|
|
dv = dv.lower() in ('true', 'yes', 'y')
|
|
elif typ == 'list':
|
|
dv = []
|
|
elif typ in ('int', 'float'):
|
|
dv = (int if typ == 'int' else float)(dv or 0)
|
|
return dv
|
|
|
|
|
|
class Options:
|
|
|
|
def __init__(self, seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str]):
|
|
self.alias_map = {}
|
|
self.seq = seq
|
|
self.names_map: Dict[str, OptionDict] = {}
|
|
self.values_map: Dict[str, Any] = {}
|
|
self.usage, self.message, self.appname = usage, message, appname
|
|
for opt in seq:
|
|
if isinstance(opt, str):
|
|
continue
|
|
for alias in opt['aliases']:
|
|
self.alias_map[alias] = opt
|
|
name = opt['dest']
|
|
self.names_map[name] = opt
|
|
self.values_map[name] = defval_for_opt(opt)
|
|
|
|
def opt_for_alias(self, alias: str) -> OptionDict:
|
|
opt = self.alias_map.get(alias)
|
|
if opt is None:
|
|
raise SystemExit('Unknown option: {}'.format(emph(alias)))
|
|
return opt
|
|
|
|
def needs_arg(self, alias: str) -> bool:
|
|
if alias in ('-h', '--help'):
|
|
print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname)
|
|
raise SystemExit(0)
|
|
opt = self.opt_for_alias(alias)
|
|
if opt['dest'] == 'version':
|
|
print(version())
|
|
raise SystemExit(0)
|
|
typ = opt.get('type', '')
|
|
return not typ.startswith('bool-')
|
|
|
|
def process_arg(self, alias: str, val: Any = None) -> None:
|
|
opt = self.opt_for_alias(alias)
|
|
typ = opt.get('type', '')
|
|
name = opt['dest']
|
|
nmap = {'float': float, 'int': int}
|
|
if typ == 'bool-set':
|
|
self.values_map[name] = True
|
|
elif typ == 'bool-reset':
|
|
self.values_map[name] = False
|
|
elif typ == 'list':
|
|
self.values_map.setdefault(name, [])
|
|
self.values_map[name].append(val)
|
|
elif typ == 'choices':
|
|
choices = opt['choices']
|
|
if val not in choices:
|
|
raise SystemExit('{} is not a valid value for the {} option. Valid values are: {}'.format(
|
|
val, emph(alias), ', '.join(choices)))
|
|
self.values_map[name] = val
|
|
elif typ in nmap:
|
|
f = nmap[typ]
|
|
try:
|
|
self.values_map[name] = f(val)
|
|
except Exception:
|
|
raise SystemExit('{} is not a valid value for the {} option, a number is required.'.format(
|
|
val, emph(alias)))
|
|
else:
|
|
self.values_map[name] = val
|
|
|
|
|
|
def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional[List[str]] = None) -> List[str]:
|
|
NORMAL, EXPECTING_ARG = 'NORMAL', 'EXPECTING_ARG'
|
|
state = NORMAL
|
|
dargs = deque(sys.argv[1:] if args is None else args)
|
|
leftover_args: List[str] = []
|
|
current_option = None
|
|
|
|
while dargs:
|
|
arg = dargs.popleft()
|
|
if state is NORMAL:
|
|
if arg.startswith('-'):
|
|
if arg == '--':
|
|
leftover_args = list(dargs)
|
|
break
|
|
parts = arg.split('=', 1)
|
|
needs_arg = oc.needs_arg(parts[0])
|
|
if not needs_arg:
|
|
if len(parts) != 1:
|
|
raise SystemExit('The {} option does not accept arguments'.format(emph(parts[0])))
|
|
oc.process_arg(parts[0])
|
|
continue
|
|
if len(parts) == 1:
|
|
current_option = parts[0]
|
|
state = EXPECTING_ARG
|
|
continue
|
|
oc.process_arg(parts[0], parts[1])
|
|
else:
|
|
leftover_args = [arg] + list(dargs)
|
|
break
|
|
elif current_option is not None:
|
|
oc.process_arg(current_option, arg)
|
|
current_option, state = None, NORMAL
|
|
if state is EXPECTING_ARG:
|
|
raise SystemExit('An argument is required for the option: {}'.format(emph(arg)))
|
|
|
|
for key, val in oc.values_map.items():
|
|
setattr(ans, key, val)
|
|
for opt in disabled:
|
|
if not isinstance(opt, str):
|
|
setattr(ans, opt['dest'], defval_for_opt(opt))
|
|
return leftover_args
|
|
|
|
|
|
WATCHER_DEFINITION = '''
|
|
--watcher -w
|
|
type=list
|
|
Path to a python file. Appropriately named functions in this file will be called
|
|
for various events, such as when the window is resized, focused or closed. See the section
|
|
on watchers in the launch command documentation :doc:`launch`. Relative paths are
|
|
resolved relative to the kitty config directory.'''
|
|
|
|
|
|
def options_spec() -> str:
|
|
if not hasattr(options_spec, 'ans'):
|
|
OPTIONS = '''
|
|
--class
|
|
dest=cls
|
|
default={appname}
|
|
condition=not is_macos
|
|
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
|
|
|
|
|
--name
|
|
condition=not is_macos
|
|
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
|
|
|
|
|
--title -T
|
|
Set the window title. This will override any title set by the program running inside kitty. So
|
|
only use this if you are running a program that does not set titles. If combined
|
|
with :option:`{appname} --session` the title will be used for all windows created by the
|
|
session, that do not set their own titles.
|
|
|
|
|
|
--config -c
|
|
type=list
|
|
{config_help}
|
|
|
|
|
|
--override -o
|
|
type=list
|
|
Override individual configuration options, can be specified multiple times.
|
|
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
|
|
|
|
|
--directory --working-directory -d
|
|
default=.
|
|
Change to the specified directory when launching
|
|
|
|
|
|
--detach
|
|
type=bool-set
|
|
condition=not is_macos
|
|
Detach from the controlling terminal, if any
|
|
|
|
|
|
--session
|
|
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
|
Use - to read from STDIN. See the README file for details and an example.
|
|
|
|
|
|
{watcher}
|
|
Note that this watcher will be added only to all initially created windows, not new windows
|
|
created after startup.
|
|
|
|
|
|
--hold
|
|
type=bool-set
|
|
Remain open after child process exits. Note that this only affects the first
|
|
window. You can quit by either using the close window shortcut or pressing any key.
|
|
|
|
|
|
--single-instance -1
|
|
type=bool-set
|
|
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
|
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
|
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
|
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
|
:option:`kitty --instance-group` option
|
|
|
|
|
|
--instance-group
|
|
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
|
with the same :option:`kitty --instance-group` will result in new windows being created
|
|
in the first :italic:`{appname}` instance within that group
|
|
|
|
|
|
--wait-for-single-instance-window-close
|
|
type=bool-set
|
|
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
|
instance and quit immediately. With this option, it will not quit till the newly opened
|
|
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
|
regardless of this option.
|
|
|
|
|
|
--listen-on
|
|
Tell kitty to listen on the specified address for control
|
|
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
|
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
|
|
also use abstract UNIX sockets, not associated with a file, like this:
|
|
:option:`{appname} --listen-on`=unix:@mykitty. Environment variables
|
|
in the setting are expanded and relative paths are resolved with
|
|
respect to the temporary directory. To control kitty, you can send
|
|
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
|
|
specify this address. This option will be ignored, unless you set
|
|
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
|
|
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
|
|
option as it is read automatically from the environment. For UNIX sockets, this
|
|
can also be specified in :file:`kitty.conf`.
|
|
|
|
|
|
--start-as
|
|
type=choices
|
|
default=normal
|
|
choices=normal,fullscreen,maximized,minimized
|
|
Control how the initial kitty window is created.
|
|
|
|
|
|
# Debugging options
|
|
|
|
--version -v
|
|
type=bool-set
|
|
The current {appname} version
|
|
|
|
|
|
--dump-commands
|
|
type=bool-set
|
|
Output commands received from child process to stdout
|
|
|
|
|
|
--replay-commands
|
|
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
|
can open a new kitty window to replay the commands with::
|
|
|
|
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
|
|
|
|
|
--dump-bytes
|
|
Path to file in which to store the raw bytes received from the child process
|
|
|
|
|
|
--debug-rendering --debug-gl
|
|
type=bool-set
|
|
Debug rendering commands. This will cause all OpenGL calls to check for errors
|
|
instead of ignoring them. Also prints out miscellaneous debug information.
|
|
Useful when debugging rendering problems
|
|
|
|
|
|
--debug-input --debug-keyboard
|
|
dest=debug_keyboard
|
|
type=bool-set
|
|
This option will cause kitty to print out key and mouse events as they are received
|
|
|
|
|
|
--debug-font-fallback
|
|
type=bool-set
|
|
Print out information about the selection of fallback fonts for characters not present in the main font.
|
|
|
|
|
|
--execute -e
|
|
type=bool-set
|
|
!
|
|
'''
|
|
setattr(options_spec, 'ans', OPTIONS.format(
|
|
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
|
|
watcher=WATCHER_DEFINITION
|
|
))
|
|
ans: str = getattr(options_spec, 'ans')
|
|
return ans
|
|
|
|
|
|
def options_for_completion() -> OptionSpecSeq:
|
|
raw = '--help -h\ntype=bool-set\nShow help for {appname} command line options\n\n{raw}'.format(
|
|
appname=appname, raw=options_spec())
|
|
return parse_option_spec(raw)[0]
|
|
|
|
|
|
def option_spec_as_rst(
|
|
ospec: Callable[[], str] = options_spec,
|
|
usage: Optional[str] = None, message: Optional[str] = None, appname: Optional[str] = None,
|
|
heading_char: str = '-'
|
|
) -> str:
|
|
options = parse_option_spec(ospec())
|
|
seq, disabled = options
|
|
oc = Options(seq, usage, message, appname)
|
|
return seq_as_rst(oc.seq, oc.usage, oc.message, oc.appname, heading_char=heading_char)
|
|
|
|
|
|
T = TypeVar('T')
|
|
|
|
|
|
def parse_args(
|
|
args: Optional[List[str]] = None,
|
|
ospec: Callable[[], str] = options_spec,
|
|
usage: Optional[str] = None,
|
|
message: Optional[str] = None,
|
|
appname: Optional[str] = None,
|
|
result_class: Optional[Type[T]] = None,
|
|
) -> Tuple[T, List[str]]:
|
|
options = parse_option_spec(ospec())
|
|
seq, disabled = options
|
|
oc = Options(seq, usage, message, appname)
|
|
if result_class is not None:
|
|
ans = result_class()
|
|
else:
|
|
ans = cast(T, CLIOptions())
|
|
return ans, parse_cmdline(oc, disabled, ans, args=args)
|
|
|
|
|
|
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
|
|
|
|
|
|
def create_opts(args: CLIOptions, accumulate_bad_lines: Optional[List[BadLineType]] = None) -> KittyOpts:
|
|
from .config import load_config
|
|
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
|
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
|
opts = load_config(*config, overrides=overrides, accumulate_bad_lines=accumulate_bad_lines)
|
|
return opts
|
|
|
|
|
|
def create_default_opts() -> KittyOpts:
|
|
from .config import load_config
|
|
config = tuple(resolve_config(SYSTEM_CONF, defconf, ()))
|
|
opts = load_config(*config)
|
|
return opts
|