Wednesday, March 28, 2012

Smile! with a Zsh prompt happy/sad face

UPDATE: Now in color!
%(?,%F{green}:%),%F{yellow}%? %F{red}:()%f

% RPROMPT='%(?,%F{green}:%),%F{yellow}%? %F{red}:()%f'
% /bin/true                                                                     :)
% /bin/false                                                                    :)
%                                                                             1 :(
Wouldn't you like a more descriptive shell prompt, that would show you the return value of the last command in a visually intuitive way? Sure, you could use %? to get the return int, zero for success and non-zero for failure. But that's just going to make your prompt more bizarre to your (pair-programming) partner.

Add this expansion sequence %(?.:%).:() to your PROMPT or RPOMPT to add a smiley/frowny face to your zsh prompt based on the return status of the previous command.

Let's see it in action! "Before" vs "After" of putting this in my RPROMPT to set the right prompt, to replace the raw %? return value I had previously.

#BEFORE
% RPROMPT='%? %~'                                                0 ~
% /bin/true                                                      0 ~
% /bin/false                                                     0 ~
%                                                                1 ~

#AFTER
% RPROMPT='%(?,:%),:() %~'                                       1 ~
% /bin/true                                                     :) ~
% /bin/false                                                    :) ~
%                                                               :( ~
Right, you're wondering how you'll live not knowing the actual failure int return code, right? Well, you don't have to give that up. Let's modify the false-text to prepend the failure int %? to the sad face:
#Add this to your PROMPT or RPROMPT: %(?,:%),%? :()

[vm53@vm53]% RPROMPT='%(?,:%),%? :() %~'                                   :) ~
[vm53@vm53]% /bin/true                                                     :) ~
[vm53@vm53]% /bin/false                                                    :) ~
[vm53@vm53]%                                                             1 :( ~

How does this work? We use the built-in conditional form, %(x.true-text.false-text), as documented in the zsh man pages under "CONDITIONAL SUBSTRINGS IN PROMPTS". Closing parenthesis must be encoded to appear in the true-text or false-text. ? checks against the return code of the previous command. Our true text makes a happy face :) and our false-text makes a sad face :(. Our updated sad face prepends the return value%? :(. I chose to prepend as the is the first item in my right prompt, this makes the smiley/frowny faces align.

Inspired by Selena Deckelmann's Use This Interview. Thanks Clark for showing me the post and then putting up the challenge to do it in zsh (and do it more cleanly).

CONDITIONAL SUBSTRINGS IN PROMPTS:

%(x.true-text.false-text)
              Specifies a ternary expression.  The character following  the  x
              is  arbitrary;  the  same character is used to separate the text
              for the `true' result from that for the  `false'  result.   This
              separator  may  not appear in the true-text, except as part of a
              %-escape sequence.  A `)' may appear in the false-text as  `%)'.
              true-text  and  false-text  may  both contain arbitrarily-nested
              escape sequences, including further ternary expressions.

              The left parenthesis may be preceded or followed by  a  positive
              integer  n,  which defaults to zero.  A negative integer will be
              multiplied by -1.  The test character x may be any of  the  fol‐
              lowing:

              !      True if the shell is running with privileges.
              #      True if the effective uid of the current process is n.
              ?      True if the exit status of the last command was n.
              _      True if at least n shell constructs were started.
              C
              /      True if the current absolute path has at least n elements
                     relative to the root directory, hence / is counted  as  0
                     elements.
              c
              .
              ~      True if the current path, with prefix replacement, has at
                     least n elements relative to the root directory, hence  /
                     is counted as 0 elements.
              D      True if the month is equal to n (January = 0).
              d      True if the day of the month is equal to n.
              g      True if the effective gid of the current process is n.
              j      True if the number of jobs is at least n.
              L      True if the SHLVL parameter is at least n.
              l      True  if  at least n characters have already been printed
                     on the current line.
              S      True if the SECONDS parameter is at least n.
              T      True if the time in hours is equal to n.
              t      True if the time in minutes is equal to n.
              v      True if the array psvar has at least n elements.
              V      True  if  element  n  of  the  array  psvar  is  set  and
                     non-empty.
              w      True if the day of the week is equal to n (Sunday = 0).