VSP: My Lightning-Fast Project Switcher & Editor Launcher
How I built a custom shell function to jump between projects and open editors in seconds using fzf, zoxide, and smart directory ranking
As a developer, I'm constantly switching between different projects throughout the day. Whether I'm working on a client project, experimenting with a new side project, or contributing to open source, the constant cd
-ing and opening editors was becoming a productivity bottleneck.
That's when I built VSP (Visual Studio Project switcher) - a custom shell function that gets me from zero to coding in under 3 seconds. Here's how it works and how you can build your own.
The Problem
Before VSP, my workflow looked like this:
cd ~/Desktop/Code
ls
to see what projects I havecd project-name
code .
ornvim .
- Repeat this 10-20 times a day
This was slow, manual, and required me to remember exact project names. I wanted something that would:
- Show me projects I've been working on recently
- Let me fuzzy search through all my projects
- Preview the project structure before opening
- Let me choose which editor to open it with
The Solution: VSP Function
Here's the core vsp
function that solved all these problems:
function vsp() {
local CODE_PATH="${HOME}/Desktop/Code"
[ ! -d "$CODE_PATH" ] && echo "Directory does not exist: $CODE_PATH" && return 1
# Get recently modified directories
local recently_modified
recently_modified="$(ls -td "${CODE_PATH}"/*/ 2>/dev/null | head -5 | sed 's:/$::')"
# Get top ranked directories from zoxide
local top_ranked=""
if command -v zoxide >/dev/null 2>&1; then
top_ranked="$(
zoxide query -ls 2>/dev/null \
| awk -v codePath="$CODE_PATH" '$3 ~ "^"codePath {print $0}' \
| sort -k1,1nr \
| head -5 \
| awk '{print $3}'
)"
fi
# Get recently accessed directories from zoxide
local top_recent=""
if command -v zoxide >/dev/null 2>&1; then
top_recent="$(
zoxide query -ls 2>/dev/null \
| awk -v codePath="$CODE_PATH" '$3 ~ "^"codePath {print $0}' \
| sort -k2,2nr \
| head -5 \
| awk '{print $3}'
)"
fi
# Get all directories
local all_dirs
all_dirs="$(ls -d "${CODE_PATH}"/*/ 2>/dev/null | sed 's:/$::')"
# Combine and deduplicate
local combined
combined="$(
{
[ -n "$recently_modified" ] && echo "$recently_modified"
[ -n "$top_ranked" ] && echo "$top_ranked"
[ -n "$top_recent" ] && echo "$top_recent"
[ -n "$all_dirs" ] && echo "$all_dirs"
} | awk 'NF && !seen[$0]++'
)"
# Format for fzf
local final_list
final_list="$(
while IFS= read -r dir; do
[ -z "$dir" ] && continue
[ -d "$dir" ] || continue
local bname
bname="$(basename "$dir")"
printf "%s\t%s\n" "$bname" "$dir"
done <<< "$combined"
)"
# Filter out any artifacts
final_list="$(echo "$final_list" | grep -v '^bname=')"
[ -z "$final_list" ] && echo "No directories found." && return 0
# Use fzf to select directory with preview
local selected_line
selected_line="$(
echo "$final_list" \
| fzf \
--prompt="Select a directory: " \
--with-nth=1 \
--delimiter='\t' \
--height=80% \
--border \
--preview='
dirpath={2}
# Try exa -> tree -> ls -al
if command -v exa >/dev/null 2>&1; then
exa -T --level=1 --long --group-directories-first "$dirpath" 2>/dev/null || ls -al "$dirpath"
else
tree -L 1 "$dirpath" 2>/dev/null || ls -al "$dirpath"
fi
echo
readmefile=$(ls -1 "$dirpath"/README* 2>/dev/null | head -n1)
if [ -f "$readmefile" ]; then
echo "=== $(basename "$readmefile") ==="
if command -v bat >/dev/null 2>&1; then
bat --paging=never "$readmefile"
else
cat "$readmefile"
fi
fi
'
)"
[ -z "$selected_line" ] && return
local selected_dir
selected_dir="$(awk -F'\t' '{print $2}' <<< "$selected_line")"
[ -z "$selected_dir" ] || [ ! -d "$selected_dir" ] && echo "Invalid directory: '$selected_dir'" && return 1
cd "$selected_dir" || return
# Choose editor
local choice
choice="$(
echo -e "code\tOpen with VS Code\ncursor\tOpen with Cursor\nnvims\tOpen with Neovim Chooser\nnvim\tOpen with Neovim\nnvim-lazy\tOpen with LazyVim\nnvim-chad\tOpen with NvChad\nnvim-astro\tOpen with AstroNvim" \
| fzf --prompt="Choose an editor: " \
--with-nth=1 \
--delimiter='\t' \
--height=10 \
--border \
--preview='echo {2}'
)"
case "$choice" in
code*) code . ;;
cursor*) cursor . ;;
nvims*) nvims ;;
nvim*) nvim ;;
nvim-lazy*) nvim-lazy ;;
nvim-chad*) nvim-chad ;;
nvim-astro*) nvim-astro ;;
*) code . ;;
esac
}
How It Works
1. Smart Directory Ranking
The function combines multiple sources to intelligently rank your projects:
- Recently modified: Projects you've worked on recently
- Most accessed: Projects you visit most often (via zoxide)
- Recently accessed: Projects you've visited recently (via zoxide)
- All projects: Everything in your Code directory
2. Fuzzy Search with Preview
Using fzf
, you can:
- Type a few letters to filter projects
- See a live preview of the project structure
- View README files automatically
- Navigate with arrow keys or continue typing
3. Editor Selection
After selecting a project, you get another fzf menu to choose your editor:
- VS Code
- Cursor
- Multiple Neovim configurations
- Defaults to VS Code if you cancel
Prerequisites
To use this function, you'll need to install these tools:
Required
# fzf - Fuzzy finder
# macOS
brew install fzf
# Ubuntu/Debian
sudo apt install fzf
# Arch Linux
sudo pacman -S fzf
Highly Recommended
# zoxide - Smart directory jumping
# macOS
brew install zoxide
# Ubuntu/Debian (requires Rust/Cargo)
curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | bash
# Arch Linux
sudo pacman -S zoxide
Optional (for better previews)
# exa - Better ls alternative
# macOS
brew install exa
# Ubuntu/Debian
sudo apt install exa
# tree - Directory tree viewer
# macOS
brew install tree
# Ubuntu/Debian
sudo apt install tree
# bat - Better cat with syntax highlighting
# macOS
brew install bat
# Ubuntu/Debian
sudo apt install bat
Setup
-
Add the
vsp
function to your shell configuration file (~/.zshrc
or~/.bashrc
) -
If using zoxide, add this to your shell config:
eval "$(zoxide init zsh)" # or bash instead of zsh
-
Make sure your projects are in
~/Desktop/Code
or modify theCODE_PATH
variable -
Reload your shell:
source ~/.zshrc
Usage
Just type vsp
anywhere in your terminal, and you'll get:
- A fuzzy-searchable list of your projects
- Live preview of project structure and README
- Quick editor selection
- Instant navigation and opening
The Result
What used to take 10-15 seconds and multiple commands now takes 2-3 seconds:
- Type
vsp
- Type a few letters of your project name
- Hit Enter
- Choose your editor
- Start coding!
Neovim Switcher Bonus
I also included a Neovim switcher function that lets me choose between different Neovim configurations:
# NeoVim Switcher
alias nvim-lazy="NVIM_APPNAME=LazyVim nvim"
alias nvim-chad="NVIM_APPNAME=NvChad nvim"
alias nvim-astro="NVIM_APPNAME=AstroNvim nvim"
function nvims() {
items=("default" "LazyVim" "NvChad" "AstroNvim")
config=$(printf "%s\n" "${items[@]}" | fzf --prompt=" Neovim Config " --height=~50% --layout=reverse --border --exit-0)
if [[ -z $config ]]; then
echo "Nothing selected"
return 0
elif [[ $config == "default" ]]; then
config=""
fi
NVIM_APPNAME=$config nvim $@
}
# Bind Ctrl+A to open nvims
bindkey -s ^a "nvims\n"
Wrap Up
This simple function has saved me countless hours and made context-switching between projects almost effortless. The combination of smart ranking, fuzzy search, and editor choice makes it incredibly powerful.
Try building your own version and customize it for your workflow. The key is making it so fast and convenient that you actually use it every day!
What's your favorite productivity hack for project switching? Let me know on Twitter!
Keep Learning
Get my latest methods and insights delivered to your inbox.