Adjusting tmux.conf options for different versions

There are a couple of common techniques described on this stack overflow post for switching up tmux.conf options based on the version. Most of them either involve loading different files for different tmux versions or using if-shell with some ugly syntax and shell arithmetic in tmux.conf. These will work, but may not be the “cleanest” methods, and may not handle the full gamut of tmux version formats (examples include 2.6, 3.1c, next-3.3).

In devising my own solution, I decided I wanted tmux.conf to look as clean as reasonably possible and to keep all settings in tmux.conf, switched in using conditionals. Here’s the conditional syntax I’m using:

# settings always used
set -wg window-status-separator " "
set -wg window-status-style default

# settings for v3.3
#? check_tmux_version('>=', '3.3')
	set -sg editor nvim
	set -sg extended-keys off
#?

# settings for v3.1c
#? check_tmux_version('>=', '3.1c')
	# use window size from latest active client
	set -wg window-size latest
#?

This syntax is processed by a bash/python script (run as part of my dotfiles setup script) to generate the actual tmux.conf. The major disadvantage of this is that it requires both bash and python to be installed (both of which are nearly universal these days anyway); but it solves most of the ugliness and version comparison issues.

The script to preprocess the conditionals into tmux.conf looks like this:

#!/bin/bash

DOTFILES="$(realpath "$(dirname "$0")")"
INPUT_TMUX_CONF="$DOTFILES/tmux.conf"
OUTPUT_TMUX_CONF="$HOME/.tmux.conf"
TEMP_TMUX_CONF="$HOME/.tmux.conf-temp"
rm -f "$TEMP_TMUX_CONF"

# Find python
if command -v python3 &>/dev/null; then
	PYTHON=python3
elif command -v python &>/dev/null; then
	PYTHON=python
else
	echo 'Could not find python3'
	exit 1
fi

# Find tmux version
if ! command -v tmux &>/dev/null; then
	echo 'Could not find tmux'
	exit 1
fi
TMUX_VERSION="`tmux -V | cut -d ' ' -f 2`"
if [[ -z $TMUX_VERSION ]]; then
	echo 'Could not find tmux version'
	exit 1
fi
echo "tmux version: $TMUX_VERSION"

# Filter tmux.conf through python script to handle conditionals
"$PYTHON" - "$INPUT_TMUX_CONF" "$TMUX_VERSION" >"$TEMP_TMUX_CONF" <<'PYEOF'
import sys
import re
tmux_version = sys.argv[-1]
tmux_conf = sys.argv[-2]

def parse_tmux_vers(vers):
	m = re.search(r'([0-9]+\.)+[0-9]+[a-z]?', vers)
	if not m:
		raise Exception('Could not parse tmux version: ' + vers)
	r = []
	for c in m.group().split('.'):
		m2 = re.search(r'[a-z]', c)
		if m2:
			r.append(int(c[:m2.start()]))
			r.append(c[m2.start():])
		else:
			r.append(int(c))
	return tuple(r)

def check_tmux_version(op, vers):
	v1 = parse_tmux_vers(tmux_version)
	v2 = parse_tmux_vers(vers)
	if op == '==' or op == '=':
		return v1 == v2
	elif op == '!=':
		return not check_tmux_version('==', vers)
	elif op == '<':
		for i in range(max(len(v1), len(v2))):
			if i >= len(v1):
				return True
			elif i >= len(v2):
				return False
			elif isinstance(v1[i], str) != isinstance(v2[i], str):
				return isinstance(v1[i], str)
			elif v1[i] != v2[i]:
				return v1[i] < v2[i]
		return False
	elif op == '<=':
		return check_tmux_version('<', vers) or check_tmux_version('==', vers)
	elif op == '>=':
		return not check_tmux_version('<', vers)
	elif op == '>':
		return not check_tmux_version('<=', vers)
	else:
		raise Exception('Invalid check_tmux_version operator: ' + op)

def eval_conditional(cond):
	return eval(cond)

condstack = [ True ]
with open(tmux_conf, 'r') as f:
	for line in f:
		if line.strip().startswith('#?'):
			condarg = line[line.index('?')+1:].strip()
			if len(condarg) > 0:
				if not condstack[-1]:
					condstack.append(False)
				else:
					condstack.append(eval_conditional(condarg))
			else:
				if len(condstack) <= 1:
					raise Exception('Invalid tmux.conf conditional syntax - too many closes')
				condstack.pop()
		else:
			if condstack[-1]:
				print(line.lstrip(), end='')
	if len(condstack) != 1:
		raise Exception('Invalid tmux.conf conditional syntax - unclosed block')
PYEOF

if [ $? -ne 0 ]; then
	echo 'tmux.conf conditional parser returned nonzero status'
	exit 1
fi

rm -f "$OUTPUT_TMUX_CONF"
mv "$TEMP_TMUX_CONF" "$OUTPUT_TMUX_CONF"
exit 0

This will run through the template tmux.conf file line-by-line filtering by the conditionals. The conditionals can be any python expression, but for version checks, would typically use check_tmux_version(operation, otherversion). Standard comparison operations are supported (==, >, <, >=, <=, !=) as well as nested conditionals.

This method seems to work pretty well for version switching. In the case that either bash or python is missing from the system, it’s also easy to include a fallback and load a basic, minimal tmux.conf.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *