From c8313409ff083778819be7751ed1db990c673083 Mon Sep 17 00:00:00 2001 From: pagedown Date: Fri, 4 Mar 2022 19:36:38 +0800 Subject: [PATCH 1/2] Explicitly call bash builtin --- shell-integration/bash/kitty.bash | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/shell-integration/bash/kitty.bash b/shell-integration/bash/kitty.bash index e676e648b..d491d3fcd 100644 --- a/shell-integration/bash/kitty.bash +++ b/shell-integration/bash/kitty.bash @@ -28,25 +28,26 @@ _ksi_main() { builtin printf "\eP@kitty-print|%s\e\\" "${b//\\n}" } - _ksi_safe_source() { - if [[ -f "$1" && -r "$1" ]]; then - builtin source "$1"; - builtin return 0; - fi - builtin return 1; - } - if [[ -n "$KITTY_BASH_INJECT" ]]; then builtin unset ENV; if [[ -z "$HOME" ]]; then HOME=~; fi if [[ -z "$KITTY_BASH_ETC_LOCATION" ]]; then KITTY_BASH_ETC_LOCATION="/etc"; fi + + _ksi_safe_source() { + if [[ -f "$1" && -r "$1" ]]; then + builtin source "$1"; + builtin return 0; + fi + builtin return 1; + } + if [[ "$KITTY_BASH_INJECT" == *"posix"* ]]; then _ksi_safe_source "$KITTY_BASH_POSIX_ENV" && builtin export ENV="$KITTY_BASH_POSIX_ENV"; else builtin set +o posix; if [[ -n "$KITTY_BASH_UNEXPORT_HISTFILE" ]]; then - export -n HISTFILE; - unset KITTY_BASH_UNEXPORT_HISTFILE; + builtin export -n HISTFILE; + builtin unset KITTY_BASH_UNEXPORT_HISTFILE; fi # See run_startup_files() in shell.c in the Bash source code @@ -70,8 +71,8 @@ _ksi_main() { builtin unset KITTY_BASH_POSIX_ENV; builtin unset KITTY_BASH_INJECT; builtin unset KITTY_BASH_ETC_LOCATION; + builtin unset -f _ksi_safe_source fi - builtin unset -f _ksi_safe_source _ksi_set_mark() { _ksi_prompt["${1}_mark"]="\[\e]133;k;${1}_kitty\a\]" From d236b34fd46014dc4489b20c8593cc0af71be462 Mon Sep 17 00:00:00 2001 From: pagedown Date: Fri, 4 Mar 2022 19:47:44 +0800 Subject: [PATCH 2/2] Shell integration: Fix running bash non-interactively In POSIX mode, bash does not perform ENV with non-interactive shell, so the mode cannot be recovered. Check the arguments and do not change the execution environment. --- kitty/shell_integration.py | 71 ++++++++++++++++++++++++++------ kitty_tests/shell_integration.py | 22 +++++++--- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/kitty/shell_integration.py b/kitty/shell_integration.py index d5fa95a62..2af338208 100644 --- a/kitty/shell_integration.py +++ b/kitty/shell_integration.py @@ -71,21 +71,66 @@ def setup_bash_env(env: Dict[str, str], argv: List[str]) -> None: inject = {'1'} posix_env = rcfile = '' remove_args = set() + + expecting_multi_chars_opt = True + expecting_option_arg = False + interactive_opt = False + expecting_file_arg = False + file_arg_set = False + for i in range(1, len(argv)): arg = argv[i] - if arg == '--posix': - inject.add('posix') - posix_env = env.get('ENV', '') - remove_args.add(i) - elif arg == '--norc': - inject.add('no-rc') - remove_args.add(i) - elif arg == '--noprofile': - inject.add('no-profile') - remove_args.add(i) - elif arg in ('--rcfile', '--init-file') and i + 1 < len(argv): - rcfile = argv[i+1] - remove_args |= {i, i+1} + if expecting_file_arg: + file_arg_set = True + break + if expecting_option_arg: + expecting_option_arg = False + continue + if arg in ('-', '--'): + if not expecting_file_arg: + expecting_file_arg = True + continue + elif len(arg) > 1 and arg[1] != '-' and (arg[0] == '-' or arg.startswith('+O')): + expecting_multi_chars_opt = False + options = arg.lstrip('-+') + # shopt option + if 'O' in options: + t = options.split('O', maxsplit=1) + if not t[1]: + expecting_option_arg = True + options = t[0] + # command string + if 'c' in options: + # non-interactive shell + # also skip `bash -ic` interactive mode with command string + return + # read from stdin and follow with args + if 's' in options: + break + # interactive option + if 'i' in options: + interactive_opt = True + elif arg.startswith('--') and expecting_multi_chars_opt: + if arg == '--posix': + inject.add('posix') + posix_env = env.get('ENV', '') + remove_args.add(i) + elif arg == '--norc': + inject.add('no-rc') + remove_args.add(i) + elif arg == '--noprofile': + inject.add('no-profile') + remove_args.add(i) + elif arg in ('--rcfile', '--init-file') and i + 1 < len(argv): + expecting_option_arg = True + rcfile = argv[i+1] + remove_args |= {i, i+1} + else: + file_arg_set = True + break + if file_arg_set and not interactive_opt: + # non-interactive shell + return env['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash') env['KITTY_BASH_INJECT'] = ' '.join(inject) if posix_env: diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index a56d415cc..a5029e7dc 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -307,17 +307,23 @@ PS1="{ps1}" setup_bash_env(ans, argv) for x in {'profile', 'bash.bashrc', '.bash_profile', '.bash_login', '.profile', '.bashrc', 'rcfile'} - excluded: with open(os.path.join(home_dir, x), 'w') as f: - print(f'echo {x}', file=f) + if x == '.bashrc' and rc: + print(rc, file=f) + else: + print(f'echo [{x}]', file=f) ans['KITTY_BASH_ETC_LOCATION'] = home_dir ans['PS1'] = 'PROMPT $ ' return ans - def run_test(argv, *expected, excluded=()): - with self.subTest(argv=argv), self.run_shell(shell='bash', setup_env=partial(setup_env, set(excluded)), cmd=argv) as pty: - pty.wait_till(lambda: 'PROMPT $' in pty.screen_contents()) + def run_test(argv, *expected, excluded=(), rc='', wait_string='PROMPT $', assert_not_in=False): + with self.subTest(argv=argv), self.run_shell(shell='bash', setup_env=partial(setup_env, set(excluded)), cmd=argv, rc=rc) as pty: + pty.wait_till(lambda: wait_string in pty.screen_contents()) q = pty.screen_contents() for x in expected: - self.assertIn(x, q) + if assert_not_in: + self.assertNotIn(f'[{x}]', q) + else: + self.assertIn(f'[{x}]', q) run_test('bash', 'bash.bashrc', '.bashrc') run_test('bash --rcfile rcfile', 'bash.bashrc', 'rcfile') @@ -327,3 +333,9 @@ PS1="{ps1}" run_test('bash --noprofile -l') run_test('bash -l', 'profile', '.bash_login', excluded=('.bash_profile',)) run_test('bash -l', 'profile', '.profile', excluded=('.bash_profile', '.bash_login')) + + # test argument parsing and non-interactive shell + run_test('bash -s arg1 --rcfile rcfile', 'rcfile', rc='echo ok;read', wait_string='ok', assert_not_in=True) + 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')