Allow using the full launch command in session files

Note this is slightly backward incompatible.
This commit is contained in:
Kovid Goyal 2021-01-31 11:37:00 +05:30
parent e9e8ef7210
commit 35517d3e6f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 52 additions and 68 deletions

View File

@ -11,6 +11,11 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
used by full screen terminal programs and even games, see
:doc:`keyboard-protocol` (:iss:`3248`)
- **Backward incompatibility**: Session files now use the full :doc:`launch <launch>`
command with all its capabilities. However, the syntax of the command is
slightly different from before. In particular watchers are now specified
directly on launch and env vars are set using ``--env``.
- diff kitten: Implement recursive diff over SSH (:iss:`3268`)
- ssh kitten: Allow using python instead of the shell on the server, useful if

View File

@ -340,7 +340,7 @@ For example:
launch zsh
# Create a window with some environment variables set and run
# vim in it
launch env FOO=BAR vim
launch --env FOO=BAR vim
# Set the title for the next window
title Chat with x
launch irssi --profile x
@ -367,14 +367,9 @@ For example:
focus
launch emacs
# Add a watcher that will be called with various events that occur
# on all subsequent windows. See the documentation of the launch command
# for details on watchers.
watcher /some/python/file.py
launch mpd
launch irssi
# Remove the watcher for further windows
watcher clear
.. note::
The :doc:`launch <launch>` command when used in a session file
cannot create new OS windows, or tabs.
Mouse features

View File

@ -177,12 +177,8 @@ class Boss:
cocoa_set_notification_activated_callback(notification_activated)
def startup_first_child(self, os_window_id: Optional[int]) -> None:
from kitty.launch import load_watch_modules
startup_sessions = create_sessions(self.opts, self.args, default_session=self.opts.startup_session)
watchers = load_watch_modules(self.args.watcher)
for startup_session in startup_sessions:
if watchers is not None and watchers.has_watchers:
startup_session.add_watchers_to_all_windows(watchers)
self.add_os_window(startup_session, os_window_id=os_window_id)
os_window_id = None
if self.args.start_as != 'normal':

View File

@ -4,7 +4,7 @@
from functools import lru_cache
from typing import Any, Dict, List, Optional, Sequence, Tuple
from typing import Any, Dict, List, NamedTuple, Optional, Sequence
from .boss import Boss
from .child import Child
@ -13,7 +13,7 @@ from .cli_stub import LaunchCLIOptions
from .constants import resolve_custom_file
from .fast_data_types import set_clipboard_string
from .tabs import Tab
from .utils import find_exe, set_primary_selection, read_shell_environment
from .utils import find_exe, read_shell_environment, set_primary_selection
from .window import Watchers, Window
try:
@ -22,6 +22,11 @@ except ImportError:
TypedDict = Dict[str, Any]
class LaunchSpec(NamedTuple):
opts: LaunchCLIOptions
args: List[str]
@lru_cache(maxsize=2)
def options_spec() -> str:
return '''
@ -159,13 +164,13 @@ Set the WM_NAME property on X11 for the newly created OS Window when using
''' + WATCHER_DEFINITION
def parse_launch_args(args: Optional[Sequence[str]] = None) -> Tuple[LaunchCLIOptions, List[str]]:
def parse_launch_args(args: Optional[Sequence[str]] = None) -> LaunchSpec:
args = list(args or ())
try:
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
except SystemExit as e:
raise ValueError from e
return opts, args
return LaunchSpec(opts, args)
def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]:
@ -234,7 +239,13 @@ class LaunchKwds(TypedDict):
stdin: Optional[bytes]
def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Optional[Tab] = None) -> Optional[Window]:
def launch(
boss: Boss,
opts: LaunchCLIOptions,
args: List[str],
target_tab: Optional[Tab] = None,
force_target_tab: bool = False
) -> Optional[Window]:
active = boss.active_window_for_cwd
active_child = getattr(active, 'child', None)
env = get_env(opts, active_child)
@ -310,6 +321,9 @@ def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Opti
set_clipboard_string(stdin)
else:
set_primary_selection(stdin)
else:
if force_target_tab:
tab = target_tab
else:
tab = tab_for_window(boss, opts, target_tab)
if tab is not None:

View File

