diff options
Diffstat (limited to 'home-manager/modules/programs/fish.nix')
-rw-r--r-- | home-manager/modules/programs/fish.nix | 497 |
1 files changed, 383 insertions, 114 deletions
diff --git a/home-manager/modules/programs/fish.nix b/home-manager/modules/programs/fish.nix index 87a17b85507..730afa79262 100644 --- a/home-manager/modules/programs/fish.nix +++ b/home-manager/modules/programs/fish.nix @@ -6,119 +6,310 @@ 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. + ''; + }; - aliasesStr = concatStringsSep "\n" ( - mapAttrsToList (k: v: "alias ${k}='${v}'") cfg.shellAliases - ); + name = mkOption { + type = types.str; + description = '' + The name of the plugin. + ''; + }; + }; + }); -in + functionModule = types.submodule { + options = { + body = mkOption { + type = types.lines; + description = '' + The function body. + ''; + }; + + argumentNames = mkOption { + type = with types; nullOr (either str (listOf str)); + default = null; + description = '' + Assigns the value of successive command line arguments to the names + given. + ''; + }; -{ + description = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A description of what the function does, suitable as a completion + description. + ''; + }; + + wraps = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Causes the function to inherit completions from the given wrapped + command. + ''; + }; + + onEvent = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Tells fish to run this function when the specified named event is + emitted. Fish internally generates named events e.g. when showing the + prompt. + ''; + }; + + onVariable = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Tells fish to run this function when the specified variable changes + value. + ''; + }; + + onJobExit = mkOption { + type = with types; nullOr (either str int); + default = null; + description = '' + Tells fish to run this function when the job with the specified group + ID exits. Instead of a PID, the stringer <literal>caller</literal> can + be specified. This is only legal when in a command substitution, and + will result in the handler being triggered by the exit of the job + which created this command substitution. + ''; + }; + + onProcessExit = mkOption { + type = with types; nullOr (either str int); + default = null; + example = "$fish_pid"; + description = '' + Tells fish to run this function when the fish child process with the + specified process ID exits. Instead of a PID, for backwards + compatibility, <literal>%self</literal> can be specified as an alias + for <literal>$fish_pid</literal>, and the function will be run when + the current fish instance exits. + ''; + }; + + onSignal = mkOption { + type = with types; nullOr (either str int); + default = null; + example = [ "SIGHUP" "HUP" 1 ]; + description = '' + Tells fish to run this function when the specified signal is + delievered. The signal can be a signal number or signal name. + ''; + }; + + noScopeShadowing = mkOption { + type = types.bool; + default = false; + description = '' + Allows the function to access the variables of calling functions. + ''; + }; + + inheritVariable = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Snapshots the value of the specified variable and defines a local + variable with that same name and value when the function is defined. + ''; + }; + }; + }; + + abbrsStr = concatStringsSep "\n" + (mapAttrsToList (k: v: "abbr --add --global -- ${k} ${escapeShellArg v}") + cfg.shellAbbrs); + + 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 = literalExample '' + { + ll = "ls -l"; + ".." = "cd .."; + } + ''; 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 = with types; attrsOf (either lines functionModule); + default = { }; + example = literalExample '' + { + __fish_command_not_found_handler = { + body = "__fish_default_command_not_found_handler $argv[1]"; + onEvent = "fish_command_not_found"; + }; + + 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/cmds/function.html"/>. + ''; + }; + }; - config = mkIf cfg.enable { - home.packages = [ cfg.package ]; + config = mkIf cfg.enable (mkMerge [ + { + home.packages = [ cfg.package ]; - xdg.dataFile."fish/home-manager_generated_completions".source = - let + xdg.dataFile."fish/home-manager_generated_completions".source = let # paths later in the list will overwrite those already linked - destructiveSymlinkJoin = - args_@{ name - , paths - , preferLocalBuild ? true - , allowSubstitutes ? false - , postBuild ? "" - , ... - }: + destructiveSymlinkJoin = args_@{ name, paths, preferLocalBuild ? true + , allowSubstitutes ? false, postBuild ? "", ... }: let - args = removeAttrs args_ [ "name" "postBuild" ] - // { inherit preferLocalBuild allowSubstitutes; }; # pass the defaults - in pkgs.runCommand name args - '' - mkdir -p $out - for i in $paths; do - if [ -z "$(find $i -prune -empty)" ]; then - cp -srf $i/* $out - fi - done - ${postBuild} - ''; - generateCompletions = package: pkgs.runCommand - "${package.name}-fish-completions" - { + args = removeAttrs args_ [ "name" "postBuild" ] // { + # pass the defaults + inherit preferLocalBuild allowSubstitutes; + }; + in pkgs.runCommand name args '' + mkdir -p $out + for i in $paths; do + if [ -z "$(find $i -prune -empty)" ]; then + cp -srf $i/* $out + fi + done + ${postBuild} + ''; + + generateCompletions = package: + pkgs.runCommand "${package.name}-fish-completions" { src = package; nativeBuildInputs = [ pkgs.python2 ]; buildInputs = [ cfg.package ]; preferLocalBuild = true; allowSubstitutes = false; - } - '' + } '' mkdir -p $out if [ -d $src/share/man ]; then find $src/share/man -type f \ @@ -126,66 +317,144 @@ in > /dev/null fi ''; - in - destructiveSymlinkJoin { - name = "${config.home.username}-fish-completions"; - paths = - let - cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0)); - in - map generateCompletions (sort cmp config.home.packages); + in destructiveSymlinkJoin { + name = "${config.home.username}-fish-completions"; + paths = + let cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0)); + in map generateCompletions (sort cmp config.home.packages); + }; + + programs.fish.interactiveShellInit = '' + # add completions generated by Home Manager to $fish_complete_path + begin + set -l joined (string join " " $fish_complete_path) + set -l prev_joined (string replace --regex "[^\s]*generated_completions.*" "" $joined) + set -l post_joined (string replace $prev_joined "" $joined) + set -l prev (string split " " (string trim $prev_joined)) + set -l post (string split " " (string trim $post_joined)) + set fish_complete_path $prev "${config.xdg.dataHome}/fish/home-manager_generated_completions" $post + end + ''; + + xdg.configFile."fish/config.fish".text = '' + # ~/.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 -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] + + ${cfg.shellInit} + # 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 + + # 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' (name: def: { + name = "fish/functions/${name}.fish"; + value = { + text = let + modifierStr = n: v: optional (v != null) ''--${n}="${toString v}"''; + modifierStrs = n: v: optional (v != null) "--${n}=${toString v}"; + modifierBool = n: v: optional (v != null && v) "--${n}"; + + mods = with def; + modifierStr "description" description ++ modifierStr "wraps" wraps + ++ modifierStr "on-event" onEvent + ++ modifierStr "on-variable" onVariable + ++ modifierStr "on-job-exit" onJobExit + ++ modifierStr "on-process-exit" onProcessExit + ++ modifierStr "on-signal" onSignal + ++ modifierBool "no-scope-shadowing" noScopeShadowing + ++ modifierStr "inherit-variable" inheritVariable + ++ modifierStrs "argument-names" argumentNames; + + modifiers = if isAttrs def then " ${toString mods}" else ""; + body = if isAttrs def then def.body else def; + in '' + function ${name}${modifiers} + ${body} + end + ''; }; + }) cfg.functions; + } - programs.fish.interactiveShellInit = '' - # add completions generated by Home Manager to $fish_complete_path - begin - set -l joined (string join " " $fish_complete_path) - set -l prev_joined (string replace --regex "[^\s]*generated_completions.*" "" $joined) - set -l post_joined (string replace $prev_joined "" $joined) - set -l prev (string split " " (string trim $prev_joined)) - set -l post (string split " " (string trim $post_joined)) - set fish_complete_path $prev "${config.xdg.dataHome}/fish/home-manager_generated_completions" $post - end - ''; - - xdg.configFile."fish/config.fish".text = '' - # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated automatically. - # 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 - fenv source ${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh > /dev/null - set -e fish_function_path[1] - - ${cfg.shellInit} - # 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 - - ${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 - ${abbrsStr} - - # Aliases - ${aliasesStr} - - ${cfg.promptInit} - ${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 - ''; - }; + # 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_complete_path[1] $plugin_dir/completions $fish_complete_path[2..-1] + end + + # Source initialization code if it exists. + if test -d $plugin_dir/conf.d + for f in $plugin_dir/conf.d/*.fish + source $f + end + 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)); + }) + ]); } |