xdir.py

I use the command line to do a lot of work instead of using GUI tools, because I find typing faster than clicking, and because I can extend my command line with simple scripts more easily than I can create new GUI tools. But I like to type as little as possible, both for speed and to reduce the strain on my poor hands and wrists.

Working with large projects from the command line means changing directories a lot. I like tab-completion, because it saves me typing, but there’s a problem with it: it’s stupid. It will only consider file names right in front of its face.

Let’s say I have this directory structure:

dev\
  com\
    sarai\
      stats\
    stellated\
      app\
      util\
      xml\

To get from the top to the util directory using cd, I would type

cd d<tab>\c<tab>\s<tab><tab>\u<tab>

Why do I have to keep track of all the intermediate levels? With a tree like this (and real ones have more noise between where you are and where you want to go), I don’t think in terms of each level in the tree, I think “I want to go to the util directory”.

xdir is a replacement for cd that works like tab completion, but with more intelligence. In our example above, I could have typed

x d\c\s\u

To xdir, all directory names are prefixes of directory names, as if tab completion were being applied, but without having to type the tab. Also notice I didn’t have to worry about the two directories starting with ‘s’. In the tab completion example, I had to type two tabs to get it to choose ‘stellated’ rather than ‘sarai’. In the xdir example, it knew I meant ‘stellated’ because there is no directory starting with ‘u’ under the ‘sarai’ directory. The entire string ‘d\c\s\u’ is unambiguous.

Even better, I could have typed

x .ut

This means “change to a subdirectory named ut* anywhere below me”. In this case, there is only one, so xdir knows what to do, and takes me there. This exactly matches my mental model of the tree, and keeps me from having to worry about intermediate levels at all.

Download: xdir.py

Download: xdir.cmd

xdir performs a number of functions relating to moving among directories. The first argument to xdir is the operation to perform:

When specifying a new directory (for ‘cd’ or ‘push’), the arguments work as follows:

In the argument list, spaces and slashes are equivalent. These both do the same thing:

x d c .ut
x d\c\.ut

If the arguments are ambiguous (because they specify more than one destination directory), the list of possible destinations are printed and the directory is not changed. You can add a last argument of .3 (for example) to choose the third choice in the list, or .-1 (for example) to choose the last choice in the list.

Unfortunately, a Python script (or any executed program) cannot change the directory in the command window. So xdir.py actually writes DOS commands to stdout, and a separate helper, xdir.cmd, collects those commands and executes them as a shell script.

To reduce typing, the script can be supported by shell aliases (in Windows, doskey macros). I use these:

doskey u=xdir.cmd cd .. $*
doskey uu=xdir.cmd cd .. .. $*
doskey uuu=xdir.cmd cd .. .. .. $*

doskey x=xdir.cmd cd $*
doskey ux=xdir.cmd cd .. $*
doskey uux=xdir.cmd cd .. .. $*
doskey uuux=xdir.cmd cd .. .. .. $*

doskey xb=xdir.cmd back $*
doskey xp=xdir.cmd push $*
doskey xq=xdir.cmd pop $*
doskey xl=xdir.cmd roll $*
doskey xs=xdir.cmd showstack $*

Comments

[gravatar]
show me the which dos command are show the visited web history
[gravatar]
Any chance of porting this to python 2.6 +
?

I played around with it a little and was not able to get it to run properly

:(
[gravatar]
For what it's worth, I've written a similar program called cpd; the main difference is that it knows about "project directories" and can find paths amongst those. (E.g., `cpd gi cp /b` on my system will change to `~/co/github.com/dot-home/cpd/bin/`, though there's a bit more to it than that.)

It's currently missing the ability to skip through an arbitrary number of subdirectories in the subdir path, but losing `**` is a bug that crept in during the switch from Python 3.6 to Python 2.7 (so it runs on more old systems) and something I may get around to fixing one day.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.