feat(prompt): ${var@P} renders through the PS1 renderer; prompt preview uses it - #159
Merged
Merged
Conversation
…ew uses it
$ V='100%done'; PS1='[${V}] '
prompt: [100%done]
prompt preview: [100/home/me/projone] # %d read as an escape
Section 1 of issue #134: `prompt preview` drew prompts that never appear.
It rendered a PS1 theme with `print -rP "${PS1//%/%%}"`. print -P is
zsh's, and like zsh's it substitutes a variable first and then reads
the value for `%` escapes. A PS1 does not; hellish follows bash's order
there. So a segment holding `100%done`, `50%off` or `\w` previewed as
something else. The doubling also hid the bilingual PS1's own `%~` from
the preview.
print -P is right to be zsh's. What was missing is a way to render a
PS1 exactly as the prompt does, and bash has one: ${var@P}. hellish
parsed it and returned the value unchanged (a documented v1 scope-out).
prompt_expand_p (prompt_expand.c) implements it as the PS1 rule itself:
- zsh_to_ps1 in the mode the dialect bit selects, then ps1_render,
the same pair prompt_normal uses for PS1;
- under `set -o zsh`, where PS1 is PROMPT, a value's `%` is an escape
again, as in the live prompt;
- ps1_render, not ps1_animated: an @p from a hook or inside a PS1
must not rebuild the live prompt's animation cells;
- \[ \] become nothing outside a line editor, as in bash;
HELLISH_DBG_PROMPT_MARKS keeps them, the window print -P already
gives the width tests.
prompt_preview draws a PS1 theme with "${PS1@P}". PROMPT themes stay on
print -rP, which is the live PROMPT's renderer.
Tests:
- tests/scripts/61_prompt_expand_P.sh, graded against bash --posix,
covers:
- the escapes both shells have, and \[ \];
- \nnn and \D{};
- $VAR, ${...} and $((...)), with values holding \w, %d and $HOME
left as they are;
- unset and empty values, positionals, a function's arguments;
- quoting and assignment contexts.
develop prints the values raw; this branch matches bash.
- tests/prompt_expand_p_test.py, at a real prompt, in-process and
forked readers, checks that:
- the live PS1 and ${PS1@P} are the same bytes, and stay so after
the variable changes;
- the same holds under set -o zsh, where the value IS read again;
- `prompt preview` prints the live prompt for a PS1 theme.
develop fails 8 of its 14 checks. Its preview line is the issue's
bug verbatim: [100/tmp/...one.
- tests/prompt_themes_test.py renders PS1 themes the way the preview
now does. All checks pass.
Refs #134
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAmeHfJNm7XjYbMNrQqvkG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Section 1 of #134:
prompt previewshowed prompts that never appear.The preview rendered a PS1 theme with
print -rP "${PS1//%/%%}".print -Pis zsh's, so it substitutes a variable first and then reads the value for%escapes. A PS1 does not do that, because hellish follows bash's order there. A segment holding100%done,50%offor\wtherefore previewed as something else. Doubling every%also hid the bilingual PS1's own%~from the preview.print -Pis right to behave like zsh. What was missing is a way to render a PS1 exactly as the prompt does. bash has one,${var@P}, but hellish parsed it and returned the value unchanged (a documented v1 scope-out).Change.
prompt_expand_p(new file,src/infrastructure/prompt_expand.c) implements@Pwith the PS1 rule itself:zsh_to_ps1in the mode the dialect bit selects, thenps1_render, the same pairprompt_normaluses for PS1;set -o zsh, where PS1 is PROMPT, a%in a value is an escape again, as in the live prompt;ps1_render, notps1_animated, so an@Pfrom a hook or inside a PS1 does not rebuild the live prompt's animation cells;\[ \]render as nothing outside a line editor, as in bash.HELLISH_DBG_PROMPT_MARKSkeeps them, the same windowprint -Palready gives the width tests.prompt previewnow draws a PS1 theme with"${PS1@P}". PROMPT themes stay onprint -rP, which is the live PROMPT's renderer.Refs #134
How I verified it
tests/scripts/61_prompt_expand_P.shis graded againstbash --posix5.3.9. It covers:\[ \];\nnnand\D{};$VAR,${…}and$((…)), with values holding\w,%dand$HOMEleft as they are;develop prints the values raw. This branch matches bash.
tests/prompt_expand_p_test.pyruns at a real prompt with both the in-process and the forked readers. It checks that:${PS1@P}are the same bytes, and stay the same after the variable changes;set -o zsh, where the value is read again;prompt previewprints the live prompt for a PS1 theme.develop fails 8 of its 14 checks. Its preview line is the issue's bug verbatim:
[100/tmp/…one.tests/prompt_themes_test.pyrenders PS1 themes the way the preview now does. Every check passes.tests/tester: 5363/5363;tests/run_scripts.shagainstbash --posix: 126/126;verify_alloc.sh: identical output on both heaps;alloc_stress.sh: all clean;tests/pty_suite.sh: 112 ok, 6 skipped, 4 failed. None of the four is this change:prompt_compat_matrix,prompt_drift_matrixandprompt_jobs_badgeexpect the non-root%/$prompt and get#, because the container runs as root. They fail the same way on develop, and CI runs them as a normal user.hxp_framework_testhit the 420 s per-file limit on this 4-core container. Run alone, it passes every check in 426 s, and develop's binary takes 440 s on the same machine. CI's runners finish it inside the limit (the pty job is green on feat(env): $EPOCHREALTIME and $SRANDOM #157).norminetteis OK on every touched file.wiki/interactive.mddocuments${PS1@P}.Notes / trade-offs
@Pon a whole list,"${@@P}"or"${a[@]@P}", is still abad substitution. The same is true of every transform (@Q,@U, …): hellish applies the operators to one value, never element by element. That gap is older than this PR and not part of Prompt renderer, extglob and shopt: 8 findings from building a 30-theme config on 2.10.2 #134, so it is left for its own change.${a[1]@P}works.set -o zshit is rescanned, because that is what the live zsh prompt does.🤖 Generated with Claude Code
https://claude.ai/code/session_01RAmeHfJNm7XjYbMNrQqvkG
Generated by Claude Code