简体   繁体   中英

ZSH prompt substitution issues

I've searched through several answers here and through Google, but I'm still not sure what's going wrong with my prompt.

According to the documentation I've read, this should work

setopt prompt_subst
autoload -U colors && colors
PROMPT="%{[00m[38;5;245m%}test %D%{[00m%}"

My prompt is the following, however:

[00m[38;5;245mtest 15-07-01[00m

Note that the date expansion actually worked, so prompt substitution is working. The ZSH man pages for prompt expansion states that %{...%} should be treated as a raw escape code, but that doesn't seem to be happening. Passing that string to print -P also results in the output above. I've found example prompts on the Internet for ZSH that also seem to indicate that the above syntax should work. See this for one example - the $FG and $FX arrays are populated with escape codes and are defined here . I've tried this example directly by merging both the files above, adding setopt prompt_subst to the beginning just to make sure it's set, then sourcing it and the prompt is a mess of escape codes.

The following works

setopt prompt_subst
autoload -U colors && colors
PROMPT=$'%{\e[00m\e[38;5;245m%}test %D%{\e[00m%}'

I get the expected result of test 15-07-01 in the proper color.

I've tested this on ZSH 5.0.5 in OSX Yosimite, 5.0.7 from MacPorts, and 4.3.17 on Debian, with the same results. I know I have provided a valid solution to my own problem here with the working example, but I'm wondering why the first syntax isn't working as it seems it should.

I think this all has to do with the timeless and perennial problem of escaping. It's worth reminding ourselves what escaping means, briefly: an escape character is an indicator to the computer that what follows should not be output literally.

So there are 2 escaping issues with: PROMPT="%{[00m[38;5;245m%}test %D%{[00m%}"

  1. Firstly, the colour escape sequences (eg; [00m ) should all start with the control character like so \\e[00m . You may have also seen it written as ^[00m and \\003[00m . What I suspect has happened is one of the variations has suffered the common fate of being inadvertently escaped by either the copy/paste of the author or the website's framework stack, whether that be somewhere in a database, HTTP rendering or JS parsing. The control character (ie, ^ , \\e or \\003 ), as you probably know, does not have a literal representation, say if you press it on the keyboard. That's why a web stack might decide to not display anything if it sees it in a string. So let's correct that now:

    PROMPT="%{\\e[00m\\e[38;5;245m%}test %D%{\\e[00m%}"

  2. This actually nicely segues into the next escaping issue. Somewhat comically \\e[ is actually a representation of ESC , it is therefore in itself an escape sequence marker that, yes, is in turn escaped by \\ . It's a riff on the old \\\\\\\\\\\\\\\\\\\\ sort of joke. Now, significantly, we must be clear on the difference between the escape expressions for the terminal and the string substitutions of the prompt, in pseudo code:

    PROMPT="%{terminal colour stuff%}test %D%{terminal colour stuff%}"

    Now what I suspect is happening, though I can't find any documentation to prove it, is that once ZSH has done its substitutions, or indeed during the substitution process, all literal characters, regardless of escape significations, are promoted to real characters¹. To yet further the farce, this promotion is likely done by escaping all the escape characters. For example if you actually want to print '\\e' on the command line, you have to do echo "\\\\\\e" . So to overcome this issue, we just need to make sure the 'terminal colour stuff' escape sequences get evaluated before being assigned to PROMPT and that can be done simply with the $'' pattern, like so:

    PROMPT=$'%{\\e[00m\\e[38;5;245m%}test %D%{\\e[00m%}'

    Note that $'' is of the same ilk as $() and ${} , except that its only function is to interpret escape sequences.

[1] My suspicion for this is based on the fact that you can actually do something like the following:

PROMPT='$(date)'

where $(date) serves the same purpose as %D , by printing a live version of the date for every new prompt output to the screen. What this specific examples serves to demonstrate is that the PROMPT variable should really be thought of as storage for a mini script, not a string (though admittedly there is overlap between the 2 concepts and thus stems confusion). Therefore, as a script, the string is first evaluated and then printed. I haven't looked at ZSH's prompt rendering code, but I assume such evaluation would benefit from native use of escape sequences. For example what if you wanted to pass an escape sequence as an argument to a command (a command that gets run for every prompt render) in the prompt? For example the following is functionally identical to the prompt discussed above:

PROMPT='%{$(print "\\e[00m\\e[38;5;245m")%}test $(date)%{$(print "\\e[00m")%}'

The escape sequences are stored literally and only interpreted at the moment of each prompt rendering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM