Allow using the full launch command in session files
Note this is slightly backward incompatible.
This commit is contained in:
parent
e9e8ef7210
commit
35517d3e6f
@ -11,6 +11,11 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
|||||||
used by full screen terminal programs and even games, see
|
used by full screen terminal programs and even games, see
|
||||||
:doc:`keyboard-protocol` (:iss:`3248`)
|
: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`)
|
- diff kitten: Implement recursive diff over SSH (:iss:`3268`)
|
||||||
|
|
||||||
- ssh kitten: Allow using python instead of the shell on the server, useful if
|
- ssh kitten: Allow using python instead of the shell on the server, useful if
|
||||||
|
|||||||
@ -340,7 +340,7 @@ For example:
|
|||||||
launch zsh
|
launch zsh
|
||||||
# Create a window with some environment variables set and run
|
# Create a window with some environment variables set and run
|
||||||
# vim in it
|
# vim in it
|
||||||
launch env FOO=BAR vim
|
launch --env FOO=BAR vim
|
||||||
# Set the title for the next window
|
# Set the title for the next window
|
||||||
title Chat with x
|
title Chat with x
|
||||||
launch irssi --profile x
|
launch irssi --profile x
|
||||||
@ -367,14 +367,9 @@ For example:
|
|||||||
focus
|
focus
|
||||||
launch emacs
|
launch emacs
|
||||||
|
|
||||||
# Add a watcher that will be called with various events that occur
|
.. note::
|
||||||
# on all subsequent windows. See the documentation of the launch command
|
The :doc:`launch <launch>` command when used in a session file
|
||||||
# for details on watchers.
|
cannot create new OS windows, or tabs.
|
||||||
watcher /some/python/file.py
|
|
||||||
launch mpd
|
|
||||||
launch irssi
|
|
||||||
# Remove the watcher for further windows
|
|
||||||
watcher clear
|
|
||||||
|
|
||||||
|
|
||||||
Mouse features
|
Mouse features
|
||||||
|
|||||||
@ -177,12 +177,8 @@ class Boss:
|
|||||||
cocoa_set_notification_activated_callback(notification_activated)
|
cocoa_set_notification_activated_callback(notification_activated)
|
||||||
|
|
||||||
def startup_first_child(self, os_window_id: Optional[int]) -> None:
|
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)
|
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:
|
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)
|
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||||
os_window_id = None
|
os_window_id = None
|
||||||
if self.args.start_as != 'normal':
|
if self.args.start_as != 'normal':
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from functools import lru_cache
|
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 .boss import Boss
|
||||||
from .child import Child
|
from .child import Child
|
||||||
@ -13,7 +13,7 @@ from .cli_stub import LaunchCLIOptions
|
|||||||
from .constants import resolve_custom_file
|
from .constants import resolve_custom_file
|
||||||
from .fast_data_types import set_clipboard_string
|
from .fast_data_types import set_clipboard_string
|
||||||
from .tabs import Tab
|
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
|
from .window import Watchers, Window
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -22,6 +22,11 @@ except ImportError:
|
|||||||
TypedDict = Dict[str, Any]
|
TypedDict = Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class LaunchSpec(NamedTuple):
|
||||||
|
opts: LaunchCLIOptions
|
||||||
|
args: List[str]
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=2)
|
@lru_cache(maxsize=2)
|
||||||
def options_spec() -> str:
|
def options_spec() -> str:
|
||||||
return '''
|
return '''
|
||||||
@ -159,13 +164,13 @@ Set the WM_NAME property on X11 for the newly created OS Window when using
|
|||||||
''' + WATCHER_DEFINITION
|
''' + 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 ())
|
args = list(args or ())
|
||||||
try:
|
try:
|
||||||
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
|
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
raise ValueError from e
|
raise ValueError from e
|
||||||
return opts, args
|
return LaunchSpec(opts, args)
|
||||||
|
|
||||||
|
|
||||||
def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]:
|
def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]:
|
||||||
@ -234,7 +239,13 @@ class LaunchKwds(TypedDict):
|
|||||||
stdin: Optional[bytes]
|
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 = boss.active_window_for_cwd
|
||||||
active_child = getattr(active, 'child', None)
|
active_child = getattr(active, 'child', None)
|
||||||
env = get_env(opts, active_child)
|
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)
|
set_clipboard_string(stdin)
|
||||||
else:
|
else:
|
||||||
set_primary_selection(stdin)
|
set_primary_selection(stdin)
|
||||||
|
else:
|
||||||
|
if force_target_tab:
|
||||||
|
tab = target_tab
|
||||||
else:
|
else:
|
||||||
tab = tab_for_window(boss, opts, target_tab)
|
tab = tab_for_window(boss, opts, target_tab)
|
||||||
if tab is not None:
|
if tab is not None:
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
from typing import Generator, List, Optional, Union
|
from typing import Generator, List, Optional, Sequence, Union
|
||||||
|
|
||||||
from .cli_stub import CLIOptions
|
from .cli_stub import CLIOptions
|
||||||
from .config_data import to_layout_names
|
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 .os_window_size import WindowSize, WindowSizeData, WindowSizes
|
||||||
from .typing import SpecialWindowInstance
|
from .typing import SpecialWindowInstance
|
||||||
from .utils import log_error, resolved_shell
|
from .utils import log_error, resolved_shell
|
||||||
from .window import Watchers
|
|
||||||
|
|
||||||
|
|
||||||
def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None) -> WindowSizeData:
|
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:
|
class Tab:
|
||||||
|
|
||||||
def __init__(self, opts: Options, name: str, watchers: Watchers):
|
def __init__(self, opts: Options, name: str):
|
||||||
self.windows: List['SpecialWindowInstance'] = []
|
from .launch import LaunchSpec
|
||||||
|
self.windows: List[Union[LaunchSpec, 'SpecialWindowInstance']] = []
|
||||||
self.name = name.strip()
|
self.name = name.strip()
|
||||||
self.active_window_idx = 0
|
self.active_window_idx = 0
|
||||||
self.enabled_layouts = opts.enabled_layouts
|
self.enabled_layouts = opts.enabled_layouts
|
||||||
self.layout = (self.enabled_layouts or ['tall'])[0]
|
self.layout = (self.enabled_layouts or ['tall'])[0]
|
||||||
self.cwd: Optional[str] = None
|
self.cwd: Optional[str] = None
|
||||||
self.next_title: Optional[str] = None
|
self.next_title: Optional[str] = None
|
||||||
self.watchers: Watchers = watchers.copy()
|
|
||||||
|
|
||||||
|
|
||||||
class Session:
|
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.tabs: List[Tab] = []
|
||||||
|
self.default_watchers = list(default_watchers)
|
||||||
self.active_tab_idx = 0
|
self.active_tab_idx = 0
|
||||||
self.default_title = default_title
|
self.default_title = default_title
|
||||||
self.os_window_size: Optional[WindowSizes] = None
|
self.os_window_size: Optional[WindowSizes] = None
|
||||||
self.os_window_class: Optional[str] = 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:
|
def add_tab(self, opts: Options, name: str = '') -> None:
|
||||||
if self.tabs and not self.tabs[-1].windows:
|
if self.tabs and not self.tabs[-1].windows:
|
||||||
del self.tabs[-1]
|
del self.tabs[-1]
|
||||||
if self.tabs:
|
self.tabs.append(Tab(opts, name))
|
||||||
self.tabs[-1].watchers = self.watchers.copy()
|
|
||||||
self.tabs.append(Tab(opts, name, self.watchers))
|
|
||||||
|
|
||||||
def set_next_title(self, title: str) -> None:
|
def set_next_title(self, title: str) -> None:
|
||||||
self.tabs[-1].next_title = title.strip()
|
self.tabs[-1].next_title = title.strip()
|
||||||
@ -75,22 +61,18 @@ class Session:
|
|||||||
self.tabs[-1].layout = val
|
self.tabs[-1].layout = val
|
||||||
|
|
||||||
def add_window(self, cmd: Union[None, str, List[str]]) -> None:
|
def add_window(self, cmd: Union[None, str, List[str]]) -> None:
|
||||||
if cmd:
|
from .launch import parse_launch_args
|
||||||
cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd
|
if isinstance(cmd, str):
|
||||||
else:
|
cmd = shlex.split(cmd)
|
||||||
cmd = None
|
spec = parse_launch_args(cmd)
|
||||||
from .tabs import SpecialWindow
|
if self.default_watchers:
|
||||||
|
spec.opts.watcher = list(spec.opts.watcher) + self.default_watchers
|
||||||
t = self.tabs[-1]
|
t = self.tabs[-1]
|
||||||
watchers: Optional[Watchers] = None
|
spec.opts.cwd = spec.opts.cwd or t.cwd
|
||||||
if self.watchers.has_watchers:
|
t.windows.append(spec)
|
||||||
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)
|
|
||||||
t.next_title = None
|
t.next_title = None
|
||||||
|
|
||||||
def add_special_window(self, sw: 'SpecialWindowInstance') -> 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)
|
self.tabs[-1].windows.append(sw)
|
||||||
|
|
||||||
def focus(self) -> None:
|
def focus(self) -> None:
|
||||||
@ -112,10 +94,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
|
|||||||
from .tabs import SpecialWindow
|
from .tabs import SpecialWindow
|
||||||
for t in ans.tabs:
|
for t in ans.tabs:
|
||||||
if not t.windows:
|
if not t.windows:
|
||||||
w: Optional[Watchers] = None
|
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), override_title=default_title))
|
||||||
if t.watchers.has_watchers:
|
|
||||||
w = t.watchers.copy()
|
|
||||||
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w, override_title=default_title))
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
ans = Session(default_title)
|
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)
|
ans.add_tab(opts, rest)
|
||||||
elif cmd == 'new_os_window':
|
elif cmd == 'new_os_window':
|
||||||
yield finalize_session(ans)
|
yield finalize_session(ans)
|
||||||
wt = ans.watchers
|
|
||||||
ans = Session(default_title)
|
ans = Session(default_title)
|
||||||
ans.watchers = wt.copy()
|
|
||||||
ans.add_tab(opts, rest)
|
ans.add_tab(opts, rest)
|
||||||
elif cmd == 'layout':
|
elif cmd == 'layout':
|
||||||
ans.set_layout(rest)
|
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))
|
ans.os_window_size = WindowSizes(WindowSize(*w), WindowSize(*h))
|
||||||
elif cmd == 'os_window_class':
|
elif cmd == 'os_window_class':
|
||||||
ans.os_window_class = rest
|
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:
|
else:
|
||||||
raise ValueError('Unknown command in session file: {}'.format(cmd))
|
raise ValueError('Unknown command in session file: {}'.format(cmd))
|
||||||
yield finalize_session(ans)
|
yield finalize_session(ans)
|
||||||
@ -194,7 +163,8 @@ def create_sessions(
|
|||||||
else:
|
else:
|
||||||
yield from parse_session(session_data, opts, getattr(args, 'title', None))
|
yield from parse_session(session_data, opts, getattr(args, 'title', None))
|
||||||
return
|
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'
|
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||||
ans.add_tab(opts)
|
ans.add_tab(opts)
|
||||||
ans.tabs[-1].layout = current_layout
|
ans.tabs[-1].layout = current_layout
|
||||||
|
|||||||
@ -137,7 +137,11 @@ class Tab: # {{{
|
|||||||
|
|
||||||
def startup(self, session_tab: 'SessionTab') -> None:
|
def startup(self, session_tab: 'SessionTab') -> None:
|
||||||
for cmd in session_tab.windows:
|
for cmd in session_tab.windows:
|
||||||
|
if isinstance(cmd, SpecialWindowInstance):
|
||||||
self.new_special_window(cmd)
|
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])
|
self.windows.set_active_window_group_for(self.windows.all_windows[session_tab.active_window_idx])
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Any]:
|
def serialize_state(self) -> Dict[str, Any]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user