diff --git a/src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt b/src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt index 882ec9afeb1..39db3318927 100644 --- a/src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt +++ b/src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt @@ -372,8 +372,6 @@ __orca_finish_legacy_prompt_dispatch() { eval "$__orca_suffix_part" done fi - trap '__orca_osc133_preexec' DEBUG - unset __orca_in_legacy_prompt_wrapper } __orca_normalize_prompt_command() { [[ -z "${__orca_prompt_command_normalized:-}" ]] || return 0 @@ -395,7 +393,8 @@ __orca_normalize_prompt_command() { __orca_prompt_status_variable="__orca_prompt_status_$$" __orca_prompt_status_capture_command="$__orca_prompt_status_variable=\$?" __orca_prompt_status_value="\${$__orca_prompt_status_variable}" - PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch' + # Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions. + PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper' fi } __orca_prepend_prompt_command() { diff --git a/src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt b/src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt index d8ba9ce90de..7bc19fd874d 100644 --- a/src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt +++ b/src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt @@ -365,8 +365,6 @@ __orca_finish_legacy_prompt_dispatch() { eval "$__orca_suffix_part" done fi - trap '__orca_osc133_preexec' DEBUG - unset __orca_in_legacy_prompt_wrapper } __orca_normalize_prompt_command() { [[ -z "${__orca_prompt_command_normalized:-}" ]] || return 0 @@ -388,7 +386,8 @@ __orca_normalize_prompt_command() { __orca_prompt_status_variable="__orca_prompt_status_$$" __orca_prompt_status_capture_command="$__orca_prompt_status_variable=\$?" __orca_prompt_status_value="\${$__orca_prompt_status_variable}" - PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch' + # Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions. + PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper' fi } __orca_prepend_prompt_command() { diff --git a/src/main/bash-prompt-command-composition.ts b/src/main/bash-prompt-command-composition.ts index b70a74d00a6..95d05ece916 100644 --- a/src/main/bash-prompt-command-composition.ts +++ b/src/main/bash-prompt-command-composition.ts @@ -133,8 +133,6 @@ __orca_finish_legacy_prompt_dispatch() { eval "$__orca_suffix_part" done fi - trap '__orca_osc133_preexec' DEBUG - unset __orca_in_legacy_prompt_wrapper } __orca_normalize_prompt_command() { [[ -z "\${__orca_prompt_command_normalized:-}" ]] || return 0 @@ -156,7 +154,8 @@ __orca_normalize_prompt_command() { __orca_prompt_status_variable="__orca_prompt_status_$$" __orca_prompt_status_capture_command="$__orca_prompt_status_variable=\\$?" __orca_prompt_status_value="\\\${$__orca_prompt_status_variable}" - PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch' + # Why: a changed DEBUG trap misses the capture command, and bash <= 4.3 reverts traps set in functions. + PROMPT_COMMAND="$__orca_prompt_status_capture_command; __orca_in_legacy_prompt_wrapper=1; __orca_prompt_status=$__orca_prompt_status_value"'; __orca_prompt_had_functrace=""; if [[ -o functrace ]]; then __orca_prompt_had_functrace=1; set +T; fi; __orca_outer_debug_trap_spec="$(trap -p DEBUG)"; [[ -z "$__orca_prompt_had_functrace" ]] || set -T; unset __orca_prompt_had_functrace; __orca_run_prompt_command_array; __orca_finish_legacy_prompt_dispatch; trap __orca_osc133_preexec DEBUG; unset __orca_in_legacy_prompt_wrapper' fi } __orca_prepend_prompt_command() { diff --git a/src/main/daemon/bash-prompt-command-composition.test.ts b/src/main/daemon/bash-prompt-command-composition.test.ts index b404ab15ebc..1ea41a453db 100644 --- a/src/main/daemon/bash-prompt-command-composition.test.ts +++ b/src/main/daemon/bash-prompt-command-composition.test.ts @@ -13,18 +13,21 @@ const bashMajor = hasBash const bashMinor = hasBash ? Number(spawnSync('bash', ['-lc', 'printf %s "${BASH_VERSINFO[1]}"']).stdout) : 0 +const LEGACY_GATE = 'BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1)' const bashPreservesOddTerminalBackslash = bashMajor > 4 || (bashMajor === 4 && bashMinor >= 4) function runInteractiveBash( profile: string, tempHome: string, - input = 'true\nfalse\nexit 0\n' + input = 'true\nfalse\nexit 0\n', + rcfileContent = getDaemonBashShellReadyRcfileContent(), + bash = 'bash' ): string { const rcfile = join(tempHome, 'rcfile') writeFileSync(join(tempHome, '.bash_profile'), profile) - writeFileSync(rcfile, getDaemonBashShellReadyRcfileContent()) + writeFileSync(rcfile, rcfileContent) const result = spawnSync( - 'bash', + bash, ['-lc', '"$BASH" --noprofile --rcfile "$1" -i 2>&1', 'bash', rcfile], { input, @@ -396,6 +399,37 @@ PROMPT_COMMAND='printf "PROMPT_REMATCH:<%s>\\n" "\${BASH_REMATCH[1]-unset}"' expectLifecycle(output) }) + // Why: macOS /bin/bash 3.2 also reverts DEBUG traps set inside functions. + const legacyBashes = process.platform === 'darwin' ? ['bash', '/bin/bash'] : ['bash'] + itWithBash.each( + legacyBashes.flatMap((bash) => [ + ['trap - DEBUG', bash], + ["trap ':' DEBUG", bash] + ]) + )( + 'starts the next command after its prompt when `%s` runs at a legacy prompt (%s)', + (trapChange, bash) => { + // Why: force the bash <= 5.0 scalar PROMPT_COMMAND dispatch on any bash. + const rcfile = getDaemonBashShellReadyRcfileContent() + expect(rcfile).toContain(LEGACY_GATE) + const legacyRcfile = rcfile.replaceAll(LEGACY_GATE, '0') + const output = runInteractiveBash( + "PS1='ORCA_PS1> '", + tempHome, + `${trapChange}\ntrue\nexit 0\n`, + legacyRcfile, + bash + ) + const startBeforePrompt = new RegExp( + `${String.fromCharCode(27)}]133;A${String.fromCharCode(7)}(?:(?!ORCA_PS1> )[\\s\\S])*${String.fromCharCode(27)}]133;C` + ) + + expect(output).toContain('ORCA_PS1> ') + expect(output).not.toMatch(startBeforePrompt) + expectLifecycle(output, 0) + } + ) + itWithBash('does not treat user command text as a bash-preexec helper', () => { const output = runInteractiveBash('', tempHome, 'echo a:__bp_x\nfalse\nexit 0\n') diff --git a/src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts b/src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts index ab25abe0815..0a20a46c868 100644 --- a/src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts +++ b/src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts @@ -398,6 +398,27 @@ describePosix('local PTY shell-ready launch config', () => { expectBashOsc133Lifecycle(output) }) + itWithBash( + 'starts the next command after its prompt when the DEBUG trap is removed at a legacy prompt', + () => { + // Why: force the bash <= 5.0 scalar PROMPT_COMMAND dispatch on any bash. + const gate = 'BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 1)' + const rcfile = getBashShellReadyRcfileContent() + expect(rcfile).toContain(gate) + const legacyRcfile = rcfile.replaceAll(gate, '0') + writeFileSync(join(userDataPath, '.bash_profile'), "PS1='ORCA_PS1> '\n") + const input = 'trap - DEBUG\nfalse\nexit 0\n' + const output = runInteractiveBashRcfile(legacyRcfile, userDataPath, input) + const startBeforePrompt = new RegExp( + `${String.fromCharCode(27)}]133;A${String.fromCharCode(7)}(?:(?!ORCA_PS1> )[\\s\\S])*${String.fromCharCode(27)}]133;C` + ) + + expect(output).toContain('ORCA_PS1> ') + expect(output).not.toMatch(startBeforePrompt) + expectBashOsc133Lifecycle(output) + } + ) + itWithBash('composes an inherited PROMPT_COMMAND ending in separators', () => { writeFileSync( join(userDataPath, '.bash_profile'),