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

working on the ‘blog’ project

I usually keep nvim running on the left “h” pane, version control and tests on the left “k” and everything else below it in the “j” pane.

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!
  • 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!

background party!

With a shared history, I don’t have to remember in which pane I opened wireshark last time, any will do

 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

btop: a fancy version of top

Moving around#

using the defaults:

  • CTRL+S 09 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 the HISTORY 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 '%%'"
warning: I (and tmux by defaults) use 0-indexed windows, you can use the 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
You can spend hours ricing your statusline, the only actually useful things I need there are

  • 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!