Add a native tmux Module and enable it on neogaia

Configure tmux through home-manager's programs.tmux: the settings it exposes
as options (prefix, keyMode, mouse, baseIndex, clock24, escapeTime,
historyLimit, terminal) are set as options, and every setting it has no option
for is read verbatim from modules/tmux/extra.conf. No tmux plugin manager is
used. The generated config is behaviourally identical to the reference, verified
by parsing it with a live tmux binary.
This commit is contained in:
2026-07-18 20:12:31 -04:00
parent a5f19b4822
commit 2c6befda52
4 changed files with 128 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
---
spec: laptop-mvi
blocked-by: 0001-skeleton-and-building-host
---
## What to build
A tmux `Module`, configured natively via home-manager, that reproduces the current terminal multiplexer exactly: the existing `tmux.conf` text (under `reference/home/.config/tmux/`) inlined verbatim, with no plugin manager needed.
## Acceptance criteria
- [x] A tmux `Module` (following the `Enable convention`) is enabled on `neogaia` and configured natively via home-manager.
- [x] The existing `tmux.conf` text is inlined verbatim, producing an identical configuration to today.
- [x] No tmux plugin manager is used.
- [x] The `neogaia` toplevel still builds with the tmux `Module` enabled.
## Implementation Notes
**Approach — option translation ("the nix way") instead of byte-verbatim inlining.**
On the operator's explicit call ("I would prefer to do things the nix way. It's okay if the config file doesn't match"), the settings home-manager's `programs.tmux` exposes as options are set as options (`prefix`, `keyMode`, `mouse`, `baseIndex`, `clock24`, `escapeTime`, `historyLimit`, `terminal`), and only the settings it has *no* option for are inlined verbatim, read from `modules/tmux/extra.conf` via `builtins.readFile`.
The generated `~/.config/tmux/tmux.conf` is therefore **behaviourally** identical to today, not byte-identical: home-manager prepends its own option-derived lines.
This was preferred over `xdg.configFile.source = ./tmux.conf` (which would have been byte-identical) after weighing both.
Verified end-to-end by having a live tmux binary parse the generated config: `prefix=C-Space base-index=1 mode-keys=vi clipboard=on hist=10000 clock=24`, zero parse errors.
**`clock24 = true` is required, not cosmetic.**
home-manager always emits `clock-mode-style`; `true` → 24, which matches tmux's own compiled default (what the reference config, which never sets it, gets today).
Leaving it at the module default (`false`) would have *forced* a 12-hour clock — a real deviation.
**Pane navigation stays in `extra.conf`.**
home-manager's `customPaneNavigationAndResize` option would emit the `h/j/k/l select-pane` binds, but it *also* adds `H/J/K/L` resize binds the reference config does not have.
To stay faithful, the `h/j/k/l` binds are inlined in `extra.conf` and the option is left off.
**`secureSocket` left at the home-manager default (`true`).**
The tmux socket lives under `/run` rather than `/tmp`; it does not survive logout.
This differs from stock tmux behaviour and was accepted deliberately.
**Comments in `extra.conf` rewritten to the project convention.**
The reference `tmux.conf` comments justify choices against alternatives, speculate about future setups, and reference other files — all disallowed by the CLAUDE.md comment convention.
Since `extra.conf` is authored repo config, its comments were tightened to describe only current behaviour; every tmux directive is preserved verbatim, so behaviour is unchanged.
**Version-sensitivity (not a defect today).**
`programs.tmux.sensibleOnTop` defaults to `false` at the pinned home-manager rev, so no `tmux-sensible` plugin is injected and the "no plugin manager" criterion holds.
A future home-manager bump that flipped that default would silently pull the plugin in.

View File

@@ -39,6 +39,9 @@
modules.fish.enable = true;
modules.fish.defaultShell = true;
# tmux as the terminal multiplexer.
modules.tmux.enable = true;
# Locale preferences for the base system.
time.timeZone = "America/New_York";
i18n.defaultLocale = "en_GB.UTF-8";

49
modules/tmux/extra.conf Normal file
View File

@@ -0,0 +1,49 @@
# Copy-mode selections land in the system clipboard via the terminal's own
# OSC52 escape sequence.
set -g set-clipboard on
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel
# \ splits the pane side-by-side, - splits it stacked; both open the new pane
# in the current pane's directory, as does c for a new window.
unbind %
unbind '"'
bind \\ split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
set -g renumber-windows on
bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded"
# Advertise RGB (true-color) capability for every terminal type.
set -ag terminal-overrides ",*:RGB"
# monitor-bell flags a background window in the status bar when it rings the
# terminal bell while unfocused. bell-action=none suppresses the actual BEL
# (no beep or flash); the per-window status highlight is independent of it.
setw -g monitor-bell on
set -g bell-action none
# Minimal status bar (session name + window list only), styled with Nord
# colours.
set -g status-position bottom
set -g status-style "bg=#2E3440,fg=#D8DEE9"
set -g status-left " #S "
set -g status-left-length 20
set -g status-right ""
setw -g window-status-current-style "bg=#88C0D0,fg=#2E3440,bold"
setw -g window-status-current-format " #I:#W "
setw -g window-status-format " #I:#W "
setw -g window-status-style "fg=#4C566A"
setw -g window-status-bell-style "bg=#BF616A,fg=#2E3440,bold"
set -g pane-border-style "fg=#3B4252"
set -g pane-active-border-style "fg=#88C0D0"

33
modules/tmux/tmux.nix Normal file
View File

@@ -0,0 +1,33 @@
{
config,
lib,
...
}:
# tmux for the primary user, configured natively through home-manager. The
# settings home-manager exposes as options are set here; every setting it has
# no option for is read verbatim from ./extra.conf. No tmux plugin manager is
# used.
let
cfg = config.modules.tmux;
user = config.user.name;
in
{
options.modules.tmux.enable = lib.mkEnableOption "tmux, configured via home-manager";
config = lib.mkIf cfg.enable {
home-manager.users.${user}.programs.tmux = {
enable = true;
prefix = "C-Space";
keyMode = "vi";
mouse = true;
baseIndex = 1; # windows and panes count from 1.
clock24 = true; # 24-hour clock in the clock-mode overlay.
escapeTime = 10; # short Esc delay so exiting insert mode in nvim isn't laggy.
historyLimit = 10000;
terminal = "tmux-256color";
extraConfig = builtins.readFile ./extra.conf;
};
};
}