aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules/programs/tmux.nix
{ 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 = ''
    ${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.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}
  '';

  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;
        }
    )];

    home.file.".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
          <filename>tmux.conf</filename>.
        '';
      };

      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>programs.tmux.extraConfig</option> 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 = pkgs.stdenv.isLinux;
        type = types.bool;
        description = ''
          Store tmux socket under <filename>/run</filename>, which is more
          secure than <filename>/tmp</filename>, 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;
      }
      (mkIf cfg.secureSocket {
        home.sessionVariables = {
          TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
        };
      })

      # config file ~/.tmux.conf
      { home.file.".tmux.conf".text = mkBefore tmuxConf; }
      (mkIf (cfg.plugins != []) configPlugins)
      { home.file.".tmux.conf".text = mkAfter cfg.extraConfig; }
    ])
  );
}