From e71b9091a39e4323230fb00a22871c692bf0c4a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 15 Apr 2022 13:33:32 +0530 Subject: [PATCH] Dont rely on env -0 for bash either --- kitty/launch.py | 55 ++++++++++++++++++++++++++----- kitty_tests/shell_integration.py | 7 ++++ shell-integration/bash/kitty.bash | 2 +- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/kitty/launch.py b/kitty/launch.py index ed48b0012..a73f0214f 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -497,6 +497,51 @@ def parse_opts_for_clone(args: List[str]) -> LaunchCLIOptions: return default_opts +def parse_bash_env(text: str) -> Dict[str, str]: + # See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html + ans = {} + pos = 0 + escapes = r'"\$`' + while pos < len(text): + idx = text.find('="', pos) + if idx < 0: + break + i = text.rfind(' ', 0, idx) + if i < 0: + break + key = text[i+1:idx] + pos = idx + 2 + buf: List[str] = [] + a = buf.append + while pos < len(text): + ch = text[pos] + pos += 1 + if ch == '\\': + if text[pos] in escapes: + a(text[pos]) + pos += 1 + continue + a(ch) + elif ch == '"': + break + else: + a(ch) + ans[key] = ''.join(buf) + return ans + + +def parse_null_env(text: str) -> Dict[str, str]: + ans = {} + for line in text.split('\0'): + if line: + try: + k, v = line.split('=', 1) + except ValueError: + continue + ans[k] = v + return ans + + class CloneCmd: def __init__(self, msg: str) -> None: @@ -504,6 +549,7 @@ class CloneCmd: self.args: List[str] = [] self.env: Optional[Dict[str, str]] = None self.cwd = '' + self.envfmt = 'default' self.pid = -1 self.parse_message(msg) self.opts = parse_opts_for_clone(self.args) @@ -520,14 +566,7 @@ class CloneCmd: if k == 'a': self.args.append(v) elif k == 'env': - self.env = {} - for line in v.split('\0'): - if line: - try: - k, v = line.split('=', 1) - except ValueError: - continue - self.env[k] = v + self.env = parse_bash_env(v) if self.envfmt == 'bash' else parse_null_env(v) elif k == 'cwd': self.cwd = v elif k == 'argv': diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index d5fe1a8d9..ad639145c 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -357,3 +357,10 @@ PS1="{ps1}" run_test('bash +O login_shell -ic "echo ok;read"', 'bash.bashrc', excluded=('.bash_profile'), wait_string='ok', assert_not_in=True) run_test('bash -l .bashrc', 'profile', rc='echo ok;read', wait_string='ok', assert_not_in=True) run_test('bash -il -- .bashrc', 'profile', rc='echo ok;read', wait_string='ok') + + with self.run_shell(rc=f'''PS1="{ps1}"\nexport ES=$'a\n `b` c\n$d' ''') as pty: + pty.callbacks.clear() + pty.send_cmd_to_child('clone-in-kitty') + pty.wait_till(lambda: len(pty.callbacks.clone_cmds) == 1) + env = pty.callbacks.clone_cmds[0].env + self.ae(env.get('ES'), 'a\n `b` c\n$d') diff --git a/shell-integration/bash/kitty.bash b/shell-integration/bash/kitty.bash index 46bb5aa85..3694b70fe 100644 --- a/shell-integration/bash/kitty.bash +++ b/shell-integration/bash/kitty.bash @@ -276,7 +276,7 @@ case :$SHELLOPTS: in *:posix:*) ;; *) clone-in-kitty() { - builtin local data="argv=${_ksi_prompt[argv]},cwd=$(builtin printf "%s" "$PWD" | builtin command base64),env=$(builtin command env -0 | builtin command base64)" + builtin local data="argv=${_ksi_prompt[argv]},cwd=$(builtin printf "%s" "$PWD" | builtin command base64),envfmt=bash,env=$(builtin export | builtin command base64)" while :; do case "$1" in "") break;;