diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix index a99c98e166dc..eb969ee1ce0c 100644 --- a/nixos/modules/programs/fish.nix +++ b/nixos/modules/programs/fish.nix @@ -27,6 +27,30 @@ in ''; type = types.bool; }; + + vendor.config.enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether fish should source configuration snippets provided by other packages. + ''; + }; + + vendor.completions.enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether fish should use completion files provided by other packages. + ''; + }; + + vendor.functions.enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether fish should autoload fish functions provided by other packages. + ''; + }; shellAliases = mkOption { default = config.environment.shellAliases; @@ -79,31 +103,72 @@ in environment.etc."fish/foreign-env/loginShellInit".text = cfge.loginShellInit; environment.etc."fish/foreign-env/interactiveShellInit".text = cfge.interactiveShellInit; + environment.etc."fish/nixos-env-preinit.fish".text = '' + # avoid clobbering the environment if it's been set by a parent shell + + # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently + # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish + set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $__fish_datadir/functions + + # source the NixOS environment config + fenv source ${config.system.build.setEnvironment} + + # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish + set -e fish_function_path + ''; + environment.etc."fish/config.fish".text = '' # /etc/fish/config.fish: DO NOT EDIT -- this file has been generated automatically. - set fish_function_path $fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions + # if our parent shell didn't source the general config, do it + if not set -q __fish_nixos_general_config_sourced + set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path + fenv source /etc/fish/foreign-env/shellInit > /dev/null + set -e fish_function_path[1] + + ${cfg.shellInit} - fenv source ${config.system.build.setEnvironment} > /dev/null ^&1 - fenv source /etc/fish/foreign-env/shellInit > /dev/null - - ${cfg.shellInit} - - if status --is-login - fenv source /etc/fish/foreign-env/loginShellInit > /dev/null - ${cfg.loginShellInit} + # and leave a note to our children to spare them the same work + set -gx __fish_nixos_general_config_sourced 1 end - if status --is-interactive + # if our parent shell didn't source the login config, do it + status --is-login; and not set -q __fish_nixos_login_config_sourced + and begin + set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path + fenv source /etc/fish/foreign-env/loginShellInit > /dev/null + set -e fish_function_path[1] + + ${cfg.loginShellInit} + + # and leave a note to our children to spare them the same work + set -gx __fish_nixos_login_config_sourced 1 + end + + # if our parent shell didn't source the interactive config, do it + status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced + and begin ${fishAliases} + + + set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path fenv source /etc/fish/foreign-env/interactiveShellInit > /dev/null + set -e fish_function_path[1] + + ${cfg.promptInit} ${cfg.interactiveShellInit} + + # and leave a note to our children to spare them the same work + set -gx __fish_nixos_interactive_config_sourced 1 end ''; # include programs that bring their own completions - environment.pathsToLink = [ "/share/fish/vendor_completions.d" ]; - + environment.pathsToLink = [] + ++ optional cfg.vendor.config.enable "/share/fish/vendor_conf.d" + ++ optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d" + ++ optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d"; + environment.systemPackages = [ pkgs.fish ]; environment.shells = [ diff --git a/pkgs/shells/fish/default.nix b/pkgs/shells/fish/default.nix index 5bdf295cc6eb..32b7694ba34b 100644 --- a/pkgs/shells/fish/default.nix +++ b/pkgs/shells/fish/default.nix @@ -2,15 +2,91 @@ nettools, kbd, bc, which, gnused, gnugrep, groff, man-db, glibc, libiconv, pcre2, gettext, ncurses, python + + , writeText + + , useOperatingSystemEtc ? true + }: with stdenv.lib; +let + etcConfigAppendixText = '' + ############### ↓ Nix hook for sourcing /etc/fish/config.fish ↓ ############### + # # + # Origin: + # This fish package was called with the attribute + # "useOperatingSystemEtc = true;". + # + # Purpose: + # Fish ordinarily sources /etc/fish/config.fish as + # $__fish_sysconfdir/config.fish, + # and $__fish_sysconfdir is defined at compile-time, baked into the C++ + # component of fish. By default, it is set to "/etc/fish". When building + # through Nix, $__fish_sysconfdir gets set to $out/etc/fish. Here we may + # have included a custom $out/etc/config.fish in the fish package, + # as specified, but according to the value of useOperatingSystemEtc, we + # may want to further source the real "/etc/fish/config.fish" file. + # + # When this option is enabled, this segment should appear the very end of + # "$out/etc/config.fish". This is to emulate the behavior of fish itself + # with respect to /etc/fish/config.fish and ~/.config/fish/config.fish: + # source both, but source the more global configuration files earlier + # than the more local ones, so that more local configurations inherit + # from but override the more global locations. + + if test -f /etc/fish/config.fish + source /etc/fish/config.fish + end + + # # + ############### ↑ Nix hook for sourcing /etc/fish/config.fish ↑ ############### + ''; + + fishPreInitHooks = '' + # source nixos environment if we're a login shell + builtin status --is-login + and test -f /etc/fish/nixos-env-preinit.fish + and source /etc/fish/nixos-env-preinit.fish + + test -n "$NIX_PROFILES" + and begin + # We ensure that __extra_* variables are read in $__fish_datadir/config.fish + # with a preference for user-configured data by making sure the package-specific + # data comes last. Files are loaded/sourced in encounter order, duplicate + # basenames get skipped, so we assure this by prepending Nix profile paths + # (ordered in reverse of the $NIX_PROFILE variable) + # + # Note that at this point in evaluation, there is nothing whatsoever on the + # fish_function_path. That means we don't have most fish builtins, e.g., `eval`. + + + # additional profiles are expected in order of precedence, which means the reverse of the + # NIX_PROFILES variable (same as config.environment.profiles) + set -l __nix_profile_paths (echo $NIX_PROFILES | ${coreutils}/bin/tr ' ' '\n')[-1..1] + + set __extra_completionsdir \ + $__nix_profile_paths"/etc/fish/completions" \ + $__nix_profile_paths"/share/fish/vendor_completions.d" \ + $__extra_completionsdir + set __extra_functionsdir \ + $__nix_profile_paths"/etc/fish/functions" \ + $__nix_profile_paths"/share/fish/vendor_functions.d" \ + $__extra_functionsdir + set __extra_confdir \ + $__nix_profile_paths"/etc/fish/conf.d" \ + $__nix_profile_paths"/share/fish/vendor_conf.d" \ + $__extra_confdir + end + ''; +in + stdenv.mkDerivation rec { name = "fish-${version}"; version = "2.5.0"; - patches = [ ./etc_config.patch ]; + etcConfigAppendix = builtins.toFile "etc-config.appendix.fish" etcConfigAppendixText; src = fetchurl { url = "http://fishshell.com/files/${version}/${name}.tar.gz"; @@ -69,15 +145,10 @@ stdenv.mkDerivation rec { "$out/share/fish/tools/create_manpage_completions.py" sed -i "s|command manpath|command ${man-db}/bin/manpath|" \ "$out/share/fish/functions/man.fish" + '' + optionalString useOperatingSystemEtc '' + tee -a $out/etc/fish/config.fish < ${(writeText "config.fish.appendix" etcConfigAppendixText)} '' + '' - tee -a $out/share/fish/config.fish << EOF - - # make fish pick up completions from nix profile - if status --is-interactive - set -l profiles (echo \$NIX_PROFILES | ${coreutils}/bin/tr ' ' '\n') - set fish_complete_path \$profiles"/share/fish/vendor_completions.d" \$fish_complete_path - end - EOF + tee -a $out/share/fish/__fish_build_paths.fish < ${(writeText "__fish_build_paths_suffix.fish" fishPreInitHooks)} ''; meta = with stdenv.lib; { diff --git a/pkgs/shells/fish/etc_config.patch b/pkgs/shells/fish/etc_config.patch deleted file mode 100644 index c0098c058124..000000000000 --- a/pkgs/shells/fish/etc_config.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/etc/config.fish b/etc/config.fish -index 9be6f07..61c9ae2 100644 ---- a/etc/config.fish -+++ b/etc/config.fish -@@ -12,3 +12,7 @@ - # if status --is-interactiv - # ... - # end -+ -+if test -f /etc/fish/config.fish -+ source /etc/fish/config.fish -+end