{ 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"; boolToStr = value: if value then "on" else "off"; tmuxConf = '' set -g default-terminal "${cfg.terminal}" set -g base-index ${toString cfg.baseIndex} setw -g pane-base-index ${toString cfg.baseIndex} ${optionalString cfg.newSession "new-session"} ${optionalString cfg.reverseSplit '' bind v split-window -h bind s split-window -v ''} set -g status-keys ${cfg.keyMode} set -g mode-keys ${cfg.keyMode} ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) '' bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R bind -r H resize-pane -L ${toString cfg.resizeAmount} bind -r J resize-pane -D ${toString cfg.resizeAmount} bind -r K resize-pane -U ${toString cfg.resizeAmount} bind -r L resize-pane -R ${toString cfg.resizeAmount} ''} ${optionalString (cfg.shortcut != defaultShortcut) '' # rebind main key: C-${cfg.shortcut} unbind C-${defaultShortcut} set -g prefix C-${cfg.shortcut} bind ${cfg.shortcut} send-prefix bind C-${cfg.shortcut} last-window ''} ${optionalString cfg.disableConfirmationPrompt '' bind-key & kill-window bind-key x kill-pane ''} 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} ${cfg.extraConfig} ''; 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 tmux.conf. ''; }; 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."; }; 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 = literalExample "pkgs.tmux"; example = literalExample "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 = true; description = '' Run the sensible plugin at the top of the configuration. It is possible to override the sensible settings using the option. ''; }; 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."; }; secureSocket = mkOption { default = true; type = types.bool; description = '' Store tmux socket under /run, which is more secure than /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 = literalExample '' 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; home.file.".tmux.conf".text = tmuxConf; } (mkIf cfg.sensibleOnTop { home.file.".tmux.conf".text = mkBefore '' # ============================================= # # Start with defaults from the Sensible plugin # # --------------------------------------------- # run-shell ${pkgs.tmuxPlugins.sensible.rtp} # ============================================= # ''; }) (mkIf cfg.secureSocket { home.sessionVariables = { TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}''; }; }) (mkIf (cfg.plugins != []) { 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; } )]; home.file.".tmux.conf".text = mkAfter '' # ============================================= # # 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)} # ============================================= # ''; }) ] ); }