diff --git a/docs/glossary.rst b/docs/glossary.rst index 9fec5a5ae..520ed02e0 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -224,6 +224,14 @@ Variables that kitty sets when running child programs Set when enabling :ref:`shell_integration`. It is automatically removed by the shell integration scripts. +.. envvar:: KITTY_SI_RUN_COMMAND_AT_STARTUP + + Set this to an expression that the kitty shell integration scripts will + ``eval`` after the shell is started. Note that this environment variable + is ignored when present in the environment in which kitty itself is launched + in. It is most useful with the ``--env`` flag for the :doc:`launch ` + action. + .. envvar:: ZDOTDIR Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing diff --git a/kitty/child.py b/kitty/child.py index 40f7667a9..69ada6cf0 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -152,6 +152,7 @@ def process_env(env: Mapping[str, str] | None = None) -> dict[str, str]: ans.pop(ssl_env_var, None) ans.pop('XDG_ACTIVATION_TOKEN', None) ans.pop('VTE_VERSION', None) # Used by the stupid VTE shell integration script that is installed system wide, sigh + ans.pop('KITTY_SI_RUN_COMMAND_AT_STARTUP', None) return ans diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index 59a298007..829d9c4bd 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -86,12 +86,14 @@ class ShellIntegration(BaseTest): with_kitten = False @contextmanager - def run_shell(self, shell='zsh', rc='', cmd='', setup_env=None): + def run_shell(self, shell='zsh', rc='', cmd='', setup_env=None, extra_env=None): home_dir = self.home_dir = os.path.realpath(tempfile.mkdtemp()) cmd = cmd or shell cmd = shlex.split(cmd.format(**locals())) env = (setup_env or safe_env_for_running_shell)(cmd, home_dir, rc=rc, shell=shell, with_kitten=self.with_kitten) env['KITTY_RUNNING_SHELL_INTEGRATION_TEST'] = '1' + if extra_env: + env.update(extra_env) try: if self.with_kitten: cmd = [kitten_exe(), 'run-shell', '--shell', shlex.join(cmd)] @@ -183,6 +185,10 @@ RPS1="{rps1}" self.assert_command(pty) env = pty.callbacks.clone_cmds[0].env self.ae(env.get('ES'), 'a\n b c\nd') + with self.run_shell(rc='PS1=XXX', extra_env={'KITTY_SI_RUN_COMMAND_AT_STARTUP': 'echo pre-start'}) as pty: + pty.wait_till(lambda: 'XXX' in pty.screen_contents()) + self.assertIn('pre-start', pty.screen_contents()) + self.assertTrue(pty.screen_contents().startswith('pre-start')) @unittest.skipUnless(shutil.which('fish'), 'fish not installed') def test_fish_integration(self): @@ -190,6 +196,7 @@ RPS1="{rps1}" completions_dir = os.path.join(kitty_base_dir, 'shell-integration', 'fish', 'vendor_completions.d') with self.run_shell( shell='fish', + extra_env={'KITTY_SI_RUN_COMMAND_AT_STARTUP': 'echo XXX'}, rc=f''' set -g fish_greeting function fish_prompt; echo -n "{fish_prompt}"; end @@ -198,7 +205,7 @@ function _test_comp_path; contains "{completions_dir}" $fish_complete_path; and function _set_key; set -g fish_key_bindings fish_$argv[1]_key_bindings; end function _set_status_prompt; function fish_prompt; echo -n "$pipestatus $status {fish_prompt}"; end; end ''') as pty: - q = fish_prompt + ' ' * (pty.screen.columns - len(fish_prompt) - len(right_prompt)) + right_prompt + q = 'XXX\n' + fish_prompt + ' ' * (pty.screen.columns - len(fish_prompt) - len(right_prompt)) + right_prompt pty.wait_till(lambda: pty.screen_contents().count(right_prompt) == 1) self.ae(pty.screen_contents(), q) @@ -366,6 +373,10 @@ PS1="{ps1}" self.ae(ps1.splitlines()[-1] + 'echo $COLUMNS', str(pty.screen.line(pty.screen.cursor.y - 1 - len(ps1.splitlines())))) self.assert_command(pty, 'echo $COLUMNS') + with self.run_shell(shell='bash', rc='PS1=XXX', extra_env={'KITTY_SI_RUN_COMMAND_AT_STARTUP': 'echo pre-start'}) as pty: + pty.wait_till(lambda: 'XXX' in pty.screen_contents()) + self.assertIn('pre-start', pty.screen_contents()) + self.assertTrue(pty.screen_contents().startswith('pre-start')) # test startup file sourcing def setup_env(excluded, argv, home_dir, rc='', shell='bash', with_kitten=self.with_kitten): diff --git a/shell-integration/bash/kitty.bash b/shell-integration/bash/kitty.bash index 9b2072ee2..dd2760c98 100644 --- a/shell-integration/bash/kitty.bash +++ b/shell-integration/bash/kitty.bash @@ -121,6 +121,8 @@ _ksi_main() { fi builtin unset SSH_KITTEN_KITTY_DIR fi + builtin local krcs="$KITTY_SI_RUN_COMMAND_AT_STARTUP" + builtin unset KITTY_SI_RUN_COMMAND_AT_STARTUP _ksi_debug_print() { # print a line to STDERR of parent kitty process @@ -351,6 +353,7 @@ _ksi_main() { fi fi builtin unset KITTY_IS_CLONE_LAUNCH KITTY_CLONE_SOURCE_STRATEGIES + if [[ -n "$krcs" ]]; then builtin eval "$krcs"; fi } _ksi_main builtin unset -f _ksi_main diff --git a/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish b/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish index b0116ad24..f88a07386 100644 --- a/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish +++ b/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish @@ -37,6 +37,8 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after end set --erase SSH_KITTEN_KITTY_DIR end + set --local krcs "$KITTY_SI_RUN_COMMAND_AT_STARTUP" + set --erase KITTY_SI_RUN_COMMAND_AT_STARTUP # Enable cursor shape changes for default mode and vi mode if not contains "no-cursor" $_ksi @@ -201,6 +203,9 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after test (count $new_path) -eq (count $PATH) or set --global --export --path PATH $new_path end + if test -n "$krcs" + eval "$krcs" + end end function edit-in-kitty --wraps "kitten edit-in-kitty" -d "Edit the specified file in a kitty overlay window with your locally installed editor" diff --git a/shell-integration/zsh/kitty-integration b/shell-integration/zsh/kitty-integration index f289cda33..eefcdf6e0 100644 --- a/shell-integration/zsh/kitty-integration +++ b/shell-integration/zsh/kitty-integration @@ -88,6 +88,8 @@ _ksi_deferred_init() { builtin local -a opt opt=(${(s: :)KITTY_SHELL_INTEGRATION}) builtin unset KITTY_SHELL_INTEGRATION + builtin local krcs="$KITTY_SI_RUN_COMMAND_AT_STARTUP" + builtin unset KITTY_SI_RUN_COMMAND_AT_STARTUP if [[ -n "$SSH_KITTEN_KITTY_DIR" ]]; then if [[ ! "$PATH" =~ (^|:)${SSH_KITTEN_KITTY_DIR}(:|$) ]] && [[ -z "$(builtin command -v kitten)" ]]; then @@ -424,6 +426,9 @@ _ksi_deferred_init() { # kitty-integration though because decent public functions aren't supposed to # to unfunction themselves when invoked. Unfunctioning is done by calling code. builtin unfunction _ksi_deferred_init + + # run startup command + if [[ -n "$krcs" ]]; then builtin eval "$krcs"; fi } _ksi_transmit_data() {