mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-18 06:25:13 +02:00
BASH integration: No longer modify .bashrc to load shell integration
I think I have things setup robustly so that the shell integration is loaded transparently via env vars and the normal bash startup files are sourced, in the same way that vanilla bash does it. Let's hope I haven't overlooked something.
This commit is contained in:
@@ -196,7 +196,7 @@ class Child:
|
||||
allow_remote_control: bool = False
|
||||
):
|
||||
self.allow_remote_control = allow_remote_control
|
||||
self.argv = argv
|
||||
self.argv = list(argv)
|
||||
if cwd_from is not None:
|
||||
try:
|
||||
cwd = cwd_of_process(cwd_from)
|
||||
@@ -233,7 +233,7 @@ class Child:
|
||||
opts = fast_data_types.get_options()
|
||||
if 'disabled' not in opts.shell_integration:
|
||||
from .shell_integration import modify_shell_environ
|
||||
modify_shell_environ(self.argv[0], opts, env)
|
||||
modify_shell_environ(opts, env, self.argv)
|
||||
env = {k: v for k, v in env.items() if v is not DELETE_ENV_VAR}
|
||||
return env
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ from .options.types import Options
|
||||
from .options.utils import DELETE_ENV_VAR
|
||||
from .os_window_size import initial_window_size_func
|
||||
from .session import get_os_window_sizing_data
|
||||
from .shell_integration import setup_shell_integration
|
||||
from .types import SingleKey
|
||||
from .utils import (
|
||||
detach, expandvars, log_error, single_instance,
|
||||
@@ -322,7 +321,6 @@ def setup_environment(opts: Options, cli_opts: CLIOptions) -> None:
|
||||
os.environ['KITTY_LISTEN_ON'] = cli_opts.listen_on
|
||||
env = opts.env.copy()
|
||||
ensure_kitty_in_path()
|
||||
setup_shell_integration(opts, env)
|
||||
kitty_path = shutil.which('kitty')
|
||||
if kitty_path:
|
||||
child_path = env.get('PATH')
|
||||
|
||||
@@ -3,65 +3,14 @@
|
||||
|
||||
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from typing import Optional, Union, Dict
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
from .options.types import Options
|
||||
from .config import atomic_save
|
||||
from .constants import shell_integration_dir
|
||||
from .utils import log_error, resolved_shell
|
||||
|
||||
posix_template = '''\
|
||||
# BEGIN_KITTY_SHELL_INTEGRATION
|
||||
if test -n "$KITTY_INSTALLATION_DIR" -a -e "$KITTY_INSTALLATION_DIR/{path}"; then source "$KITTY_INSTALLATION_DIR/{path}"; fi
|
||||
# END_KITTY_SHELL_INTEGRATION\
|
||||
'''
|
||||
from .utils import log_error
|
||||
|
||||
|
||||
def atomic_write(path: str, data: Union[str, bytes]) -> None:
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
atomic_save(data, path)
|
||||
|
||||
|
||||
def safe_read(path: str) -> str:
|
||||
with suppress(FileNotFoundError):
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
return ''
|
||||
|
||||
|
||||
def rc_inset(shell_name: str = 'bash', template: str = posix_template) -> str:
|
||||
return template.format(path=f"shell-integration/{shell_name}/kitty.{shell_name}")
|
||||
|
||||
|
||||
def setup_integration(shell_name: str, rc_path: str, template: str = posix_template) -> None:
|
||||
import re
|
||||
rc_path = os.path.realpath(rc_path)
|
||||
rc = safe_read(rc_path)
|
||||
integration = rc_inset(shell_name, template)
|
||||
newrc, num_subs = re.subn(
|
||||
r'^# BEGIN_KITTY_SHELL_INTEGRATION.+?^# END_KITTY_SHELL_INTEGRATION',
|
||||
integration, rc, flags=re.DOTALL | re.MULTILINE)
|
||||
if num_subs < 1:
|
||||
newrc = newrc.rstrip() + '\n\n' + integration
|
||||
if newrc != rc:
|
||||
atomic_write(rc_path, newrc)
|
||||
|
||||
|
||||
def setup_zsh_integration(env: Dict[str, str]) -> None:
|
||||
pass # this is handled in the zsh env modifier
|
||||
|
||||
|
||||
def setup_bash_integration(env: Dict[str, str]) -> None:
|
||||
setup_integration('bash', os.path.expanduser('~/.bashrc'))
|
||||
|
||||
|
||||
def setup_fish_integration(env: Dict[str, str]) -> None:
|
||||
pass # this is handled in the fish env modifier
|
||||
|
||||
|
||||
def setup_fish_env(env: Dict[str, str]) -> None:
|
||||
def setup_fish_env(env: Dict[str, str], argv: List[str]) -> None:
|
||||
val = env.get('XDG_DATA_DIRS')
|
||||
env['KITTY_FISH_XDG_DATA_DIR'] = shell_integration_dir
|
||||
if not val:
|
||||
@@ -88,7 +37,7 @@ def is_new_zsh_install(env: Dict[str, str]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def setup_zsh_env(env: Dict[str, str]) -> None:
|
||||
def setup_zsh_env(env: Dict[str, str], argv: List[str]) -> None:
|
||||
if is_new_zsh_install(env):
|
||||
# dont prevent zsh-newuser-install from running
|
||||
# zsh-newuser-install never runs as root but we assume that it does
|
||||
@@ -103,48 +52,58 @@ def setup_zsh_env(env: Dict[str, str]) -> None:
|
||||
env['ZDOTDIR'] = os.path.join(shell_integration_dir, 'zsh')
|
||||
|
||||
|
||||
SUPPORTED_SHELLS = {
|
||||
'zsh': setup_zsh_integration,
|
||||
'bash': setup_bash_integration,
|
||||
'fish': setup_fish_integration,
|
||||
}
|
||||
def setup_bash_env(env: Dict[str, str], argv: List[str]) -> None:
|
||||
inject = {'1'}
|
||||
posix_env = rcfile = ''
|
||||
remove_args = set()
|
||||
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}
|
||||
env['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash')
|
||||
env['KITTY_BASH_INJECT'] = ' '.join(inject)
|
||||
if posix_env:
|
||||
env['KITTY_BASH_POSIX_ENV'] = posix_env
|
||||
if rcfile:
|
||||
env['KITTY_BASH_RCFILE'] = rcfile
|
||||
for i in sorted(remove_args, reverse=True):
|
||||
del argv[i]
|
||||
argv.insert(1, '--posix')
|
||||
|
||||
|
||||
ENV_MODIFIERS = {
|
||||
'fish': setup_fish_env,
|
||||
'zsh': setup_zsh_env,
|
||||
'bash': setup_bash_env,
|
||||
}
|
||||
|
||||
|
||||
def get_supported_shell_name(path: str) -> Optional[str]:
|
||||
name = os.path.basename(path).split('.')[0].lower()
|
||||
name = name.replace('-', '')
|
||||
if name in SUPPORTED_SHELLS:
|
||||
return name
|
||||
return None
|
||||
name = os.path.basename(path)
|
||||
if name.lower().endswith('.exe'):
|
||||
name = name.rpartition('.')[0]
|
||||
if name.startswith('-'):
|
||||
name = name[1:]
|
||||
return name if name in ENV_MODIFIERS else None
|
||||
|
||||
|
||||
def shell_integration_allows_rc_modification(opts: Options) -> bool:
|
||||
return not (opts.shell_integration & {'disabled', 'no-rc'})
|
||||
|
||||
|
||||
def setup_shell_integration(opts: Options, env: Dict[str, str]) -> bool:
|
||||
if not shell_integration_allows_rc_modification(opts):
|
||||
return False
|
||||
shell = get_supported_shell_name(resolved_shell(opts)[0])
|
||||
if shell is None:
|
||||
return False
|
||||
func = SUPPORTED_SHELLS[shell]
|
||||
try:
|
||||
func(env)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
log_error(f'Failed to setup shell integration for: {shell}')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def modify_shell_environ(argv0: str, opts: Options, env: Dict[str, str]) -> None:
|
||||
shell = get_supported_shell_name(argv0)
|
||||
def modify_shell_environ(opts: Options, env: Dict[str, str], argv: List[str]) -> None:
|
||||
shell = get_supported_shell_name(argv[0])
|
||||
if shell is None or 'disabled' in opts.shell_integration:
|
||||
return
|
||||
env['KITTY_SHELL_INTEGRATION'] = ' '.join(opts.shell_integration)
|
||||
@@ -153,7 +112,7 @@ def modify_shell_environ(argv0: str, opts: Options, env: Dict[str, str]) -> None
|
||||
f = ENV_MODIFIERS.get(shell)
|
||||
if f is not None:
|
||||
try:
|
||||
f(env)
|
||||
f(env, argv)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
Reference in New Issue
Block a user