diff --git a/docs/changelog.rst b/docs/changelog.rst index b580bd889..ba83b6c32 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,8 @@ Changelog - Render the text under the cursor in a fixed color, configurable via the option :opt:`cursor_text_color` (:iss:`126`) +- Add an option :opt:`env` to set environment variables in child processes + from kitty.conf 0.11.3 [2018-07-10] ------------------------------ diff --git a/kitty/child.py b/kitty/child.py index 123ac89a8..e9e62cbfe 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -30,6 +30,20 @@ def remove_cloexec(fd): fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC) +def default_env(): + try: + return default_env.env + except AttributeError: + return os.environ + + +def set_default_env(val=None): + env = os.environ.copy() + if val: + env.update(val) + default_env.env = env + + class Child: child_fd = pid = None @@ -63,7 +77,7 @@ class Child: remove_cloexec(stdin_read_fd) else: stdin_read_fd = stdin_write_fd = -1 - env = os.environ.copy() + env = default_env().copy() env.update(self.env) env['TERM'] = self.opts.term env['COLORTERM'] = 'truecolor' diff --git a/kitty/config.py b/kitty/config.py index efc9d9c68..f34edd4c8 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -245,28 +245,68 @@ def parse_send_text(val, key_definitions): return parse_key(key_str, key_definitions) +special_handlers = {} + + +def special_handler(func): + special_handlers[func.__name__.partition('_')[2]] = func + + +@special_handler +def handle_map(key, val, ans): + parse_key(val, ans['key_definitions']) + + +@special_handler +def handle_symbol_map(key, val, ans): + ans['symbol_map'].update(parse_symbol_map(val)) + + +@special_handler +def handle_send_text(key, val, ans): + # For legacy compatibility + parse_send_text(val, ans['key_definitions']) + + +@special_handler +def handle_clear_all_shortcuts(key, val, ans): + if to_bool(val): + ans['key_definitions'] = [None] + + +def expandvars(val, env): + + def sub(m): + key = m.group(1) + result = env.get(key) + if result is None: + result = os.environ.get(key) + if result is None: + result = m.group() + return result + + return re.sub(r'$\{(\S+)\}', sub, val) + + +@special_handler +def handle_env(key, val, ans): + key, val = val.partition('=')[::2] + key, val = key.strip(), val.strip() + ans['env'][key] = expandvars(val, ans['env']) + + def special_handling(key, val, ans): - if key == 'map': - parse_key(val, ans['key_definitions']) + func = special_handlers.get(key) + if func is not None: + func(key, val, ans) return True - if key == 'symbol_map': - ans['symbol_map'].update(parse_symbol_map(val)) - return True - if key == 'send_text': - # For legacy compatibility - parse_send_text(val, ans['key_definitions']) - return True - if key == 'clear_all_shortcuts': - if to_bool(val): - ans['key_definitions'] = [None] - return defaults = None def parse_config(lines, check_keys=True): - ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []} + ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}} parse_config_base( lines, defaults, diff --git a/kitty/config_data.py b/kitty/config_data.py index 7d171bdd3..8242e1a84 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -646,6 +646,19 @@ opening new windows, closing windows, reading the content of windows, etc. Note that this even works over ssh connections. ''')) +o( + '+env', '', + add_to_default=False, + long_text=_(''' +Specify environment variables to set in all child processes. Note that +environment variables are expanded recursively, so if you use:: + + env MYVAR1=a + env MYVAR2=${MYVAR}/${HOME}/b + +The value of MYVAR2 will be :code:`a//b`. +''')) + def startup_session(x): if x.lower() == 'none': diff --git a/kitty/main.py b/kitty/main.py index b967ed56b..f8817f494 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -9,6 +9,7 @@ from contextlib import contextmanager from .borders import load_borders_program from .boss import Boss +from .child import set_default_env from .cli import create_opts, parse_args from .config import cached_values_for, initial_window_size_func from .constants import ( @@ -202,6 +203,13 @@ def macos_cmdline(): return ans +def setup_environment(opts): + extra_env = opts.env.copy() + if opts.editor != '.': + os.environ['EDITOR'] = opts.editor + set_default_env(extra_env) + + def _main(): try: sys.setswitchinterval(1000.0) # we have only a single python thread @@ -256,8 +264,7 @@ def _main(): return init_glfw(args.debug_keyboard) # needed for parsing native keysyms opts = create_opts(args) - if opts.editor != '.': - os.environ['EDITOR'] = opts.editor + setup_environment(opts) try: with setup_profiling(args): # Avoid needing to launch threads to reap zombies