Reminding me what I’m doing

zsh makes it easy (there are sentences that begin like that, I promise) (and this would be simple in bash, too, but it gives me an opportunity to discuss the more confusing points of zsh) to include a little status in your prompt to call attention to certain directories.

E.g. if I’m in my ROS directory ~/stuff/ros (I have a folder under ~ called stuff. Please forgive me) I like my prompt to read:

[ROS] 17:36:33 ~ros

Where “[ROS]” reminds me, when I have too many shells open, which one is being used for ROS.

(“~ros” is zsh’s named directory feature (http://www.jpeek.com/talks/ukuug_20000720/))

The easiest way to do clever prompts in zsh is to define a function with the magic name “precmd”, which is called before every prompt redraw.

My prompt code looks like

setopt prompt_subst
PROMPT=$'%{\e[31m%}$PROMPT_SPECIAL%{\e[32m%}%*%{\e[00m%} %{\e[33m%}%~%{\e[0m%} '

So ‘precmd’ has the job of setting $PROMPT_SPECIAL.

In doing this, I discovered zsh actually has associative arrays. This lets me set up a table of directory->[NAME], like

typeset -A special_dirs
special_dirs=()

special_dirs[$HOME/stuff/ros]="ROS"
special_dirs[$HOME/stuff/vex]="VEX"
special_dirs[$HOME/src/pulsetor-software]="PT"

Things I always get wrong here:

  • Don’t put a space before “=”.
  • Apparently ~ isn’t expanded inside [].

BUT, what I really want is for the [TAG] to appear not just in the specified directory, but in all subdirectories. Luckily, zsh provides pattern matching on associative array keys.

But here’s the trick: what you’d expect, and what I tried first, doesn’t work. Try this:

typeset -A foo
foo=()

foo[a]=1
foo[ab]=2
foo[b]=3

Now use the (k) flag to [] to do a pattern search.

% print ${foo[(k)a]}
1

works as expected, but

% print ${foo[(k)a*]}

produces no matches.

Well, it turns out, that the (k) flag treats the actual keys as patterns, and the argument to [] as the text to match against. WTF?

EXCEPT, that is exactly what I want. I can phrase the elements of $special_dirs like so:

special_dirs[$HOME/stuff/ros(|/)*]="ROS"
special_dirs[$HOME/stuff/vex(|/)*]="VEX"
special_dirs[$HOME/src/pulsetor-software(|/)*]="PT"

and match pwd against them like

${special_dirs[(k)$PWD]}

(First I tried using zsh’s nice /**/* recursive glob, but apparently that doesn’t work with (k). Can anyone explain to me why?)

The whole code from my .zshrc is

setopt prompt_subst
PROMPT=$'%{\e[31m%}$PROMPT_SPECIAL%{\e[32m%}%*%{\e[00m%} %{\e[33m%}%~%{\e[0m%} '

typeset -A special_dirs
special_dirs=()

function precmd {
	PROMPT_SPECIAL=${special_dirs[(k)$PWD]}
}

function mkspecdir {
	special_dirs[$1(|/)*]="[$2] "
}

mkspecdir ~/stuff/ros ROS
mkspecdir ~/stuff/vex VEX
mkspecdir ~/src/pulsetor-software PT
Advertisements
This entry was posted in zsh. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s