TMUX workflow
In this post I’ll only talk about how I use tmux. Hopefully you’ll find something to assimilate in you own workflow!
This is not a guide or a tutorial on using tmux.
Read the wiki’s Getting-Starderd page for a detailed explanation on how
tmux works
Check out my .tmux.init
for the actual configuration, and
tmux_init.sh for the session-init script
let’s get started!
Overview#
A printscreen is worth almost 1000 words: this is my typical setup, tmux running a single session ‘main’,
ten windows, the current one split in three panes.
working on the ‘blog’ project
Here Hugo’s dev server is runnging in “j”… its proper place would be a pane in the “0 bg” window, with the other daemons running in foreground, more on that later
Prefix#
I use CTRL+S instead of CTRL+B, is just easier to type
# FILE: .tmux.conf
set -g prefix C-s
warning: using default terminal settings (eg when you are not using tmux),
this sequence will be interpreted as stop: stty -a | grep stop
: start = ^Q; stop = ^S;...
.
Should you send the stop signal by mistake, use CTRL+Q to resume you session
Workflows#
Enough chit-chat, let’s see some workflows
Working on an existing project#
This one is easy, just switch to its window if already open, or to an empty window if not. Example:
CTRL+S 3 for window number 3
wait, an empty window? Yes, when tmux starts I have it open ten windows, more on that later
Opening a project in an empty window#
Ever used vim’s session file? if not, vim -S
opens the editor, and sources Session.vim
to
resume the session saved with the :mksession!
vim command. Pretty handy.
I am doing something similar with tmux, by (manually) sourcing .tmux.sh
on an empty window.
Each project’s root folder contains such a file, which git promptly ignored thank to a global
.gitignore
file (alongside Session.vim
, Shada.vim
, etc)
.tmux.sh
define the “layout” of the project window, here are a couple of examples
basic layout - 3 panes#
This is by far the layout I use the most
0# FILE: .tmux.sh
1s3
2tmux rename-window "#I blog"
- line 1:
s3
, see below - line 2: rename this window
4 blog
#I
gets replaced by the current window number
To split a window in three, I use the s3
function defined in my .bashrc
.
The function also takes care of setting a persistent bash history file for each pane: That’s right,
each pane has its own commands history!
0# FILE: .bashrc
1HIST_FOLDER="$HOME/.var/bash_history"
2function s3 () {
3 which tmux >/dev/null || return
4 n_panes=$(tmux list-panes | wc -l)
5 if [ "$n_panes" -eq 1 ]; then
6 mkdir -p "$HIST_FOLDER"
7 p="$(pwd | sed 's%/%_%g; s/_//1')"
8 histfile=${HIST_FOLDER}/${p}
9 export HISTFILE=${histfile}_h
10 history -c
11 history -r
12 tmux split-window -t .0 -h -e HISTFILE=${histfile}_k
13 tmux split-window -t .1 -v -e HISTFILE=${histfile}_j
14 fi
15 tmux resize-pane -t .1 -x 78
16 tmux resize-pane -t .2 -y 10
17 tmux select-pane -t .0
18}
- lines 1, 7 and 8: set up history file for each pane
- for the “h” pane in the printscreen above:
~/.var/bash_history/home_fbtd_repos_fbtd_blog_h
- problem: if you name a folder
.../user_repos/
and another.../user/repos/
they ’ll ends up sharing the same history files - solution: don’t do that!
- for the “h” pane in the printscreen above:
- lines 15 and 16: set the panes sizes
shared history layout#
Sometimes I want all panes in a “project” to share a common command history, for example in the 0-th window “bg” I put
- daemons running in foreground, tmux takes care keeping them alive when i close the terminal
- fire-and-forget commands that clog the terminal (emphasis on the forget)
- GUI applications that might print something useful to stdout
background party!
0# FILE: ~/bg/.tmux.sh
1histfile=$(pwd)/.bash_history_local
2
3export HISTFILE=${histfile}
4history -c
5history -r
6tmux split-window -e HISTFILE=${histfile}
7tmux split-window -e HISTFILE=${histfile}
8tmux split-window -e HISTFILE=${histfile}
9tmux select-layout even-vertical
10
11tmux rename-window "#I bg"
- line 1: this time the history file is kept locally
- line 9: vertical split allows to read the stdout without too many line-wraps
single application layout#
the .tmux.sh
file is just a shell script, you can use it to start applications as well:
0# FILE: ~/system-system/.tmux.sh
1tmux rename-window "#I SYSTEM-SYSTEM"
2btop

