Tuesday, May 17

cheap shared persistent directory history in zsh

These could probably go in your .zshrc.

First, a function:

# record directory history
function chpwd {
  echo $(pwd) >> ~/.directory_history

The function gets fired every time you change directories, apparently by virtue of being called chpwd. It looks like zsh has a whole set of special functions which fire under various circumstances. It’s weird that these are just kind of out there in the namespace, but ok, they seem useful.

All it does is append the output of pwd, which some googling suggests stands for either “print working directory” or “present working directory” (this one says “print” for sure, but I’m not sure how that maps to $PWD as the environment variable for the working directory, or chpwd, which seems more likely to stand for “change present working directory” than “change print working directory”), to a .directory_history flatfile.

Second, an alias:

alias h='cd $(tail -50 ~/.directory_history | tac | dmenu_unique)'

This alias cds to the result of a subshell which grabs the last 50 directories off of the end of the list file, reverses them, and feeds them to dmenu_unique, which is this script:

#!/usr/bin/env sh
awk '!x[$0]++' < /dev/stdin | dmenu -l 15 -fn '-xos4-terminus-medium-r-*-*-32*'

Unsurprisingly, I found the awk portion of this on Stack Overflow. It’s more or less just a version of a thing I’ve done in Perl or PHP a hundred times, where you loop through input, incrementing a counter in a hash for every unique line / string. I think it passes the line through if… If, uh, it’s not already set in the hash when it hits? Yeah, that’s got to pretty much be it. I sometimes regret not learning awk sooner in life.

(Side note: I am pretty sure that running /bin/sh by way of env here is fundamentally pointless for several reasons. Correspondence to the effect that I am or am not an idiot re: this point is welcome.)

dmenu is a minimalist type-to-complete menu program for X with a couple of options, which I know from its use as part of a standard xmonad configuration. Basically, it lets you pick from a from a list of things. In this case, it looks something like:

an animation of the h alias in action

Normally, dmenu’s list display is oriented horizontally. That’s overridden by -l 15, which shows 15 lines of input vertically. The -fn ... bit tells it to use a larger font than usual. (Specifying fonts is one of those domains where X rapidly becomes horrible and inscrutable.)

It seems like this pretty much has to be in an alias, because you can’t really change the parent shell’s directory from within a child process.

Third, a rationale:

My mind is of course slowly disintegrating and I have too many terminal windows open at any one time. I lose track of things easily in the seedy, rusty-razor-edged morass of my desktop environment. Often I find myself closing a terminal in some deeply-nested or esoterically named directory, and then immediately needing to open it again. In other cases, I open a new terminal and need to quickly center it on files I already have open.

I can definitely imagine more elegant solutions to this problem, but in practice, indexing into a shared log of the places all terminals visit catches a lot of the use cases.

I spend so much of my time trying to kill accidental statefulness that I sometimes forget how useful stashing a little state can be.

This isn’t a very coherent writeup, but it’s late and I’ve had some wine.