diff --git a/scripts/shell-init.sh b/scripts/shell-init.sh index 0a3f4d1..0eb0168 100644 --- a/scripts/shell-init.sh +++ b/scripts/shell-init.sh @@ -37,18 +37,43 @@ fi (( INTERACTIVE )) || return 0 +# ---- run-once guard -------------------------------------------------------- + +# Installed as both loginShellInit (/etc/profile) and interactiveShellInit +# (/etc/bashrc), and /etc/profile sources /etc/bashrc, so a login shell would run +# this twice and register starship's hooks twice. Not exported: child shells must +# initialise on their own. +if [[ -n ${NANUQSAURUS_SHELL_INIT_DONE:-} ]]; then + log_shell_init "already initialised, skipping" + return 0 +fi +NANUQSAURUS_SHELL_INIT_DONE=1 + # ---- bash-preexec ---------------------------------------------------------- if [[ -n ${BASH_VERSION:-} && -z ${bash_preexec_imported:-} && -r "@BASHPREEXEC@" ]]; then # shellcheck disable=SC1090 . "@BASHPREEXEC@" - # Do NOT call __bp_install here. bash-preexec must install on the FIRST prompt, - # after starship/atuin/zoxide/ghostty have modified PROMPT_COMMAND, so that - # __bp_interactive_mode stays the LAST PROMPT_COMMAND entry. Installing eagerly - # lets later hooks land after it and silently breaks preexec (atuin recording). log_shell_init "bash-preexec loaded" fi +# bash-preexec arms its DEBUG trap from __bp_interactive_mode, which it appends to +# the END of PROMPT_COMMAND -- but its lazy installer runs in the MIDDLE of the +# first prompt, because zoxide (below) and ghostty (~/.bashrc) append their hooks +# after it. Those hooks then disarm the trap again, so the first command of every +# session never reaches preexec and atuin never records it. PS0 is expanded once +# per interactive command, before the DEBUG trap, so arming there is independent of +# PROMPT_COMMAND ordering. Needs the non-forking ${ ...; } funsub (bash >= 5.3); +# older bash keeps the previous behaviour. +if [[ -n ${bash_preexec_imported:-} ]] && + ((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then + # shellcheck disable=SC2016 + __nanuq_bp_arm='${ __bp_preexec_interactive_mode=on; }' + [[ ${PS0-} == *"$__nanuq_bp_arm"* ]] || PS0=$__nanuq_bp_arm${PS0-} + unset __nanuq_bp_arm + log_shell_init "bash-preexec PS0 arming enabled" +fi + # ---- starship -------------------------------------------------------------- if have @STARSHIP@; then @@ -57,8 +82,7 @@ if have @STARSHIP@; then */bash) eval "$(@STARSHIP@ init bash)" ;; */zsh) eval "$(@STARSHIP@ init zsh)" ;; esac - # starship's bash init registers starship_precmd/starship_preexec_all with - # bash-preexec directly; no manual PROMPT_COMMAND wiring needed. + # registers starship_precmd/starship_preexec_all with bash-preexec directly else log_shell_init "starship skipped" fi @@ -77,8 +101,7 @@ if have @ATUIN@; then */bash) eval "$(@ATUIN@ init bash)" ;; */zsh) eval "$(@ATUIN@ init zsh)" ;; esac - # atuin's init registers __atuin_preexec/__atuin_precmd with bash-preexec - # itself; manual array wiring only caused duplicate hook entries. + # registers __atuin_preexec/__atuin_precmd with bash-preexec itself else log_shell_init "atuin skipped" fi @@ -91,9 +114,7 @@ if have zoxide; then */bash) eval "$(zoxide init bash)" ;; */zsh) eval "$(zoxide init zsh)" ;; esac - # zoxide's bash init hooks into bash-preexec (precmd_functions) when it is - # present; manual PROMPT_COMMAND wiring only pushed hooks past - # __bp_interactive_mode and broke preexec ordering. + # appends __zoxide_hook to PROMPT_COMMAND; see the PS0 note above else log_shell_init "zoxide skipped" fi