daneharrigan.com

Tab completion with CDPATH in ksh

I used bash and zsh for most of my Linux/BSD life. I switched to ksh when I started using OpenBSD as my daily driver since that’s the default. CDPATH is one of my favorite features of these shells. It allows me to navigate between projects quickly and from anywhere on my file system. For example, if I had three projects, foo, bar, and baz:

$ ls ~/Projects/src/**/*
/home/dane/Projects/src/github.com/dane:
foo bar baz

By exporting my CDPATH to CDPATH=$CDPATH:~/Projects/src, I can navigate between them:

$ pwd
/home/dane
$ cd github.com/dane/foo
$ pwd
/home/dane/Projects/src/github.com/dane/foo

bash and zsh have cd tab complete paths and subpaths declared in CDPATH, but ksh does not. I wanted to have a similar experience with ksh. There is a built-in means of creating tab completions in ksh for any command, but you must declare all possible values that can be completed. This is done with:

$ set -A complete_{COMMAND}_{ARG_COUNT} -- {VALUES}

For example, if I wanted to use tab completion to build the path github.com/dane/foo, I would run the following:

$ export CDPATH=$CDPATH:~/Projects/src
$ set -A complete_cd_1 -- github.com/dane/foo
$ cd git<TAB>

I wanted cd to complete all of my project paths so I needed to create a command that would list all possible paths:

$ find ~/Projects/src -type d -maxdepth 3 
/home/dane/Projects/src
/home/dane/Projects/src/github.com
/home/dane/Projects/src/github.com/dane
/home/dane/Projects/src/github.com/dane/foo
/home/dane/Projects/src/github.com/dane/bar
/home/dane/Projects/src/github.com/dane/baz

Next, I needed to remove the /home/dane/Projects/src/ prefix:

find ~/Projects/src -type d -maxdepth 3 | sed "s#^$HOME/Projects/src/##g"
/home/dane/Projects/src
github.com
github.com/dane
github.com/dane/foo
github.com/dane/bar
github.com/dane/baz

find has an -exec flag, I know, but I couldn’t get it to work so I piped the output to sed instead. Let me know if you can get -exec working!

With the directory list generated it just needed to be passed to the set -A:

set -A complete_cd_1 -- `
  find ~/Projects/src -type d -maxdepth 3 | \
    sed "s#^$HOME/Projects/src/##g"
`

My solution isn’t perfect. It only targets one path where CDPATH may contain many. I can’t cd into a new project path until the complete_cd_1 values are regenerated. /home/dane/Projects/src is unnecessarily passed to complete_cd_1. Knowing this, I feel like my approach struck a balance between addressing my need while remaining simple.