a1162e04b3
Previously, it was not possible to set an arbitrary tmux prefix since CTRL was hardcoded in the module. To avoid breaking existing configs, a new option was implemented that conveniently uses the tmux terminology but defaults to null and does not affect previous behavior when set to null. The behavior for the shortcut option was not completely replicated, i.e., it does not bind "b" to send-prefix but stick to the default of the prefix binding sending prefix (C-b C-b instead of C-b b) and it does not bind repetition of the prefix (C-b C-b) to `last-window`, both of these bring the option closer to the default tmux configuration. Fixes #1237
345 lines
9.6 KiB
Nix
345 lines
9.6 KiB
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";
|
|
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 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}
|
|
''}
|
|
|
|
${if cfg.prefix != null
|
|
then ''
|
|
# rebind main key: ${cfg.prefix}
|
|
unbind C-${defaultShortcut}
|
|
set -g prefix ${cfg.prefix}
|
|
bind ${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 ${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.
|
|
'';
|
|
};
|
|
|
|
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 <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; }
|
|
])
|
|
);
|
|
}
|