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`) - 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`) - 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`) - 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 The :doc:`launch <launch>` command when used in a session file cannot create
new OS windows, or tabs. 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 Creating tabs/windows
------------------------------- -------------------------------

View File

@ -760,9 +760,11 @@ class Boss:
return None return None
args.args = rest args.args = rest
opts = create_opts(args) opts = create_opts(args)
if args.session == '-': if data['session_data']:
from .session import PreReadSession 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): if not os.path.isabs(args.directory):
args.directory = os.path.join(data['cwd'], args.directory) args.directory = os.path.join(data['cwd'], args.directory)
focused_os_window = os_window_id = 0 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" completion=type:file ext:session relative:conf group:"Session files"
Path to a file containing the startup :italic:`session` (tabs, windows, layout, 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 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 an example. Environment variables in the file name are expanded,
to the kitty configuration directory. relative paths are resolved relative to the kitty configuration directory.
--hold --hold

View File

@ -80,12 +80,16 @@ def set_custom_ibeam_cursor() -> None:
def talk_to_instance(args: CLIOptions) -> None: def talk_to_instance(args: CLIOptions) -> None:
import json import json
import socket import socket
stdin = '' session_data = ''
if args.session == '-': 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', []), 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'), '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 notify_socket = None
if args.wait_for_single_instance_window_close: if args.wait_for_single_instance_window_close:
address = f'\0{appname}-os-window-close-notify-{os.getpid()}-{os.geteuid()}' 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.detach:
if cli_opts.session == '-': if cli_opts.session == '-':
from .session import PreReadSession from .session import PreReadSession
cli_opts.session = PreReadSession(sys.stdin.read()) cli_opts.session = PreReadSession(sys.stdin.read(), os.environ)
detach() detach()
if cli_opts.replay_commands: if cli_opts.replay_commands:
from kitty.client import main as client_main from kitty.client import main as client_main

View File

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