An Over-Engineered Workflow To Organizing Projects With tmuxp And Shell Autocompletes
Organizing projects is something that affects every developer. Some people work on a few projects, some many, and everyone has their own way of storing all those 1’s and 0’s . Recently I had a discussion with some friends about it and it was so interesting to hear how they worked that I thought it might be fun to share my work flow. This is by no means the “best” way, it’s more of a conversation starter. Feel free to steal the ideas and code and implement them on your own.
Part 1: Folder Layout
~/Work
├── organization1
│ ├── project1
│ │ ├── code
│ │ └── extra
│ └── project2
│ └── code
├── organization2
│ └── project3
│ ├── code
│ └── extra
└── mine
├── project4
│ └── code
└── project5
└── code
└── extra
Figure 1: Sample Folder Layout
Nothing fancy but everything going forward builds on this structure:
- Level 1 (
~/Work
): Everything is in here. - Level 2 (Organizations): Organizations are companies or groups that I am doing
work for. There is also a folder called
mine
where code for my personal projects is stored. - Level 3 (Project): This folder contains the project, usually named after the
github repo. (E.g
thornycrackers/docker-neovim
would bedocker-neovim
). - Level 4 (Files):
code
is where the actual code of the project lives. I usually have a folder calledextra
where I store files related to the project.
I find this structure simple and robust enough that everything has a place. The
first function I’ll share is for cloning repositories, since now I store them in
the code
folder I want it autocreated when I clone a repository:
gfcc () {
# This function assumes urls of one of the following formats. All others
# will not work:
#
# git@github.com:user/repo.git
# https://github.com/user/repo
PROTOCOL=$(echo "$1" | cut -c1-3)
if [[ "$PROTOCOL" == 'git' ]]; then
REPO=$(echo "$1" | cut -d'/' -f2 | cut -d'.' -f1)
elif [[ "$PROTOCOL" == 'htt' ]]; then
REPO=$(echo "$1" | cut -d'/' -f5 )
fi
git clone "$1" "$REPO"/code
}
Figure 2: Git clone function
After you add the function to your ~/.bashrc
, ~/.zshrc
or wherever you store
functions you can use the function as such:
$ gfcc https://github.com/thornycrackers/docker-neovim
$ gfcc git@github.com:thornycrackers/docker-neovim.git
Figure 3: Clone examples
Part 2: tmux Templating and Usage
I use tmuxp templating but there are many alternatives like tmuxomatic and tmuxinator. Choose your poison. My templates are stored in my tmux github repo with my project template stored here. Here is a current version of the template:
session_name: "${PROJECT}"
start_directory: "${DIR}"
windows:
- window_name: code
layout: even-vertical
focus: true
panes:
- shell_command:
- nvim
focus: true
- window_name: docker
layout: even-vertical
panes:
- pane
- pane
- pane
Figure 4: tmuxp template
Which corresponds to a setup like:
Window 1 Window 2
111111111 222222222
111111111 222222222
111111111 222222222
111111111 333333333
111111111 333333333
111111111 333333333
111111111 444444444
111111111 444444444
111111111 444444444
Figure 5: tmux Window Setup
A sample command to load up a project looks like:
PROJECT="docker-neovim" DIR="/home/thorny/Work/mine/docker-neovim" tmuxp load ~/.tmux/templates/template.yaml
Figure 6: tmuxp command example
This is obviously too long to type out each time we want to boot up a project so we will add another function to help automate this since we have a folder structure that is predictable:
tlo() { # tmux templating
if [[ -z "$1" ]]; then
return
fi
# Search in all folders under 'Work'
local PROJS=($(find "$HOME"/Work/* -mindepth 1 -maxdepth 1 -type d))
local PROJ_NAME=''
local PROJ_DIR=''
# Find if we have a match
for dir in "${PROJS[@]}"; do
if [[ $(basename "$dir") == "$1" ]]; then
PROJ_NAME=$(basename "$dir")
PROJ_DIR="$dir/code"
fi
done
if [[ -z "$PROJ_NAME" ]]; then
echo 'Project not found'
return
fi
cd "$PROJ_DIR"
PROJECT="$PROJ_NAME" DIR="$PROJ_DIR" tmuxp load ~/.tmux/templates/template.yaml
cd -
}
Figure 6: tmux template bash command
The tlo
command will stop if:
- No argument is provided
- A project cannot be found
Referring to Figure 1 we can now execute a tmux sesion for a project with tlo projec1
or tlo project4
. We don’t have to worry which organization the code
is under we just need to know the project name. If there are dupicated names
across organizations I will just namespace the folders organization_proj_name
.
Refering to Figure 5, I usually don’t like to have multiple panes open at one
time and I used window 2 as a buffer of extra panes. I used tmux-drawer to
quickly create a small window at the bottom of the screen and the following tmux
command to switch that pane with other panes in window 2.
bind C-e command-prompt -p destination 'swap-pane -s 1.2 -t 2.%1'
Figure 7: tmux pane swap command
So I can hit ^B^M
and type 3<CR>
which will swap the bottom pane for the 3rd
pane on the second window.
Part 3: Autocompletions for Shell commands
This part is extra but when I type tlo <tab>
I would like to have the projects
suggest as autocompletion. I’ll give examples of how to do auto complete in bash
as well as zsh.
Part 3.1: ZSH
ZSH will look for custom autocompletes by looking through the $fpath
variable
and looking for a file based on the command we are using (E.g. tlo
autocompletes should be stored on the $fpath
in a file called _tlo
). A
minimal working example for ~/.zshrc
would look like the following:
tlo (){
...
}
# Custom autocompletes
fpath=($HOME/autocompletes $fpath)
# Initialize the autocompletion
autoload -Uz compinit && compinit -i
Figure 8: zshrc example
Now we will create our custom autocomplete at ~/autocompletes/_tlo
with the
following content.
#compdef tlo
local -a options
options=$(find "$HOME"/Work/* -mindepth 1 -maxdepth 1 -type d | xargs -n1 basename)
_alternative "args:projects:($options)"
Figure 9: tlo zsh autocomplete file
After reloading your zsh shell you should now be able to autocomplete project
names for the tlo
command.
Part 3.2: Bash
The quickest way to create your own bash autocompletions is to just put them in
your ~/.bashrc
file or wherever you store your tlo
function.
tlo() {
...
}
_tlo()
{
local cur
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$(find "$HOME"/Work/* -mindepth 1 -maxdepth 1 -type d | xargs -n1 basename)
COMPREPLY=($(compgen -W "${opts}" $cur))
}
complete -F _tlo tlo
Figure 10: tlo bash autocompletion
Final Thoughts
The main “issues” that I’ve tried to solve within my own workflow with this setup are:
- Launch a project to work on with a templated tmux session. Because everything is repeatable it’s quick to get up and running after a system restart.
- Work on multiple projects simultaneously with easy switching. This is great for anyone who’s constantly juggling projects.
- Project related files are not cluttering up home or downloads folder.
- Autocompletes in case you are having troubles remembering a projects name.
Hopefully this post sparked some ideas to make your dev work flow easier.