aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/programs/fish.nix194
-rw-r--r--tests/default.nix1
-rw-r--r--tests/modules/programs/fish/default.nix5
-rw-r--r--tests/modules/programs/fish/functions.nix32
-rw-r--r--tests/modules/programs/fish/no-functions.nix22
-rw-r--r--tests/modules/programs/fish/plugins.nix58
6 files changed, 284 insertions, 28 deletions
diff --git a/modules/programs/fish.nix b/modules/programs/fish.nix
index 87a17b85507..38a51c74fa6 100644
--- a/modules/programs/fish.nix
+++ b/modules/programs/fish.nix
@@ -6,83 +6,160 @@ let
cfg = config.programs.fish;
- abbrsStr = concatStringsSep "\n" (
- mapAttrsToList (k: v: "abbr --add --global ${k} '${v}'") cfg.shellAbbrs
- );
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ src = mkOption {
+ type = types.path;
+ description = ''
+ Path to the plugin folder.
+ </para><para>
+ Relevant pieces will be added to the fish function path and
+ the completion path. The <filename>init.fish</filename> and
+ <filename>key_binding.fish</filename> files are sourced if
+ they exist.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the plugin.
+ '';
+ };
+ };
+ });
+
+ abbrsStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "abbr --add --global -- ${k} ${escapeShellArg v}")
+ cfg.shellAbbrs);
- aliasesStr = concatStringsSep "\n" (
- mapAttrsToList (k: v: "alias ${k}='${v}'") cfg.shellAliases
- );
+ aliasesStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "alias ${k} ${escapeShellArg v}") cfg.shellAliases);
in
{
options = {
programs.fish = {
- enable = mkEnableOption "fish friendly interactive shell";
+ enable = mkEnableOption "fish, the friendly interactive shell";
package = mkOption {
+ type = types.package;
default = pkgs.fish;
defaultText = literalExample "pkgs.fish";
description = ''
The fish package to install. May be used to change the version.
'';
- type = types.package;
};
shellAliases = mkOption {
- default = {};
+ type = with types; attrsOf str;
+ default = { };
+ example = { ".." = "cd .."; ll = "ls -l"; };
description = ''
- Set of aliases for fish shell. See
- <option>environment.shellAliases</option> for an option
- format description.
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to command strings or directly to build outputs.
'';
- type = types.attrs;
};
shellAbbrs = mkOption {
- default = {};
+ type = with types; attrsOf str;
+ default = { };
+ example = {
+ l = "less";
+ gco = "git checkout";
+ };
description = ''
- Set of abbreviations for fish shell.
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to abbreviations. Abbreviations are expanded with
+ the longer phrase after they are entered.
'';
- type = types.attrs;
};
shellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during fish shell initialisation.
+ Shell script code called during fish shell
+ initialisation.
'';
- type = types.lines;
};
loginShellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during fish login shell initialisation.
+ Shell script code called during fish login shell
+ initialisation.
'';
- type = types.lines;
};
interactiveShellInit = mkOption {
+ type = types.lines;
default = "";
description = ''
- Shell script code called during interactive fish shell initialisation.
+ Shell script code called during interactive fish shell
+ initialisation.
'';
- type = types.lines;
};
promptInit = mkOption {
+ type = types.lines;
default = "";
description = ''
Shell script code used to initialise fish prompt.
'';
- type = types.lines;
};
};
+
+ programs.fish.plugins = mkOption {
+ type = types.listOf pluginModule;
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ name = "z";
+ src = pkgs.fetchFromGitHub {
+ owner = "jethrokuan";
+ repo = "z";
+ rev = "ddeb28a7b6a1f0ec6dae40c636e5ca4908ad160a";
+ sha256 = "0c5i7sdrsp0q3vbziqzdyqn4fmp235ax4mn4zslrswvn8g3fvdyh";
+ };
+ }
+
+ # oh-my-fish plugins are stored in their own repositories, which
+ # makes them simple to import into home-manager.
+ {
+ name = "fasd";
+ src = pkgs.fetchFromGitHub {
+ owner = "oh-my-fish";
+ repo = "plugin-fasd";
+ rev = "38a5b6b6011106092009549e52249c6d6f501fba";
+ sha256 = "06v37hqy5yrv5a6ssd1p3cjd9y3hnp19d3ab7dag56fs1qmgyhbs";
+ };
+ }
+ ]
+ '';
+ description = ''
+ The plugins to source in
+ <filename>conf.d/99plugins.fish</filename>.
+ '';
+ };
+
+ programs.fish.functions = mkOption {
+ type = types.attrsOf types.lines;
+ default = { };
+ example = { gitignore = "curl -sL https://www.gitignore.io/api/$argv"; };
+ description = ''
+ Basic functions to add to fish. For more information see
+ <link xlink:href="https://fishshell.com/docs/current/commands.html#function"/>.
+ '';
+ };
+
};
- config = mkIf cfg.enable {
+ config = mkIf cfg.enable (mkMerge [{
+
home.packages = [ cfg.package ];
xdg.dataFile."fish/home-manager_generated_completions".source =
@@ -149,10 +226,13 @@ in
'';
xdg.configFile."fish/config.fish".text = ''
- # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated automatically.
+ # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated
+ # automatically by home-manager.
+
# if we haven't sourced the general config, do it
if not set -q __fish_general_config_sourced
- set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+
+ set -p fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions
fenv source ${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh > /dev/null
set -e fish_function_path[1]
@@ -160,32 +240,90 @@ in
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_general_config_sourced 1
+
end
+
# if we haven't sourced the login config, do it
status --is-login; and not set -q __fish_login_config_sourced
and begin
+ # Login shell initialisation
${cfg.loginShellInit}
+
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew)
set -g __fish_login_config_sourced 1
+
end
+
# if we haven't sourced the interactive config, do it
status --is-interactive; and not set -q __fish_interactive_config_sourced
and begin
- # Abbrs
+
+ # Abbreviations
${abbrsStr}
# Aliases
${aliasesStr}
+ # Prompt initialisation
${cfg.promptInit}
+
+ # Interactive shell intialisation
${cfg.interactiveShellInit}
+
# and leave a note so we don't source this config section again from
# this very shell (children will source the general config anew,
# allowing configuration changes in, e.g, aliases, to propagate)
set -g __fish_interactive_config_sourced 1
+
end
'';
- };
+
+ } {
+
+ xdg.configFile = mapAttrs' (fName: fBody: {
+ name = "fish/functions/${fName}.fish";
+ value = {"text" = ''
+ function ${fName}
+ ${fBody}
+ end
+ '';};
+ }) cfg.functions;
+
+ }
+
+ # Each plugin gets a corresponding conf.d/plugin-NAME.fish file to load
+ # in the paths and any initialization scripts.
+ (mkIf (length cfg.plugins > 0) {
+ xdg.configFile = mkMerge (
+ (map (plugin: { "fish/conf.d/plugin-${plugin.name}.fish".text = ''
+ # Plugin ${plugin.name}
+ set -l plugin_dir ${plugin.src}
+
+ # Set paths to import plugin components
+ if test -d $plugin_dir/functions
+ set fish_function_path $fish_function_path[1] $plugin_dir/functions $fish_function_path[2..-1]
+ end
+
+ if test -d $plugin_dir/completions
+ set fish_complete_path $fish_function_path[1] $plugin_dir/completions $fish_complete_path[2..-1]
+ end
+
+ # Source initialization code if it exists.
+ if test -d $plugin_dir/conf.d
+ source $plugin_dir/conf.d/*.fish
+ end
+
+ if test -f $plugin_dir/key_bindings.fish
+ source $plugin_dir/key_bindings.fish
+ end
+
+ if test -f $plugin_dir/init.fish
+ source $plugin_dir/init.fish
+ end
+ '';
+ }) cfg.plugins));
+ })
+ ]);
}
diff --git a/tests/default.nix b/tests/default.nix
index 49c27239730..df69dc22a1d 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -29,6 +29,7 @@ import nmt {
./modules/programs/alacritty
./modules/programs/bash
./modules/programs/browserpass
+ ./modules/programs/fish
./modules/programs/git
./modules/programs/gpg
./modules/programs/mbsync
diff --git a/tests/modules/programs/fish/default.nix b/tests/modules/programs/fish/default.nix
new file mode 100644
index 00000000000..99fe8136700
--- /dev/null
+++ b/tests/modules/programs/fish/default.nix
@@ -0,0 +1,5 @@
+{
+ fish-functions = ./functions.nix;
+ fish-no-functions = ./no-functions.nix;
+ fish-plugins = ./plugins.nix;
+}
diff --git a/tests/modules/programs/fish/functions.nix b/tests/modules/programs/fish/functions.nix
new file mode 100644
index 00000000000..b939a208696
--- /dev/null
+++ b/tests/modules/programs/fish/functions.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ func = pkgs.writeText "func.fish" ''
+ function func
+ echo "Hello"
+ end
+ '';
+
+in {
+ config = {
+ programs.fish = {
+ enable = true;
+
+ functions = { func = ''echo "Hello"''; };
+ };
+
+ nmt = {
+ description =
+ "if fish.function is set, check file exists and contents match";
+ script = ''
+ assertFileExists home-files/.config/fish/functions/func.fish
+ echo ${func}
+ assertFileContent home-files/.config/fish/functions/func.fish ${func}
+ '';
+
+ };
+ };
+}
diff --git a/tests/modules/programs/fish/no-functions.nix b/tests/modules/programs/fish/no-functions.nix
new file mode 100644
index 00000000000..c817b388953
--- /dev/null
+++ b/tests/modules/programs/fish/no-functions.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.fish = {
+ enable = true;
+
+ functions = { };
+ };
+
+ nmt = {
+ description =
+ "if fish.functions is blank, the functions folder should not exist.";
+ script = ''
+ assertPathNotExists home-files/.config/fish/functions
+ '';
+
+ };
+ };
+}
diff --git a/tests/modules/programs/fish/plugins.nix b/tests/modules/programs/fish/plugins.nix
new file mode 100644
index 00000000000..cb7d71040cd
--- /dev/null
+++ b/tests/modules/programs/fish/plugins.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ fooPluginSrc = pkgs.writeText "fooPluginSrc" "";
+
+ generatedConfdFile = pkgs.writeText "plugin-foo.fish" ''
+ # Plugin foo
+ set -l plugin_dir ${fooPluginSrc}
+
+ # Set paths to import plugin components
+ if test -d $plugin_dir/functions
+ set fish_function_path $fish_function_path[1] $plugin_dir/functions $fish_function_path[2..-1]
+ end
+
+ if test -d $plugin_dir/completions
+ set fish_complete_path $fish_function_path[1] $plugin_dir/completions $fish_complete_path[2..-1]
+ end
+
+ # Source initialization code if it exists.
+ if test -d $plugin_dir/conf.d
+ source $plugin_dir/conf.d/*.fish
+ end
+
+ if test -f $plugin_dir/key_bindings.fish
+ source $plugin_dir/key_bindings.fish
+ end
+
+ if test -f $plugin_dir/init.fish
+ source $plugin_dir/init.fish
+ end
+ '';
+
+in {
+ config = {
+ programs.fish = {
+ enable = true;
+
+ plugins = [{
+ name = "foo";
+ src = fooPluginSrc;
+ }];
+ };
+
+ nmt = {
+ description =
+ "if fish.plugins set, check conf.d file exists and contents match";
+ script = ''
+ assertDirectoryExists home-files/.config/fish/conf.d
+ assertFileExists home-files/.config/fish/conf.d/plugin-foo.fish
+ assertFileContent home-files/.config/fish/conf.d/plugin-foo.fish ${generatedConfdFile}
+ '';
+
+ };
+ };
+}