15

I'd like to put my current git branch into my multi-line ZSH prompt. However, this messes up the two lines - I'd like them to line up nicely.


┌─(simont@charmander:s000)─[master *]────────────────
───(~  )─┐  
└─(127:15:44)──                       ──(Sat,May12)─┘

should be:


┌─(simont@charmander:s000)─[master *]─────────(~  )─┐  
└─(127:15:44)──                       ──(Sat,May12)─┘

The git branch is grabbed from an oh-my-zsh function, git_prompt_info(), which gives me the branch, dirty status, and a bunch of prompt-escapes to color things nicely.

How do I count the characters that will be visibly inserted into the ZSH prompt - not the prompt escape sequences?

simont
  • 68,704
  • 18
  • 117
  • 136

2 Answers2

14

Assuming that the prompt-escaped string is stored in a variable FOO, this will count only user-visible characters:

                                                                                                                                
FOO=$(git_prompt_info)                                                                                                                     
local zero='%([BSUbfksu]|([FK]|){*})'
FOOLENGTH=${#${(S%%)FOO//$~zero/}} 

This comes from this .zshrc.

This is a rough explanation of why it works, liberally quoting from man zshexpn, section PARAMETER EXPANSION. I'm not 100% sure of the details, so, if you're using this to develop your own equivalent, read the relevant man zshall sections.

Working from the line FOOLENGTH=${#${(S%%)FOO//$~zero/}}, we've got a number of bits. Going from the inside out:

  1. $~zero: The ~ ensures that zero, which we've defined as '%([BSUbfksu]|([FB]|){*})', is treated as a pattern rather than as a plain string.

  2. ${(S%%)FOO//$~zero/}: This matches ${name//pattern/repl}:

    Replace the longest possible match of pattern in the expansion of parameter name by string repl

    Note that we don't have a repl; we replace the longest possible match of pattern with nothing, thereby removing it.
    (S%%)FOO conducts an expansion on FOO with several flags set. I don't quite follow it.

  3. ${#${(S%%)FOO//$~zero/}}: ${#spec} will substitue the length in characters of the result of the substitution spec, if spec is a substitution. In our case, spec is the result of the substitution ${(S%%)FOO//$~zero/}; so this basically returns the length of characters in the result of the regular expression s/zero// on FOO, where zero is the pattern above.

Adaephon
  • 16,929
  • 1
  • 54
  • 71
simont
  • 68,704
  • 18
  • 117
  • 136
  • 5
    `(S)` makes matching non-greedy thus turning “longest possible match” into “shortest possible match” (without it `{*}` in a pattern will turn thing like `%F{blue}%M%F{yellow}%#%f` into just `%#`, while it should turn it into `%M%#`), `(%%)` performs prompt expansion on the string, following PROMPT_* options. Note: this method should be avoided if PROMPT string contains a command with some side-effects: for example, if you want to use it to count how often the prompt is shown. Most of time you won’t suffer from this. – ZyX May 12 '12 at 17:46
  • And yes, all `(*)` flags are described in PARAMETER EXPANSION section of `man zshexpn`. – ZyX May 12 '12 at 17:48
  • @ZyX Thanks. I saw the explanation for `S` in the `zshexpn` page, but wasn't sure how it fitted in. – simont May 12 '12 at 17:52
  • Does not work with date formatting, like `%D{%a %b %d, %I:%M:%S%P}` – dbkaplun Aug 30 '13 at 07:03
  • @beardtree True. But the string returned by that date format will not include prompt-specific characters (such as color codes). You can count it using `date='%D{%a %b %d, %I:%M:%S%P}', length=${#${date}}}`. – simont Aug 30 '13 at 08:30
  • @beardtree If you're trying to get your prompt to line up, [this](http://aperiodic.net/phil/prompt/) might be of interest to you - it's got a brief discussion on the way he did it. – simont Aug 30 '13 at 08:33
  • Just mentioned: your variant is not going to output width of a number of Unicode strings: Unicode contains things like fullwidth characters (they occupy two display cells), diacritics (most of time they occupy no display cells) and tab. Either three are treated by `${#}` as one character, but zsh recognizes their display width correctly. There are also characters which have ambiguous width (this may mean that their width depends on terminal settings) (ambiguous == one or two display cells). – ZyX Jun 24 '14 at 19:48
  • @ZyX Thanks for pointing that out - it's not an issue I have noticed (yet), or know how to fix - if I find some solution, I'll amend the answer. – simont Jun 25 '14 at 02:25
2

Not sure how to do this with builtin zsh commands, but color information can be stripped with sed (as documented here):

sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"

e.g.

plain_str=$(git_prompt_info | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g")

Which would strip all escape sequences from the string. The length is now simply:

echo $#plain_str
Thor
  • 45,082
  • 11
  • 119
  • 130