diff --git a/kitty/child.py b/kitty/child.py index da1ac808b..00e179990 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -245,6 +245,7 @@ class Child: env['TERMINFO'] = tdir env['KITTY_INSTALLATION_DIR'] = kitty_base_dir opts = fast_data_types.get_options() + self.unmodified_argv = list(self.argv) if 'disabled' not in opts.shell_integration: from .shell_integration import modify_shell_environ modify_shell_environ(opts, env, self.argv) diff --git a/kitty/launch.py b/kitty/launch.py index 06d588c31..c5d598a45 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -545,10 +545,10 @@ def parse_null_env(text: str) -> Dict[str, str]: class CloneCmd: def __init__(self, msg: str) -> None: - self.cmdline: List[str] = [] self.args: List[str] = [] self.env: Optional[Dict[str, str]] = None self.cwd = '' + self.shell = '' self.envfmt = 'default' self.pid = -1 self.parse_message(msg) @@ -556,43 +556,55 @@ class CloneCmd: def parse_message(self, msg: str) -> None: import base64 - import json + simple = 'pid', 'envfmt', 'shell' for x in msg.split(','): k, v = x.split('=', 1) - if k == 'pid': - self.pid = int(v) - continue - if k == 'envfmt': - self.envfmt = v + if k in simple: + setattr(self, k, int(v) if k == 'pid' else v) continue v = base64.standard_b64decode(v).decode('utf-8', 'replace') if k == 'a': self.args.append(v) elif k == 'env': env = parse_bash_env(v) if self.envfmt == 'bash' else parse_null_env(v) - self.env = {k: v for k, v in env.items() if k not in ( - 'HOME', 'LOGNAME', 'USER', + self.env = {k: v for k, v in env.items() if k not in { + 'HOME', 'LOGNAME', 'USER', 'PWD', # some people export these. We want the shell rc files to recreate them 'PS0', 'PS1', 'PS2', 'PS3', 'PS4', 'RPS1', 'PROMPT_COMMAND', 'SHLVL', + # conda state env vars + 'CONDA_SHLVL', 'CONDA_PREFIX', 'CONDA_EXE', 'CONDA_PROMPT_MODIFIER', 'CONDA_EXE', 'CONDA_PYTHON_EXE', # skip SSH environment variables 'SSH_CLIENT', 'SSH_CONNECTION', 'SSH_ORIGINAL_COMMAND', 'SSH_TTY', 'SSH2_TTY', - )} + }} elif k == 'cwd': self.cwd = v - elif k == 'argv': - self.cmdline = json.loads(v) def clone_and_launch(msg: str, window: Window) -> None: from .child import cmdline_of_process + from .shell_integration import serialize_env c = CloneCmd(msg) if c.cwd and not c.opts.cwd: c.opts.cwd = c.cwd c.opts.copy_colors = True c.opts.copy_env = False - c.opts.env = list(c.opts.env) + ['KITTY_IS_CLONE_LAUNCH=1'] - cmdline = c.cmdline - if c.pid > -1: + serialized_env = serialize_env(c.shell, c.env or {}) + ssh_kitten_cmdline = window.ssh_kitten_cmdline() + if ssh_kitten_cmdline: + from kittens.ssh.main import set_cwd_in_cmdline, set_env_in_cmdline, patch_cmdline + cmdline = ssh_kitten_cmdline + if c.opts.cwd: + set_cwd_in_cmdline(c.opts.cwd, cmdline) + c.opts.cwd = None + if c.env: + set_env_in_cmdline({'KITTY_IS_CLONE_LAUNCH': serialized_env}, cmdline) + c.env = None + if c.opts.env: + for entry in reversed(c.opts.env): + patch_cmdline('env', entry, cmdline) + c.opts.env = [] + else: + c.opts.env = list(c.opts.env) + ['KITTY_IS_CLONE_LAUNCH=' + serialized_env] try: cmdline = cmdline_of_process(c.pid) except Exception: @@ -601,18 +613,6 @@ def clone_and_launch(msg: str, window: Window) -> None: cmdline = list(window.child.argv) if cmdline and cmdline[0] == window.child.final_argv0: cmdline[0] = window.child.final_exe - ssh_kitten_cmdline = window.ssh_kitten_cmdline() - if ssh_kitten_cmdline: - from kittens.ssh.main import set_cwd_in_cmdline, set_env_in_cmdline, patch_cmdline - cmdline[:] = ssh_kitten_cmdline - if c.opts.cwd: - set_cwd_in_cmdline(c.opts.cwd, cmdline) - c.opts.cwd = None - if c.env: - set_env_in_cmdline(c.env, cmdline) - c.env = None - if c.opts.env: - for entry in reversed(c.opts.env): - patch_cmdline('env', entry, cmdline) - c.opts.env = [] + if cmdline and cmdline == [window.child.final_exe] + window.child.argv[1:]: + cmdline = window.child.unmodified_argv launch(get_boss(), c.opts, cmdline, base_env=c.env, active=window) diff --git a/kitty/shell_integration.py b/kitty/shell_integration.py index 54081e219..f1a8dc22a 100644 --- a/kitty/shell_integration.py +++ b/kitty/shell_integration.py @@ -2,8 +2,6 @@ # License: GPLv3 Copyright: 2021, Kovid Goyal -import base64 -import json import os import subprocess from contextlib import suppress @@ -135,7 +133,6 @@ def setup_bash_env(env: Dict[str, str], argv: List[str]) -> None: return env['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash') env['KITTY_BASH_INJECT'] = ' '.join(inject) - env['KITTY_BASH_ORIGINAL_ARGV'] = base64.standard_b64encode(json.dumps(argv).encode('utf-8')).decode('ascii') if posix_env: env['KITTY_BASH_POSIX_ENV'] = posix_env if rcfile: @@ -155,7 +152,8 @@ def as_str_literal(x: str) -> str: def as_fish_str_literal(x: str) -> str: - return x.replace('\\', '\\\\').replace("'", "\\'") + x = x.replace('\\', '\\\\').replace("'", "\\'") + return f"'{x}'" def posix_serialize_env(env: Dict[str, str], prefix: str = 'builtin export', sep: str = '=') -> str: @@ -199,6 +197,8 @@ def shell_integration_allows_rc_modification(opts: Options) -> bool: def serialize_env(path: str, env: Dict[str, str]) -> str: + if not env: + return '' name = get_supported_shell_name(path) if not name: raise ValueError(f'{path} is not a supported shell') @@ -231,4 +231,3 @@ def modify_shell_environ(opts: Options, env: Dict[str, str], argv: List[str]) -> import traceback traceback.print_exc() log_error(f'Failed to setup shell integration for: {shell}') - return diff --git a/shell-integration/bash/kitty.bash b/shell-integration/bash/kitty.bash index 6e86726ed..e37f1ed15 100644 --- a/shell-integration/bash/kitty.bash +++ b/shell-integration/bash/kitty.bash @@ -6,19 +6,10 @@ if [[ -z "$KITTY_SHELL_INTEGRATION" ]]; then builtin return; fi _ksi_inject() { # Load the normal bash startup files if [[ -n "$KITTY_BASH_INJECT" ]]; then - if [ -n "$KITTY_IS_CLONE_LAUNCH" ]; then - # store some vars before the rc files have a chance to change them - builtin declare -Ag _ksi_pre_rc - _ksi_pre_rc=( - [path]="$PATH" [conda_default_env]="$CONDA_DEFAULT_ENV" [python_venv]="$VIRTUAL_ENV" [is_clone_launch]="$KITTY_IS_CLONE_LAUNCH" - ) - fi - builtin local kitty_bash_inject="$KITTY_BASH_INJECT" builtin local ksi_val="$KITTY_SHELL_INTEGRATION" builtin unset KITTY_SHELL_INTEGRATION # ensure manual sourcing of this file in bashrc does not have any effect - builtin unset KITTY_BASH_INJECT - builtin unset ENV + builtin unset KITTY_BASH_INJECT ENV if [[ -z "$HOME" ]]; then HOME=~; fi if [[ -z "$KITTY_BASH_ETC_LOCATION" ]]; then KITTY_BASH_ETC_LOCATION="/etc"; fi @@ -58,16 +49,13 @@ _ksi_inject() { fi fi fi - builtin unset KITTY_BASH_RCFILE - builtin unset KITTY_BASH_POSIX_ENV - builtin unset KITTY_BASH_ETC_LOCATION + builtin unset KITTY_BASH_RCFILE KITTY_BASH_POSIX_ENV KITTY_BASH_ETC_LOCATION builtin unset -f _ksi_safe_source builtin export KITTY_SHELL_INTEGRATION="$ksi_val" fi } _ksi_inject builtin unset -f _ksi_inject -builtin unset KITTY_IS_CLONE_LAUNCH if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then builtin unset KITTY_SHELL_INTEGRATION @@ -86,9 +74,8 @@ fi builtin declare -A _ksi_prompt _ksi_prompt=( [cursor]='y' [title]='y' [mark]='y' [complete]='y' [cwd]='y' [ps0]='' [ps0_suffix]='' [ps1]='' [ps1_suffix]='' [ps2]='' - [hostname_prefix]='' [sourced]='y' [last_reported_cwd]='' [argv]="$KITTY_BASH_ORIGINAL_ARGV" + [hostname_prefix]='' [sourced]='y' [last_reported_cwd]='' ) -builtin unset KITTY_BASH_ORIGINAL_ARGV _ksi_main() { builtin local ifs="$IFS" @@ -251,12 +238,7 @@ _ksi_main() { if [[ -n "${_ksi_prompt[ps2]}" ]]; then _ksi_prompt[ps2]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps2]}${_ksi_prompt[end_mark]}" fi - builtin unset _ksi_prompt[start_mark] - builtin unset _ksi_prompt[end_mark] - builtin unset _ksi_prompt[start_suffix_mark] - builtin unset _ksi_prompt[end_suffix_mark] - builtin unset _ksi_prompt[start_secondary_mark] - builtin unset _ksi_prompt[end_secondary_mark] + builtin unset _ksi_prompt[start_mark] _ksi_prompt[end_mark] _ksi_prompt[start_suffix_mark] _ksi_prompt[end_suffix_mark] _ksi_prompt[start_secondary_mark] _ksi_prompt[end_secondary_mark] # install our prompt command, using an array if it is unset or already an array, # otherwise append a string. We check if _ksi_prompt_command exists as some shell @@ -277,14 +259,16 @@ _ksi_main() { builtin eval "$oldval" PROMPT_COMMAND+="; $pc" fi - if [ -n "${_ksi_pre_rc[is_clone_launch]}" ]; then - builtin export PATH="${_ksi_pre_rc[path]}" + if [ -n "${KITTY_IS_CLONE_LAUNCH}" ]; then + builtin local orig_conda_env="$CONDA_DEFAULT_ENV" + builtin eval "${KITTY_IS_CLONE_LAUNCH}" builtin hash -r 2> /dev/null 1> /dev/null - if [ -n "${_ksi_pre_rc[python_venv]}" -a -r "${_ksi_pre_rc[python_venv]}/bin/activate" ]; then + builtin local venv="${VIRTUAL_ENV}/bin/activate" + if [ -n "${VIRTUAL_ENV}" -a -r "$venv" ]; then builtin unset VIRTUAL_ENV - . "${_ksi_pre_rc[python_venv]}/bin/activate" - elif [ -n "${_ksi_pre_rc[conda_default_env]}" ] && builtin command -v conda >/dev/null 2>/dev/null && [ "${_ksi_pre_rc[conda_default_env]}" != "$CONDA_DEFAULT_ENV" ]; then - conda activate "${_ksi_pre_rc[conda_default_env]}" + . "$venv" + elif [ -n "${CONDA_DEFAULT_ENV}" ] && builtin command -v conda >/dev/null 2>/dev/null && [ "${CONDA_DEFAULT_ENV}" != "$orig_conda_env" ]; then + conda activate "${CONDA_DEFAULT_ENV}" fi # Ensure PATH has no duplicate entries if [ -n "$PATH" ]; then @@ -301,7 +285,7 @@ _ksi_main() { PATH=${PATH#:} fi fi - builtin unset _ksi_pre_rc + builtin unset KITTY_IS_CLONE_LAUNCH } _ksi_main builtin unset -f _ksi_main @@ -310,7 +294,7 @@ case :$SHELLOPTS: in *:posix:*) ;; *) clone-in-kitty() { - builtin local data="argv=${_ksi_prompt[argv]},cwd=$(builtin printf "%s" "$PWD" | builtin command base64),envfmt=bash,env=$(builtin export | builtin command base64)" + builtin local data="shell=bash,pid=$$,cwd=$(builtin printf "%s" "$PWD" | builtin command base64),envfmt=bash,env=$(builtin export | builtin command base64)" while :; do case "$1" in "") break;; 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 617d32c47..ddb45e2bc 100644 --- a/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish +++ b/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish @@ -24,13 +24,6 @@ not functions -q __ksi_schedule || exit 0 set -q fish_killring || set -q status_generation || string match -qnv "3.1.*" "$version" or echo -en \eP@kitty-print\|V2FybmluZzogVXBkYXRlIGZpc2ggdG8gdmVyc2lvbiAzLjMuMCsgdG8gZW5hYmxlIGtpdHR5IHNoZWxsIGludGVncmF0aW9uLgo=\e\\ && exit 0 || exit 0 -if test -n "$KITTY_IS_CLONE_LAUNCH" - set -g __ksi_is_clone_launch "$KITTY_IS_CLONE_LAUNCH" - set --erase KITTY_IS_CLONE_LAUNCH - set -g __ksi_pre_rc_path "$PATH" - set -g __ksi_pre_rc_conda_default_env "$CONDA_DEFAULT_ENV" - set -g __ksi_pre_rc_python_venv "$VIRTUAL_ENV" -end function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after other scripts have run, we hope" functions --erase __ksi_schedule @@ -116,19 +109,21 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after end # Handle clone launches - if test -n "$__ksi_is_clone_launch" - set -gx --path PATH "$__ksi_pre_rc_path" - if test -n "$__ksi_pre_rc_python_venv" -a -r "$__ksi_pre_rc_python_venv/bin/activate.fish" + if test -n "$KITTY_IS_CLONE_LAUNCH" + set -l orig_conda_env "$CONDA_DEFAULT_ENV" + eval "$KITTY_IS_CLONE_LAUNCH" + set -l venv "$VIRTUAL_ENV/bin/activate.fish" + if test -n "$VIRTUAL_ENV" -a -r "$venv" set -e VIRTUAL_ENV _OLD_FISH_PROMPT_OVERRIDE # activate.fish stupidly exports _OLD_FISH_PROMPT_OVERRIDE - source "$__ksi_pre_rc_python_venv/bin/activate.fish" - else if test -n "$__ksi_pre_rc_conda_default_env" + source "$venv" + else if test -n "$CONDA_DEFAULT_ENV" and type -q conda - and test "$__ksi_pre_rc_conda_default_env" != "$CONDA_DEFAULT_ENV" + and test "$CONDA_DEFAULT_ENV" != "$orig_conda_env" # for some reason that I cant be bothered to figure out this doesnt take effect # conda activate $_ksi_pre_rc_conda_default_env - eval ($CONDA_EXE shell.fish activate $__ksi_pre_rc_conda_default_env) + eval ($CONDA_EXE shell.fish activate $CONDA_DEFAULT_ENV) end - set --erase __ksi_is_clone_launch __ksi_pre_rc_path __ksi_pre_rc_conda_default_env __ksi_pre_rc_python_venv + set --erase KITTY_IS_CLONE_LAUNCH end end @@ -150,7 +145,7 @@ function clone-in-kitty -d "Clone the current fish session into a new kitty wind end set --local b64_envs (string join0 $envs | base64) set --local b64_cwd (printf "%s" "$PWD" | base64) - set --prepend data "pid=$fish_pid" "cwd=$b64_cwd" "env=$b64_envs" + set --prepend data "shell=fish,pid=$fish_pid" "cwd=$b64_cwd" "env=$b64_envs" set data (string join "," -- $data | tr -d "\t\n\r ") set --local data_len (string length -- "$data") set --local pos 1 diff --git a/shell-integration/zsh/kitty-integration b/shell-integration/zsh/kitty-integration index bd2650753..b2ab656bd 100644 --- a/shell-integration/zsh/kitty-integration +++ b/shell-integration/zsh/kitty-integration @@ -32,16 +32,6 @@ builtin emulate -L zsh -o no_warn_create_global -o no_aliases # 2: none of the above. builtin typeset -gi _ksi_state -if [ -n "$KITTY_IS_CLONE_LAUNCH" ]; then - # store some vars before the rc files have a chance to change them - builtin typeset -gA _ksi_pre_rc - _ksi_pre_rc[path]="$PATH" - _ksi_pre_rc[conda_default_env]="$CONDA_DEFAULT_ENV" - _ksi_pre_rc[python_venv]="$VIRTUAL_ENV" - _ksi_pre_rc[is_clone_launch]="$KITTY_IS_CLONE_LAUNCH" - builtin unset KITTY_IS_CLONE_LAUNCH -fi - # Attempt to create a writable file descriptor to the TTY so that we can print # to the TTY later even when STDOUT is redirected. This code is fairly subtle. # @@ -359,19 +349,21 @@ _ksi_deferred_init() { precmd_functions=(${precmd_functions:#_ksi_deferred_init}) fi - if [ -n "${_ksi_pre_rc[is_clone_launch]}" ]; then - builtin export PATH="${_ksi_pre_rc[path]}" + if [ -n "${KITTY_IS_CLONE_LAUNCH}" ]; then + builtin local orig_conda_env="$CONDA_DEFAULT_ENV" + builtin eval "${KITTY_IS_CLONE_LAUNCH}" builtin hash -r 2> /dev/null 1> /dev/null - if [ -n "${_ksi_pre_rc[python_venv]}" -a -r "${_ksi_pre_rc[python_venv]}/bin/activate" ]; then + builtin local venv="${VIRTUAL_ENV}/bin/activate" + if [ -n "${VIRTUAL_ENV}" -a -r "$venv" ]; then builtin unset VIRTUAL_ENV - . "${_ksi_pre_rc[python_venv]}/bin/activate" - elif [[ -n "${_ksi_pre_rc[conda_default_env]}" && (( $+commands[conda] )) && "${_ksi_pre_rc[conda_default_env]}" != "$CONDA_DEFAULT_ENV" ]]; then - conda activate "${_ksi_pre_rc[conda_default_env]}" + . "$venv" + elif [[ -n "${CONDA_DEFAULT_ENV}" && (( $+commands[conda] )) && "${CONDA_DEFAULT_ENV}" != "$orig_conda_env" ]]; then + conda activate "${CONDA_DEFAULT_ENV}" fi # Ensure PATH has no duplicate entries typeset -U path fi - builtin unset _ksi_pre_rc + builtin unset KITTY_IS_CLONE_LAUNCH # Unfunction _ksi_deferred_init to save memory. Don't unfunction # kitty-integration though because decent public functions aren't supposed to @@ -380,7 +372,7 @@ _ksi_deferred_init() { } clone-in-kitty() { - builtin local data="pid=$$,cwd=$(builtin printf "%s" "$PWD" | builtin command base64)" + builtin local data="shell=zsh,pid=$$,cwd=$(builtin printf "%s" "$PWD" | builtin command base64)" while :; do case "$1" in "") break;;