Who is this for?
- You work in the terminal a lot
- You have to jump between projects
- You think easy context switching might make you curse less
- (Bonus) you use terminal-based editors
Lately I’ve found myself with an D-tier superpower - near-painless workspace switching.
Normalizing your workflow
I regularly work with a dozen different projects - none of which have consistent command usage. For example, to run the unit test suite for any given project:
# These are all equivalent, depending on the project
pytest tests
poetry run pytest tests
docker-compose run whatever-project pytest tests
docker-compose run whatever-project python manage.py test
docker-compose -f ./compose/docker-compose-test.yml run whatever-project pytest tests
...
This is cognitive overhead - and usually involves a few screwups each time I context switch. I wanted a setup where I could simply type test
in each project and not have to think about it.
Enter desk
desk is a lightweight workspace manager - essentially just a wrapper for shell scripts that define each workspace.
I maintain a deskfile
(really just a .sh
file) for each project I work with regularly. By default, deskfiles live at ~/.desk/desks/
- a great folder to keep under version control.
So what goes into a desk workspace? Whatever fits your workflow. For me, the vast majority falls under:
- run - to run the project
- lint - for any linting tools
- test - for the test runner
- build - to build the project and/or container
- format - for code formatting
- interact - to enter a project’s docker container
- typecheck - to run static type analysis
These are simply aliases in shell files; you can do whatever makes your workflow easier - as sparse or comprehensive as you want. Here’s how a couple of mine look.
A project based on Python Poetry
#!/bin/bash
#
# Description: video editing library
#
basedir=~/Workspaces/matt/ergot
cd $basedir || exit
# code formatting
alias format="poetry run black ."
# run unit tests
alias test="poetry run pytest tests/"
# build the project
alias build="poetry build"
# static type checking
alias typecheck="poetry run mypy ."
# run flake8
alias flake8="poetry run flake8 --max-complexity 10 ."
# run pylint
alias pylint="poetry run pylint tests/ ergot/"
# run linting
alias lint="flake8 && pylint"
# run bandit security check
alias bandit="poetry run bandit -r ergot/"
# run code quality
alias quality="test; format; typecheck; bandit; lint"
# run the ergot command line
alias run="poetry run python main.py"
A project with the development flow built in docker compose
#!/bin/bash
#
# Description: scheduled and queue driven jobs
#
basedir=~/Workspaces/matt/JobSlob
export COMPOSEPREFIX="docker-compose -f ${basedir}/docker-compose.yaml"
export PYTHONPATH=$basedir
cd $basedir || exit
# close running instances
alias down="$(echo "${COMPOSEPREFIX}") down --remove-orphans"
# code formatting
alias format="$(echo "${COMPOSEPREFIX}") run jobslob formatting"
# run unit tests
alias test="$(echo "${COMPOSEPREFIX}") run jobslob test"
# build the project
alias build="$(echo "${COMPOSEPREFIX}") build"
# static type checking
alias typecheck="$(echo "${COMPOSEPREFIX}") run jobslob typecheck"
# run linting
alias lint="$(echo "${COMPOSEPREFIX}") run jobslob lint"
# run code quality
alias quality="test; format; typecheck; lint"
# run bash inside the app container
alias interact="$(echo "${COMPOSEPREFIX}") run --entrypoint bash jobslob"
I’d welcome a better solution than my weird echo "${COMPOSEPREFIX}"
nonsense. Email me, shell experts.
Obviously some projects will need more or fewer aliases, but these workspaces are much more uniform than before. It doesn’t matter which workspace I’m in - test
runs the test suite. Whatever you want.
If you need to know what’s available, desk will give you a nice rundown of commands.
Persisting and switching between your workspaces
Desk is great for rapidly switching between terminal workspaces, but we can still do better. This is where terminal multiplexers come in.
To borrow from tmux’s wiki (which says this far better than I could), terminal multiplexers:
[let] you switch easily between several programs in one terminal, detach them (they keep running in the background) and reattach them to a different terminal.
With tmux, we can easily keep many workspaces running simultaneously and rapidly switch between them.
I’ll focus on integrating desk with tmux, but I’m sure the same thing can be accomplished with other multiplexers like screen.
New to tmux?
If you need a quickstart, tmux has a very solid getting started guide and there are countless tutorials out there that I can’t beat.
For our purposes, the following should be enough.
- tmux uses
ctrl+b
as its prefix hotkey by default. After enteringctrl+b
, you can either type a hotkey sequence or use:
and enter a command. - to detach from a running tmux session:
ctrl+b, d
- to list and switch between running sessions:
ctrl+b, s
- (Optional) to create a new named session from within tmux:
ctrl+b, :new -s new-session-name
Integrating with desk
The real magic happens when you add the following to your .bashrc
/.zshrc
/.whatevershellrc
file:
tsession=$([[ -n "${TMUX+set}" ]] && tmux display-message -p "#S")
# strip suffixes so that you can make
# multiple instances of the same desk
# by using deskname-1, deskname-2, etc
desk=$(echo "$tsession" | sed -E "s/-[0-9]+$//g")
if [ "x$tsession" != "x" ] && [ "$tsession" != "$WITHIN_DESK" ]
then
export WITHIN_DESK="$tsession"
desk . "$desk"
fi;
This snippet sets up your shell to detect whether you’re running in a tmux session. If you are, the shell will attempt to activate a desk with the same name as the session.
At this stage, if you have a desk called whatevs
, you can run tmux new-session -s whatevs
to create a new tmux session that automatically activates your whatevs
desk. If it’s already running, you could use tmux attach -t whatevs
to attach to it.
But again, we can make it easier.
t () {
tmux attach -t $1 || tmux new-session -s $1
}
Adding this to your .bashrc
/.zshrc
/.whatevershellrc
file, you can just run t whatevs
and not worry about its state. tmux will drop you into your workspace.
Take advantage of tmux session switching
This is how you switch between open workspaces quickly. By default, tmux will give you a list of sessions when you enter ctrl+b, s
. You can navigate the list with your arrow keys or use the numbers displayed alongside each session.
It’s so easy to jump between projects - and even more useful if you rely on terminal-based editors. I’ve never had such a fluid workflow.
Deleted scenes
Alternatives
direnv
If desk doesn’t suite your purposes (to be fair, it hasn’t had much development lately), I’ve found that direnv can meet many of the same needs.
For me, the advantage goes to desk for the following reasons:
- desk stores workspaces in a single folder that’s trivial to put in version control
- direnv would require a completely different method to integrate with tmux in a similar way
tmuxinator
tmuxinator seems like it could be quite useful for workspace management and customization, but I’ve never personally used it.
Desired future improvements
- Cleanly integrate pass or some other credential manager with this setup
- Get rid of the
not found
warning starting tmux with a name that has no matching desk