diff options
author | Kaiden Fey <kookie@spacekookie.de> | 2020-09-21 14:12:32 +0200 |
---|---|---|
committer | Katharina Fey <kookie@spacekookie.de> | 2020-09-21 14:12:32 +0200 |
commit | f80843dd45d7acd563d0a5b014cec3a2ea686fc2 (patch) | |
tree | 87189d873d6f932d85f9c1a480462b37d96cd6a5 /home-manager/modules/programs | |
parent | e0800985dab8f8ebb4cebdfd7e361fd1fafdb2a7 (diff) | |
parent | 9b1b55ba0264a55add4b7b4e022bdc2832b531f6 (diff) |
Merge commit '9b1b55ba0264a55add4b7b4e022bdc2832b531f6'
Diffstat (limited to 'home-manager/modules/programs')
55 files changed, 3645 insertions, 529 deletions
diff --git a/home-manager/modules/programs/abook.nix b/home-manager/modules/programs/abook.nix new file mode 100644 index 00000000000..4ddc080ad51 --- /dev/null +++ b/home-manager/modules/programs/abook.nix @@ -0,0 +1,40 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.abook; + +in { + options.programs.abook = { + enable = mkEnableOption "Abook"; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + field pager = Pager + view CONTACT = name, email + set autosave=true + ''; + description = '' + Extra lines added to <filename>$HOME/.config/abook/abookrc</filename>. + Available configuration options are described in the abook repository: + <link xlink:href="https://sourceforge.net/p/abook/git/ci/master/tree/sample.abookrc" />. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.abook ]; + xdg.configFile."abook/abookrc" = mkIf (cfg.extraConfig != "") { + text = '' + # Generated by Home Manager. + # See http://abook.sourceforge.net/ + + ${cfg.extraConfig} + ''; + }; + }; +} diff --git a/home-manager/modules/programs/alacritty.nix b/home-manager/modules/programs/alacritty.nix index 69b9ea9673d..ea908f2b056 100644 --- a/home-manager/modules/programs/alacritty.nix +++ b/home-manager/modules/programs/alacritty.nix @@ -11,6 +11,13 @@ in { programs.alacritty = { enable = mkEnableOption "Alacritty"; + package = mkOption { + type = types.package; + default = pkgs.alacritty; + defaultText = literalExample "pkgs.alacritty"; + description = "The Alacritty package to install."; + }; + settings = mkOption { type = types.attrs; default = { }; @@ -41,7 +48,7 @@ in { config = mkMerge [ (mkIf cfg.enable { - home.packages = [ pkgs.alacritty ]; + home.packages = [ cfg.package ]; xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != { }) { text = diff --git a/home-manager/modules/programs/alot.nix b/home-manager/modules/programs/alot.nix index 2b28f34caa3..e907cd3e0ac 100644 --- a/home-manager/modules/programs/alot.nix +++ b/home-manager/modules/programs/alot.nix @@ -7,167 +7,231 @@ let cfg = config.programs.alot; - alotAccounts = filter (a: a.notmuch.enable) - (attrValues config.accounts.email.accounts); + alotAccounts = + filter (a: a.notmuch.enable) (attrValues config.accounts.email.accounts); boolStr = v: if v then "True" else "False"; - accountStr = account: with account; - concatStringsSep "\n" ( - [ "[[${name}]]" ] - ++ mapAttrsToList (n: v: n + "=" + v) ( - { - address = address; - realname = realName; - sendmail_command = - optionalString (alot.sendMailCommand != null) alot.sendMailCommand; - sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent; - draft_box = "maildir" + "://"+ maildir.absPath + "/" + folders.drafts; - } - // optionalAttrs (aliases != []) { - aliases = concatStringsSep "," aliases; - } - // optionalAttrs (gpg != null) { - gpg_key = gpg.key; - encrypt_by_default = if gpg.encryptByDefault then "all" else "none"; - sign_by_default = boolStr gpg.signByDefault; - } - // optionalAttrs (signature.showSignature != "none") { - signature = pkgs.writeText "signature.txt" signature.text; - signature_as_attachment = - boolStr (signature.showSignature == "attach"); - } - ) - ++ [ alot.extraConfig ] - ++ [ "[[[abook]]]" ] - ++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion - ); - - configFile = - let - bindingsToStr = attrSet: - concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet); - in - '' - # Generated by Home Manager. - # See http://alot.readthedocs.io/en/latest/configuration/config_options.html - - ${cfg.extraConfig} - - [bindings] - ${bindingsToStr cfg.bindings.global} - - [[bufferlist]] - ${bindingsToStr cfg.bindings.bufferlist} - [[search]] - ${bindingsToStr cfg.bindings.search} - [[envelope]] - ${bindingsToStr cfg.bindings.envelope} - [[taglist]] - ${bindingsToStr cfg.bindings.taglist} - [[thread]] - ${bindingsToStr cfg.bindings.thread} - - [accounts] - - ${concatStringsSep "\n\n" (map accountStr alotAccounts)} - ''; - -in - -{ - options.programs.alot = { - enable = mkOption { - type = types.bool; - default = false; - example = true; - description = '' - Whether to enable the Alot mail user agent. Alot uses the - Notmuch email system and will therefore be automatically - enabled for each email account that is managed by Notmuch. - ''; - }; + mkKeyValue = key: value: + let value' = if isBool value then boolStr value else toString value; + in "${key} = ${value'}"; + + mk2ndLevelSectionName = name: "[" + name + "]"; + + tagSubmodule = types.submodule { + options = { + translated = mkOption { + type = types.nullOr types.str; + description = '' + Fixed string representation for this tag. The tag can be + hidden from view, if the key translated is set to + <literal>""</literal>, the empty string. + ''; + }; - hooks = mkOption { - type = types.lines; - default = ""; - description = '' - Content of the hooks file. - ''; - }; + translation = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A pair of strings that define a regular substitution to + compute the string representation on the fly using + <literal>re.sub</literal>. + ''; + }; - bindings = mkOption { - type = types.submodule { - options = { - global = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Global keybindings."; - }; + normal = mkOption { + type = types.nullOr types.str; + default = null; + example = "'','', 'white','light red', 'white','#d66'"; + description = '' + How to display the tag when unfocused. + See <link xlink:href="https://alot.readthedocs.io/en/latest/configuration/theming.html#tagstring-formatting"/>. + ''; + }; - bufferlist = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Bufferlist mode keybindings."; - }; + focus = mkOption { + type = types.nullOr types.str; + default = null; + description = "How to display the tag when focused."; + }; + }; + }; - search = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Search mode keybindings."; - }; + accountStr = account: + with account; + concatStringsSep "\n" ([ "[[${name}]]" ] + ++ mapAttrsToList (n: v: n + "=" + v) ({ + address = address; + realname = realName; + sendmail_command = + optionalString (alot.sendMailCommand != null) alot.sendMailCommand; + sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent; + draft_box = "maildir" + "://" + maildir.absPath + "/" + folders.drafts; + } // optionalAttrs (aliases != [ ]) { + aliases = concatStringsSep "," aliases; + } // optionalAttrs (gpg != null) { + gpg_key = gpg.key; + encrypt_by_default = if gpg.encryptByDefault then "all" else "none"; + sign_by_default = boolStr gpg.signByDefault; + } // optionalAttrs (signature.showSignature != "none") { + signature = pkgs.writeText "signature.txt" signature.text; + signature_as_attachment = boolStr (signature.showSignature == "attach"); + }) ++ [ alot.extraConfig ] ++ [ "[[[abook]]]" ] + ++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion); + + configFile = let + bindingsToStr = attrSet: + concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet); + in '' + # Generated by Home Manager. + # See http://alot.readthedocs.io/en/latest/configuration/config_options.html + + ${generators.toKeyValue { inherit mkKeyValue; } cfg.settings} + ${cfg.extraConfig} + [tags] + '' + (let + submoduleToAttrs = m: + filterAttrs (name: v: name != "_module" && v != null) m; + in generators.toINI { mkSectionName = mk2ndLevelSectionName; } + (mapAttrs (name: x: submoduleToAttrs x) cfg.tags)) + '' + [bindings] + ${bindingsToStr cfg.bindings.global} + + [[bufferlist]] + ${bindingsToStr cfg.bindings.bufferlist} + [[search]] + ${bindingsToStr cfg.bindings.search} + [[envelope]] + ${bindingsToStr cfg.bindings.envelope} + [[taglist]] + ${bindingsToStr cfg.bindings.taglist} + [[thread]] + ${bindingsToStr cfg.bindings.thread} + + [accounts] + + ${concatStringsSep "\n\n" (map accountStr alotAccounts)} + ''; + +in { + options = { + programs.alot = { + enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to enable the Alot mail user agent. Alot uses the + Notmuch email system and will therefore be automatically + enabled for each email account that is managed by Notmuch. + ''; + }; - envelope = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Envelope mode keybindings."; - }; + hooks = mkOption { + type = types.lines; + default = ""; + description = '' + Content of the hooks file. + ''; + }; - taglist = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Taglist mode keybindings."; + bindings = mkOption { + type = types.submodule { + options = { + global = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Global keybindings."; + }; + + bufferlist = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Bufferlist mode keybindings."; + }; + + search = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Search mode keybindings."; + }; + + envelope = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Envelope mode keybindings."; + }; + + taglist = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Taglist mode keybindings."; + }; + + thread = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Thread mode keybindings."; + }; }; + }; + default = { }; + description = '' + Keybindings. + ''; + }; - thread = mkOption { - type = types.attrsOf types.str; - default = {}; - description = "Thread mode keybindings."; - }; + tags = mkOption { + type = types.attrsOf tagSubmodule; + default = { }; + description = "How to display the tags."; + }; + + settings = mkOption { + type = with types; + let primitive = either (either (either str int) bool) float; + in attrsOf primitive; + default = { + initial_command = "search tag:inbox AND NOT tag:killed"; + auto_remove_unread = true; + handle_mouse = true; + prefer_plaintext = true; }; + example = literalExample '' + { + auto_remove_unread = true; + ask_subject = false; + thread_indent_replies = 2; + } + ''; + description = '' + Configuration options added to alot configuration file. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra lines added to alot configuration file. + ''; }; - default = {}; - description = '' - Keybindings. - ''; }; - extraConfig = mkOption { - type = types.lines; - default = '' - auto_remove_unread = True - ask_subject = False - handle_mouse = True - initial_command = "search tag:inbox AND NOT tag:killed" - input_timeout = 0.3 - prefer_plaintext = True - thread_indent_replies = 4 - ''; - description = '' - Extra lines added to alot configuration file. - ''; + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./alot-accounts.nix pkgs)); }; }; config = mkIf cfg.enable { - home.packages = [ pkgs.alot ]; + home.packages = [ pkgs.alot ]; xdg.configFile."alot/config".text = configFile; - xdg.configFile."alot/hooks.py".text = - '' + xdg.configFile."alot/hooks.py" = mkIf (cfg.hooks != "") { + text = '' # Generated by Home Manager. - '' - + cfg.hooks; + '' + cfg.hooks; + }; }; } diff --git a/home-manager/modules/programs/aria2.nix b/home-manager/modules/programs/aria2.nix new file mode 100644 index 00000000000..d1317ff7616 --- /dev/null +++ b/home-manager/modules/programs/aria2.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.aria2; + + formatLine = n: v: + let + formatValue = v: + if builtins.isBool v then + (if v then "true" else "false") + else + toString v; + in "${n}=${formatValue v}"; +in { + meta.maintainers = [ hm.maintainers.justinlovinger ]; + + options.programs.aria2 = { + enable = mkEnableOption "aria2"; + + settings = mkOption { + type = with types; attrsOf (oneOf [ bool float int str ]); + default = { }; + description = '' + Options to add to <filename>aria2.conf</filename> file. + See + <citerefentry> + <refentrytitle>aria2c</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for options. + ''; + example = literalExample '' + { + listen-port = 60000; + dht-listen-port = 60000; + seed-ratio = 1.0; + max-upload-limit = "50K"; + ftp-pasv = true; + } + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra lines added to <filename>aria2.conf</filename> file. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.aria2 ]; + + xdg.configFile."aria2/aria2.conf".text = concatStringsSep "\n" ([ ] + ++ mapAttrsToList formatLine cfg.settings + ++ optional (cfg.extraConfig != "") cfg.extraConfig); + }; +} diff --git a/home-manager/modules/programs/astroid.nix b/home-manager/modules/programs/astroid.nix index 8b3762fac0b..af12b10edbb 100644 --- a/home-manager/modules/programs/astroid.nix +++ b/home-manager/modules/programs/astroid.nix @@ -98,6 +98,10 @@ in { ''; }; }; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./astroid-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/autorandr.nix b/home-manager/modules/programs/autorandr.nix index 02fc77c1e58..40cad704db9 100644 --- a/home-manager/modules/programs/autorandr.nix +++ b/home-manager/modules/programs/autorandr.nix @@ -55,6 +55,13 @@ let default = true; }; + crtc = mkOption { + type = types.nullOr types.ints.unsigned; + description = "Output video display controller."; + default = null; + example = 0; + }; + primary = mkOption { type = types.bool; description = "Whether output should be marked as primary"; @@ -244,21 +251,22 @@ let ]); fingerprintToString = name: edid: "${name} ${edid}"; configToString = name: config: - if config.enable then '' - output ${name} - ${optionalString (config.position != "") "pos ${config.position}"} - ${optionalString config.primary "primary"} - ${optionalString (config.dpi != null) "dpi ${toString config.dpi}"} - ${optionalString (config.gamma != "") "gamma ${config.gamma}"} - ${optionalString (config.mode != "") "mode ${config.mode}"} - ${optionalString (config.rate != "") "rate ${config.rate}"} - ${optionalString (config.rotate != null) "rotate ${config.rotate}"} - ${optionalString (config.scale != null) - ((if config.scale.method == "factor" then "scale" else "scale-from") - + " ${toString config.scale.x}x${toString config.scale.y}")} - ${optionalString (config.transform != null) ("transform " - + concatMapStringsSep "," toString (flatten config.transform))} - '' else '' + if config.enable then + concatStringsSep "\n" ([ "output ${name}" ] + ++ optional (config.position != "") "pos ${config.position}" + ++ optional (config.crtc != null) "crtc ${toString config.crtc}" + ++ optional config.primary "primary" + ++ optional (config.dpi != null) "dpi ${toString config.dpi}" + ++ optional (config.gamma != "") "gamma ${config.gamma}" + ++ optional (config.mode != "") "mode ${config.mode}" + ++ optional (config.rate != "") "rate ${config.rate}" + ++ optional (config.rotate != null) "rotate ${config.rotate}" + ++ optional (config.transform != null) ("transform " + + concatMapStringsSep "," toString (flatten config.transform)) + ++ optional (config.scale != null) + ((if config.scale.method == "factor" then "scale" else "scale-from") + + " ${toString config.scale.x}x${toString config.scale.y}")) + else '' output ${name} off ''; @@ -315,6 +323,7 @@ in { eDP1.enable = false; DP1 = { enable = true; + crtc = 0; primary = true; position = "0x0"; mode = "3840x2160"; diff --git a/home-manager/modules/programs/bash.nix b/home-manager/modules/programs/bash.nix index 82a9fbe8f8b..45fe368bddc 100644 --- a/home-manager/modules/programs/bash.nix +++ b/home-manager/modules/programs/bash.nix @@ -82,7 +82,12 @@ in shellAliases = mkOption { default = {}; type = types.attrsOf types.str; - example = { ll = "ls -l"; ".." = "cd .."; }; + example = literalExample '' + { + ll = "ls -l"; + ".." = "cd .."; + } + ''; description = '' An attribute set that maps aliases (the top level attribute names in this option) to command strings or directly to build outputs. @@ -171,10 +176,10 @@ in ${aliasesStr} - ${cfg.initExtra} - ${optionalString cfg.enableAutojump ". ${pkgs.autojump}/share/autojump/autojump.bash"} + + ${cfg.initExtra} fi ''; diff --git a/home-manager/modules/programs/bat.nix b/home-manager/modules/programs/bat.nix index aa0df9abd45..e2b30ea9333 100644 --- a/home-manager/modules/programs/bat.nix +++ b/home-manager/modules/programs/bat.nix @@ -24,14 +24,35 @@ in { ''; }; + themes = mkOption { + type = types.attrsOf types.lines; + default = { }; + example = literalExample '' + { + dracula = builtins.readFile (pkgs.fetchFromGitHub { + owner = "dracula"; + repo = "sublime"; # Bat uses sublime syntax for its themes + rev = "26c57ec282abcaa76e57e055f38432bd827ac34e"; + sha256 = "019hfl4zbn4vm4154hh3bwk6hm7bdxbr1hdww83nabxwjn99ndhv"; + } + "/Dracula.tmTheme"); + } + ''; + description = '' + Additional themes to provide. + ''; + }; + }; config = mkIf cfg.enable { home.packages = [ pkgs.bat ]; - xdg.configFile."bat/config" = mkIf (cfg.config != { }) { - text = concatStringsSep "\n" - (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config); - }; + xdg.configFile = mkMerge ([{ + "bat/config" = mkIf (cfg.config != { }) { + text = concatStringsSep "\n" + (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config); + }; + }] ++ flip mapAttrsToList cfg.themes + (name: body: { "bat/themes/${name}.tmTheme" = { text = body; }; })); }; } diff --git a/home-manager/modules/programs/broot.nix b/home-manager/modules/programs/broot.nix index eac31b56801..6951e035d32 100644 --- a/home-manager/modules/programs/broot.nix +++ b/home-manager/modules/programs/broot.nix @@ -172,11 +172,11 @@ in { xdg.configFile."broot/conf.toml".source = configFile brootConf; # Dummy file to prevent broot from trying to reinstall itself - xdg.configFile."broot/launcher/installed".text = ""; + xdg.configFile."broot/launcher/installed-v1".text = ""; programs.bash.initExtra = mkIf cfg.enableBashIntegration ( - # Using mkAfter to make it more likely to appear after other - # manipulations of the prompt. + # Using mkAfter to make it more likely to appear after other + # manipulations of the prompt. mkAfter '' # This script was automatically generated by the broot function # More information can be found in https://github.com/Canop/broot diff --git a/home-manager/modules/programs/dircolors.nix b/home-manager/modules/programs/dircolors.nix new file mode 100644 index 00000000000..026de72d711 --- /dev/null +++ b/home-manager/modules/programs/dircolors.nix @@ -0,0 +1,223 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.dircolors; + + formatLine = n: v: "${n} ${toString v}"; +in { + meta.maintainers = [ hm.maintainers.justinlovinger ]; + + options.programs.dircolors = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to manage <filename>.dir_colors</filename> + and set <code>LS_COLORS</code>. + ''; + }; + + enableBashIntegration = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable Bash integration. + ''; + }; + + enableFishIntegration = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable Fish integration. + ''; + }; + + enableZshIntegration = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable Zsh integration. + ''; + }; + + settings = mkOption { + type = with types; attrsOf str; + default = { }; + description = '' + Options to add to <filename>.dir_colors</filename> file. + See <command>dircolors --print-database</command> + for options. + ''; + example = literalExample '' + { + OTHER_WRITABLE = "30;46"; + ".sh" = "01;32"; + ".csh" = "01;32"; + } + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra lines added to <filename>.dir_colors</filename> file. + ''; + }; + }; + + config = mkIf cfg.enable { + # Add default settings from `dircolors --print-database`. + programs.dircolors.settings = { + RESET = mkDefault "0"; + DIR = mkDefault "01;34"; + LINK = mkDefault "01;36"; + MULTIHARDLINK = mkDefault "00"; + FIFO = mkDefault "40;33"; + SOCK = mkDefault "01;35"; + DOOR = mkDefault "01;35"; + BLK = mkDefault "40;33;01"; + CHR = mkDefault "40;33;01"; + ORPHAN = mkDefault "40;31;01"; + MISSING = mkDefault "00"; + SETUID = mkDefault "37;41"; + SETGID = mkDefault "30;43"; + CAPABILITY = mkDefault "30;41"; + STICKY_OTHER_WRITABLE = mkDefault "30;42"; + OTHER_WRITABLE = mkDefault "34;42"; + STICKY = mkDefault "37;44"; + EXEC = mkDefault "01;32"; + ".tar" = mkDefault "01;31"; + ".tgz" = mkDefault "01;31"; + ".arc" = mkDefault "01;31"; + ".arj" = mkDefault "01;31"; + ".taz" = mkDefault "01;31"; + ".lha" = mkDefault "01;31"; + ".lz4" = mkDefault "01;31"; + ".lzh" = mkDefault "01;31"; + ".lzma" = mkDefault "01;31"; + ".tlz" = mkDefault "01;31"; + ".txz" = mkDefault "01;31"; + ".tzo" = mkDefault "01;31"; + ".t7z" = mkDefault "01;31"; + ".zip" = mkDefault "01;31"; + ".z" = mkDefault "01;31"; + ".dz" = mkDefault "01;31"; + ".gz" = mkDefault "01;31"; + ".lrz" = mkDefault "01;31"; + ".lz" = mkDefault "01;31"; + ".lzo" = mkDefault "01;31"; + ".xz" = mkDefault "01;31"; + ".zst" = mkDefault "01;31"; + ".tzst" = mkDefault "01;31"; + ".bz2" = mkDefault "01;31"; + ".bz" = mkDefault "01;31"; + ".tbz" = mkDefault "01;31"; + ".tbz2" = mkDefault "01;31"; + ".tz" = mkDefault "01;31"; + ".deb" = mkDefault "01;31"; + ".rpm" = mkDefault "01;31"; + ".jar" = mkDefault "01;31"; + ".war" = mkDefault "01;31"; + ".ear" = mkDefault "01;31"; + ".sar" = mkDefault "01;31"; + ".rar" = mkDefault "01;31"; + ".alz" = mkDefault "01;31"; + ".ace" = mkDefault "01;31"; + ".zoo" = mkDefault "01;31"; + ".cpio" = mkDefault "01;31"; + ".7z" = mkDefault "01;31"; + ".rz" = mkDefault "01;31"; + ".cab" = mkDefault "01;31"; + ".wim" = mkDefault "01;31"; + ".swm" = mkDefault "01;31"; + ".dwm" = mkDefault "01;31"; + ".esd" = mkDefault "01;31"; + ".jpg" = mkDefault "01;35"; + ".jpeg" = mkDefault "01;35"; + ".mjpg" = mkDefault "01;35"; + ".mjpeg" = mkDefault "01;35"; + ".gif" = mkDefault "01;35"; + ".bmp" = mkDefault "01;35"; + ".pbm" = mkDefault "01;35"; + ".pgm" = mkDefault "01;35"; + ".ppm" = mkDefault "01;35"; + ".tga" = mkDefault "01;35"; + ".xbm" = mkDefault "01;35"; + ".xpm" = mkDefault "01;35"; + ".tif" = mkDefault "01;35"; + ".tiff" = mkDefault "01;35"; + ".png" = mkDefault "01;35"; + ".svg" = mkDefault "01;35"; + ".svgz" = mkDefault "01;35"; + ".mng" = mkDefault "01;35"; + ".pcx" = mkDefault "01;35"; + ".mov" = mkDefault "01;35"; + ".mpg" = mkDefault "01;35"; + ".mpeg" = mkDefault "01;35"; + ".m2v" = mkDefault "01;35"; + ".mkv" = mkDefault "01;35"; + ".webm" = mkDefault "01;35"; + ".ogm" = mkDefault "01;35"; + ".mp4" = mkDefault "01;35"; + ".m4v" = mkDefault "01;35"; + ".mp4v" = mkDefault "01;35"; + ".vob" = mkDefault "01;35"; + ".qt" = mkDefault "01;35"; + ".nuv" = mkDefault "01;35"; + ".wmv" = mkDefault "01;35"; + ".asf" = mkDefault "01;35"; + ".rm" = mkDefault "01;35"; + ".rmvb" = mkDefault "01;35"; + ".flc" = mkDefault "01;35"; + ".avi" = mkDefault "01;35"; + ".fli" = mkDefault "01;35"; + ".flv" = mkDefault "01;35"; + ".gl" = mkDefault "01;35"; + ".dl" = mkDefault "01;35"; + ".xcf" = mkDefault "01;35"; + ".xwd" = mkDefault "01;35"; + ".yuv" = mkDefault "01;35"; + ".cgm" = mkDefault "01;35"; + ".emf" = mkDefault "01;35"; + ".ogv" = mkDefault "01;35"; + ".ogx" = mkDefault "01;35"; + ".aac" = mkDefault "00;36"; + ".au" = mkDefault "00;36"; + ".flac" = mkDefault "00;36"; + ".m4a" = mkDefault "00;36"; + ".mid" = mkDefault "00;36"; + ".midi" = mkDefault "00;36"; + ".mka" = mkDefault "00;36"; + ".mp3" = mkDefault "00;36"; + ".mpc" = mkDefault "00;36"; + ".ogg" = mkDefault "00;36"; + ".ra" = mkDefault "00;36"; + ".wav" = mkDefault "00;36"; + ".oga" = mkDefault "00;36"; + ".opus" = mkDefault "00;36"; + ".spx" = mkDefault "00;36"; + ".xspf" = mkDefault "00;36"; + }; + + home.file.".dir_colors".text = concatStringsSep "\n" ([ ] + ++ optional (cfg.extraConfig != "") cfg.extraConfig + ++ mapAttrsToList formatLine cfg.settings) + "\n"; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors) + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + eval (${pkgs.coreutils}/bin/dircolors -c ~/.dir_colors) + ''; + + # Set `LS_COLORS` before Oh My Zsh and `initExtra`. + programs.zsh.initExtraBeforeCompInit = mkIf cfg.enableZshIntegration '' + eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors) + ''; + }; +} diff --git a/home-manager/modules/programs/direnv.nix b/home-manager/modules/programs/direnv.nix index beb40a96261..1d1374b8e26 100644 --- a/home-manager/modules/programs/direnv.nix +++ b/home-manager/modules/programs/direnv.nix @@ -70,6 +70,11 @@ in { Whether to enable Fish integration. ''; }; + + enableNixDirenvIntegration = mkEnableOption '' + <link + xlink:href="https://github.com/nix-community/nix-direnv">nix-direnv</link>, + a fast, persistent use_nix implementation for direnv''; }; config = mkIf cfg.enable { @@ -78,12 +83,15 @@ in { xdg.configFile."direnv/config.toml" = mkIf (cfg.config != { }) { source = configFile cfg.config; }; - xdg.configFile."direnv/direnvrc" = - mkIf (cfg.stdlib != "") { text = cfg.stdlib; }; + xdg.configFile."direnv/direnvrc" = let + text = concatStringsSep "\n" (optional (cfg.stdlib != "") cfg.stdlib + ++ optional cfg.enableNixDirenvIntegration + "source ${pkgs.nix-direnv}/share/nix-direnv/direnvrc"); + in mkIf (text != "") { inherit text; }; programs.bash.initExtra = mkIf cfg.enableBashIntegration ( - # Using mkAfter to make it more likely to appear after other - # manipulations of the prompt. + # Using mkAfter to make it more likely to appear after other + # manipulations of the prompt. mkAfter '' eval "$(${pkgs.direnv}/bin/direnv hook bash)" ''); diff --git a/home-manager/modules/programs/eclipse.nix b/home-manager/modules/programs/eclipse.nix index 8ce605b106a..21973ab937e 100644 --- a/home-manager/modules/programs/eclipse.nix +++ b/home-manager/modules/programs/eclipse.nix @@ -13,6 +13,16 @@ in { programs.eclipse = { enable = mkEnableOption "Eclipse"; + package = mkOption { + type = types.package; + default = pkgs.eclipses.eclipse-platform; + defaultText = literalExample "pkgs.eclipses.eclipse-platform"; + example = literalExample "pkgs.eclipses.eclipse-java"; + description = '' + The Eclipse package to install. + ''; + }; + enableLombok = mkOption { type = types.bool; default = false; @@ -40,7 +50,7 @@ in { config = mkIf cfg.enable { home.packages = [ (pkgs.eclipses.eclipseWithPlugins { - eclipse = pkgs.eclipses.eclipse-platform; + eclipse = cfg.package; jvmArgs = cfg.jvmArgs ++ optional cfg.enableLombok "-javaagent:${pkgs.lombok}/share/java/lombok.jar"; plugins = cfg.plugins; diff --git a/home-manager/modules/programs/emacs.nix b/home-manager/modules/programs/emacs.nix index 987a9f2431e..b785f71358c 100644 --- a/home-manager/modules/programs/emacs.nix +++ b/home-manager/modules/programs/emacs.nix @@ -8,16 +8,12 @@ let # Copied from all-packages.nix, with modifications to support # overrides. - emacsPackages = - let - epkgs = pkgs.emacsPackagesGen cfg.package; - in - epkgs.overrideScope' cfg.overrides; - emacsWithPackages = emacsPackages.emacsWithPackages; + emacsPackages = let epkgs = pkgs.emacsPackagesFor cfg.package; + in epkgs.overrideScope' cfg.overrides; -in + emacsWithPackages = emacsPackages.emacsWithPackages; -{ +in { meta.maintainers = [ maintainers.rycee ]; options = { @@ -33,7 +29,7 @@ in }; extraPackages = mkOption { - default = self: []; + default = self: [ ]; type = hm.types.selectorFunction; defaultText = "epkgs: []"; example = literalExample "epkgs: [ epkgs.emms epkgs.magit ]"; @@ -45,7 +41,7 @@ in }; overrides = mkOption { - default = self: super: {}; + default = self: super: { }; type = hm.types.overlayFunction; defaultText = "self: super: {}"; example = literalExample '' diff --git a/home-manager/modules/programs/firefox.nix b/home-manager/modules/programs/firefox.nix index 17c64752d66..d5003f59edc 100644 --- a/home-manager/modules/programs/firefox.nix +++ b/home-manager/modules/programs/firefox.nix @@ -23,8 +23,15 @@ let then "${firefoxConfigPath}/Profiles" else firefoxConfigPath; + # The extensions path shared by all profiles; will not be supported + # by future Firefox versions. extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; + extensionsEnvPkg = pkgs.buildEnv { + name = "hm-firefox-extensions"; + paths = cfg.extensions; + }; + profiles = flip mapAttrs' cfg.profiles (_: profile: nameValuePair "Profile${toString profile.id}" { @@ -59,6 +66,13 @@ in { meta.maintainers = [ maintainers.rycee ]; + imports = [ + (mkRemovedOptionModule ["programs" "firefox" "enableGoogleTalk"] + "Support for this option has been removed.") + (mkRemovedOptionModule ["programs" "firefox" "enableIcedTea"] + "Support for this option has been removed.") + ]; + options = { programs.firefox = { enable = mkEnableOption "Firefox"; @@ -87,9 +101,29 @@ in ] ''; description = '' - List of Firefox add-on packages to install. Note, it is - necessary to manually enable these extensions inside Firefox - after the first installation. + List of Firefox add-on packages to install. Some + pre-packaged add-ons are accessible from NUR, + <link xlink:href="https://github.com/nix-community/NUR"/>. + Once you have NUR installed run + + <screen language="console"> + <prompt>$</prompt> <userinput>nix-env -f '<nixpkgs>' -qaP -A nur.repos.rycee.firefox-addons</userinput> + </screen> + + to list the available Firefox add-ons. + + </para><para> + + Note that it is necessary to manually enable these + extensions inside Firefox after the first installation. + + </para><para> + + Extensions listed here will only be available in Firefox + profiles managed through the + <link linkend="opt-programs.firefox.profiles">programs.firefox.profiles</link> + option. This is due to recent changes in the way Firefox + handles extension side-loading. ''; }; @@ -137,7 +171,7 @@ in userChrome = mkOption { type = types.lines; default = ""; - description = "Custom Firefox CSS."; + description = "Custom Firefox user chrome CSS."; example = '' /* Hide tab bar in FF Quantum */ @-moz-document url("chrome://browser/content/browser.xul") { @@ -153,6 +187,16 @@ in ''; }; + userContent = mkOption { + type = types.lines; + default = ""; + description = "Custom Firefox user content CSS."; + example = '' + /* Hide scrollbar in FF Quantum */ + *{scrollbar-width:none !important} + ''; + }; + path = mkOption { type = types.str; default = name; @@ -176,38 +220,6 @@ in default = false; description = "Whether to enable the unfree Adobe Flash plugin."; }; - - enableGoogleTalk = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the unfree Google Talk plugin. This option - is <emphasis>deprecated</emphasis> and will only work if - - <programlisting language="nix"> - programs.firefox.package = pkgs.firefox-esr-52-unwrapped; - </programlisting> - - and the <option>plugin.load_flash_only</option> Firefox - option has been disabled. - ''; - }; - - enableIcedTea = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the Java applet plugin. This option is - <emphasis>deprecated</emphasis> and will only work if - - <programlisting language="nix"> - programs.firefox.package = pkgs.firefox-esr-52-unwrapped; - </programlisting> - - and the <option>plugin.load_flash_only</option> Firefox - option has been disabled. - ''; - }; }; }; @@ -250,8 +262,6 @@ in # The configuration expected by the Firefox wrapper. fcfg = { enableAdobeFlash = cfg.enableAdobeFlash; - enableGoogleTalkPlugin = cfg.enableGoogleTalk; - icedtea = cfg.enableIcedTea; }; # A bit of hackery to force a config into the wrapper. @@ -273,17 +283,10 @@ in home.file = mkMerge ( [{ - "${mozillaConfigPath}/${extensionPath}" = mkIf (cfg.extensions != []) ( - let - extensionsEnv = pkgs.buildEnv { - name = "hm-firefox-extensions"; - paths = cfg.extensions; - }; - in { - source = "${extensionsEnv}/share/mozilla/${extensionPath}"; - recursive = true; - } - ); + "${mozillaConfigPath}/${extensionPath}" = mkIf (cfg.extensions != []) { + source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; + recursive = true; + }; "${firefoxConfigPath}/profiles.ini" = mkIf (cfg.profiles != {}) { text = profilesIni; @@ -295,10 +298,21 @@ in text = profile.userChrome; }; + "${profilesPath}/${profile.path}/chrome/userContent.css" = + mkIf (profile.userContent != "") { + text = profile.userContent; + }; + "${profilesPath}/${profile.path}/user.js" = mkIf (profile.settings != {} || profile.extraConfig != "") { text = mkUserJs profile.settings profile.extraConfig; }; + + "${profilesPath}/${profile.path}/extensions" = mkIf (cfg.extensions != []) { + source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}"; + recursive = true; + force = true; + }; }) ); }; 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)); + }) + ]); } diff --git a/home-manager/modules/programs/fzf.nix b/home-manager/modules/programs/fzf.nix index 36eb3a1cdba..3aee57768ea 100644 --- a/home-manager/modules/programs/fzf.nix +++ b/home-manager/modules/programs/fzf.nix @@ -100,6 +100,14 @@ in { Whether to enable Zsh integration. ''; }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; }; config = mkIf cfg.enable { @@ -130,5 +138,9 @@ in { . ${pkgs.fzf}/share/fzf/key-bindings.zsh fi ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + source ${pkgs.fzf}/share/fzf/key-bindings.fish && fzf_key_bindings + ''; }; } diff --git a/home-manager/modules/programs/getmail.nix b/home-manager/modules/programs/getmail.nix index 2c3919dcf2f..f83c469ff24 100644 --- a/home-manager/modules/programs/getmail.nix +++ b/home-manager/modules/programs/getmail.nix @@ -49,6 +49,12 @@ let ".getmail/getmail${if a.primary then "rc" else a.name}"; in { + options = { + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./getmail-accounts.nix)); + }; + }; + config = mkIf getmailEnabled { home.file = foldl' (a: b: a // b) { } (map (a: { "${renderConfigFilepath a}".text = renderAccountConfig a; }) diff --git a/home-manager/modules/programs/git.nix b/home-manager/modules/programs/git.nix index a56aa10d50e..312269de316 100644 --- a/home-manager/modules/programs/git.nix +++ b/home-manager/modules/programs/git.nix @@ -19,10 +19,20 @@ let else ''${section} "${subsection}"''; + mkValueString = v: + let + escapedV = '' + "${ + replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v + }"''; + in generators.mkValueStringDefault { } (if isString v then escapedV else v); + # generation for multiple ini values mkKeyValue = k: v: - let mkKeyValue = generators.mkKeyValueDefault { } "=" k; - in concatStringsSep "\n" (map mkKeyValue (toList v)); + let + mkKeyValue = + generators.mkKeyValueDefault { inherit mkValueString; } " = " k; + in concatStringsSep "\n" (map (kv: " " + mkKeyValue kv) (toList v)); # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI gitFlattenAttrs = let @@ -205,6 +215,36 @@ in { ''; }; }; + + delta = { + enable = mkEnableOption "" // { + description = '' + Whether to enable the <command>delta</command> syntax highlighter. + See <link xlink:href="https://github.com/dandavison/delta" />. + ''; + }; + + options = mkOption { + type = with types; + let + primitiveType = either str (either bool int); + sectionType = attrsOf primitiveType; + in attrsOf (either primitiveType sectionType); + default = { }; + example = { + features = "decorations"; + whitespace-error-style = "22 reverse"; + decorations = { + commit-decoration-style = "bold yellow box ul"; + file-style = "bold yellow ul"; + file-decoration-style = "none"; + }; + }; + description = '' + Options to configure delta. + ''; + }; + }; }; }; @@ -237,7 +277,14 @@ in { genIdentity = name: account: with account; nameValuePair "sendemail.${name}" ({ - smtpEncryption = if smtp.tls.enable then "tls" else ""; + smtpEncryption = if smtp.tls.enable then + (if smtp.tls.useStartTls + || versionOlder config.home.stateVersion "20.09" then + "tls" + else + "ssl") + else + ""; smtpServer = smtp.host; smtpUser = userName; from = address; @@ -299,5 +346,15 @@ in { ([ "git-lfs" "smudge" ] ++ skipArg ++ [ "--" "%f" ]); }; }) + + (mkIf cfg.delta.enable { + programs.git.iniContent = + let deltaCommand = "${pkgs.gitAndTools.delta}/bin/delta"; + in { + core.pager = deltaCommand; + interactive.diffFilter = "${deltaCommand} --color-only"; + delta = cfg.delta.options; + }; + }) ]); } diff --git a/home-manager/modules/programs/gnome-terminal.nix b/home-manager/modules/programs/gnome-terminal.nix index 570a1fc7df0..f1b15862130 100644 --- a/home-manager/modules/programs/gnome-terminal.nix +++ b/home-manager/modules/programs/gnome-terminal.nix @@ -6,11 +6,6 @@ let cfg = config.programs.gnome-terminal; - vteInitStr = '' - # gnome-terminal: Show current directory in the terminal window title. - . ${pkgs.gnome3.vte}/etc/profile.d/vte.sh - ''; - backForeSubModule = types.submodule ({ ... }: { options = { foreground = mkOption { @@ -81,6 +76,12 @@ let description = "The terminal colors, null to use system default."; }; + cursorBlinkMode = mkOption { + default = "system"; + type = types.enum [ "system" "on" "off" ]; + description = "The cursor blink mode."; + }; + cursorShape = mkOption { default = "block"; type = types.enum [ "block" "ibeam" "underline" ]; @@ -121,6 +122,20 @@ let The number of scrollback lines to keep, null for infinite. ''; }; + + customCommand = mkOption { + default = null; + type = types.nullOr types.str; + description = '' + The command to use to start the shell, or null for default shell. + ''; + }; + + loginShell = mkOption { + default = false; + type = types.bool; + description = "Run command as a login shell."; + }; }; }); @@ -130,7 +145,14 @@ let scrollbar-policy = if pcfg.showScrollbar then "always" else "never"; scrollback-lines = pcfg.scrollbackLines; cursor-shape = pcfg.cursorShape; - } // (if (pcfg.font == null) then { + cursor-blink-mode = pcfg.cursorBlinkMode; + login-shell = pcfg.loginShell; + } // (if (pcfg.customCommand != null) then { + use-custom-command = true; + custom-command = pcfg.customCommand; + } else { + use-custom-command = false; + }) // (if (pcfg.font == null) then { use-system-font = true; } else { use-system-font = false; @@ -179,7 +201,7 @@ in { themeVariant = mkOption { default = "default"; - type = types.enum [ "default" "light" "dark" ]; + type = types.enum [ "default" "light" "dark" "system" ]; description = "The theme variation to request"; }; @@ -192,7 +214,7 @@ in { }; config = mkIf cfg.enable { - home.packages = [ pkgs.gnome3.gnome_terminal ]; + home.packages = [ pkgs.gnome3.gnome-terminal ]; dconf.settings = let dconfPath = "org/gnome/terminal/legacy"; in { @@ -210,7 +232,7 @@ in { (n: v: nameValuePair ("${dconfPath}/profiles:/:${n}") (buildProfileSet v)) cfg.profile; - programs.bash.initExtra = mkBefore vteInitStr; - programs.zsh.initExtra = vteInitStr; + programs.bash.enableVteIntegration = true; + programs.zsh.enableVteIntegration = true; }; } diff --git a/home-manager/modules/programs/go.nix b/home-manager/modules/programs/go.nix index 983769d26af..4b85ec854ad 100644 --- a/home-manager/modules/programs/go.nix +++ b/home-manager/modules/programs/go.nix @@ -62,6 +62,18 @@ in { example = ".local/bin.go"; description = "GOBIN relative to HOME"; }; + + goPrivate = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "*.corp.example.com" "rsc.io/private" ]; + description = '' + The <envar>GOPRIVATE</envar> environment variable controls + which modules the go command considers to be private (not + available publicly) and should therefore not use the proxy + or checksum database. + ''; + }; }; }; @@ -85,5 +97,9 @@ in { home.sessionVariables.GOBIN = builtins.toPath "${config.home.homeDirectory}/${cfg.goBin}"; }) + + (mkIf (cfg.goPrivate != [ ]) { + home.sessionVariables.GOPRIVATE = concatStringsSep "," cfg.goPrivate; + }) ]); } diff --git a/home-manager/modules/programs/htop.nix b/home-manager/modules/programs/htop.nix index 84966040534..1fb397cdc38 100644 --- a/home-manager/modules/programs/htop.nix +++ b/home-manager/modules/programs/htop.nix @@ -61,6 +61,9 @@ let CGROUP = 112; OOM = 113; IO_PRIORITY = 114; + M_PSS = 118; + M_SWAP = 119; + M_PSSWP = 120; }; # Mapping from names to defaults @@ -76,16 +79,32 @@ let Hostname = 2; AllCPUs = 1; AllCPUs2 = 1; + AllCPUs4 = 1; LeftCPUs = 1; RightCPUs = 1; + Right = 1; + CPUs = 1; LeftCPUs2 = 1; RightCPUs2 = 1; + LeftCPUs4 = 1; + RightCPUs4 = 1; Blank = 2; + PressureStallCPUSome = 2; + PressureStallIOSome = 2; + PressureStallIOFull = 2; + PressureStallMemorySome = 2; + PressureStallMemoryFull = 2; + ZFSARC = 2; + ZFSCARC = 2; CPU = 1; "CPU(1)" = 1; "CPU(2)" = 1; "CPU(3)" = 1; "CPU(4)" = 1; + "CPU(5)" = 1; + "CPU(6)" = 1; + "CPU(7)" = 1; + "CPU(8)" = 1; }; singleMeterType = let @@ -268,6 +287,18 @@ in { description = "Count CPUs from 0 instead of 1."; }; + showCpuUsage = mkOption { + type = types.bool; + default = false; + description = "Show CPU usage frequency."; + }; + + showCpuFrequency = mkOption { + type = types.bool; + default = false; + description = "Show CPU frequency."; + }; + updateProcessNames = mkOption { type = types.bool; default = false; @@ -287,6 +318,12 @@ in { description = "Which color scheme to use."; }; + enableMouse = mkOption { + type = types.bool; + default = true; + description = "Enable mouse support."; + }; + delay = mkOption { type = types.int; default = 15; @@ -328,6 +365,11 @@ in { type = meterType; }; + vimMode = mkOption { + type = types.bool; + default = false; + description = "Vim key bindings."; + }; }; config = mkIf cfg.enable { @@ -357,14 +399,18 @@ in { header_margin=${bool cfg.headerMargin} detailed_cpu_time=${bool cfg.detailedCpuTime} cpu_count_from_zero=${bool cfg.cpuCountFromZero} + show_cpu_usage=${bool cfg.showCpuUsage} + show_cpu_frequency=${bool cfg.showCpuFrequency} update_process_names=${bool cfg.updateProcessNames} account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter} color_scheme=${toString cfg.colorScheme} + enable_mouse=${bool cfg.enableMouse} delay=${toString cfg.delay} left_meters=${list leftMeters} left_meter_modes=${list leftModes} right_meters=${list rightMeters} right_meter_modes=${list rightModes} + vim_mode=${bool cfg.vimMode} ''; }; } diff --git a/home-manager/modules/programs/i3status.nix b/home-manager/modules/programs/i3status.nix new file mode 100644 index 00000000000..c1e12fe71d7 --- /dev/null +++ b/home-manager/modules/programs/i3status.nix @@ -0,0 +1,208 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.i3status; + + enabledModules = filterAttrs (n: v: v.enable) cfg.modules; + + formatOrder = n: ''order += "${n}"''; + + formatModule = n: v: + let + formatLine = n: v: + let + formatValue = v: + if isBool v then + (if v then "true" else "false") + else if isString v then + ''"${v}"'' + else + toString v; + in "${n} = ${formatValue v}"; + in '' + ${n} { + ${concatStringsSep "\n " (mapAttrsToList formatLine v)} + } + ''; + + settingsType = with types; attrsOf (oneOf [ bool int str ]); + + sortAttrNamesByPosition = comparator: set: + let pos = n: set."${n}".position; + in sort (a: b: comparator (pos a) (pos b)) (attrNames set); +in { + meta.maintainers = [ hm.maintainers.justinlovinger ]; + + options.programs.i3status = { + enable = mkEnableOption "i3status"; + + enableDefault = mkOption { + type = types.bool; + default = true; + description = '' + Whether or not to enable + the default configuration. + ''; + }; + + general = mkOption { + type = settingsType; + default = { }; + description = '' + Configuration to add to i3status <filename>config</filename> + <code>general</code> section. + See + <citerefentry> + <refentrytitle>i3status</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for options. + ''; + example = literalExample '' + { + colors = true; + color_good = "#e0e0e0"; + color_degraded = "#d7ae00"; + color_bad = "#f69d6a"; + interval = 1; + } + ''; + }; + + modules = mkOption { + type = types.attrsOf (types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether or not to enable this module. + ''; + }; + position = mkOption { + type = with types; either int float; + description = '' + Position of this module in i3status <code>order</code>. + ''; + }; + settings = mkOption { + type = settingsType; + default = { }; + description = '' + Configuration to add to this i3status module. + See + <citerefentry> + <refentrytitle>i3status</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for options. + ''; + example = literalExample '' + { + format = "♪ %volume"; + format_muted = "♪ muted (%volume)"; + device = "pulse:1"; + } + ''; + }; + }; + }); + default = { }; + description = '' + Modules to add to i3status <filename>config</filename> file. + See + <citerefentry> + <refentrytitle>i3status</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for options. + ''; + example = literalExample '' + { + "volume master" = { + position = 1; + settings = { + format = "♪ %volume"; + format_muted = "♪ muted (%volume)"; + device = "pulse:1"; + }; + }; + "disk /" = { + position = 2; + settings = { + format = "/ %avail"; + }; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + programs.i3status = mkIf cfg.enableDefault { + general = { + colors = mkDefault true; + interval = mkDefault 5; + }; + + modules = { + ipv6 = { position = mkDefault 1; }; + + "wireless _first_" = { + position = mkDefault 2; + settings = { + format_up = mkDefault "W: (%quality at %essid) %ip"; + format_down = mkDefault "W: down"; + }; + }; + + "ethernet _first_" = { + position = mkDefault 3; + settings = { + format_up = mkDefault "E: %ip (%speed)"; + format_down = mkDefault "E: down"; + }; + }; + + "battery all" = { + position = mkDefault 4; + settings = { format = mkDefault "%status %percentage %remaining"; }; + }; + + "disk /" = { + position = mkDefault 5; + settings = { format = mkDefault "%avail"; }; + }; + + load = { + position = mkDefault 6; + settings = { format = mkDefault "%1min"; }; + }; + + memory = { + position = mkDefault 7; + settings = { + format = mkDefault "%used | %available"; + threshold_degraded = mkDefault "1G"; + format_degraded = mkDefault "MEMORY < %available"; + }; + }; + + "tztime local" = { + position = mkDefault 8; + settings = { format = mkDefault "%Y-%m-%d %H:%M:%S"; }; + }; + }; + }; + + home.packages = [ pkgs.i3status ]; + + xdg.configFile."i3status/config".text = concatStringsSep "\n" ([ ] + ++ optional (cfg.general != { }) (formatModule "general" cfg.general) + ++ map formatOrder (sortAttrNamesByPosition lessThan enabledModules) + ++ mapAttrsToList formatModule + (mapAttrs (n: v: v.settings) enabledModules)); + }; +} diff --git a/home-manager/modules/programs/info.nix b/home-manager/modules/programs/info.nix index 9e4a5d4aaff..a7d2692b515 100644 --- a/home-manager/modules/programs/info.nix +++ b/home-manager/modules/programs/info.nix @@ -1,22 +1,21 @@ -# info.nix -- install texinfo, set INFOPATH, create `dir` file +# info.nix -- install texinfo and create `dir` file # This is a helper for the GNU info documentation system. By default, # the `info` command (and the Info subsystem within Emacs) gives easy # access to the info files stored system-wide, but not info files in # your ~/.nix-profile. -# We set $INFOPATH to include `/run/current-system/sw/share/info` and -# `~/.nix-profile/share/info` but it's not enough. Although info can -# then find files when you explicitly ask for them, it doesn't show -# them to you in the table of contents on startup. To do that requires -# a `dir` file. NixOS keeps the system-wide `dir` file up to date, but -# ignores home-installed packages. +# Specifically, although info can then find files when you explicitly +# ask for them, it doesn't show them to you in the table of contents +# on startup. To do that requires a `dir` file. NixOS keeps the +# system-wide `dir` file up to date, but ignores files installed in +# user profiles. -# So this module contains an activation script that generates the -# `dir` for your home profile. Then when you start info (and both -# `dir` files are in your $INFOPATH), it will *merge* the contents of -# the two files, showing you a unified table of contents for all -# packages. This is really nice. +# This module contains extra profile commands that generate the `dir` +# for your home profile. Then when you start info (and both `dir` +# files are in your $INFOPATH), it will *merge* the contents of the +# two files, showing you a unified table of contents for all packages. +# This is really nice. { config, lib, pkgs, ... }: @@ -26,50 +25,39 @@ let cfg = config.programs.info; - # Indexes info files found in this location - homeInfoPath = "${config.home.profileDirectory}/share/info"; - # Installs this package -- the interactive just means that it # includes the curses `info` program. We also use `install-info` # from this package in the activation script. infoPkg = pkgs.texinfoInteractive; in { - options = { - programs.info = { - enable = mkEnableOption "GNU Info"; + imports = [ + (mkRemovedOptionModule [ "programs" "info" "homeInfoDirLocation" ] '' + The `dir` file is now generated as part of the Home Manager profile and + will no longer be placed in your home directory. + '') + ]; - homeInfoDirLocation = mkOption { - default = "\${XDG_CACHE_HOME:-$HOME/.cache}/info"; - description = '' - Directory in which to store the info <filename>dir</filename> - file within your home. - ''; - }; - }; - }; + options.programs.info.enable = mkEnableOption "GNU Info"; config = mkIf cfg.enable { - home.sessionVariables.INFOPATH = - "${cfg.homeInfoDirLocation}\${INFOPATH:+:}\${INFOPATH}"; + home.packages = [ + infoPkg - home.activation.createHomeInfoDir = - hm.dag.entryAfter [ "installPackages" ] '' - oPATH=$PATH - export PATH="${lib.makeBinPath [ pkgs.gzip ]}''${PATH:+:}$PATH" - $DRY_RUN_CMD mkdir -p "${cfg.homeInfoDirLocation}" - $DRY_RUN_CMD rm -f "${cfg.homeInfoDirLocation}/dir" - if [[ -d "${homeInfoPath}" ]]; then - find -L "${homeInfoPath}" \( -name '*.info' -o -name '*.info.gz' \) \ - -exec $DRY_RUN_CMD ${infoPkg}/bin/install-info '{}' \ - "${cfg.homeInfoDirLocation}/dir" \; - fi - export PATH="$oPATH" - unset oPATH - ''; - - home.packages = [ infoPkg ]; + # Make sure the target directory is a real directory. + (pkgs.runCommandLocal "dummy-info-dir1" { } "mkdir -p $out/share/info") + (pkgs.runCommandLocal "dummy-info-dir2" { } "mkdir -p $out/share/info") + ]; home.extraOutputsToInstall = [ "info" ]; + + home.extraProfileCommands = let infoPath = "$out/share/info"; + in '' + if [[ -w "${infoPath}" && ! -e "${infoPath}/dir" ]]; then + PATH="${lib.makeBinPath [ pkgs.gzip infoPkg ]}''${PATH:+:}$PATH" \ + find -L "${infoPath}" \( -name '*.info' -o -name '*.info.gz' \) \ + -exec install-info '{}' "${infoPath}/dir" ';' + fi + ''; }; } diff --git a/home-manager/modules/programs/kakoune.nix b/home-manager/modules/programs/kakoune.nix index faf2542dc70..6db311a1376 100644 --- a/home-manager/modules/programs/kakoune.nix +++ b/home-manager/modules/programs/kakoune.nix @@ -49,6 +49,7 @@ let "InsertCompletionShow" "InsertCompletionHide" "InsertCompletionSelect" + "ModuleLoaded" ]; example = "SetOption"; description = '' @@ -96,16 +97,7 @@ let keyMapping = types.submodule { options = { mode = mkOption { - type = types.enum [ - "insert" - "normal" - "prompt" - "menu" - "user" - "goto" - "view" - "object" - ]; + type = types.str; example = "user"; description = '' The mode in which the mapping takes effect. @@ -497,6 +489,10 @@ let }; }; + kakouneWithPlugins = pkgs.wrapKakoune pkgs.kakoune-unwrapped { + configure = { plugins = cfg.plugins; }; + }; + configFile = let wrapOptions = with cfg.config.wrapLines; concatStrings [ @@ -513,6 +509,25 @@ let "${optionalString (separator != null) " -separator ${separator}"}" ]; + showWhitespaceOptions = with cfg.config.showWhitespace; + let + quoteSep = sep: + if sep == "'" then + ''"'"'' + else if lib.strings.stringLength sep == 1 then + "'${sep}'" + else + sep; # backwards compat, in case sep == "' '", etc. + + in concatStrings [ + (optionalString (tab != null) " -tab ${quoteSep tab}") + (optionalString (tabStop != null) " -tabpad ${quoteSep tabStop}") + (optionalString (space != null) " -spc ${quoteSep space}") + (optionalString (nonBreakingSpace != null) + " -nbsp ${quoteSep nonBreakingSpace}") + (optionalString (lineFeed != null) " -lf ${quoteSep lineFeed}") + ]; + uiOptions = with cfg.config.ui; concatStringsSep " " [ "ncurses_set_title=${if setTitle then "true" else "false"}" @@ -533,6 +548,21 @@ let }" ]; + userModeString = mode: + optionalString (!builtins.elem mode [ + "insert" + "normal" + "prompt" + "menu" + "user" + "goto" + "view" + "object" + ]) "try %{declare-user-mode ${mode}}"; + + userModeStrings = map userModeString + (lists.unique (map (km: km.mode) cfg.config.keyMappings)); + keyMappingString = km: concatStringsSep " " [ "map global" @@ -566,12 +596,14 @@ let ++ optional (autoComplete != null) "set-option global autocomplete ${concatStringsSep "|" autoComplete}" ++ optional (autoReload != null) - "set-option global/ autoreload ${autoReload}" + "set-option global autoreload ${autoReload}" ++ optional (wrapLines != null && wrapLines.enable) "add-highlighter global/ wrap${wrapOptions}" ++ optional (numberLines != null && numberLines.enable) "add-highlighter global/ number-lines${numberLinesOptions}" ++ optional showMatching "add-highlighter global/ show-matching" + ++ optional (showWhitespace != null && showWhitespace.enable) + "add-highlighter global/ show-whitespaces${showWhitespaceOptions}" ++ optional (scrollOff != null) "set-option global scrolloff ${toString scrollOff.lines},${ toString scrollOff.columns @@ -580,7 +612,8 @@ let ++ [ "# UI options" ] ++ optional (ui != null) "set-option global ui_options ${uiOptions}" - ++ [ "# Key mappings" ] ++ map keyMappingString keyMappings + ++ [ "# User modes" ] ++ userModeStrings ++ [ "# Key mappings" ] + ++ map keyMappingString keyMappings ++ [ "# Hooks" ] ++ map hookString hooks); in pkgs.writeText "kakrc" @@ -605,11 +638,22 @@ in { <filename>~/.config/kak/kakrc</filename>. ''; }; + + plugins = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExample "[ pkgs.kakounePlugins.kak-fzf ]"; + description = '' + List of kakoune plugins to install. To get a list of + supported plugins run: + <command>nix-env -f '<nixpkgs>' -qaP -A kakounePlugins</command>. + ''; + }; }; }; config = mkIf cfg.enable { - home.packages = [ pkgs.kakoune ]; + home.packages = [ kakouneWithPlugins ]; xdg.configFile."kak/kakrc".source = configFile; }; } diff --git a/home-manager/modules/programs/kitty.nix b/home-manager/modules/programs/kitty.nix new file mode 100644 index 00000000000..313a0bfadd7 --- /dev/null +++ b/home-manager/modules/programs/kitty.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.kitty; + + eitherStrBoolInt = with types; either str (either bool int); + + optionalPackage = opt: + optional (opt != null && opt.package != null) opt.package; + + toKittyConfig = generators.toKeyValue { + mkKeyValue = key: value: + let + value' = if isBool value then + (if value then "yes" else "no") + else + toString value; + in "${key} ${value'}"; + }; + + toKittyKeybindings = generators.toKeyValue { + mkKeyValue = key: command: "map ${key} ${command}"; + }; + +in { + options.programs.kitty = { + enable = mkEnableOption "Kitty terminal emulator"; + + settings = mkOption { + type = types.attrsOf eitherStrBoolInt; + default = { }; + example = literalExample '' + { + scrollback_lines = 10000; + enable_audio_bell = false; + update_check_interval = 0; + } + ''; + description = '' + Configuration written to + <filename>~/.config/kitty/kitty.conf</filename>. See + <link xlink:href="https://sw.kovidgoyal.net/kitty/conf.html" /> + for the documentation. + ''; + }; + + font = mkOption { + type = types.nullOr hm.types.fontType; + default = null; + description = "The font to use."; + }; + + keybindings = mkOption { + type = types.attrsOf types.str; + default = { }; + description = "Mapping of keybindings to actions."; + example = literalExample '' + { + "ctrl+c" = "copy_or_interrupt"; + "ctrl+f>2" = "set_font_size 20"; + } + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = "Additional configuration to add."; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.kitty ] ++ optionalPackage cfg.font; + + xdg.configFile."kitty/kitty.conf".text = '' + # Generated by Home Manager. + # See https://sw.kovidgoyal.net/kitty/conf.html + + ${optionalString (cfg.font != null) "font_family ${cfg.font.name}"} + + ${toKittyConfig cfg.settings} + + ${toKittyKeybindings cfg.keybindings} + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/lf.nix b/home-manager/modules/programs/lf.nix new file mode 100644 index 00000000000..ee4e9b5bfce --- /dev/null +++ b/home-manager/modules/programs/lf.nix @@ -0,0 +1,219 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.lf; + + knownSettings = { + anchorfind = types.bool; + color256 = types.bool; + dircounts = types.bool; + dirfirst = types.bool; + drawbox = types.bool; + globsearch = types.bool; + icons = types.bool; + hidden = types.bool; + ignorecase = types.bool; + ignoredia = types.bool; + incsearch = types.bool; + preview = types.bool; + reverse = types.bool; + smartcase = types.bool; + smartdia = types.bool; + wrapscan = types.bool; + wrapscroll = types.bool; + number = types.bool; + relativenumber = types.bool; + findlen = types.int; + period = types.int; + scrolloff = types.int; + tabstop = types.int; + errorfmt = types.str; + filesep = types.str; + ifs = types.str; + promptfmt = types.str; + shell = types.str; + sortby = types.str; + timefmt = types.str; + ratios = types.str; + info = types.str; + shellopts = types.str; + }; + + lfSettingsType = types.submodule { + options = let + opt = name: type: + mkOption { + type = types.nullOr type; + default = null; + visible = false; + }; + in mapAttrs opt knownSettings; + }; +in { + meta.maintainers = [ hm.maintainers.owm111 ]; + + options = { + programs.lf = { + enable = mkEnableOption "lf"; + + settings = mkOption { + type = lfSettingsType; + default = { }; + example = { + tabstop = 4; + number = true; + ratios = "1:1:2"; + }; + description = '' + An attribute set of lf settings. The attribute names and corresponding + values must be among the following supported options. + + <informaltable frame="none"><tgroup cols="1"><tbody> + ${concatStringsSep "\n" (mapAttrsToList (n: v: '' + <row> + <entry><varname>${n}</varname></entry> + <entry>${v.description}</entry> + </row> + '') knownSettings)} + </tbody></tgroup></informaltable> + + See the lf documentation for detailed descriptions of these options. + Note, use <varname>previewer</varname> to set lf's + <varname>previewer</varname> option, and + <varname>extraConfig</varname> for any other option not listed above. + All string options are quoted with double quotes. + ''; + }; + + commands = mkOption { + type = with types; attrsOf (nullOr str); + default = { }; + example = { + get-mime-type = ''%xdg-mime query filetype "$f"''; + open = "$$OPENER $f"; + }; + description = '' + Commands to declare. Commands set to null or an empty string are + deleted. + ''; + }; + + keybindings = mkOption { + type = with types; attrsOf (nullOr str); + default = { }; + example = { + gh = "cd ~"; + D = "trash"; + i = "$less $f"; + U = "!du -sh"; + gg = null; + }; + description = + "Keys to bind. Keys set to null or an empty string are deleted."; + }; + + cmdKeybindings = mkOption { + type = with types; attrsOf (nullOr str); + default = { }; + example = literalExample ''{ "<c-g>" = "cmd-escape"; }''; + description = '' + Keys to bind to command line commands which can only be one of the + builtin commands. Keys set to null or an empty string are deleted. + ''; + }; + + previewer.source = mkOption { + type = with types; nullOr path; + default = null; + example = literalExample '' + pkgs.writeShellScript "pv.sh" ''' + #!/bin/sh + + case "$1" in + *.tar*) tar tf "$1";; + *.zip) unzip -l "$1";; + *.rar) unrar l "$1";; + *.7z) 7z l "$1";; + *.pdf) pdftotext "$1" -;; + *) highlight -O ansi "$1" || cat "$1";; + esac + ''' + ''; + description = '' + Script or executable to use to preview files. Sets lf's + <varname>previewer</varname> option. + ''; + }; + + previewer.keybinding = mkOption { + type = with types; nullOr str; + default = null; + example = "i"; + description = '' + Key to bind to the script at <varname>previewer.source</varname> and + pipe through less. Setting to null will not bind any key. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + $mkdir -p ~/.trash + ''; + description = "Custom lfrc lines."; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.lf ]; + + xdg.configFile."lf/lfrc".text = let + fmtSetting = k: v: + optionalString (v != null) "set ${ + if isBool v then + "${optionalString (!v) "no"}${k}" + else + "${k} ${if isInt v then toString v else ''"${v}"''}" + }"; + + settingsStr = concatStringsSep "\n" (filter (x: x != "") + (mapAttrsToList fmtSetting + (builtins.intersectAttrs knownSettings cfg.settings))); + + fmtCmdMap = before: k: v: + "${before} ${k}${optionalString (v != null && v != "") " ${v}"}"; + fmtCmd = fmtCmdMap "cmd"; + fmtMap = fmtCmdMap "map"; + fmtCmap = fmtCmdMap "cmap"; + + commandsStr = concatStringsSep "\n" (mapAttrsToList fmtCmd cfg.commands); + keybindingsStr = + concatStringsSep "\n" (mapAttrsToList fmtMap cfg.keybindings); + cmdKeybindingsStr = + concatStringsSep "\n" (mapAttrsToList fmtCmap cfg.cmdKeybindings); + + previewerStr = optionalString (cfg.previewer.source != null) '' + set previewer ${cfg.previewer.source} + ${optionalString (cfg.previewer.keybinding != null) '' + map ${cfg.previewer.keybinding} ''$${cfg.previewer.source} "$f" | less -R + ''} + ''; + in '' + ${settingsStr} + + ${commandsStr} + + ${keybindingsStr} + + ${cmdKeybindingsStr} + + ${previewerStr} + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/lieer-accounts.nix b/home-manager/modules/programs/lieer-accounts.nix new file mode 100644 index 00000000000..238049065b3 --- /dev/null +++ b/home-manager/modules/programs/lieer-accounts.nix @@ -0,0 +1,69 @@ +{ lib, ... }: + +with lib; + +{ + options.lieer = { + enable = mkEnableOption "lieer Gmail synchronization for notmuch"; + + timeout = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + HTTP timeout in seconds. 0 means forever or system timeout. + ''; + }; + + replaceSlashWithDot = mkOption { + type = types.bool; + default = false; + description = '' + Replace '/' with '.' in Gmail labels. + ''; + }; + + dropNonExistingLabels = mkOption { + type = types.bool; + default = false; + description = '' + Allow missing labels on the Gmail side to be dropped. + ''; + }; + + ignoreTagsLocal = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Set custom tags to ignore when syncing from local to + remote (after translations). + ''; + }; + + ignoreTagsRemote = mkOption { + type = types.listOf types.str; + default = [ + "CATEGORY_FORUMS" + "CATEGORY_PROMOTIONS" + "CATEGORY_UPDATES" + "CATEGORY_SOCIAL" + "CATEGORY_PERSONAL" + ]; + description = '' + Set custom tags to ignore when syncing from remote to + local (before translations). + ''; + }; + + notmuchSetupWarning = mkOption { + type = types.bool; + default = true; + description = '' + Warn if Notmuch is not also enabled for this account. + </para><para> + This can safely be disabled if <command>notmuch init</command> + has been used to configure this account outside of Home + Manager. + ''; + }; + }; +} diff --git a/home-manager/modules/programs/lieer.nix b/home-manager/modules/programs/lieer.nix new file mode 100644 index 00000000000..e34a247af46 --- /dev/null +++ b/home-manager/modules/programs/lieer.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.lieer; + + lieerAccounts = + filter (a: a.lieer.enable) (attrValues config.accounts.email.accounts); + + nonGmailAccounts = + map (a: a.name) (filter (a: a.flavor != "gmail.com") lieerAccounts); + + nonGmailConfigHelp = + map (name: ''accounts.email.accounts.${name}.flavor = "gmail.com";'') + nonGmailAccounts; + + missingNotmuchAccounts = map (a: a.name) + (filter (a: !a.notmuch.enable && a.lieer.notmuchSetupWarning) + lieerAccounts); + + notmuchConfigHelp = + map (name: "accounts.email.accounts.${name}.notmuch.enable = true;") + missingNotmuchAccounts; + + configFile = account: { + name = "${account.maildir.absPath}/.gmailieer.json"; + value = { + text = builtins.toJSON { + inherit (account.lieer) timeout; + account = account.address; + replace_slash_with_dot = account.lieer.replaceSlashWithDot; + drop_non_existing_label = account.lieer.dropNonExistingLabels; + ignore_tags = account.lieer.ignoreTagsLocal; + ignore_remote_labels = account.lieer.ignoreTagsRemote; + } + "\n"; + }; + }; + +in { + meta.maintainers = [ maintainers.tadfisher ]; + + options = { + programs.lieer.enable = + mkEnableOption "lieer Gmail synchronization for notmuch"; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./lieer-accounts.nix)); + }; + }; + + config = mkIf cfg.enable (mkMerge [ + (mkIf (missingNotmuchAccounts != [ ]) { + warnings = ['' + lieer is enabled for the following email accounts, but notmuch is not: + + ${concatStringsSep "\n " missingNotmuchAccounts} + + Notmuch can be enabled with: + + ${concatStringsSep "\n " notmuchConfigHelp} + + If you have configured notmuch outside of Home Manager, you can suppress this + warning with: + + programs.lieer.notmuchSetupWarning = false; + '']; + }) + + { + assertions = [{ + assertion = nonGmailAccounts == [ ]; + message = '' + lieer is enabled for non-Gmail accounts: + + ${concatStringsSep "\n " nonGmailAccounts} + + If these accounts are actually Gmail accounts, you can + fix this error with: + + ${concatStringsSep "\n " nonGmailConfigHelp} + ''; + }]; + + home.packages = [ pkgs.gmailieer ]; + + # Notmuch should ignore non-mail files created by lieer. + programs.notmuch.new.ignore = [ "/.*[.](json|lock|bak)$/" ]; + + home.file = listToAttrs (map configFile lieerAccounts); + } + ]); +} diff --git a/home-manager/modules/programs/man.nix b/home-manager/modules/programs/man.nix index 0ed376780d4..b235b02fe2d 100644 --- a/home-manager/modules/programs/man.nix +++ b/home-manager/modules/programs/man.nix @@ -4,19 +4,69 @@ with lib; { options = { - programs.man.enable = mkOption { - type = types.bool; - default = true; - description = '' - Whether to enable manual pages and the <command>man</command> - command. This also includes "man" outputs of all - <literal>home.packages</literal>. - ''; + programs.man = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable manual pages and the <command>man</command> + command. This also includes "man" outputs of all + <literal>home.packages</literal>. + ''; + }; + + generateCaches = mkOption { + type = types.bool; + default = false; + description = '' + Whether to generate the manual page index caches using + <citerefentry> + <refentrytitle>mandb</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry>. This allows searching for a page or + keyword using utilities like <citerefentry> + <refentrytitle>apropos</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>. + </para><para> + This feature is disabled by default because it slows down + building. If you don't mind waiting a few more seconds when + Home Manager builds a new generation, you may safely enable + this option. + ''; + }; }; }; config = mkIf config.programs.man.enable { home.packages = [ pkgs.man ]; home.extraOutputsToInstall = [ "man" ]; + + # This is mostly copy/pasted/adapted from NixOS' documentation.nix. + home.file = mkIf config.programs.man.generateCaches { + ".manpath".text = let + # Generate a directory containing installed packages' manpages. + manualPages = pkgs.buildEnv { + name = "man-paths"; + paths = config.home.packages; + pathsToLink = [ "/share/man" ]; + extraOutputsToInstall = [ "man" ]; + ignoreCollisions = true; + }; + + # Generate a database of all manpages in ${manualPages}. + manualCache = pkgs.runCommandLocal "man-cache" { } '' + # Generate a temporary man.conf so mandb knows where to + # write cache files. + echo "MANDB_MAP ${manualPages}/share/man $out" > man.conf + + # Run mandb to generate cache files: + ${pkgs.man-db}/bin/mandb -C man.conf --no-straycats --create \ + ${manualPages}/share/man + ''; + in '' + MANDB_MAP ${config.home.profileDirectory}/share/man ${manualCache} + ''; + }; }; } diff --git a/home-manager/modules/programs/mbsync.nix b/home-manager/modules/programs/mbsync.nix index 6ade10986fe..f2814b393d0 100644 --- a/home-manager/modules/programs/mbsync.nix +++ b/home-manager/modules/programs/mbsync.nix @@ -122,6 +122,10 @@ in { ''; }; }; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./mbsync-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/mcfly.nix b/home-manager/modules/programs/mcfly.nix new file mode 100644 index 00000000000..1206f9da566 --- /dev/null +++ b/home-manager/modules/programs/mcfly.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + + cfg = config.programs.mcfly; + +in { + meta.maintainers = [ maintainers.marsam ]; + + options.programs.mcfly = { + enable = mkEnableOption "mcfly"; + + keyScheme = mkOption { + type = types.enum [ "emacs" "vim" ]; + default = "emacs"; + description = '' + Key scheme to use. + ''; + }; + + enableLightTheme = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable light mode theme. + ''; + }; + + enableBashIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Bash integration. + ''; + }; + + enableZshIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Zsh integration. + ''; + }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { + home.packages = [ pkgs.mcfly ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.bash" + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.zsh" + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + source "${pkgs.mcfly}/share/mcfly/mcfly.fish" + if status is-interactive + mcfly_key_bindings + end + ''; + + home.sessionVariables.MCFLY_KEY_SCHEME = cfg.keyScheme; + } + + (mkIf cfg.enableLightTheme { home.sessionVariables.MCFLY_LIGHT = "TRUE"; }) + ]); +} diff --git a/home-manager/modules/programs/mpv.nix b/home-manager/modules/programs/mpv.nix index 3a4e5092f9a..a5b0517fe0a 100644 --- a/home-manager/modules/programs/mpv.nix +++ b/home-manager/modules/programs/mpv.nix @@ -125,7 +125,7 @@ in { (if cfg.scripts == [ ] then pkgs.mpv else - pkgs.mpv-with-scripts.override { scripts = cfg.scripts; }) + pkgs.wrapMpv pkgs.mpv-unwrapped { scripts = cfg.scripts; }) ]; } (mkIf (cfg.config != { } || cfg.profiles != { }) { diff --git a/home-manager/modules/programs/msmtp.nix b/home-manager/modules/programs/msmtp.nix index f34fd72f8b1..7b6704860e0 100644 --- a/home-manager/modules/programs/msmtp.nix +++ b/home-manager/modules/programs/msmtp.nix @@ -56,6 +56,10 @@ in { ''; }; }; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./msmtp-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/ncmpcpp.nix b/home-manager/modules/programs/ncmpcpp.nix new file mode 100644 index 00000000000..a39baab6ca5 --- /dev/null +++ b/home-manager/modules/programs/ncmpcpp.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.ncmpcpp; + + renderSettings = settings: + concatStringsSep "\n" (mapAttrsToList renderSetting settings); + + renderSetting = name: value: "${name}=${renderValue value}"; + + renderValue = option: + { + int = toString option; + bool = if option then "yes" else "no"; + string = option; + }.${builtins.typeOf option}; + + renderBindings = bindings: concatStringsSep "\n" (map renderBinding bindings); + + renderBinding = { key, command }: + concatStringsSep "\n " ([ ''def_key "${key}"'' ] ++ maybeWrapList command); + + maybeWrapList = xs: if isList xs then xs else [ xs ]; + + valueType = with types; oneOf [ bool int str ]; + + bindingType = types.submodule ({ name, config, ... }: { + options = { + key = mkOption { + type = types.str; + description = "Key to bind."; + example = "j"; + }; + + command = mkOption { + type = with types; either str (listOf str); + description = "Command or sequence of commands to be executed."; + example = "scroll_down"; + }; + }; + }); + +in { + meta.maintainers = with maintainers; [ olmokramer ]; + + options.programs.ncmpcpp = { + enable = + mkEnableOption "ncmpcpp - an ncurses Music Player Daemon (MPD) client"; + + package = mkOption { + type = types.package; + default = pkgs.ncmpcpp; + defaultText = literalExample "pkgs.ncmpcpp"; + description = '' + Package providing the <code>ncmpcpp</code> command. + ''; + example = + literalExample "pkgs.ncmpcpp.override { visualizerSupport = true; }"; + }; + + mpdMusicDir = mkOption { + type = types.nullOr types.path; + default = let mpdCfg = config.services.mpd; + in if pkgs.stdenv.hostPlatform.isLinux && mpdCfg.enable then + mpdCfg.musicDirectory + else + null; + defaultText = literalExample '' + if pkgs.stdenv.hostPlatform.isLinux && config.services.mpd.enable then + config.services.mpd.musicDirectory + else + null + ''; + description = '' + Value of the <code>mpd_music_dir</code> setting. On Linux platforms the + value of <varname>services.mpd.musicDirectory</varname> is used as the + default if <varname>services.mpd.enable</varname> is + <literal>true</literal>. + ''; + example = "~/music"; + }; + + settings = mkOption { + type = types.attrsOf valueType; + default = { }; + description = '' + Attribute set from name of a setting to its value. For available options + see + <citerefentry> + <refentrytitle>ncmpcpp</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>. + ''; + example = { ncmpcpp_directory = "~/.local/share/ncmpcpp"; }; + }; + + bindings = mkOption { + type = types.listOf bindingType; + default = [ ]; + description = "List of keybindings."; + example = literalExample '' + [ + { key = "j"; command = "scroll_down"; } + { key = "k"; command = "scroll_up"; } + { key = "J"; command = [ "select_item" "scroll_down" ]; } + { key = "K"; command = [ "select_item" "scroll_up" ]; } + ] + ''; + }; + }; + + config = mkIf cfg.enable { + warnings = mkIf (cfg.settings ? mpd_music_dir && cfg.mpdMusicDir != null) [ + ("programs.ncmpcpp.settings.mpd_music_dir will be overridden by" + + " programs.ncmpcpp.mpdMusicDir.") + ]; + + home.packages = [ cfg.package ]; + + xdg.configFile = { + "ncmpcpp/config" = let + settings = cfg.settings // optionalAttrs (cfg.mpdMusicDir != null) { + mpd_music_dir = toString cfg.mpdMusicDir; + }; + in mkIf (settings != { }) { text = renderSettings settings + "\n"; }; + + "ncmpcpp/bindings" = mkIf (cfg.bindings != [ ]) { + text = renderBindings cfg.bindings + "\n"; + }; + }; + }; +} diff --git a/home-manager/modules/programs/ne.nix b/home-manager/modules/programs/ne.nix new file mode 100644 index 00000000000..a88d23d9133 --- /dev/null +++ b/home-manager/modules/programs/ne.nix @@ -0,0 +1,95 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.ne; + + autoPrefFiles = let + autoprefs = cfg.automaticPreferences + // optionalAttrs (cfg.defaultPreferences != "") { + ".default" = cfg.defaultPreferences; + }; + + gen = fileExtension: configText: + nameValuePair ".ne/${fileExtension}#ap" { + text = configText; + }; # Generates [path].text format expected by home.file. + in mapAttrs' gen autoprefs; + +in { + meta.maintainers = [ hm.maintainers.cwyc ]; + + options.programs.ne = { + enable = mkEnableOption "ne"; + + keybindings = mkOption { + type = types.lines; + default = ""; + example = '' + KEY 7f BS + SEQ "\x1b[1;5D" 7f + ''; + description = '' + Keybinding file for ne. + ''; + }; + + defaultPreferences = mkOption { + type = types.lines; + default = ""; + description = '' + Default preferences for ne. + </para><para> + Equivalent to <literal>programs.ne.automaticPreferences.".default"</literal>. + ''; + }; + + automaticPreferences = mkOption { + type = types.attrsOf types.lines; + default = { }; + example = literalExample '' + { + nix = ''' + TAB 0 + TS 2 + '''; + js = ''' + TS 4 + '''; + } + ''; + description = '' + Automatic preferences files for ne. + ''; + }; + + menus = mkOption { + type = types.lines; + default = ""; + description = "Menu configuration file for ne."; + }; + + virtualExtensions = mkOption { + type = types.lines; + default = ""; + example = '' + sh 1 ^#!\s*/.*\b(bash|sh|ksh|zsh)\s* + csh 1 ^#!\s*/.*\b(csh|tcsh)\s* + ''; + description = "Virtual extensions configuration file for ne."; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.ne ]; + + home.file = { + ".ne/.keys" = mkIf (cfg.keybindings != "") { text = cfg.keybindings; }; + ".ne/.extensions" = + mkIf (cfg.virtualExtensions != "") { text = cfg.virtualExtensions; }; + ".ne/.menus" = mkIf (cfg.menus != "") { text = cfg.menus; }; + } // autoPrefFiles; + }; +} diff --git a/home-manager/modules/programs/neomutt-accounts.nix b/home-manager/modules/programs/neomutt-accounts.nix index 033db38eb0a..009cf1fa7e8 100644 --- a/home-manager/modules/programs/neomutt-accounts.nix +++ b/home-manager/modules/programs/neomutt-accounts.nix @@ -8,7 +8,16 @@ with lib; sendMailCommand = mkOption { type = types.nullOr types.str; - default = null; + default = if config.msmtp.enable then + "msmtpq --read-envelope-from --read-recipients" + else + null; + defaultText = literalExample '' + if config.msmtp.enable then + "msmtpq --read-envelope-from --read-recipients" + else + null + ''; example = "msmtpq --read-envelope-from --read-recipients"; description = '' Command to send a mail. If not set, neomutt will be in charge of sending mails. @@ -24,11 +33,4 @@ with lib; ''; }; }; - - config = mkIf config.neomutt.enable { - neomutt.sendMailCommand = mkOptionDefault (if config.msmtp.enable then - "msmtpq --read-envelope-from --read-recipients" - else - null); - }; } diff --git a/home-manager/modules/programs/neomutt.nix b/home-manager/modules/programs/neomutt.nix index 85af0353b6c..f2a6bbfff08 100644 --- a/home-manager/modules/programs/neomutt.nix +++ b/home-manager/modules/programs/neomutt.nix @@ -38,6 +38,19 @@ let }; }; + sortOptions = [ + "date" + "date-received" + "from" + "mailbox-order" + "score" + "size" + "spam" + "subject" + "threads" + "to" + ]; + bindModule = types.submodule { options = { map = mkOption { @@ -98,7 +111,9 @@ let } else let smtpProto = if smtp.tls.enable then "smtps" else "smtp"; - smtpBaseUrl = "${smtpProto}://${escape userName}@${smtp.host}"; + smtpPort = if smtp.port != null then ":${toString smtp.port}" else ""; + smtpBaseUrl = + "${smtpProto}://${escape userName}@${smtp.host}${smtpPort}"; in { smtp_url = "'${smtpBaseUrl}'"; smtp_pass = "'`${passCmd}`'"; @@ -211,18 +226,9 @@ in { }; sort = mkOption { - type = types.enum [ - "date" - "date-received" - "from" - "mailbox-order" - "score" - "size" - "spam" - "subject" - "threads" - "to" - ]; + # allow users to choose any option from sortOptions, or any option prefixed with "reverse-" + type = types.enum + (sortOptions ++ (map (option: "reverse-" + option) sortOptions)); default = "threads"; description = "Sorting method on messages."; }; @@ -258,6 +264,10 @@ in { description = "Extra configuration appended to the end."; }; }; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./neomutt-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/neovim.nix b/home-manager/modules/programs/neovim.nix index 4101dc0f4e7..858f5576ad1 100644 --- a/home-manager/modules/programs/neovim.nix +++ b/home-manager/modules/programs/neovim.nix @@ -43,7 +43,7 @@ in type = types.bool; default = false; description = '' - Symlink `vi` to `nvim` binary. + Symlink <command>vi</command> to <command>nvim</command> binary. ''; }; @@ -51,7 +51,15 @@ in type = types.bool; default = false; description = '' - Symlink `vim` to `nvim` binary. + Symlink <command>vim</command> to <command>nvim</command> binary. + ''; + }; + + vimdiffAlias = mkOption { + type = types.bool; + default = false; + description = '' + Alias <command>vimdiff</command> to <command>nvim -d</command>. ''; }; @@ -203,5 +211,9 @@ in configure = cfg.configure // moduleConfigure; }; + + programs.bash.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; + programs.fish.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; + programs.zsh.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; }; }; } diff --git a/home-manager/modules/programs/newsboat.nix b/home-manager/modules/programs/newsboat.nix index 6b59ed713d8..793b30680bf 100644 --- a/home-manager/modules/programs/newsboat.nix +++ b/home-manager/modules/programs/newsboat.nix @@ -102,7 +102,11 @@ in { mkQueryEntry = n: v: ''"query:${n}:${escape [ ''"'' ] v}"''; queries = mapAttrsToList mkQueryEntry cfg.queries; - in concatStringsSep "\n" (urls ++ queries) + "\n"; + in concatStringsSep "\n" + (if versionAtLeast config.home.stateVersion "20.03" then + queries ++ urls + else + urls ++ queries) + "\n"; home.file.".newsboat/config".text = '' max-items ${toString cfg.maxItems} diff --git a/home-manager/modules/programs/notmuch.nix b/home-manager/modules/programs/notmuch.nix index 7e7a140b20c..9070d755671 100644 --- a/home-manager/modules/programs/notmuch.nix +++ b/home-manager/modules/programs/notmuch.nix @@ -143,6 +143,10 @@ in { }; }; }; + + accounts.email.accounts = mkOption { + type = with types; attrsOf (submodule (import ./notmuch-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/nushell.nix b/home-manager/modules/programs/nushell.nix new file mode 100644 index 00000000000..1eb42f9515c --- /dev/null +++ b/home-manager/modules/programs/nushell.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.nushell; + + configFile = config: + pkgs.runCommand "config.toml" { + buildInputs = [ pkgs.remarshal ]; + preferLocalBuild = true; + allowSubstitutes = false; + } '' + remarshal -if json -of toml \ + < ${pkgs.writeText "config.json" (builtins.toJSON config)} \ + > $out + ''; + +in { + meta.maintainers = [ maintainers.Philipp-M ]; + + options.programs.nushell = { + enable = mkEnableOption "nushell"; + + package = mkOption { + type = types.package; + default = pkgs.nushell; + defaultText = literalExample "pkgs.nushell"; + description = "The package to use for nushell."; + }; + + settings = mkOption { + type = with types; + let + prim = oneOf [ bool int str ]; + primOrPrimAttrs = either prim (attrsOf prim); + entry = either prim (listOf primOrPrimAttrs); + entryOrAttrsOf = t: either entry (attrsOf t); + entries = entryOrAttrsOf (entryOrAttrsOf entry); + in attrsOf entries // { description = "Nushell configuration"; }; + default = { }; + example = literalExample '' + { + edit_mode = "vi"; + startup = [ "alias la [] { ls -a }" "alias e [msg] { echo $msg }" ]; + key_timeout = 10; + completion_mode = "circular"; + no_auto_pivot = true; + } + ''; + description = '' + Configuration written to + <filename>~/.config/nushell/config.toml</filename>. + </para><para> + See <link xlink:href="https://www.nushell.sh/book/en/configuration.html" /> for the full list + of options. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + xdg.configFile."nu/config.toml" = + mkIf (cfg.settings != { }) { source = configFile cfg.settings; }; + }; +} diff --git a/home-manager/modules/programs/offlineimap.nix b/home-manager/modules/programs/offlineimap.nix index 4ce12ec0a61..b6ba847e9b7 100644 --- a/home-manager/modules/programs/offlineimap.nix +++ b/home-manager/modules/programs/offlineimap.nix @@ -147,6 +147,11 @@ in { ''; }; }; + + accounts.email.accounts = mkOption { + type = with types; + attrsOf (submodule (import ./offlineimap-accounts.nix)); + }; }; config = mkIf cfg.enable { diff --git a/home-manager/modules/programs/powerline-go.nix b/home-manager/modules/programs/powerline-go.nix new file mode 100644 index 00000000000..a4cd233cf70 --- /dev/null +++ b/home-manager/modules/programs/powerline-go.nix @@ -0,0 +1,123 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.powerline-go; + + # Convert an option value to a string to be passed as argument to + # powerline-go: + valueToString = value: + if builtins.isList value then + builtins.concatStringsSep "," (builtins.map valueToString value) + else if builtins.isAttrs value then + valueToString + (mapAttrsToList (key: val: "${valueToString key}=${valueToString val}") + value) + else + builtins.toString value; + + modulesArgument = optionalString (cfg.modules != null) + "-modules ${valueToString cfg.modules}"; + + newlineArgument = optionalString cfg.newline "-newline"; + + pathAliasesArgument = optionalString (cfg.pathAliases != null) + "-path-aliases ${valueToString cfg.pathAliases}"; + + otherSettingPairArgument = name: value: + if value == true then "-${name}" else "-${name} ${valueToString value}"; + + otherSettingsArgument = optionalString (cfg.settings != { }) + (concatStringsSep " " + (mapAttrsToList otherSettingPairArgument cfg.settings)); + + commandLineArguments = '' + ${modulesArgument} ${newlineArgument} ${pathAliasesArgument} ${otherSettingsArgument} + ''; + +in { + meta.maintainers = [ maintainers.DamienCassou ]; + + options = { + programs.powerline-go = { + enable = mkEnableOption + "Powerline-go, a beautiful and useful low-latency prompt for your shell"; + + modules = mkOption { + default = null; + type = types.nullOr (types.listOf types.str); + description = '' + List of module names to load. The list of all available + modules as well as the choice of default ones are at + <link xlink:href="https://github.com/justjanne/powerline-go"/>. + ''; + example = [ "host" "ssh" "cwd" "gitlite" "jobs" "exit" ]; + }; + + newline = mkOption { + default = false; + type = types.bool; + description = '' + Set to true if the prompt should be on a line of its own. + ''; + example = true; + }; + + pathAliases = mkOption { + default = null; + type = types.nullOr (types.attrsOf types.str); + description = '' + Pairs of full-path and corresponding desired short name. You + may use '~' to represent your home directory but you should + protect it to avoid shell substitution. + ''; + example = literalExample '' + { "\\~/projects/home-manager" = "prj:home-manager"; } + ''; + }; + + settings = mkOption { + default = { }; + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + description = '' + This can be any key/value pair as described in + <link xlink:href="https://github.com/justjanne/powerline-go"/>. + ''; + example = literalExample '' + { + hostname-only-if-ssh = true; + numeric-exit-codes = true; + cwd-max-depth = 7; + ignore-repos = [ "/home/me/big-project" "/home/me/huge-project" ]; + } + ''; + }; + + extraUpdatePS1 = mkOption { + default = ""; + description = "Shell code to execute after the prompt is set."; + example = '' + PS1=$PS1"NixOS> "; + ''; + type = types.str; + }; + }; + }; + + config = mkIf (cfg.enable && config.programs.bash.enable) { + programs.bash.initExtra = '' + function _update_ps1() { + local old_exit_status=$? + PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $old_exit_status ${commandLineArguments})" + ${cfg.extraUpdatePS1} + return $old_exit_status + } + + if [ "$TERM" != "linux" ]; then + PROMPT_COMMAND="_update_ps1;$PROMPT_COMMAND" + fi + ''; + }; +} diff --git a/home-manager/modules/programs/qutebrowser.nix b/home-manager/modules/programs/qutebrowser.nix new file mode 100644 index 00000000000..798363fb187 --- /dev/null +++ b/home-manager/modules/programs/qutebrowser.nix @@ -0,0 +1,268 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.qutebrowser; + + formatLine = o: n: v: + let + formatValue = v: + if builtins.isNull v then + "None" + else if builtins.isBool v then + (if v then "True" else "False") + else if builtins.isString v then + ''"${v}"'' + else if builtins.isList v then + "[${concatStringsSep ", " (map formatValue v)}]" + else + builtins.toString v; + in if builtins.isAttrs v then + concatStringsSep "\n" (mapAttrsToList (formatLine "${o}${n}.") v) + else + "${o}${n} = ${formatValue v}"; + + formatDictLine = o: n: v: ''${o}['${n}'] = "${v}"''; + + formatKeyBindings = m: b: + let + formatKeyBinding = m: k: c: + ''config.bind("${k}", "${escape [ ''"'' ] c}", mode="${m}")''; + in concatStringsSep "\n" (mapAttrsToList (formatKeyBinding m) b); + +in { + options.programs.qutebrowser = { + enable = mkEnableOption "qutebrowser"; + + package = mkOption { + type = types.package; + default = pkgs.qutebrowser; + defaultText = literalExample "pkgs.qutebrowser"; + description = "Qutebrowser package to install."; + }; + + aliases = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Aliases for commands. + ''; + }; + + searchEngines = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Search engines that can be used via the address bar. Maps a search + engine name (such as <literal>DEFAULT</literal>, or + <literal>ddg</literal>) to a URL with a <literal>{}</literal> + placeholder. The placeholder will be replaced by the search term, use + <literal>{{</literal> and <literal>}}</literal> for literal + <literal>{/}</literal> signs. The search engine named + <literal>DEFAULT</literal> is used when + <literal>url.auto_search</literal> is turned on and something else than + a URL was entered to be opened. Other search engines can be used by + prepending the search engine name to the search term, for example + <literal>:open google qutebrowser</literal>. + ''; + example = literalExample '' + { + w = "https://en.wikipedia.org/wiki/Special:Search?search={}&go=Go&ns0=1"; + aw = "https://wiki.archlinux.org/?search={}"; + nw = "https://nixos.wiki/index.php?search={}"; + g = "https://www.google.com/search?hl=en&q={}"; + } + ''; + }; + + settings = mkOption { + type = types.attrs; + default = { }; + description = '' + Options to add to qutebrowser <filename>config.py</filename> file. + See <link xlink:href="https://qutebrowser.org/doc/help/settings.html"/> + for options. + ''; + example = literalExample '' + { + colors = { + hints = { + bg = "#000000"; + fg = "#ffffff"; + }; + tabs.bar.bg = "#000000"; + }; + tabs.tabs_are_windows = true; + } + ''; + }; + + keyMappings = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + This setting can be used to map keys to other keys. When the key used + as dictionary-key is pressed, the binding for the key used as + dictionary-value is invoked instead. This is useful for global + remappings of keys, for example to map Ctrl-[ to Escape. Note that when + a key is bound (via <literal>bindings.default</literal> or + <literal>bindings.commands</literal>), the mapping is ignored. + ''; + }; + + enableDefaultBindings = mkOption { + type = types.bool; + default = true; + description = '' + Disable to prevent loading default key bindings. + ''; + }; + + keyBindings = mkOption { + type = types.attrsOf (types.attrsOf types.str); + default = { }; + description = '' + Key bindings mapping keys to commands in different modes. This setting + is a dictionary containing mode names and dictionaries mapping keys to + commands: <literal>{mode: {key: command}}</literal> If you want to map + a key to another key, check the <literal>keyMappings</literal> setting + instead. For modifiers, you can use either <literal>-</literal> or + <literal>+</literal> as delimiters, and these names: + + <itemizedlist> + <listitem><para> + Control: <literal>Control</literal>, <literal>Ctrl</literal> + </para></listitem> + <listitem><para> + Meta: <literal>Meta</literal>, <literal>Windows</literal>, + <literal>Mod4</literal> + </para></listitem> + <listitem><para> + Alt: <literal>Alt</literal>, <literal>Mod1</literal> + </para></listitem> + <listitem><para> + Shift: <literal>Shift</literal> + </para></listitem> + </itemizedlist> + + For simple keys (no <literal><></literal>-signs), a capital + letter means the key is pressed with Shift. For special keys (with + <literal><></literal>-signs), you need to explicitly add + <literal>Shift-</literal> to match a key pressed with shift. If you + want a binding to do nothing, bind it to the <literal>nop</literal> + command. If you want a default binding to be passed through to the + website, bind it to null. Note that some commands which are only useful + for bindings (but not used interactively) are hidden from the command + completion. See <literal>:</literal>help for a full list of available + commands. The following modes are available: + + <variablelist> + <varlistentry> + <term><literal>normal</literal></term> + <listitem><para> + Default mode, where most commands are invoked. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>insert</literal></term> + <listitem><para> + Entered when an input field is focused on a website, or by + pressing i in normal mode. Passes through almost all keypresses + to the website, but has some bindings like + <literal><Ctrl-e></literal> to open an external editor. + Note that single keys can’t be bound in this mode. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>hint</literal></term> + <listitem><para> + Entered when f is pressed to select links with the keyboard. Note + that single keys can’t be bound in this mode. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>passthrough</literal></term> + <listitem><para> + Similar to insert mode, but passes through all keypresses except + <literal><Escape></literal> to leave the mode. It might be + useful to bind <literal><Escape></literal> to some other + key in this mode if you want to be able to send an Escape key to + the website as well. Note that single keys can’t be bound in this + mode. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>command</literal></term> + <listitem><para> + Entered when pressing the : key in order to enter a command. Note + that single keys can’t be bound in this mode. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>prompt</literal></term> + <listitem><para> + Entered when there’s a prompt to display, like for download + locations or when invoked from JavaScript. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>yesno</literal></term> + <listitem><para> + Entered when there’s a yes/no prompt displayed. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>caret</literal></term> + <listitem><para> + Entered when pressing the v mode, used to select text using the + keyboard. + </para></listitem> + </varlistentry> + <varlistentry> + <term><literal>register</literal></term> + <listitem><para> + Entered when qutebrowser is waiting for a register name/key for + commands like <literal>:set-mark</literal>. + </para></listitem> + </varlistentry> + </variablelist> + ''; + example = literalExample '' + { + normal = { + "<Ctrl-v>" = "spawn mpv {url}"; + ",p" = "spawn --userscript qute-pass"; + ",l" = '''config-cycle spellcheck.languages ["en-GB"] ["en-US"]'''; + }; + prompt = { + "<Ctrl-y>" = "prompt-yes"; + }; + } + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra lines added to qutebrowser <filename>config.py</filename> file. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + xdg.configFile."qutebrowser/config.py".text = concatStringsSep "\n" ([ ] + ++ mapAttrsToList (formatLine "c.") cfg.settings + ++ mapAttrsToList (formatDictLine "c.aliases") cfg.aliases + ++ mapAttrsToList (formatDictLine "c.url.searchengines") cfg.searchEngines + ++ mapAttrsToList (formatDictLine "c.bindings.key_mappings") + cfg.keyMappings + ++ optional (!cfg.enableDefaultBindings) "c.bindings.default = {}" + ++ mapAttrsToList formatKeyBindings cfg.keyBindings + ++ optional (cfg.extraConfig != "") cfg.extraConfig); + }; +} diff --git a/home-manager/modules/programs/rofi.nix b/home-manager/modules/programs/rofi.nix index f344e88e2ff..734bcc423e6 100644 --- a/home-manager/modules/programs/rofi.nix +++ b/home-manager/modules/programs/rofi.nix @@ -131,6 +131,17 @@ in { enable = mkEnableOption "Rofi: A window switcher, application launcher and dmenu replacement"; + package = mkOption { + default = pkgs.rofi; + type = types.package; + description = '' + Package providing the <command>rofi</command> binary. + ''; + example = literalExample '' + pkgs.rofi.override { plugins = [ pkgs.rofi-emoji ]; }; + ''; + }; + width = mkOption { default = null; type = types.nullOr types.int; @@ -295,7 +306,7 @@ in { ''; }]; - home.packages = [ pkgs.rofi ]; + home.packages = [ cfg.package ]; home.file."${cfg.configPath}".text = '' ${setOption "width" cfg.width} diff --git a/home-manager/modules/programs/ssh.nix b/home-manager/modules/programs/ssh.nix index 6b0747dd9b1..ae1f221803c 100644 --- a/home-manager/modules/programs/ssh.nix +++ b/home-manager/modules/programs/ssh.nix @@ -56,7 +56,7 @@ let }; }; - matchBlockModule = types.submodule ({ name, ... }: { + matchBlockModule = types.submodule ({ dagName, ... }: { options = { host = mkOption { type = types.str; @@ -143,6 +143,15 @@ let "Set timeout in seconds after which response will be requested."; }; + serverAliveCountMax = mkOption { + type = types.ints.positive; + default = 3; + description = '' + Sets the number of server alive messages which may be sent + without SSH receiving any messages back from the server. + ''; + }; + sendEnv = mkOption { type = types.listOf types.str; default = []; @@ -266,7 +275,7 @@ let }; }; - config.host = mkDefault name; + config.host = mkDefault dagName; }); matchBlockStr = cf: concatStringsSep "\n" ( @@ -281,7 +290,9 @@ let ++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}" ++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}" ++ optional (cf.serverAliveInterval != 0) - " ServerAliveInterval ${toString cf.serverAliveInterval}" + " ServerAliveInterval ${toString cf.serverAliveInterval}" + ++ optional (cf.serverAliveCountMax != 3) + " ServerAliveCountMax ${toString cf.serverAliveCountMax}" ++ optional (cf.compression != null) " Compression ${yn cf.compression}" ++ optional (!cf.checkHostIP) " CheckHostIP no" ++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}" @@ -325,6 +336,15 @@ in ''; }; + serverAliveCountMax = mkOption { + type = types.ints.positive; + default = 3; + description = '' + Sets the default number of server alive messages which may be + sent without SSH receiving any messages back from the server. + ''; + }; + hashKnownHosts = mkOption { default = false; type = types.bool; @@ -392,7 +412,7 @@ in }; matchBlocks = mkOption { - type = types.loaOf matchBlockModule; + type = hm.types.listOrDagOf matchBlockModule; default = {}; example = literalExample '' { @@ -400,7 +420,7 @@ in hostname = "example.com"; user = "john"; }; - foo = { + foo = lib.hm.dag.entryBefore ["john.example.com"] { hostname = "example.com"; identityFile = "/home/john/.ssh/foo_rsa"; }; @@ -408,11 +428,15 @@ in ''; description = '' Specify per-host settings. Note, if the order of rules matter - then this must be a list. See + then use the DAG functions to express the dependencies as + shown in the example. + </para><para> + See <citerefentry> <refentrytitle>ssh_config</refentrytitle> <manvolnum>5</manvolnum> - </citerefentry>. + </citerefentry> + for more information. ''; }; }; @@ -432,23 +456,30 @@ in checkLocal = block: any' checkBindAndHost block.localForwards; checkRemote = block: any' checkBindAndHost block.remoteForwards; checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ]; - in any' checkMatchBlock (builtins.attrValues cfg.matchBlocks); + in any' checkMatchBlock (map (block: block.data) (builtins.attrValues cfg.matchBlocks)); message = "Forwarded paths cannot have ports."; } ]; - home.file.".ssh/config".text = '' + home.file.".ssh/config".text = + let + sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks; + sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks; + matchBlocks = + if sortedMatchBlocks ? result + then sortedMatchBlocks.result + else abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}"; + in '' ${concatStringsSep "\n" ( mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)} - ${concatStringsSep "\n\n" ( - map matchBlockStr ( - builtins.attrValues cfg.matchBlocks))} + ${concatStringsSep "\n\n" (map (block: matchBlockStr block.data) matchBlocks)} Host * ForwardAgent ${yn cfg.forwardAgent} Compression ${yn cfg.compression} ServerAliveInterval ${toString cfg.serverAliveInterval} + ServerAliveCountMax ${toString cfg.serverAliveCountMax} HashKnownHosts ${yn cfg.hashKnownHosts} UserKnownHostsFile ${cfg.userKnownHostsFile} ControlMaster ${cfg.controlMaster} diff --git a/home-manager/modules/programs/starship.nix b/home-manager/modules/programs/starship.nix index 7c7819865f7..8462d331501 100644 --- a/home-manager/modules/programs/starship.nix +++ b/home-manager/modules/programs/starship.nix @@ -31,8 +31,23 @@ in { }; settings = mkOption { - type = types.attrs; + type = with types; + let + prim = either bool (either int str); + primOrPrimAttrs = either prim (attrsOf prim); + entry = either prim (listOf primOrPrimAttrs); + entryOrAttrsOf = t: either entry (attrsOf t); + entries = entryOrAttrsOf (entryOrAttrsOf entry); + in attrsOf entries // { description = "Starship configuration"; }; default = { }; + example = literalExample '' + { + add_newline = false; + prompt_order = [ "line_break" "package" "line_break" "character" ]; + scan_timeout = 10; + character.symbol = "➜"; + } + ''; description = '' Configuration written to <filename>~/.config/starship.toml</filename>. @@ -74,7 +89,7 @@ in { mkIf (cfg.settings != { }) { source = configFile cfg.settings; }; programs.bash.initExtra = mkIf cfg.enableBashIntegration '' - if [[ -z $INSIDE_EMACS ]]; then + if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then eval "$(${cfg.package}/bin/starship init bash)" fi ''; @@ -85,8 +100,8 @@ in { fi ''; - programs.fish.shellInit = mkIf cfg.enableFishIntegration '' - if test -z "$INSIDE_EMACS" + programs.fish.promptInit = mkIf cfg.enableFishIntegration '' + if test "$TERM" != "dumb" -a \( -z "$INSIDE_EMACS" -o "$INSIDE_EMACS" = "vterm" \) eval (${cfg.package}/bin/starship init fish) end ''; diff --git a/home-manager/modules/programs/termite.nix b/home-manager/modules/programs/termite.nix index 8a05db03558..e3d704424e8 100644 --- a/home-manager/modules/programs/termite.nix +++ b/home-manager/modules/programs/termite.nix @@ -362,7 +362,7 @@ in { ${optionalString "cursor" cfg.cursorColor} ${optionalString "cursor_foreground" cfg.cursorForegroundColor} ${optionalString "foreground" cfg.foregroundColor} - ${optionalString "foregroundBold" cfg.foregroundBoldColor} + ${optionalString "foreground_bold" cfg.foregroundBoldColor} ${optionalString "highlight" cfg.highlightColor} ${cfg.colorsExtra} diff --git a/home-manager/modules/programs/tmux.nix b/home-manager/modules/programs/tmux.nix index 766bc6238ba..a71c302ac6f 100644 --- a/home-manager/modules/programs/tmux.nix +++ b/home-manager/modules/programs/tmux.nix @@ -6,7 +6,7 @@ let cfg = config.programs.tmux; - pluginName = p: if types.package.check p then p.name else p.plugin.name; + pluginName = p: if types.package.check p then p.pname else p.plugin.pname; pluginModule = types.submodule { options = { @@ -31,6 +31,13 @@ let boolToStr = value: if value then "on" else "off"; tmuxConf = '' + ${optionalString cfg.sensibleOnTop '' + # ============================================= # + # Start with defaults from the Sensible plugin # + # --------------------------------------------- # + run-shell ${pkgs.tmuxPlugins.sensible.rtp} + # ============================================= # + ''} set -g default-terminal "${cfg.terminal}" set -g base-index ${toString cfg.baseIndex} setw -g pane-base-index ${toString cfg.baseIndex} @@ -74,10 +81,40 @@ let 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} ''; + configPlugins = { + assertions = [( + let + hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p)); + badPlugins = filter hasBadPluginName cfg.plugins; + in + { + assertion = badPlugins == []; + message = + "Invalid tmux plugin (not prefixed with \"tmuxplugins\"): " + + concatMapStringsSep ", " pluginName badPlugins; + } + )]; + + home.file.".tmux.conf".text = '' + # ============================================= # + # Load plugins with Home Manager # + # --------------------------------------------- # + + ${(concatMapStringsSep "\n\n" (p: '' + # ${pluginName p} + # --------------------- + ${p.extraConfig or ""} + run-shell ${ + if types.package.check p + then p.rtp + else p.plugin.rtp + } + '') cfg.plugins)} + # ============================================= # + ''; + }; in { @@ -214,7 +251,7 @@ in }; secureSocket = mkOption { - default = true; + default = pkgs.stdenv.isLinux; type = types.bool; description = '' Store tmux socket under <filename>/run</filename>, which is more @@ -258,63 +295,22 @@ in }; config = mkIf cfg.enable ( - mkMerge [ + mkMerge ([ { home.packages = [ cfg.package ] ++ optional cfg.tmuxinator.enable pkgs.tmuxinator ++ optional cfg.tmuxp.enable pkgs.tmuxp; - - home.file.".tmux.conf".text = tmuxConf; } - - (mkIf cfg.sensibleOnTop { - home.file.".tmux.conf".text = mkBefore '' - # ============================================= # - # Start with defaults from the Sensible plugin # - # --------------------------------------------- # - run-shell ${pkgs.tmuxPlugins.sensible.rtp} - # ============================================= # - ''; - }) - (mkIf cfg.secureSocket { home.sessionVariables = { TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}''; }; }) - (mkIf (cfg.plugins != []) { - assertions = [( - let - hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p)); - badPlugins = filter hasBadPluginName cfg.plugins; - in - { - assertion = badPlugins == []; - message = - "Invalid tmux plugin (not prefixed with \"tmuxplugins\"): " - + concatMapStringsSep ", " pluginName badPlugins; - } - )]; - - home.file.".tmux.conf".text = mkAfter '' - # ============================================= # - # Load plugins with Home Manager # - # --------------------------------------------- # - - ${(concatMapStringsSep "\n\n" (p: '' - # ${pluginName p} - # --------------------- - ${p.extraConfig or ""} - run-shell ${ - if types.package.check p - then p.rtp - else p.plugin.rtp - } - '') cfg.plugins)} - # ============================================= # - ''; - }) - ] + # config file ~/.tmux.conf + { home.file.".tmux.conf".text = mkBefore tmuxConf; } + (mkIf (cfg.plugins != []) configPlugins) + { home.file.".tmux.conf".text = mkAfter cfg.extraConfig; } + ]) ); } diff --git a/home-manager/modules/programs/vim.nix b/home-manager/modules/programs/vim.nix index 39826a9a5d6..3325bf22516 100644 --- a/home-manager/modules/programs/vim.nix +++ b/home-manager/modules/programs/vim.nix @@ -5,7 +5,7 @@ with lib; let cfg = config.programs.vim; - defaultPlugins = [ pkgs.vimPlugins.sensible ]; + defaultPlugins = [ pkgs.vimPlugins.vim-sensible ]; knownSettings = { background = types.enum [ "dark" "light" ]; diff --git a/home-manager/modules/programs/vscode.nix b/home-manager/modules/programs/vscode.nix index cf7ac722210..099760c834a 100644 --- a/home-manager/modules/programs/vscode.nix +++ b/home-manager/modules/programs/vscode.nix @@ -20,11 +20,14 @@ let "vscodium" = "vscode-oss"; }.${vscodePname}; - configFilePath = + userDir = if pkgs.stdenv.hostPlatform.isDarwin then - "Library/Application Support/${configDir}/User/settings.json" + "Library/Application Support/${configDir}/User" else - "${config.xdg.configHome}/${configDir}/User/settings.json"; + "${config.xdg.configHome}/${configDir}/User"; + + configFilePath = "${userDir}/settings.json"; + keybindingsFilePath = "${userDir}/keybindings.json"; # TODO: On Darwin where are the extensions? extensionPath = ".${extensionDir}/extensions"; @@ -59,6 +62,45 @@ in ''; }; + keybindings = mkOption { + type = types.listOf (types.submodule { + options = { + key = mkOption { + type = types.str; + example = "ctrl+c"; + description = "The key or key-combination to bind."; + }; + + command = mkOption { + type = types.str; + example = "editor.action.clipboardCopyAction"; + description = "The VS Code command to execute."; + }; + + when = mkOption { + type = types.str; + default = ""; + example = "textInputFocus"; + description = "Optional context filter."; + }; + }; + }); + default = []; + example = literalExample '' + [ + { + key = "ctrl+c"; + command = "editor.action.clipboardCopyAction"; + when = "textInputFocus"; + } + ] + ''; + description = '' + Keybindings written to Visual Studio Code's + <filename>keybindings.json</filename>. + ''; + }; + extensions = mkOption { type = types.listOf types.package; default = []; @@ -77,15 +119,13 @@ in # Adapted from https://discourse.nixos.org/t/vscode-extensions-setup/1801/2 home.file = let + subDir = "share/vscode/extensions"; toPaths = path: - let - p = "${path}/share/vscode/extensions"; - in - # Links every dir in p to the extension path. - mapAttrsToList (k: v: - { - "${extensionPath}/${k}".source = "${p}/${k}"; - }) (builtins.readDir p); + # Links every dir in path to the extension path. + mapAttrsToList (k: _: + { + "${extensionPath}/${k}".source = "${path}/${subDir}/${k}"; + }) (builtins.readDir (path + "/${subDir}")); toSymlink = concatMap toPaths cfg.extensions; in foldr @@ -95,6 +135,10 @@ in mkIf (cfg.userSettings != {}) { text = builtins.toJSON cfg.userSettings; }; + "${keybindingsFilePath}" = + mkIf (cfg.keybindings != []) { + text = builtins.toJSON cfg.keybindings; + }; } toSymlink; }; diff --git a/home-manager/modules/programs/waybar.nix b/home-manager/modules/programs/waybar.nix new file mode 100644 index 00000000000..369f6e32aba --- /dev/null +++ b/home-manager/modules/programs/waybar.nix @@ -0,0 +1,363 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.programs.waybar; + + # Used when generating warnings + modulesPath = "programs.waybar.settings.[].modules"; + + # Taken from <https://github.com/Alexays/Waybar/blob/adaf84304865e143e4e83984aaea6f6a7c9d4d96/src/factory.cpp> + defaultModuleNames = [ + "sway/mode" + "sway/workspaces" + "sway/window" + "wlr/taskbar" + "idle_inhibitor" + "memory" + "cpu" + "clock" + "disk" + "tray" + "network" + "backlight" + "pulseaudio" + "mpd" + "temperature" + "bluetooth" + "battery" + ]; + + isValidCustomModuleName = x: + elem x defaultModuleNames || (hasPrefix "custom/" x && stringLength x > 7); + + margins = let + mkMargin = name: { + "margin-${name}" = mkOption { + type = types.nullOr types.int; + default = null; + example = 10; + description = "Margins value without unit."; + }; + }; + margins = map mkMargin [ "top" "left" "bottom" "right" ]; + in foldl' mergeAttrs { } margins; + + waybarBarConfig = with lib.types; + submodule { + options = { + layer = mkOption { + type = nullOr (enum [ "top" "bottom" ]); + default = null; + description = '' + Decide if the bar is displayed in front (<code>"top"</code>) + of the windows or behind (<code>"bottom"</code>). + ''; + example = "top"; + }; + + output = mkOption { + type = nullOr (either str (listOf str)); + default = null; + example = literalExample '' + [ "DP-1" "!DP-2" "!DP-3" ] + ''; + description = '' + Specifies on which screen this bar will be displayed. + Exclamation mark(!) can be used to exclude specific output. + ''; + }; + + position = mkOption { + type = nullOr (enum [ "top" "bottom" "left" "right" ]); + default = null; + example = "right"; + description = "Bar position relative to the output."; + }; + + height = mkOption { + type = nullOr ints.unsigned; + default = null; + example = 5; + description = + "Height to be used by the bar if possible. Leave blank for a dynamic value."; + }; + + width = mkOption { + type = nullOr ints.unsigned; + default = null; + example = 5; + description = + "Width to be used by the bar if possible. Leave blank for a dynamic value."; + }; + + modules-left = mkOption { + type = nullOr (listOf str); + default = null; + description = "Modules that will be displayed on the left."; + example = literalExample '' + [ "sway/workspaces" "sway/mode" "wlr/taskbar" ] + ''; + }; + + modules-center = mkOption { + type = nullOr (listOf str); + default = null; + description = "Modules that will be displayed in the center."; + example = literalExample '' + [ "sway/window" ] + ''; + }; + + modules-right = mkOption { + type = nullOr (listOf str); + default = null; + description = "Modules that will be displayed on the right."; + example = literalExample '' + [ "mpd" "custom/mymodule#with-css-id" "temperature" ] + ''; + }; + + modules = mkOption { + type = attrsOf unspecified; + default = { }; + description = "Modules configuration."; + example = literalExample '' + { + "sway/window" = { + max-length = 50; + }; + "clock" = { + format-alt = "{:%a, %d. %b %H:%M}"; + }; + } + ''; + }; + + margin = mkOption { + type = nullOr str; + default = null; + description = "Margins value using the CSS format without units."; + example = "20 5"; + }; + + inherit (margins) margin-top margin-left margin-bottom margin-right; + + name = mkOption { + type = nullOr str; + default = null; + description = + "Optional name added as a CSS class, for styling multiple waybars."; + example = "waybar-1"; + }; + + gtk-layer-shell = mkOption { + type = nullOr bool; + default = null; + example = false; + description = + "Option to disable the use of gtk-layer-shell for popups."; + }; + }; + }; +in { + meta.maintainers = [ hm.maintainers.berbiche ]; + + options.programs.waybar = with lib.types; { + enable = mkEnableOption "Waybar"; + + package = mkOption { + type = package; + default = pkgs.waybar; + defaultText = literalExample "${pkgs.waybar}"; + description = '' + Waybar package to use. Set to <code>null</code> to use the default module. + ''; + }; + + settings = mkOption { + type = listOf waybarBarConfig; + default = [ ]; + description = '' + Configuration for Waybar, see <link + xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/> + for supported values. + ''; + example = literalExample '' + [ + { + layer = "top"; + position = "top"; + height = 30; + output = [ + "eDP-1" + "HDMI-A-1" + ]; + modules-left = [ "sway/workspaces" "sway/mode" "wlr/taskbar" ]; + modules-center = [ "sway/window" "custom/hello-from-waybar" ]; + modules-right = [ "mpd" "custom/mymodule#with-css-id" "temperature" ]; + modules = { + "sway/workspaces" = { + disable-scroll = true; + all-outputs = true; + }; + "custom/hello-from-waybar" = { + format = "hello {}"; + max-length = 40; + interval = "once"; + exec = pkgs.writeShellScript "hello-from-waybar" ''' + echo "from within waybar" + '''; + }; + }; + } + ] + ''; + }; + + systemd.enable = mkEnableOption "Waybar systemd integration"; + + style = mkOption { + type = nullOr str; + default = null; + description = '' + CSS style of the bar. + See <link xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/> + for the documentation. + ''; + example = '' + * { + border: none; + border-radius: 0; + font-family: Source Code Pro; + } + window#waybar { + background: #16191C; + color: #AAB2BF; + } + #workspaces button { + padding: 0 5px; + } + ''; + }; + }; + + config = let + # Inspired by https://github.com/NixOS/nixpkgs/pull/89781 + writePrettyJSON = name: x: + pkgs.runCommandLocal name { } '' + ${pkgs.jq}/bin/jq . > $out <<<${escapeShellArg (builtins.toJSON x)} + ''; + + configSource = let + # Removes nulls because Waybar ignores them for most values + removeNulls = filterAttrs (_: v: v != null); + + # Makes the actual valid configuration Waybar accepts + # (strips our custom settings before converting to JSON) + makeConfiguration = configuration: + let + # The "modules" option is not valid in the JSON + # as its descendants have to live at the top-level + settingsWithoutModules = + filterAttrs (n: _: n != "modules") configuration; + settingsModules = + optionalAttrs (configuration.modules != { }) configuration.modules; + in removeNulls (settingsWithoutModules // settingsModules); + # The clean list of configurations + finalConfiguration = map makeConfiguration cfg.settings; + in writePrettyJSON "waybar-config.json" finalConfiguration; + + warnings = let + mkUnreferencedModuleWarning = name: + "The module '${name}' defined in '${modulesPath}' is not referenced " + + "in either `modules-left`, `modules-center` or `modules-right` of Waybar's options"; + mkUndefinedModuleWarning = settings: name: + let + # Locations where the module is undefined (a combination modules-{left,center,right}) + locations = flip filter [ "left" "center" "right" ] + (x: elem name settings."modules-${x}"); + mkPath = loc: "'${modulesPath}-${loc}'"; + # The modules-{left,center,right} configuration that includes + # an undefined module + path = concatMapStringsSep " and " mkPath locations; + in "The module '${name}' defined in ${path} is neither " + + "a default module or a custom module declared in '${modulesPath}'"; + mkInvalidModuleNameWarning = name: + "The custom module '${name}' defined in '${modulesPath}' is not a valid " + + "module name. A custom module's name must start with 'custom/' " + + "like 'custom/mymodule' for instance"; + + # Find all modules in `modules-{left,center,right}` and `modules` not declared/referenced. + # `cfg.settings` is a list of Waybar configurations + # and we need to preserve the index for appropriate warnings + allFaultyModules = flip map cfg.settings (settings: + let + allModules = unique + (concatMap (x: attrByPath [ "modules-${x}" ] [ ] settings) [ + "left" + "center" + "right" + ]); + declaredModules = attrNames settings.modules; + # Modules declared in `modules` but not referenced in `modules-{left,center,right}` + unreferencedModules = subtractLists allModules declaredModules; + # Modules listed in modules-{left,center,right} that are not default modules + nonDefaultModules = subtractLists defaultModuleNames allModules; + # Modules referenced in `modules-{left,center,right}` but not declared in `modules` + undefinedModules = subtractLists declaredModules nonDefaultModules; + # Check for invalid module names + invalidModuleNames = + filter (m: !isValidCustomModuleName m) (attrNames settings.modules); + in { + # The Waybar bar configuration (since config.settings is a list) + settings = settings; + undef = undefinedModules; + unref = unreferencedModules; + invalidName = invalidModuleNames; + }); + + allWarnings = flip concatMap allFaultyModules + ({ settings, undef, unref, invalidName }: + let + unreferenced = map mkUnreferencedModuleWarning unref; + undefined = map (mkUndefinedModuleWarning settings) undef; + invalid = map mkInvalidModuleNameWarning invalidName; + in undefined ++ unreferenced ++ invalid); + in allWarnings; + + in mkIf cfg.enable (mkMerge [ + { home.packages = [ cfg.package ]; } + (mkIf (cfg.settings != [ ]) { + # Generate warnings about defined but unreferenced modules + inherit warnings; + + xdg.configFile."waybar/config".source = configSource; + }) + (mkIf (cfg.style != null) { + xdg.configFile."waybar/style.css".text = cfg.style; + }) + (mkIf cfg.systemd.enable { + systemd.user.services.waybar = { + Unit = { + Description = + "Highly customizable Wayland bar for Sway and Wlroots based compositors."; + Documentation = "https://github.com/Alexays/Waybar/wiki"; + PartOf = [ "graphical-session.target" ]; + Requisite = [ "dbus.service" ]; + After = [ "dbus.service" ]; + }; + + Service = { + Type = "dbus"; + BusName = "fr.arouillard.waybar"; + ExecStart = "${cfg.package}/bin/waybar"; + Restart = "always"; + RestartSec = "1sec"; + }; + + Install = { WantedBy = [ "graphical-session.target" ]; }; + }; + }) + ]); +} diff --git a/home-manager/modules/programs/zoxide.nix b/home-manager/modules/programs/zoxide.nix new file mode 100644 index 00000000000..842ff109294 --- /dev/null +++ b/home-manager/modules/programs/zoxide.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.zoxide; + +in { + meta.maintainers = [ maintainers.marsam ]; + + options.programs.zoxide = { + enable = mkEnableOption "zoxide"; + + package = mkOption { + type = types.package; + default = pkgs.zoxide; + defaultText = literalExample "pkgs.zoxide"; + description = '' + Zoxide package to install. + ''; + }; + + options = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--no-aliases" ]; + description = '' + List of options to pass to zoxide. + ''; + }; + + enableBashIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Bash integration. + ''; + }; + + enableZshIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Zsh integration. + ''; + }; + + enableFishIntegration = mkOption { + default = true; + type = types.bool; + description = '' + Whether to enable Fish integration. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + eval "$(${cfg.package}/bin/zoxide init bash ${ + concatStringsSep " " cfg.options + })" + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + eval "$(${cfg.package}/bin/zoxide init zsh ${ + concatStringsSep " " cfg.options + })" + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + ${cfg.package}/bin/zoxide init fish ${ + concatStringsSep " " cfg.options + } | source + ''; + }; +} diff --git a/home-manager/modules/programs/zplug.nix b/home-manager/modules/programs/zplug.nix new file mode 100644 index 00000000000..6cb5e98e313 --- /dev/null +++ b/home-manager/modules/programs/zplug.nix @@ -0,0 +1,60 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.zsh.zplug; + + pluginModule = types.submodule ({ config, ... }: { + options = { + name = mkOption { + type = types.str; + description = "The name of the plugin."; + }; + + tags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "The plugin tags."; + }; + }; + + }); + +in { + options.programs.zsh.zplug = { + enable = mkEnableOption "zplug - a zsh plugin manager"; + + plugins = mkOption { + default = [ ]; + type = types.listOf pluginModule; + description = "List of zplug plugins."; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.zplug ]; + + programs.zsh.initExtraBeforeCompInit = '' + source ${pkgs.zplug}/init.zsh + + ${optionalString (cfg.plugins != [ ]) '' + ${concatStrings (map (plugin: '' + zplug "${plugin.name}"${ + optionalString (plugin.tags != [ ]) '' + ${concatStrings (map (tag: ", ${tag}") plugin.tags)} + '' + } + '') cfg.plugins)} + ''} + + if ! zplug check; then + zplug install + fi + + zplug load + ''; + + }; +} diff --git a/home-manager/modules/programs/zsh.nix b/home-manager/modules/programs/zsh.nix index c5694e1f704..ed65d5fe487 100644 --- a/home-manager/modules/programs/zsh.nix +++ b/home-manager/modules/programs/zsh.nix @@ -18,6 +18,10 @@ let mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") cfg.shellAliases ); + globalAliasesStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: "alias -g ${k}=${lib.escapeShellArg v}") cfg.shellGlobalAliases + ); + zdotdir = "$HOME/" + cfg.dotDir; bindkeyCommands = { @@ -152,6 +156,17 @@ let Name of the theme to be used by oh-my-zsh. ''; }; + + extraConfig = mkOption { + default = ""; + example = '' + zstyle :omz:plugins:ssh-agent identities id_rsa id_rsa2 id_github + ''; + type = types.lines; + description = '' + Extra settings for plugins. + ''; + }; }; }; @@ -170,6 +185,14 @@ in type = types.nullOr types.bool; }; + cdpath = mkOption { + default = []; + description = '' + List of paths to autocomplete calls to `cd`. + ''; + type = types.listOf types.str; + }; + dotDir = mkOption { default = null; example = ".config/zsh"; @@ -183,7 +206,12 @@ in shellAliases = mkOption { default = {}; - example = { ll = "ls -l"; ".." = "cd .."; }; + example = literalExample '' + { + ll = "ls -l"; + ".." = "cd .."; + } + ''; description = '' An attribute set that maps aliases (the top level attribute names in this option) to command strings or directly to build outputs. @@ -191,6 +219,21 @@ in type = types.attrsOf types.str; }; + shellGlobalAliases = mkOption { + default = {}; + example = literalExample '' + { + UUID = "$(uuidgen | tr -d \\n)"; + G = "| grep"; + } + ''; + description = '' + Similar to <varname><link linkend="opt-programs.zsh.shellAliases">opt-programs.zsh.shellAliases</link></varname>, + but are substituted anywhere on a line. + ''; + type = types.attrsOf types.str; + }; + enableCompletion = mkOption { default = true; description = '' @@ -357,6 +400,10 @@ in home.file."${relToDotDir ".zshrc"}".text = '' typeset -U path cdpath fpath manpath + ${optionalString (cfg.cdpath != []) '' + cdpath+=(${concatStringsSep " " cfg.cdpath}) + ''} + for profile in ''${(z)NIX_PROFILES}; do fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions) done @@ -393,6 +440,8 @@ in ${envVarsStr} ${optionalString cfg.oh-my-zsh.enable '' + # oh-my-zsh extra settings for plugins + ${cfg.oh-my-zsh.extraConfig} # oh-my-zsh configuration generated by NixOS ${optionalString (cfg.oh-my-zsh.plugins != []) "plugins=(${concatStringsSep " " cfg.oh-my-zsh.plugins})" @@ -433,6 +482,9 @@ in # Aliases ${aliasesStr} + + # Global Aliases + ${globalAliasesStr} ''; } |