diff --git a/modules/programs/tmux.nix b/modules/programs/tmux.nix index 050168bed..b436948bb 100644 --- a/modules/programs/tmux.nix +++ b/modules/programs/tmux.nix @@ -23,13 +23,136 @@ let }; }; + 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 + ''} + + 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. + ''; + }; + 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; @@ -38,6 +161,19 @@ in 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; @@ -48,6 +184,32 @@ in ''; }; + 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"; @@ -79,15 +241,6 @@ in ] ''; }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = '' - Additional configuration to add to - tmux.conf. - ''; - }; }; }; @@ -98,7 +251,7 @@ in ++ optional cfg.tmuxinator.enable pkgs.tmuxinator ++ optional cfg.tmuxp.enable pkgs.tmuxp; - home.file.".tmux.conf".text = cfg.extraConfig; + home.file.".tmux.conf".text = tmuxConf; } (mkIf cfg.sensibleOnTop { diff --git a/tests/default.nix b/tests/default.nix index aa14fc248..42c178197 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -28,5 +28,5 @@ import nmt { xresources = ./modules/xresources.nix; } // pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { i3-keybindings = ./modules/services/window-managers/i3-keybindings.nix; - }; + } // import ./modules/programs/tmux; } diff --git a/tests/modules/programs/tmux/default.nix b/tests/modules/programs/tmux/default.nix new file mode 100644 index 000000000..5bc311772 --- /dev/null +++ b/tests/modules/programs/tmux/default.nix @@ -0,0 +1,5 @@ +{ + tmux-emacs-with-plugins = ./emacs-with-plugins.nix; + tmux-not-enabled = ./not-enabled.nix; + tmux-vi-all-true = ./vi-all-true.nix; +} diff --git a/tests/modules/programs/tmux/emacs-with-plugins.conf b/tests/modules/programs/tmux/emacs-with-plugins.conf new file mode 100644 index 000000000..039ae740f --- /dev/null +++ b/tests/modules/programs/tmux/emacs-with-plugins.conf @@ -0,0 +1,52 @@ +# ============================================= # +# Start with defaults from the Sensible plugin # +# --------------------------------------------- # +run-shell @tmuxplugin_sensible_rtp@ +# ============================================= # + +set -g default-terminal "screen" +set -g base-index 0 +setw -g pane-base-index 0 + +new-session + +bind v split-window -h +bind s split-window -v + + +set -g status-keys emacs +set -g mode-keys emacs + + + + + +setw -g aggressive-resize on +setw -g clock-mode-style 24 +set -s escape-time 500 +set -g history-limit 2000 + + + +# ============================================= # +# Load plugins with Home Manager # +# --------------------------------------------- # + +# tmuxplugin-logging +# --------------------- + +run-shell @tmuxplugin_logging@/share/tmux-plugins/logging/logging.tmux + + +# tmuxplugin-prefix-highlight +# --------------------- + +run-shell @tmuxplugin_prefix_highlight@/share/tmux-plugins/prefix-highlight/prefix_highlight.tmux + + +# tmuxplugin-fzf-tmux-url +# --------------------- + +run-shell @tmuxplugin_fzf_tmux_url@/share/tmux-plugins/fzf-tmux-url/fzf-url.tmux + +# ============================================= # diff --git a/tests/modules/programs/tmux/emacs-with-plugins.nix b/tests/modules/programs/tmux/emacs-with-plugins.nix new file mode 100644 index 000000000..5e147b729 --- /dev/null +++ b/tests/modules/programs/tmux/emacs-with-plugins.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + substituteExpected = path: pkgs.substituteAll { + src = path; + + tmuxplugin_fzf_tmux_url = pkgs.tmuxPlugins.fzf-tmux-url; + tmuxplugin_logging = pkgs.tmuxPlugins.logging; + tmuxplugin_prefix_highlight = pkgs.tmuxPlugins.prefix-highlight; + tmuxplugin_sensible_rtp = pkgs.tmuxPlugins.sensible.rtp; + }; + +in + +{ + config = { + programs.tmux = { + aggressiveResize = true; + clock24 = true; + enable = true; + keyMode = "emacs"; + newSession = true; + reverseSplit = true; + + plugins = with pkgs.tmuxPlugins; [ + logging + prefix-highlight + fzf-tmux-url + ]; + }; + + nmt.script = '' + assertFileExists home-files/.tmux.conf + assertFileContent home-files/.tmux.conf \ + ${substituteExpected ./emacs-with-plugins.conf} + ''; + }; +} diff --git a/tests/modules/programs/tmux/not-enabled.nix b/tests/modules/programs/tmux/not-enabled.nix new file mode 100644 index 000000000..d74f30648 --- /dev/null +++ b/tests/modules/programs/tmux/not-enabled.nix @@ -0,0 +1,13 @@ +{ config, lib, ... }: + +with lib; + +{ + config = { + programs.tmux = { enable = false; }; + + nmt.script = '' + assertFileNotExists home-files/.tmux.conf + ''; + }; +} diff --git a/tests/modules/programs/tmux/vi-all-true.conf b/tests/modules/programs/tmux/vi-all-true.conf new file mode 100644 index 000000000..15bbc9107 --- /dev/null +++ b/tests/modules/programs/tmux/vi-all-true.conf @@ -0,0 +1,29 @@ +# ============================================= # +# Start with defaults from the Sensible plugin # +# --------------------------------------------- # +run-shell @sensible_rtp@ +# ============================================= # + +set -g default-terminal "screen" +set -g base-index 0 +setw -g pane-base-index 0 + +new-session + +bind v split-window -h +bind s split-window -v + + +set -g status-keys vi +set -g mode-keys vi + + + + + +setw -g aggressive-resize on +setw -g clock-mode-style 24 +set -s escape-time 500 +set -g history-limit 2000 + + diff --git a/tests/modules/programs/tmux/vi-all-true.nix b/tests/modules/programs/tmux/vi-all-true.nix new file mode 100644 index 000000000..e88ed587c --- /dev/null +++ b/tests/modules/programs/tmux/vi-all-true.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + substituteExpected = path: pkgs.substituteAll { + src = path; + + sensible_rtp = pkgs.tmuxPlugins.sensible.rtp; + }; + +in { + config = { + programs.tmux = { + aggressiveResize = true; + clock24 = true; + enable = true; + keyMode = "vi"; + newSession = true; + reverseSplit = true; + }; + + nmt.script = '' + assertFileExists home-files/.tmux.conf + assertFileContent home-files/.tmux.conf \ + ${substituteExpected ./vi-all-true.conf} + ''; + }; +}