at master 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 10 cfge = config.environment; 11 12 cfg = config.programs.fish; 13 14 fishAbbrs = lib.concatStringsSep "\n" ( 15 lib.mapAttrsToList (k: v: "abbr -a ${k} -- ${lib.escapeShellArg v}") cfg.shellAbbrs 16 ); 17 18 fishAliases = lib.concatStringsSep "\n" ( 19 lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}") ( 20 lib.filterAttrs (k: v: v != null) cfg.shellAliases 21 ) 22 ); 23 24 envShellInit = pkgs.writeText "shellInit" cfge.shellInit; 25 26 envLoginShellInit = pkgs.writeText "loginShellInit" cfge.loginShellInit; 27 28 envInteractiveShellInit = pkgs.writeText "interactiveShellInit" cfge.interactiveShellInit; 29 30 sourceEnv = 31 file: 32 if cfg.useBabelfish then 33 "source /etc/fish/${file}.fish" 34 else 35 '' 36 set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $fish_function_path 37 fenv source /etc/fish/foreign-env/${file} > /dev/null 38 set -e fish_function_path[1] 39 ''; 40 41 babelfishTranslate = 42 path: name: 43 pkgs.runCommand "${name}.fish" { 44 preferLocalBuild = true; 45 nativeBuildInputs = [ pkgs.babelfish ]; 46 } "babelfish < ${path} > $out;"; 47 48in 49 50{ 51 52 options = { 53 54 programs.fish = { 55 56 enable = lib.mkOption { 57 default = false; 58 description = '' 59 Whether to configure fish as an interactive shell. 60 ''; 61 type = lib.types.bool; 62 }; 63 64 package = lib.mkPackageOption pkgs "fish" { }; 65 66 useBabelfish = lib.mkOption { 67 type = lib.types.bool; 68 default = false; 69 description = '' 70 If enabled, the configured environment will be translated to native fish using [babelfish](https://github.com/bouk/babelfish). 71 Otherwise, [foreign-env](https://github.com/oh-my-fish/plugin-foreign-env) will be used. 72 ''; 73 }; 74 75 generateCompletions = lib.mkEnableOption "generating completion files from man pages" // { 76 default = true; 77 example = false; 78 }; 79 80 vendor.config.enable = lib.mkOption { 81 type = lib.types.bool; 82 default = true; 83 description = '' 84 Whether fish should source configuration snippets provided by other packages. 85 ''; 86 }; 87 88 vendor.completions.enable = lib.mkOption { 89 type = lib.types.bool; 90 default = true; 91 description = '' 92 Whether fish should use completion files provided by other packages. 93 ''; 94 }; 95 96 vendor.functions.enable = lib.mkOption { 97 type = lib.types.bool; 98 default = true; 99 description = '' 100 Whether fish should autoload fish functions provided by other packages. 101 ''; 102 }; 103 104 shellAbbrs = lib.mkOption { 105 default = { }; 106 example = { 107 gco = "git checkout"; 108 npu = "nix-prefetch-url"; 109 }; 110 description = '' 111 Set of fish abbreviations. 112 ''; 113 type = with lib.types; attrsOf str; 114 }; 115 116 shellAliases = lib.mkOption { 117 default = { }; 118 description = '' 119 Set of aliases for fish shell, which overrides {option}`environment.shellAliases`. 120 See {option}`environment.shellAliases` for an option format description. 121 ''; 122 type = with lib.types; attrsOf (nullOr (either str path)); 123 }; 124 125 shellInit = lib.mkOption { 126 default = ""; 127 description = '' 128 Shell script code called during fish shell initialisation. 129 ''; 130 type = lib.types.lines; 131 }; 132 133 loginShellInit = lib.mkOption { 134 default = ""; 135 description = '' 136 Shell script code called during fish login shell initialisation. 137 ''; 138 type = lib.types.lines; 139 }; 140 141 interactiveShellInit = lib.mkOption { 142 default = ""; 143 description = '' 144 Shell script code called during interactive fish shell initialisation. 145 ''; 146 type = lib.types.lines; 147 }; 148 149 promptInit = lib.mkOption { 150 default = ""; 151 description = '' 152 Shell script code used to initialise fish prompt. 153 ''; 154 type = lib.types.lines; 155 }; 156 157 }; 158 159 }; 160 161 config = lib.mkIf cfg.enable { 162 163 programs.fish.shellAliases = lib.mapAttrs (name: lib.mkDefault) cfge.shellAliases; 164 165 # Required for man completions 166 documentation.man.generateCaches = lib.mkDefault true; 167 168 environment = lib.mkMerge [ 169 (lib.mkIf cfg.useBabelfish { 170 etc."fish/setEnvironment.fish".source = 171 babelfishTranslate config.system.build.setEnvironment "setEnvironment"; 172 etc."fish/shellInit.fish".source = babelfishTranslate envShellInit "shellInit"; 173 etc."fish/loginShellInit.fish".source = babelfishTranslate envLoginShellInit "loginShellInit"; 174 etc."fish/interactiveShellInit.fish".source = 175 babelfishTranslate envInteractiveShellInit "interactiveShellInit"; 176 }) 177 178 (lib.mkIf (!cfg.useBabelfish) { 179 etc."fish/foreign-env/shellInit".source = envShellInit; 180 etc."fish/foreign-env/loginShellInit".source = envLoginShellInit; 181 etc."fish/foreign-env/interactiveShellInit".source = envInteractiveShellInit; 182 }) 183 184 { 185 etc."fish/nixos-env-preinit.fish".text = 186 if cfg.useBabelfish then 187 '' 188 # source the NixOS environment config 189 if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ] 190 source /etc/fish/setEnvironment.fish 191 end 192 '' 193 else 194 '' 195 # This happens before $__fish_datadir/config.fish sets fish_function_path, so it is currently 196 # unset. We set it and then completely erase it, leaving its configuration to $__fish_datadir/config.fish 197 set fish_function_path ${pkgs.fishPlugins.foreign-env}/share/fish/vendor_functions.d $__fish_datadir/functions 198 199 # source the NixOS environment config 200 if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ] 201 fenv source ${config.system.build.setEnvironment} 202 end 203 204 # clear fish_function_path so that it will be correctly set when we return to $__fish_datadir/config.fish 205 set -e fish_function_path 206 ''; 207 } 208 209 { 210 etc."fish/config.fish".text = '' 211 # /etc/fish/config.fish: DO NOT EDIT -- this file has been generated automatically. 212 213 # if we haven't sourced the general config, do it 214 if not set -q __fish_nixos_general_config_sourced 215 ${sourceEnv "shellInit"} 216 217 ${cfg.shellInit} 218 219 # and leave a note so we don't source this config section again from 220 # this very shell (children will source the general config anew) 221 set -g __fish_nixos_general_config_sourced 1 222 end 223 224 # if we haven't sourced the login config, do it 225 status is-login; and not set -q __fish_nixos_login_config_sourced 226 and begin 227 ${sourceEnv "loginShellInit"} 228 229 ${cfg.loginShellInit} 230 231 # and leave a note so we don't source this config section again from 232 # this very shell (children will source the general config anew) 233 set -g __fish_nixos_login_config_sourced 1 234 end 235 236 # if we haven't sourced the interactive config, do it 237 status is-interactive; and not set -q __fish_nixos_interactive_config_sourced 238 and begin 239 ${fishAbbrs} 240 ${fishAliases} 241 242 ${sourceEnv "interactiveShellInit"} 243 244 ${cfg.promptInit} 245 ${cfg.interactiveShellInit} 246 247 # and leave a note so we don't source this config section again from 248 # this very shell (children will source the general config anew, 249 # allowing configuration changes in, e.g, aliases, to propagate) 250 set -g __fish_nixos_interactive_config_sourced 1 251 end 252 ''; 253 } 254 255 (lib.mkIf cfg.generateCompletions { 256 etc."fish/generated_completions".source = 257 let 258 patchedGenerator = pkgs.stdenv.mkDerivation { 259 name = "fish_patched-completion-generator"; 260 srcs = [ 261 "${cfg.package}/share/fish/tools/create_manpage_completions.py" 262 "${cfg.package}/share/fish/tools/deroff.py" 263 ]; 264 unpackCmd = "cp $curSrc $(basename $curSrc)"; 265 sourceRoot = "."; 266 patches = [ ./fish_completion-generator.patch ]; # to prevent collisions of identical completion files 267 dontBuild = true; 268 installPhase = '' 269 mkdir -p $out 270 cp * $out/ 271 ''; 272 preferLocalBuild = true; 273 allowSubstitutes = false; 274 }; 275 generateCompletions = 276 package: 277 pkgs.runCommand 278 ( 279 with lib.strings; 280 let 281 storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash 282 pathName = substring storeLength (stringLength package - storeLength) package; 283 in 284 (package.name or pathName) + "_fish-completions" 285 ) 286 ( 287 { 288 inherit package; 289 preferLocalBuild = true; 290 } 291 // lib.optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; } 292 ) 293 '' 294 mkdir -p $out 295 if [ -d $package/share/man ]; then 296 find -L $package/share/man -type f | xargs ${pkgs.python3.pythonOnBuildForHost.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null 297 fi 298 ''; 299 in 300 pkgs.buildEnv { 301 name = "system_fish-completions"; 302 ignoreCollisions = true; 303 paths = builtins.map generateCompletions config.environment.systemPackages; 304 }; 305 }) 306 307 # include programs that bring their own completions 308 { 309 pathsToLink = 310 [ ] 311 ++ lib.optional cfg.vendor.config.enable "/share/fish/vendor_conf.d" 312 ++ lib.optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d" 313 ++ lib.optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d"; 314 } 315 316 { systemPackages = [ cfg.package ]; } 317 318 { 319 shells = [ 320 "/run/current-system/sw/bin/fish" 321 (lib.getExe cfg.package) 322 ]; 323 } 324 ]; 325 326 programs.fish.interactiveShellInit = 327 lib.optionalString cfg.generateCompletions '' 328 # add completions generated by NixOS to $fish_complete_path 329 begin 330 # joins with null byte to accommodate all characters in paths, then respectively gets all paths before (exclusive) / after (inclusive) the first one including "generated_completions", 331 # splits by null byte, and then removes all empty lines produced by using 'string' 332 set -l prev (string join0 $fish_complete_path | string match --regex "^.*?(?=\x00[^\x00]*generated_completions.*)" | string split0 | string match -er ".") 333 set -l post (string join0 $fish_complete_path | string match --regex "[^\x00]*generated_completions.*" | string split0 | string match -er ".") 334 set fish_complete_path $prev "/etc/fish/generated_completions" $post 335 end 336 '' 337 + '' 338 # prevent fish from generating completions on first run 339 if not test -d $__fish_user_data_dir/generated_completions 340 ${pkgs.coreutils}/bin/mkdir $__fish_user_data_dir/generated_completions 341 end 342 ''; 343 344 }; 345 meta.maintainers = with lib.maintainers; [ sigmasquadron ]; 346}