btop: a fancy version of top
Moving around#
using the defaults:
- CTRL+S 0…9 jump to other windows
- CTRL+S A: next pane
and adding a couple of keybinidings:
- CTRL+S CTRL+S : switch between this and last window
- CTRL+S H: go to “h” pane (pane 0)
- CTRL+S K: go to “k” pane (pane 1)
- CTRL+S J: go to “j” pane (pane 2)
- CTRL+S ’: go to pane 3
# FILE: .tmux.conf
bind-key C-s last-window
bind h select-pane -t:.0 \; select-pane -e
bind k select-pane -t:.1 \; select-pane -e
bind j select-pane -t:.2 \; select-pane -e
bind "'" select-pane -t:.3 \; select-pane -e
Running commands on pane “k”#
Switching pane, executing a command (almost always the last command) and going back to the original
pane is something I used to do all the time.
With a couple of mapping I avoid 90% of pane switches
- CTRL+S T: rerun the last command on pane “k”
- CTRL+S C: send the CTRL+C sequence to pane “k”
- CTRL+S page-up: scroll up on pane “k”
- CTRL+S page-down: scroll down on pane “k”
0# FILE: .tmux.conf
1bind t send-keys -t .1 '!!' C-m
2bind e send-keys -t .1 C-c
3bind PageUp copy-mode -t 1 -eu
4bind PageDown send-keys -X -t 1 page-down
- line 1:
!!
is a bash event designator that refers to the last command
check out theHISTORY EXPANSION
section of bash’s manpage for more details
Windows names#
newly created windows (CTRL+S C) gets their window number as name
I use CTRL+S . to rename a window, the
prompt is populated with the current window number
0# FILE: .tmux.conf
1set-window-option -g automatic-rename off
2bind c new-window 'bash -i -c "tmux display -p \"#I\" | xargs tmux rename-window; $SHELL"'
3bind . command-prompt -I "#I " "rename-window '%%'"
base-index 1
option to start at one, as the 0 key is all the way on the right of the keyboard
Statusline#
Something like:
main:4.0 /dev/pts/14 ... [0 bg] [1 tmp] [2 dots] [3 scripts] {4 blog} ...
0# FILE: .tmux.conf
1set -g status-justify centre
2
3set -g window-status-format "[#W]"
4set -g window-status-current-format "{#W}"
5
6set -g status-left "#S:#I.#P #{pane_tty}"
7set -g status-right ""
8set -g status-left-length 30
9set -g status-right-length 0
- line 6,
#P
: the current pane - line 6,
#{pane_tty}
: the tty in used in the current shell
Initialization#
setting up the “main” session is done with a bash script:
0# FILE: tmux.init.sh
1#!/usr/bin/env bash
2
3DEFAULT_WINDOW=1
4SESSION_NAME="main"
5CONFIG_FOLDER="$HOME/.config/tmux_init"
6DELAY=0.2
7
8say() {
9 printf 'tmux_init: %s\n' "$1"
10}
11
12err() {
13 say "$1" >&2
14 exit 1
15}
16
17generate_command () {
18 config_path="$CONFIG_FOLDER/$1"
19 [[ ! -h $config_path ]] && echo -n "bash -i" && return 0
20 source_cmd=""
21 if [[ -f $config_path/.tmux.sh ]]; then
22 source_cmd="&& . ./.tmux.sh"
23 fi
24 cd_cmd="cd "$(readlink $config_path)
25 echo -n "bash -i -c \"$cd_cmd $source_cmd && exec bash\""
26}
27
28which tmux >/dev/null || err "tmux not found"
29[ -v TMUX ] && err "do not nest tmux sessions!"
30
31tmux list-sessions || tmux start-server
32tmux list-sessions | grep "^$SESSION_NAME" && err "session already running"
33
34tmux new-session -x $COLUMNS -y $LINES -d -n 1 -s $SESSION_NAME "$(generate_command 0)"
35for i in $(seq 1 9); do
36 sleep $DELAY
37 tmux new-window -n $i -t $SESSION_NAME "$(generate_command $i)"
38done
39
40sleep $DELAY
41tmux attach -t $SESSION_NAME:$DEFAULT_WINDOW
- line 6: the configuration folder
- lines 34 and 37: open a new windows in a detached session
- line 41: attach to the newly created session
Startup windows#
The script always creates ten windows, you can set up default projects for each one by creating
symbolic links in the $CONFIG_FOLDER
defined above.
This works exactly like sites-available
and sites-enabled
for nginx, with symbolic links pointing to
project folders.
Make sure to have a .tmux.sh
in each folder: it will be automatically sourced by tmux_init.sh
Adding new projects is as easy as adding a new link:
ln -s /home/fbtd/repos/fbtd_blog ~/.config/tmux_init/4
0$ ls -l ~/.config/tmux_init/
1# lrwxrwxrwx - fbtd 8 Jul 17:30 0 -> /home/fbtd/bg
2# lrwxrwxrwx - fbtd 8 Jul 17:30 1 -> /home/fbtd/tmp
3# lrwxrwxrwx - fbtd 8 Jul 17:30 2 -> /home/fbtd/dotfiles
4# lrwxrwxrwx - fbtd 9 Jul 00:20 3 -> /home/fbtd/scripts
5# lrwxrwxrwx - fbtd 20 Jul 15:00 4 -> /home/fbtd/repos/fbtd_blog
6# lrwxrwxrwx - fbtd 8 Jul 22:23 6 -> /home/fbtd/man
7# lrwxrwxrwx - fbtd 20 Jul 20:46 9 -> /home/fbtd/system_system
Copy paste#
Check out this
article from the official wiki to learn how to interact with your system clipboard
Tmux’s copy mode, accessed using CTRL+S [ is incrediby useful
for scrolling, copying and pasting.
Mouse selection#
Sometimes I want to select a couple lines from my terminal, paste in another application, go back to the terminal to grab some other lines etc
It is quite cumbersome to enter copy mode, copy one or two lines, then re-enter copy mode for more
This is one of the few usecases where the mouse is your friend:
- CTRL+S space: toggle “full screen” for selected panel
mapping:bind Space resize-pane -Z
this hides other panels and allows me to select mutiple lines without ending up in another pane - if you are copying from a text editor, deactivate line numbers
- do all of your copy-pasting using the mouse
- toggle back “full screen” and line numbers
nvim and line numbers#
off topic: here is how i deal with line numbers in nvim
- leader+shift+tab: deactivate line numbers
- leader+tab: reactivate line numbers
- shift+tab: toggle between line numbers and relative line numbers
-- FILE: init.lua
vim.keymap.set('n', '<leader><S-tab>', ':set nonu nornu signcolumn=no<cr>')
vim.keymap.set('n', '<leader><tab>', ':set nu nornu signcolumn=yes<cr>')
vim.keymap.set('n', '<S-tab>', ':set rnu! nu<cr>')
Stuff I don’t use#
let’s end with some features of tmux I haven’t found a good use yet:
No sessions#
ok… One session, but only one.
The extra layer provided by having more than one session opened at the same always felt
like overkill.
No plugins#
there are some great plugins, but many of them provide:
- system notifications and stats that I don’t think belong in the terminal
- functionality that can be achieved with a couple shell scripts
- bloat
So for the time being, I am sticking to built-in functionalities
No mouse support#
you can use mouse events as key in tmux! check out the MOUSE SUPPORT
section of the man page
if interested
Copy-pasting does not count - thats a terminal emulator’s feature
***
Comments, suggestions or ideas? Let me know here!