productivity, Tmux Published by Phil Balchin

Tmux is a tool that I depend on daily. It is a cornerstone of my development environment. But it was not always like that, for years I only scratched the surface in what it could do. Recently I've invested some time sharpening the touch points.

If I'm going to be critical of tmux, the out-of-the-box experience with the default configuration presents a challenge. keyboard bindings are far from intuitive, and obvious features are either hidden or non existent, and man tmux, usually a good starting point for most tools, renders a waterfall of options, commands, formats.

I like to keep a pretty simple configuration for tmux, without resorting to too many plugins and customisations. You can fin the full config on my dotfiles repo: https://github.com/phil/dotfiles/blob/master/tmux/.config/tmux/tmux.conf. Hopefully this config can provide an easier onboarding experience for anyone new to tmux.

Preamble

Firstly, On Mac OS X, I have changed my caps lock key to control via keyboard settings. this makes all tmux and vim bindings easier to hit and allows maximum reach across the keyboard with a single hand

Secondly, each project I work on is assigned a tmux session. I only have a single terminal window open that runs tmux (currently ghostty), and a system wide shortcut to switch to ghostty. This means I can near instantly get to any project codebase.

Prefix and reload

set -g prefix C-a
unbind C-b
bind C-a send-prefix # Passthrough Crtl+a

The default ctrl+b prefix key is remapped to ctrl+a. I spend a lot of time in the terminal, and a lot of time sending tmux commands.

bind r source-file $XDG_CONFIG_HOME/tmux/tmux.conf \; display "Configuration Reloaded"

As I'm constantly tinkering with this config, might as well setup a binding to reload the config.

Plugins

Plugins are handled with the excellent Tmux Plugin Manager (TPM), and we can quickly install (and update) plugins with prefix+r, prefix+i (prefix+u).

set-environment -g TMUX_PLUGIN_MANAGER_PATH "$XDG_STATE_HOME/tmux/plugins/"

Not necessary, but se the install location to prevent issues when syncing dotfile directories across machines. Note that I have XDG variables set, you might need to set relative paths instead.

set -g @plugin 'tmux-plugins/tpm'

Initialise Tmux Plugin Manager

set -g @plugin 'christoomey/vim-tmux-navigator'
set -g @vim_navigator_mapping_left "C-Left C-h"  # use C-h and C-Left
set -g @vim_navigator_mapping_right "C-Right C-l"
set -g @vim_navigator_mapping_up "C-k"
set -g @vim_navigator_mapping_down "C-j"
set -g @vim_navigator_mapping_prev ""  # removes the C-\ binding
set -g @vim_navigator_prefix_mapping_clear_screen ""

vim-tmux-navigator is one of those plugins that you quickly forget is a plugin because it works so well, it disappears into the background. Effortlessly move between vim and tmux splits using the same binding. You'll also need to install the corresponding vim plugin to use the same shortcut in vim and tmux.

set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @resurrect-strategy-nvim 'session'
set -g @resurrect-processes 'gh-dash-tui jira-dash-tui'
# usage: prefix Ctrl-s to save the session
# usage: prefix Ctrl-r to restore the session

set -g @plugin 'tmux-plugins/tmux-continuum'
# Auto save sessions every 15 minutes

tmux-resurrect and tmux-continuum work together to store your sessions and recreate them after a reboot., and can also restart programs. This forms the backbone of organising my development machine, need to reboot, not a problem, as all my projects/sessions are rebuilt exactly as I left them.

For my use cases, I also like to set @resurrect-processes to apps I run and want to automatically restart when rebuilding the sessions.

set -g @plugin 'phil/tmux-lattice'
set -g @lattice_equalise_key '='

A recent plugin of my own construction. Tmux-lattice provides a vim-like (ctrl+w, =) resize all panes to fit the space equally. Tmux provides "layouts", but these are inflexible and often I find end up messing up layout. Now, prefix+= brings order to chaotic windows.

set -g @plugin "phil/tmux-devcontainers"
# usage: prefix E to create a window and exec onto the container
# usage: prefix C-E to show the command menu

tmux-devcontainers is another home grown plugin, and provides status bar indicators for running devecontainers for the current project, as well as a menu and helpers to mange devcontainer lifecycle and creating new windows logged into the devcontainer.

Mouse support

set -g mouse on
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"

Sometimes using the mouse is just quicker, e.g., resizing split panes. As for better copy mode and clipboard integration, I have no idea if this works, it's a relic in the config.

Base setup

set -sg escape-time 0
set -g default-terminal "xterm-256color"

set -g base-index 1
setw -g pane-base-index 1

# prevent apps from renaming the window title
set -g allow-rename off

# prevent apps from renaming the pane title
set -g allow-set-title off

# Splitting Panes
bind | split-window -h
bind - split-window -v

For base setup, we set windows to be indexed from 1 (instead of 0), set colours, and set sensible bindings for splits.

Preventing apps from renaming windows and panes is a recent addition that makes it a whole easier to target specific panes that I use in a pane switcher tool. This is required by some custom bindings later in the config.

Styles

set-option -g set-titles on

set-option -g pane-active-border-style fg=white
set-option -g pane-border-style fg=#666666
set-option -g pane-border-lines double
set-option -g pane-border-indicators arrows
set-option -g pane-border-status top

set-option -g popup-border-style fg=white
set-option -g popup-border-lines double

set-option -g menu-border-style fg=white
set-option -g menu-border-lines double

# status bar
set-option -g status-left-length 30
set-option -g status-right-length 150
set-option -g status-interval 60
set-option -g status-left '#[bg=colour236]#[fg=white,bold] #S '
set-option -g status-right '#{tmux_plugins_status} #[fg=black,bold]#{devcontainers_workspace} #[fg=default,nobold]#{devcontainers_status} '

# Window Status
set-window-option -g window-status-separator ""

set-window-option -g window-status-style fg=color0,bold,bg=color2
set-window-option -g window-status-current-style fg=white,bold,bg=color0

set-window-option -g window-status-format " #I:#W#{?window_flags,#{window_flags}, } "
set-window-option -g window-status-current-format " #I:#W#{?window_flags,#{window_flags}, } "

set-window-option -g window-status-activity-style fg=color3,bg=color2
set-window-option -g window-status-bell-style fg=color1,bg=color2

set-option -g visual-activity off 

set -g display-time 5000

# Refresh Status Bar
bind R refresh-client -S

This section is mostly about tweaking the general style of tmux, setting any custom status bar items, and making the active pane easier to distinguish. I keep to a simple visual style, no fancy icons and hide anything I don't use.

Custom bindings

# Simple Session Switcher
bind C-a display-popup -E "tmux list-sessions -F '#{session_name}' | fzf --reverse | xargs tmux switch-client -t" 

Of all my custom bindings, I think this is the perfect demonstration of what makes tmux an awesome developer tool. Double tapping ctrl+a pops a fuzzy session finder. Nothing fancy, but it's fast and never needs updating. I don't manually create sessions very often, so I don't need a complicated session manager, I just need a lightning quick way to move between projects. For me, this is the definition of keeping your tools sharp.

# LazyGit
bind C-y display-popup -d "#{pane_current_path}" -w 80% -h 80% -E "lazygit"
# LazyDocker
bind C-d display-popup -d "#{pane_current_path}" -w 80% -h 80% -E "lazydocker"
# File Explorer: Ranger
bind C-f display-popup -d "#{pane_current_path}" -w 80% -h 80% -E "ranger"

bind T display-popup -d "#{pane_current_path}" -w 50% -h 50% -E "flint --create"

Next up we have a suite of popup windows to provide additional utilities based on the current path of the window. All pretty self explanatory, stage commits, inspect containers, rename files … etc. Flint is task list that uses Obsidian as the datastore, this binding makes it easy to add tasks from any session.

# Goto Panes
bind § run "goto-tmux-pane home:dashboard:claude"
bind C-t run "goto-tmux-pane home:dashboard:tasks"
bind C-g run "goto-tmux-pane home:dashboard:github"
bind C-j run "goto-tmux-pane home:dashboard:jira"
bind C-c run "goto-tmux-pane home:dashboard:claude-dashboard"

bind BSpace run "goto-tmux-pane --previous"

In addition to a quick session switcher, I also have bindings to quickly switch to specific panes (this relies on preventing the application from renaming the pane or window). A new feature of my tmux setup is an interactive dashboard. Using tmux as the framework, and custom CLI TUIs to display relevant information. Using these bindings, I can quickly switch to panes in the Dashboard, action something, then switch back to where I was. I think everyone wants a personalised dashboard, and I think most developers have tried to build their own, I know I have. Where I've always gone wrong was using the wrong tools and getting stuck in the small details, HTML+JS for example, needs a place to be hosted, then you secrets management, and then not everything you want to include is accessible online. Now I have a collection agent coded TUIs written in Go, all running inside tmux panes. Tmux handles the small details, and the TUIs display the important information, and allow for simple interaction. For me, I have a TUI that displays all open PRs for my team, so I can quickly jump on review and keep track of progress. As I'm always returning the dashboard, a quick glance, and up to date.

goto-tmux-pane can be found https://github.com/phil/dotfiles/blob/master/scripts/bin/goto-tmux-pane.

# Load Plugins as the last thing
run "$XDG_STATE_HOME/tmux/plugins/tpm/tpm"

Last but not least, load the plugins.

Summary

Tmux is a fantastic tool, and a tool that I have been learning for many years, and I'm not afraid to say I've still only touched on what can be achieved. As with a lot of tools, it takes time to get really good with it.

I think what my experience show though, is that the default settings are far from easy to pickup and often require updating, splits are an obvious example, window numbing is another. Tmux also lacks decent session management, but all these things can be configured and solved.

I think for anyone starting or new to tmux, expect a very steep learning curve, take it slowly, commit key binding to muscle memory. My configuration might not be best for you, but hopefully it provides a good place to start and learn, and maybe give you some ideas for how you can customise tmux to fit into your development evironment.

GitHub: phil/dotfiles

https://github.com/phil/dotfiles

Stars: 3 Forks: 0

About the Author

Phil Balchin is a full-time software developer at Zendesk, previously at Heroku/Salesforce, and Kyan, as well as a part-time photographer living in Guildford, Surrey, UK.

facebook.com/phil.balchin | instagram.com/maniacalrobot | last.fm/users/maniacalrobot | picfair.com/maniacalrobot | maniacalrobot.tumblr.com | twitter.com/maniacalrobot