kitty/kitty/session.py
Kovid Goyal 6b8e5ea225
Rename kitty-tool to kitten
It's easier to type, and cuter.

Also, most, if not all of the TUI parts of
kitty's kittens will eventually be re-written into kitten.

The only downside I can see is that we cant tab complete kitty
anymore, but hopefully there will be less reason to run kitty
from the shell as command line tools migrate to kitten.

Meowrrrr!!!
2023-01-14 15:44:52 +05:30

217 lines
7.8 KiB
Python

#!/usr/bin/env python3
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import shlex
import sys
from typing import TYPE_CHECKING, Generator, Iterator, List, Optional, Tuple, Union
from .cli_stub import CLIOptions
from .constants import kitten_exe
from .layout.interface import all_layouts
from .options.types import Options
from .options.utils import resize_window, to_layout_names, window_size
from .os_window_size import WindowSize, WindowSizeData, WindowSizes
from .typing import SpecialWindowInstance
from .utils import log_error, resolve_custom_file, resolved_shell
if TYPE_CHECKING:
from .launch import LaunchSpec
from .window import CwdRequest
def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None) -> WindowSizeData:
if session is None or session.os_window_size is None:
sizes = WindowSizes(WindowSize(*opts.initial_window_width), WindowSize(*opts.initial_window_height))
else:
sizes = session.os_window_size
return WindowSizeData(sizes, opts.remember_window_size, opts.single_window_margin_width, opts.window_margin_width, opts.window_padding_width)
ResizeSpec = Tuple[str, int]
class WindowSpec:
def __init__(self, launch_spec: Union['LaunchSpec', 'SpecialWindowInstance']):
self.launch_spec = launch_spec
self.resize_spec: Optional[ResizeSpec] = None
class Tab:
def __init__(self, opts: Options, name: str):
self.windows: List[WindowSpec] = []
self.pending_resize_spec: Optional[ResizeSpec] = None
self.name = name.strip()
self.active_window_idx = 0
self.enabled_layouts = opts.enabled_layouts
self.layout = (self.enabled_layouts or ['tall'])[0]
self.cwd: Optional[str] = None
self.next_title: Optional[str] = None
class Session:
def __init__(self, default_title: Optional[str] = None):
self.tabs: List[Tab] = []
self.active_tab_idx = 0
self.default_title = default_title
self.os_window_size: Optional[WindowSizes] = None
self.os_window_class: Optional[str] = None
self.focus_os_window: bool = False
def add_tab(self, opts: Options, name: str = '') -> None:
if self.tabs and not self.tabs[-1].windows:
del self.tabs[-1]
self.tabs.append(Tab(opts, name))
def set_next_title(self, title: str) -> None:
self.tabs[-1].next_title = title.strip()
def set_layout(self, val: str) -> None:
if val.partition(':')[0] not in all_layouts:
raise ValueError(f'{val} is not a valid layout')
self.tabs[-1].layout = val
def add_window(self, cmd: Union[None, str, List[str]]) -> None:
from .launch import parse_launch_args
if isinstance(cmd, str):
cmd = shlex.split(cmd)
spec = parse_launch_args(cmd)
t = self.tabs[-1]
if t.next_title and not spec.opts.window_title:
spec.opts.window_title = t.next_title
spec.opts.cwd = spec.opts.cwd or t.cwd
t.windows.append(WindowSpec(spec))
t.next_title = None
if t.pending_resize_spec is not None:
t.windows[-1].resize_spec = t.pending_resize_spec
t.pending_resize_spec = None
def resize_window(self, args: List[str]) -> None:
s = resize_window('resize_window', shlex.join(args))[1]
spec: ResizeSpec = s[0], s[1]
t = self.tabs[-1]
if t.windows:
t.windows[-1].resize_spec = spec
else:
t.pending_resize_spec = spec
def add_special_window(self, sw: 'SpecialWindowInstance') -> None:
self.tabs[-1].windows.append(WindowSpec(sw))
def focus(self) -> None:
self.active_tab_idx = max(0, len(self.tabs) - 1)
self.tabs[-1].active_window_idx = max(0, len(self.tabs[-1].windows) - 1)
def set_enabled_layouts(self, raw: str) -> None:
self.tabs[-1].enabled_layouts = to_layout_names(raw)
if self.tabs[-1].layout not in self.tabs[-1].enabled_layouts:
self.tabs[-1].layout = self.tabs[-1].enabled_layouts[0]
def set_cwd(self, val: str) -> None:
self.tabs[-1].cwd = val
def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
def finalize_session(ans: Session) -> Session:
from .tabs import SpecialWindow
for t in ans.tabs:
if not t.windows:
t.windows.append(WindowSpec(SpecialWindow(cmd=resolved_shell(opts))))
return ans
ans = Session()
ans.add_tab(opts)
for line in raw.splitlines():
line = line.strip()
if line and not line.startswith('#'):
parts = line.split(maxsplit=1)
if len(parts) == 1:
cmd, rest = parts[0], ''
else:
cmd, rest = parts
cmd, rest = cmd.strip(), rest.strip()
if cmd == 'new_tab':
ans.add_tab(opts, rest)
elif cmd == 'new_os_window':
yield finalize_session(ans)
ans = Session()
ans.add_tab(opts, rest)
elif cmd == 'layout':
ans.set_layout(rest)
elif cmd == 'launch':
ans.add_window(rest)
elif cmd == 'focus':
ans.focus()
elif cmd == 'focus_os_window':
ans.focus_os_window = True
elif cmd == 'enabled_layouts':
ans.set_enabled_layouts(rest)
elif cmd == 'cd':
ans.set_cwd(rest)
elif cmd == 'title':
ans.set_next_title(rest)
elif cmd == 'os_window_size':
w, h = map(window_size, rest.split(maxsplit=1))
ans.os_window_size = WindowSizes(WindowSize(*w), WindowSize(*h))
elif cmd == 'os_window_class':
ans.os_window_class = rest
elif cmd == 'resize_window':
ans.resize_window(rest.split())
else:
raise ValueError(f'Unknown command in session file: {cmd}')
yield finalize_session(ans)
class PreReadSession(str):
def __new__(cls, val: str) -> 'PreReadSession':
ans: PreReadSession = str.__new__(cls, val)
ans.pre_read = True # type: ignore
return ans
def create_sessions(
opts: Options,
args: Optional[CLIOptions] = None,
special_window: Optional['SpecialWindowInstance'] = None,
cwd_from: Optional['CwdRequest'] = None,
respect_cwd: bool = False,
default_session: Optional[str] = None
) -> Iterator[Session]:
if args and args.session:
if isinstance(args.session, PreReadSession):
session_data = '' + str(args.session)
else:
if args.session == '-':
f = sys.stdin
else:
f = open(resolve_custom_file(args.session))
with f:
session_data = f.read()
yield from parse_session(session_data, opts)
return
if default_session and default_session != 'none':
try:
with open(default_session) as f:
session_data = f.read()
except OSError:
log_error(f'Failed to read from session file, ignoring: {default_session}')
else:
yield from parse_session(session_data, opts)
return
ans = Session()
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
ans.add_tab(opts)
ans.tabs[-1].layout = current_layout
if special_window is None:
cmd = args.args if args and args.args else resolved_shell(opts)
if args and args.hold:
cmd = [kitten_exe(), '__hold_till_enter__'] + cmd
from kitty.tabs import SpecialWindow
cwd: Optional[str] = args.directory if respect_cwd and args else None
special_window = SpecialWindow(cmd, cwd_from=cwd_from, cwd=cwd)
ans.add_special_window(special_window)
yield ans