@ -4,7 +4,7 @@
import shlex
import sys
from typing import Generator, List, Optional, Union
from typing import Generator, List, Optional, Sequence, Union
from .cli_stub import CLIOptions
from .config_data import to_layout_names
@ -14,7 +14,6 @@ from .options_stub import Options
from .os_window_size import WindowSize, WindowSizeData, WindowSizes
from .typing import SpecialWindowInstance
from .utils import log_error, resolved_shell
from .window import Watchers
def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None) -> WindowSizeData:
@ -27,44 +26,31 @@ def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None
class Tab:
def __init__(self, opts: Options, name: str, watchers: Watchers):
self.windows: List['SpecialWindowInstance'] = []
def __init__(self, opts: Options, name: str):
from .launch import LaunchSpec
self.windows: List[Union[LaunchSpec, 'SpecialWindowInstance']] = []
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
self.watchers: Watchers = watchers.copy()
class Session:
def __init__(self, default_title: Optional[str] = None):
def __init__(self, default_title: Optional[str] = None, default_watchers: Sequence[str] = ()):
self.tabs: List[Tab] = []
self.default_watchers = list(default_watchers)
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.watchers = Watchers()
def add_watchers_to_all_windows(self, watchers: Watchers) -> None:
def add(w: 'SpecialWindowInstance') -> 'SpecialWindowInstance':
if w.watchers is None:
return w._replace(watchers=watchers)
wt = w.watchers.copy()
wt.add(watchers)
return w._replace(watchers=wt)
for tab in self.tabs:
tab.windows = [add(w) for w in tab.windows]
def add_tab(self, opts: Options, name: str = '') -> None:
if self.tabs and not self.tabs[-1].windows:
del self.tabs[-1]
if self.tabs:
self.tabs[-1].watchers = self.watchers.copy()
self.tabs.append(Tab(opts, name, self.watchers))
self.tabs.append(Tab(opts, name))
def set_next_title(self, title: str) -> None:
self.tabs[-1].next_title = title.strip()
@ -75,22 +61,18 @@ class Session:
self.tabs[-1].layout = val
def add_window(self, cmd: Union[None, str, List[str]]) -> None:
if cmd:
cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd
else:
cmd = None
from .tabs import SpecialWindow
from .launch import parse_launch_args
if isinstance(cmd, str):
cmd = shlex.split(cmd)
spec = parse_launch_args(cmd)
if self.default_watchers:
spec.opts.watcher = list(spec.opts.watcher) + self.default_watchers
t = self.tabs[-1]
watchers: Optional[Watchers] = None
if self.watchers.has_watchers:
watchers = self.watchers.copy()
sw = SpecialWindow(cmd, cwd=t.cwd, override_title=t.next_title or self.default_title, watchers=watchers)
t.windows.append(sw)
spec.opts.cwd = spec.opts.cwd or t.cwd
t.windows.append(spec)
t.next_title = None
def add_special_window(self, sw: 'SpecialWindowInstance') -> None:
if self.watchers.has_watchers:
sw = sw._replace(watchers=self.watchers.copy())
self.tabs[-1].windows.append(sw)
def focus(self) -> None:
@ -112,10 +94,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
from .tabs import SpecialWindow
for t in ans.tabs:
if not t.windows:
w: Optional[Watchers] = None
if t.watchers.has_watchers:
w = t.watchers.copy()
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w, override_title=default_title))
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), override_title=default_title))
return ans
ans = Session(default_title)
@ -133,9 +112,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
ans.add_tab(opts, rest)
elif cmd == 'new_os_window':
yield finalize_session(ans)
wt = ans.watchers
ans = Session(default_title)
ans.watchers = wt.copy()
ans.add_tab(opts, rest)
elif cmd == 'layout':
ans.set_layout(rest)
@ -155,14 +132,6 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
ans.os_window_size = WindowSizes(WindowSize(*w), WindowSize(*h))
elif cmd == 'os_window_class':
ans.os_window_class = rest
elif cmd == 'watcher':
from .launch import load_watch_modules
if rest == 'clear':
ans.watchers = Watchers()
else:
watchers = load_watch_modules((rest,))
if watchers is not None:
ans.watchers.add(watchers)
else:
raise ValueError('Unknown command in session file: {}'.format(cmd))
yield finalize_session(ans)
@ -194,7 +163,8 @@ def create_sessions(
else:
yield from parse_session(session_data, opts, getattr(args, 'title', None))
return
ans = Session()
default_watchers = args.watcher if args else ()
ans = Session(default_watchers=default_watchers)
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
ans.add_tab(opts)
ans.tabs[-1].layout = current_layout

View File

@ -137,7 +137,11 @@ class Tab: # {{{
def startup(self, session_tab: 'SessionTab') -> None:
for cmd in session_tab.windows:
if isinstance(cmd, SpecialWindowInstance):
self.new_special_window(cmd)
else:
from .launch import launch
launch(get_boss(), cmd.opts, cmd.args, target_tab=self, force_target_tab=True)
self.windows.set_active_window_group_for(self.windows.all_windows[session_tab.active_window_idx])
def serialize_state(self) -> Dict[str, Any]: