{ config, lib, pkgs, ... }: with lib; let cfg = config.programs.tmux; pluginName = p: if types.package.check p then p.pname else p.plugin.pname; pluginModule = types.submodule { options = { plugin = mkOption { type = types.package; description = "Path of the configuration file to include."; }; extraConfig = mkOption { type = types.lines; description = "Additional configuration for the associated plugin."; default = ""; }; }; }; defaultKeyMode = "emacs"; defaultResize = 5; defaultShortcut = "b"; defaultTerminal = "screen"; defaultShell = null; boolToStr = value: if value then "on" else "off"; tmuxConf = '' ${optionalString cfg.sensibleOnTop '' # ============================================= # # Start with defaults from the Sensible plugin # # --------------------------------------------- # run-shell ${pkgs.tmuxPlugins.sensible.rtp} # ============================================= # ''} set -g default-terminal "${cfg.terminal}" set -g base-index ${toString cfg.baseIndex} setw -g pane-base-index ${toString cfg.baseIndex} ${optionalString (cfg.shell != null) '' # We need to set default-shell before calling new-session set -g default-shell "${cfg.shell}" ''} ${optionalString cfg.newSession "new-session"} ${optionalString cfg.reverseSplit '' bind -N "Split the pane into two, left and right" v split-window -h bind -N "Split the pane into two, top and bottom" s split-window -v ''} set -g status-keys ${cfg.keyMode} set -g mode-keys ${cfg.keyMode} ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) '' bind -N "Select pane to the left of the active pane" h select-pane -L bind -N "Select pane below the active pane" j select-pane -D bind -N "Select pane above the active pane" k select-pane -U bind -N "Select pane to the right of the active pane" l select-pane -R bind -r -N "Resize the pane left by ${toString cfg.resizeAmount}" \ H resize-pane -L ${toString cfg.resizeAmount} bind -r -N "Resize the pane down by ${toString cfg.resizeAmount}" \ J resize-pane -D ${toString cfg.resizeAmount} bind -r -N "Resize the pane up by ${toString cfg.resizeAmount}" \ K resize-pane -U ${toString cfg.resizeAmount} bind -r -N "Resize the pane right by ${toString cfg.resizeAmount}" \ L resize-pane -R ${toString cfg.resizeAmount} ''} ${if cfg.prefix != null then '' # rebind main key: ${cfg.prefix} unbind C-${defaultShortcut} set -g prefix ${cfg.prefix} bind -N "Send the prefix key through to the application" \ ${cfg.prefix} send-prefix '' else optionalString (cfg.shortcut != defaultShortcut) '' # rebind main key: C-${cfg.shortcut} unbind C-${defaultShortcut} set -g prefix C-${cfg.shortcut} bind -N "Send the prefix key through to the application" \ ${cfg.shortcut} send-prefix bind C-${cfg.shortcut} last-window ''} ${optionalString cfg.disableConfirmationPrompt '' bind-key -N "Kill the current window" & kill-window bind-key -N "Kill the current pane" x kill-pane ''} set -g mouse ${boolToStr cfg.mouse} set -g focus-events ${boolToStr cfg.focusEvents} setw -g aggressive-resize ${boolToStr cfg.aggressiveResize} setw -g clock-mode-style ${if cfg.clock24 then "24" else "12"} set -s escape-time ${toString cfg.escapeTime} set -g history-limit ${toString cfg.historyLimit} ''; configPlugins = { assertions = [ (let hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p)); badPlugins = filter hasBadPluginName cfg.plugins; in { assertion = badPlugins == [ ]; message = ''Invalid tmux plugin (not prefixed with "tmuxplugins"): '' + concatMapStringsSep ", " pluginName badPlugins; }) ]; xdg.configFile."tmux/tmux.conf".text = '' # ============================================= # # Load plugins with Home Manager # # --------------------------------------------- # ${(concatMapStringsSep "\n\n" (p: '' # ${pluginName p} # --------------------- ${p.extraConfig or ""} run-shell ${if types.package.check p then p.rtp else p.plugin.rtp} '') cfg.plugins)} # ============================================= # ''; }; in { options = { programs.tmux = { aggressiveResize = mkOption { default = false; type = types.bool; description = '' Resize the window to the size of the smallest session for which it is the current window. ''; }; baseIndex = mkOption { default = 0; example = 1; type = types.ints.unsigned; description = "Base index for windows and panes."; }; clock24 = mkOption { default = false; type = types.bool; description = "Use 24 hour clock."; }; customPaneNavigationAndResize = mkOption { default = false; type = types.bool; description = '' Override the hjkl and HJKL bindings for pane navigation and resizing in VI mode. ''; }; disableConfirmationPrompt = mkOption { default = false; type = types.bool; description = '' Disable confirmation prompt before killing a pane or window ''; }; enable = mkEnableOption "tmux"; escapeTime = mkOption { default = 500; example = 0; type = types.ints.unsigned; description = '' Time in milliseconds for which tmux waits after an escape is input. ''; }; extraConfig = mkOption { type = types.lines; default = ""; description = '' Additional configuration to add to {file}`tmux.conf`. ''; }; focusEvents = mkOption { default = false; type = types.bool; description = '' On supported terminals, request focus events and pass them through to applications running in tmux. ''; }; historyLimit = mkOption { default = 2000; example = 5000; type = types.ints.positive; description = "Maximum number of lines held in window history."; }; keyMode = mkOption { default = defaultKeyMode; example = "vi"; type = types.enum [ "emacs" "vi" ]; description = "VI or Emacs style shortcuts."; }; mouse = mkEnableOption "mouse support"; newSession = mkOption { default = false; type = types.bool; description = '' Automatically spawn a session if trying to attach and none are running. ''; }; package = mkOption { type = types.package; default = pkgs.tmux; defaultText = literalExpression "pkgs.tmux"; example = literalExpression "pkgs.tmux"; description = "The tmux package to install"; }; reverseSplit = mkOption { default = false; type = types.bool; description = "Reverse the window split shortcuts."; }; resizeAmount = mkOption { default = defaultResize; example = 10; type = types.ints.positive; description = "Number of lines/columns when resizing."; }; sensibleOnTop = mkOption { type = types.bool; default = false; description = '' Run the sensible plugin at the top of the configuration. It is possible to override the sensible settings using the {option}`programs.tmux.extraConfig` option. ''; }; prefix = mkOption { default = null; example = "C-a"; type = types.nullOr types.str; description = '' Set the prefix key. Overrules the "shortcut" option when set. ''; }; shortcut = mkOption { default = defaultShortcut; example = "a"; type = types.str; description = '' CTRL following by this key is used as the main shortcut. ''; }; terminal = mkOption { default = defaultTerminal; example = "screen-256color"; type = types.str; description = "Set the $TERM variable."; }; shell = mkOption { default = defaultShell; example = "\${pkgs.zsh}/bin/zsh"; type = with types; nullOr str; description = "Set the default-shell tmux variable."; }; secureSocket = mkOption { default = pkgs.stdenv.isLinux; type = types.bool; description = '' Store tmux socket under {file}`/run`, which is more secure than {file}`/tmp`, but as a downside it doesn't survive user logout. ''; }; tmuxp.enable = mkEnableOption "tmuxp"; tmuxinator.enable = mkEnableOption "tmuxinator"; plugins = mkOption { type = with types; listOf (either package pluginModule) // { description = "list of plugin packages or submodules"; }; description = '' List of tmux plugins to be included at the end of your tmux configuration. The sensible plugin, however, is defaulted to run at the top of your configuration. ''; default = [ ]; example = literalExpression '' with pkgs; [ tmuxPlugins.cpu { plugin = tmuxPlugins.resurrect; extraConfig = "set -g @resurrect-strategy-nvim 'session'"; } { plugin = tmuxPlugins.continuum; extraConfig = ''' set -g @continuum-restore 'on' set -g @continuum-save-interval '60' # minutes '''; } ] ''; }; }; }; config = mkIf cfg.enable (mkMerge ([ { home.packages = [ cfg.package ] ++ optional cfg.tmuxinator.enable pkgs.tmuxinator ++ optional cfg.tmuxp.enable pkgs.tmuxp; } { xdg.configFile."tmux/tmux.conf".text = mkBefore tmuxConf; } { xdg.configFile."tmux/tmux.conf".text = mkAfter cfg.extraConfig; } (mkIf cfg.secureSocket { home.sessionVariables = { TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}''; }; }) (mkIf (cfg.plugins != [ ]) configPlugins) ])); }