I gave an introduction into the new promptsys themes in the last few days. The themes are part of grml's current zsh setup and cures one of the major issues I had with the setup. So that's great.

Recently however, fellow grml developer Evgeni Golov had a problem with the way in which Virtualenv extends existing shell prompts to reflect the effects of the package. What they did is essentially this:

PS1="foobar $PS1"

Which doesn't work with the way zsh's prompt themes work. As I've explained before, the prompt themes work as code that is registered to certain hooks, that takes control of the prompt variables. In our case, that's PS1 and RSP1. If you change PS1 in someplace, the theme code will just replace that value, with what the theme intents the prompt to look like. In any case, your change is gone.

Now, we could certainly add a special case for handling Virtualenv, but then the next day, the Ruby crowd will have something else that will require a special case; then any the the gazillion of linux distributions will have a special case... and that goes on and on. So instead, let's add something that enables the user to add items, he/she can then use in the items style for configuring the grml prompt theme. That would be a general solution, that people can use to extend the prompt's information gathering with at will. We like general solutions, so that's what we went with.

First, let's take a look at how to add new stuff: That's where the new `grml_theme_add_token()' function comes into play. If called without arguments, it will produce a short help text:

Usage: grml_theme_add_token <name> [-f|-i] <token/function> [<pre> <post>]

<name> is the name for the newly added token. If the `-f' or `-i' options
are used, <token/function> is the name of the function (see below for
details). Otherwise it is the literal token string to be used. <pre> and
<post> are optional.

Options:

 -f <function>   Use a function named `<function>' each time the token
                 is to be expanded.

 -i <function>   Use a function named `<function>' to initialise the
                 value of the token _once_ at runtime.

The functions are called with one argument: the token's new name. The
return value is expected in the $REPLY parameter. The use of these
options is mutually exclusive.

So it is as easy as this to add an item called `day', that is printed in green foreground colour:

grml_theme_add_token day '%D{%A}' '%F{green}' '%f'

Let's take a look at the Virtualenv case: The actual code does this:

PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1")

We are using zsh, so we'll replace the basename with an expansion modifier. But other than that, the following could be a first version:

grml_theme_add_token virtual-env ${VIRTUAL_ENV+(${VIRTUAL_ENV:t}) }

Looks weird? It isn't. The parameter expansion form ${parameter+value} expands to value if $parameter is non-empty. So, if $VIRTUAL_ENV is non-empty, the value of the newly created `*virtual-env*' token is a string that is enclosed by parentheses and a trailing space. The string itself is ${VIRTUAL_ENV:t}. The ":t" has the effect of `basename': If you the value of the variable in question is "/some/where/on/disk", the ":t" modifier will cause the expansion to result in "disk".

This is what we wanted, so: YAY!

But wait a minute. What if the value of $VIRTUAL_ENV could potentially change during the lifetime of a shell session? This is only a static solution that wouldn't pick up on changes like that. Now, we could change the value a little bit and use zsh's "PROMPT_SUBST" option. That would work, but I don't like that option, because it makes the prompt somewhat fragile, depending on what strings you put in there. Luckily, our helper function provides its "-f" option. With that, instead of a static value, you may supply the name of a function, that produces the right expansion each time the prompt theme code runs. Let's introduce a function, called `virtual_env_prompt':

function virtual_env_prompt () {
    REPLY=${VIRTUAL_ENV+(${VIRTUAL_ENV:t}) }
}

That's exactly the same expansion as before but now it is done dynamically in a function, that is called at the right times. The system expects the function to return the value it produces in the $REPLY parameter. Which is exactly what the code does.

Now we just need to add a new token, that calls the function. If we'd like the new token to appear in magenta foreground colour whent it's expanded, as well, the definition call has to look like this:

grml_theme_add_token virtual-env -f virtual_env_prompt '%F{magenta}' '%f'

Now we got a new item, that we can just include in the `*items*' style, that configures the prompt (again, the default items list is available in the output of "prompt -h grml"). One appropriate customisation may look like this:

zstyle ':prompt:grml:left:setup' items rc virtual-env change-root \
                                       user at host path vcs percent

You can apply this sort of extension to pretty much any problem you encounter. This should also be the final requirement for our new prompt themes. If you really want to do something that goes beyond this, write your own prompt theme. ;)

Posted Thu 21 Mar 2013 13:18:05 CET Tags: