Session files: Expand environment variables

This commit is contained in:
Kovid Goyal 2023-01-23 17:34:53 +05:30
parent 11f98592f7
commit 60791bb57b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 51 additions and 16 deletions

View File

@ -57,6 +57,8 @@ Detailed list of changes
- Allow using the cwd of the original process for :option:`launch --cwd` (:iss:`5672`)
- Session files: Expand environment variables (:disc:`5917`)
- Pass key events mapped to scroll actions to the program running in the terminal when the terminal is in alternate screen mode (:iss:`5839`)
- Implement :ref:`edit-in-kitty <edit_file>` using the new ``kitten`` static executable (:iss:`5546`, :iss:`5630`)

View File

@ -173,6 +173,11 @@ option in :file:`kitty.conf`. An example, showing all available commands:
The :doc:`launch <launch>` command when used in a session file cannot create
new OS windows, or tabs.
.. note::
Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
expanded in the session file, except in the *arguments* (not options) to the
launch command.
Creating tabs/windows
-------------------------------

View File

@ -760,9 +760,11 @@ class Boss:
return None
args.args = rest
opts = create_opts(args)
if args.session == '-':
if data['session_data']:
from .session import PreReadSession
args.session = PreReadSession(data['stdin'])
args.session = PreReadSession(data['session_data'], data['environ'])
else:
args.session = ''
if not os.path.isabs(args.directory):
args.directory = os.path.join(data['cwd'], args.directory)
focused_os_window = os_window_id = 0

View File

@ -890,8 +890,8 @@ Detach from the controlling terminal, if any.
completion=type:file ext:session relative:conf group:"Session files"
Path to a file containing the startup :italic:`session` (tabs, windows, layout,
programs). Use - to read from STDIN. See the :file:`README` file for details and
an example. Environment variables are expanded, relative paths are resolved relative
to the kitty configuration directory.
an example. Environment variables in the file name are expanded,
relative paths are resolved relative to the kitty configuration directory.
--hold

View File

@ -80,12 +80,16 @@ def set_custom_ibeam_cursor() -> None:
def talk_to_instance(args: CLIOptions) -> None:
import json
import socket
stdin = ''
session_data = ''
if args.session == '-':
stdin = sys.stdin.read()
session_data = sys.stdin.read()
elif args.session:
with open(args.session) as f:
session_data = f.read()
data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'cmdline_args_for_open': getattr(sys, 'cmdline_args_for_open', []),
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'), 'activation_token': os.environ.get('XDG_ACTIVATION_TOKEN'),
'cwd': os.getcwd(), 'stdin': stdin}
'cwd': os.getcwd(), 'session_data': session_data, 'environ': dict(os.environ)}
notify_socket = None
if args.wait_for_single_instance_window_close:
address = f'\0{appname}-os-window-close-notify-{os.getpid()}-{os.geteuid()}'
@ -461,7 +465,7 @@ def _main() -> None:
if cli_opts.detach:
if cli_opts.session == '-':
from .session import PreReadSession
cli_opts.session = PreReadSession(sys.stdin.read())
cli_opts.session = PreReadSession(sys.stdin.read(), os.environ)
detach()
if cli_opts.replay_commands:
from kitty.client import main as client_main

View File

@ -1,9 +1,12 @@
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shlex
import sys
from typing import TYPE_CHECKING, Generator, Iterator, List, Optional, Tuple, Union
from contextlib import suppress
from functools import partial
from typing import TYPE_CHECKING, Callable, Generator, Iterator, List, Mapping, Optional, Tuple, Union
from .cli_stub import CLIOptions
from .constants import kitten_exe
@ -12,7 +15,7 @@ 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
from .utils import expandvars, log_error, resolve_custom_file, resolved_shell
if TYPE_CHECKING:
from .launch import LaunchSpec
@ -73,11 +76,22 @@ class Session:
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:
def add_window(self, cmd: Union[None, str, List[str]], expand: Callable[[str], str] = lambda x: x) -> None:
from .launch import parse_launch_args
needs_expandvars = False
if isinstance(cmd, str):
needs_expandvars = True
cmd = shlex.split(cmd)
spec = parse_launch_args(cmd)
if needs_expandvars:
assert isinstance(cmd, list)
limit = len(cmd)
if len(spec.args):
with suppress(ValueError):
limit = cmd.index(spec.args[0])
cmd = [(expand(x) if i < limit else x) for i, x in enumerate(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
@ -113,7 +127,7 @@ class Session:
self.tabs[-1].cwd = val
def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
def parse_session(raw: str, opts: Options, environ: Optional[Mapping[str, str]] = None) -> Generator[Session, None, None]:
def finalize_session(ans: Session) -> Session:
from .tabs import SpecialWindow
@ -122,6 +136,9 @@ def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
t.windows.append(WindowSpec(SpecialWindow(cmd=resolved_shell(opts))))
return ans
if environ is None:
environ = os.environ
expand = partial(expandvars, env=environ, fallback_to_os_env=False)
ans = Session()
ans.add_tab(opts)
for line in raw.splitlines():
@ -133,6 +150,8 @@ def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
else:
cmd, rest = parts
cmd, rest = cmd.strip(), rest.strip()
if cmd != 'launch':
rest = expand(rest)
if cmd == 'new_tab':
ans.add_tab(opts, rest)
elif cmd == 'new_os_window':
@ -142,7 +161,7 @@ def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
elif cmd == 'layout':
ans.set_layout(rest)
elif cmd == 'launch':
ans.add_window(rest)
ans.add_window(rest, expand)
elif cmd == 'focus':
ans.focus()
elif cmd == 'focus_os_window':
@ -167,9 +186,10 @@ def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
class PreReadSession(str):
def __new__(cls, val: str) -> 'PreReadSession':
def __new__(cls, val: str, associated_environ: Mapping[str, str]) -> 'PreReadSession':
ans: PreReadSession = str.__new__(cls, val)
ans.pre_read = True # type: ignore
ans.associated_environ = associated_environ # type: ignore
return ans
@ -179,11 +199,13 @@ def create_sessions(
special_window: Optional['SpecialWindowInstance'] = None,
cwd_from: Optional['CwdRequest'] = None,
respect_cwd: bool = False,
default_session: Optional[str] = None
default_session: Optional[str] = None,
) -> Iterator[Session]:
if args and args.session:
environ: Optional[Mapping[str, str]] = None
if isinstance(args.session, PreReadSession):
session_data = '' + str(args.session)
environ = args.session.associated_environ # type: ignore
else:
if args.session == '-':
f = sys.stdin
@ -191,7 +213,7 @@ def create_sessions(
f = open(resolve_custom_file(args.session))
with f:
session_data = f.read()
yield from parse_session(session_data, opts)
yield from parse_session(session_data, opts, environ=environ)
return
if default_session and default_session != 'none':
try: