up
: jump multiple directory levels quickly
Introduction
Recently in my day to day, I have been cd
ing around deeply nested directories, and I have been finding myself often typing cd ..
repeatedly. I often find myself wishing I can just quickly jump up a few directory levels from the current working directory in a quick and natural way. I was also curious to learn about how tab autocomplete is implemented in the shell, so I was inspired to make a tool to enable this.
Here is a short demo of up
in action:
You can type up d
, where d
is either a directory above you, or a prefix of a directory above you, and you will be cd
'd up to that directory.
Implementing autocomplete for bash
Bash provides a mechanism for user-created autocompletion with the complete
command. You pass it the name of the command you want to support autocomplete for, and then the name of a function that dynamically generates the set of possible autocompletes, based on the current user input:
up_completion() {
completions=$($BINARY_PATH --complete $COMP_CWORD $COMP_LINE)
COMPREPLY=($completions)
}
complete -F up_completion up
Here, we get the user input from $COMP_CWORD
(an index into a space-separated list of input words) and $COMP_LINE
(the entire user input line) and pass them to a Rust binary that computes the possible autocompletes. Setting $COMPREPLY
supplies the autocomplete options to the user.
In the Rust binary, we get the current working directory, determine each directory name, and then return a list of directory names that contain the input as a prefix:
fn generate_completion_options(working_dir: &str, comp_line: &str, comp_cword: usize) -> Vec<String> {
// If there is already a whole argument, don't suggest more autocompletes.
if comp_cword > 1 {
return vec![];
}
let directories = working_dir
.split('/')
.map(|s| String::from(s))
.collect::<Vec<String>>();
let user_input = match comp_line.split(" ").nth(1) {
Some(arg) => arg,
// If there is no input yet, list all directories.
None => return directories,
};
let mut result = Vec::new();
for (_, directory) in directories.iter().enumerate() {
if directory.starts_with(user_input) {
result.push(directory.to_string());
}
}
if result.is_empty() {
directories
} else {
result
}
}
Other similar things
There are lots of other useful and creative tools for navigating directories quickly:
In the Android repository, there is an environment setup script that sets up an alias, croot
, that cd
's you to the root of the project.
Try it out
Try it out by installing it with Homebrew:
brew tap tedbauer/homebrew-up
brew install tedbauer/homebrew-up/up
Alternatively, you can install it manually. You can clone the repo:
git clone https://github.com/tedbauer/homebrew-up
The installation comprises of two main things:
up-path-gen
: a Rust binary that computes the paths.up.sh
: contains a functionup
, which is what the user ends up invoking. It invokesup-path-gen
.
You can build the binary, and install it somewhere like /usr/lib
, and similarly install up.sh
as well:
cd Formula/up-path-gen && cargo build --release
cp target/release/up-path-gen /usr/lib
cd ..
cp up.sh /usr/lib
Then, source up.sh
in your shell configuration:
export BINARY_PATH="/usr/lib/up-path-gen"
source /usr/lib/up.sh