diff --git a/.claude/tasks/0006-tmux-module.md b/.claude/tasks/0006-tmux-module.md new file mode 100644 index 0000000..48fc377 --- /dev/null +++ b/.claude/tasks/0006-tmux-module.md @@ -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. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 3e5a605..c877b48 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -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"; diff --git a/modules/tmux/extra.conf b/modules/tmux/extra.conf new file mode 100644 index 0000000..11bf371 --- /dev/null +++ b/modules/tmux/extra.conf @@ -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" diff --git a/modules/tmux/tmux.nix b/modules/tmux/tmux.nix new file mode 100644 index 0000000..4c41605 --- /dev/null +++ b/modules/tmux/tmux.nix @@ -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; + }; + }; +}