diff options
author | Katharina Fey <kookie@spacekookie.de> | 2019-10-05 22:42:42 +0000 |
---|---|---|
committer | Katharina Fey <kookie@spacekookie.de> | 2019-10-05 22:44:50 +0000 |
commit | 73d865b1dae7585d0eff167271dabe77c9d0b8e6 (patch) | |
tree | 337324fab29014f3d60a8bff4979e397fb556d88 /home-manager/modules/programs | |
parent | 670a2de0037acadb83433165344710dd3ac03adf (diff) | |
parent | e14d8e29606feddb29d7c27ea62dd514ef80f1e4 (diff) |
Replacing nixcfg with libkookierebuild
Generally, nixcfg grew out of a dotfiles repository, that happened to
also have some scripts in it. As more and more of the configuration
was replaced with nix specifics (home-manager, etc...), so did nixcfg
change over time (previously "stuff").
As part of this, kookiepkgs was introduced along-side nixcfg, to make
it easier to add custom things to nixpkgs-based systems
(NixOS). Additionally, the core system configuration was handled via
private infrastructure repositories, each specific to the machine in
question.
The problem with this approach is a lot of redundancy when building
non-userspace (read home-manager) systems and a lot of chaos with
having to cherry-pick commits from different branches to work with
nixpkgs trees in development.
Ultimately, keeping both new package definitions, patches and
configuration for the root system and userspace (home-manager) in the
same repository is a _much_ better approach to solving these issues.
And as such, libkookie was started: the general idea is that it
includes all nix expressions that are relevant to _any_ of my
computers. Under `roots`, a machine can have it's primary
configuration file which is built andcopied into the nix store, so
that nixpkgs can always point at the version a generation was built
with, not what is on disk).
Overlays contains everything that kookiepkgs used to, modules contains
both system-level modules (only required on NixOS), as well as
anything that is being built with home-manager. Modules are all kept
in the same tree, however some require system-level access while
others don't. There could be some kind of list to distinguish the two,
so that userspace-only systems can still take advantage of libkookie.
Diffstat (limited to 'home-manager/modules/programs')
72 files changed, 9690 insertions, 0 deletions
diff --git a/home-manager/modules/programs/afew.nix b/home-manager/modules/programs/afew.nix new file mode 100644 index 00000000000..99bae88c0ee --- /dev/null +++ b/home-manager/modules/programs/afew.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.afew; + +in + +{ + options.programs.afew = { + enable = mkEnableOption "the afew initial tagging script for Notmuch"; + + extraConfig = mkOption { + type = types.lines; + default = '' + [SpamFilter] + [KillThreadsFilter] + [ListMailsFilter] + [ArchiveSentMailsFilter] + [InboxFilter] + ''; + example = '' + [SpamFilter] + + [Filter.0] + query = from:pointyheaded@boss.com + tags = -new;+boss + message = Message from above + + [InboxFilter] + ''; + description = '' + Extra lines added to afew configuration file. Available + configuration options are described in the afew manual: + <link xlink:href="https://afew.readthedocs.io/en/latest/configuration.html" />. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.afew ]; + + xdg.configFile."afew/config".text = '' + # Generated by Home Manager. + # See https://afew.readthedocs.io/ + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/alacritty.nix b/home-manager/modules/programs/alacritty.nix new file mode 100644 index 00000000000..84675cb1c8a --- /dev/null +++ b/home-manager/modules/programs/alacritty.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.alacritty; + +in + +{ + options = { + programs.alacritty = { + enable = mkEnableOption "Alacritty"; + + settings = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + window.dimensions = { + lines = 3; + columns = 200; + }; + key_bindings = [ + { + key = "K"; + mods = "Control"; + chars = "\\x0c"; + } + ]; + } + ''; + description = '' + Configuration written to + <filename>~/.config/alacritty/alacritty.yml</filename>. See + <link xlink:href="https://github.com/jwilm/alacritty/blob/master/alacritty.yml"/> + for the default configuration. + ''; + }; + }; + }; + + config = mkMerge [ + (mkIf cfg.enable { + home.packages = [ pkgs.alacritty ]; + + xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != {}) { + text = replaceStrings ["\\\\"] ["\\"] (builtins.toJSON cfg.settings); + }; + }) + ]; +} diff --git a/home-manager/modules/programs/alot-accounts.nix b/home-manager/modules/programs/alot-accounts.nix new file mode 100644 index 00000000000..8f3ffdfb31e --- /dev/null +++ b/home-manager/modules/programs/alot-accounts.nix @@ -0,0 +1,61 @@ +pkgs: +{ config, lib, ... }: + +with lib; + +{ + options.alot = { + sendMailCommand = mkOption { + type = types.nullOr types.str; + description = '' + Command to send a mail. If msmtp is enabled for the account, + then this is set to + <command>msmtpq --read-envelope-from --read-recipients</command>. + ''; + }; + + contactCompletion = mkOption { + type = types.attrsOf types.str; + default = { + type = "shellcommand"; + command = "'${pkgs.notmuch}/bin/notmuch address --format=json --output=recipients date:6M..'"; + regexp = + "'\\[?{" + + ''"name": "(?P<name>.*)", '' + + ''"address": "(?P<email>.+)", '' + + ''"name-addr": ".*"'' + + "}[,\\]]?'"; + shellcommand_external_filtering = "False"; + }; + example = literalExample '' + { + type = "shellcommand"; + command = "abook --mutt-query"; + regexp = "'^(?P<email>[^@]+@[^\t]+)\t+(?P<name>[^\t]+)'"; + ignorecase = "True"; + } + ''; + description = '' + Contact completion configuration as expected per alot. + See <link xlink:href="http://alot.readthedocs.io/en/latest/configuration/contacts_completion.html">alot's wiki</link> for + explanation about possible values. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra settings to add to this Alot account configuration. + ''; + }; + }; + + config = mkIf config.notmuch.enable { + alot.sendMailCommand = mkOptionDefault ( + if config.msmtp.enable + then "msmtpq --read-envelope-from --read-recipients" + else null + ); + }; +} diff --git a/home-manager/modules/programs/alot.nix b/home-manager/modules/programs/alot.nix new file mode 100644 index 00000000000..2b28f34caa3 --- /dev/null +++ b/home-manager/modules/programs/alot.nix @@ -0,0 +1,173 @@ +# alot config loader is sensitive to leading space ! +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.alot; + + 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. + ''; + }; + + hooks = mkOption { + type = types.lines; + default = ""; + description = '' + Content of the hooks file. + ''; + }; + + 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. + ''; + }; + + 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. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.alot ]; + + xdg.configFile."alot/config".text = configFile; + + xdg.configFile."alot/hooks.py".text = + '' + # Generated by Home Manager. + '' + + cfg.hooks; + }; +} diff --git a/home-manager/modules/programs/astroid-accounts.nix b/home-manager/modules/programs/astroid-accounts.nix new file mode 100644 index 00000000000..bc94a301db0 --- /dev/null +++ b/home-manager/modules/programs/astroid-accounts.nix @@ -0,0 +1,33 @@ +{ config, lib, ... }: + +with lib; + +{ + options.astroid = { + enable = mkEnableOption "Astroid"; + + sendMailCommand = mkOption { + type = types.str; + description = '' + Command to send a mail. If msmtp is enabled for the account, + then this is set to + <command>msmtpq --read-envelope-from --read-recipients</command>. + ''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + example = { select_query = ""; }; + description = '' + Extra settings to add to this astroid account configuration. + ''; + }; + }; + + config = mkIf config.notmuch.enable { + astroid.sendMailCommand = mkIf config.msmtp.enable ( + mkOptionDefault "msmtpq --read-envelope-from --read-recipients" + ); + }; +} diff --git a/home-manager/modules/programs/astroid-config-template.json b/home-manager/modules/programs/astroid-config-template.json new file mode 100644 index 00000000000..87e3f764f9c --- /dev/null +++ b/home-manager/modules/programs/astroid-config-template.json @@ -0,0 +1,113 @@ +{ + "astroid": { + "config": { + "version": "11" + }, + "debug": { + "dryrun_sending": "false" + }, + "hints": { + "level": "0" + }, + "log": { + "syslog": "false", + "stdout": "true", + "level": "info" + } + }, + "startup": { + "queries": { + "inbox": "tag:inbox" + } + }, + "terminal": { + "height": "10", + "font_description": "default" + }, + "thread_index": { + "page_jump_rows": "6", + "sort_order": "newest", + "cell": { + "font_description": "default", + "line_spacing": "2", + "date_length": "10", + "message_count_length": "4", + "authors_length": "20", + "subject_color": "#807d74", + "subject_color_selected": "#000000", + "background_color_selected": "", + "background_color_marked": "#fff584", + "background_color_marked_selected": "#bcb559", + "tags_length": "80", + "tags_upper_color": "#e5e5e5", + "tags_lower_color": "#333333", + "tags_alpha": "0.5", + "hidden_tags": "attachment,flagged,unread" + } + }, + "general": { + "time": { + "clock_format": "local", + "same_year": "%b %-e", + "diff_year": "%x" + } + }, + "editor": { + "charset": "utf-8", + "save_draft_on_force_quit": "true", + "attachment_words": "attach", + "attachment_directory": "~", + "markdown_processor": "marked" + }, + "mail": { + "reply": { + "quote_line": "Excerpts from %1's message of %2:", + "mailinglist_reply_to_sender": "true" + }, + "forward": { + "quote_line": "Forwarding %1's message of %2:", + "disposition": "inline" + }, + "sent_tags": "sent", + "message_id_fqdn": "", + "message_id_user": "", + "user_agent": "default", + "send_delay": "2", + "close_on_success": "false", + "format_flowed": "false" + }, + "poll": { + "interval": "60", + "always_full_refresh": "false" + }, + "attachment": { + "external_open_cmd": "xdg-open" + }, + "thread_view": { + "open_html_part_external": "false", + "preferred_type": "plain", + "preferred_html_only": "false", + "allow_remote_when_encrypted": "false", + "open_external_link": "xdg-open", + "default_save_directory": "~", + "indent_messages": "false", + "gravatar": { + "enable": "true" + }, + "mark_unread_delay": "0.5", + "expand_flagged": "true" + }, + "crypto": { + "gpg": { + "path": "gpg2", + "always_trust": "true", + "enabled": "true" + } + }, + "saved_searches": { + "show_on_startup": "false", + "save_history": "true", + "history_lines_to_show": "15", + "history_lines": "1000" + } +} diff --git a/home-manager/modules/programs/astroid.nix b/home-manager/modules/programs/astroid.nix new file mode 100644 index 00000000000..0463cd15528 --- /dev/null +++ b/home-manager/modules/programs/astroid.nix @@ -0,0 +1,139 @@ +{ config, lib, pkgs, ... }: + +with lib; +with builtins; + +let + + cfg = config.programs.astroid; + + astroidAccounts = + filterAttrs + (n: v: v.astroid.enable) + config.accounts.email.accounts; + + boolOpt = b: if b then "true" else "false"; + + accountAttr = account: with account; { + email = address; + name = realName; + sendmail = astroid.sendMailCommand; + additional_sent_tags = ""; + default = boolOpt primary; + save_drafts_to = folders.drafts; + save_sent = "true"; + save_sent_to = folders.sent; + select_query = ""; + } + // optionalAttrs (signature.showSignature != "none") { + signature_attach = boolOpt (signature.showSignature == "attach"); + signature_default_on = boolOpt (signature.showSignature != "none"); + signature_file = pkgs.writeText "signature.txt" signature.text; + signature_file_markdown = "false"; + signature_separate = "true"; # prepends '--\n' to the signature + } + // optionalAttrs (gpg != null) { + always_gpg_sign = boolOpt gpg.signByDefault; + gpgkey = gpg.key; + } + // astroid.extraConfig; + + # See https://github.com/astroidmail/astroid/wiki/Configuration-Reference + configFile = mailAccounts: + let + template = fromJSON (readFile ./astroid-config-template.json); + astroidConfig = foldl' recursiveUpdate template [ + { + astroid.notmuch_config = "${config.xdg.configHome}/notmuch/notmuchrc"; + accounts = mapAttrs (n: accountAttr) astroidAccounts; + crypto.gpg.path = "${pkgs.gnupg}/bin/gpg"; + } + cfg.extraConfig + cfg.externalEditor + ]; + in + builtins.toJSON astroidConfig; + +in + +{ + options = { + programs.astroid = { + enable = mkEnableOption "Astroid"; + + pollScript = mkOption { + type = types.str; + default = ""; + example = "mbsync gmail"; + description = '' + Script to run to fetch/update mails. + ''; + }; + + externalEditor = mkOption { + type = types.nullOr types.str; + default = null; + # Converts it into JSON that can be merged into the configuration. + apply = cmd: + optionalAttrs (cmd != null) { + editor = { + "external_editor" = "true"; + "cmd" = cmd; + }; + }; + example = "nvim-qt -- -c 'set ft=mail' '+set fileencoding=utf-8' '+set ff=unix' '+set enc=utf-8' '+set fo+=w' %1"; + description = '' + You can use <code>%1</code>, <code>%2</code>, and + <code>%3</code> to refer respectively to: + <orderedlist numeration="arabic"> + <listitem><para>file name</para></listitem> + <listitem><para>server name</para></listitem> + <listitem><para>socket ID</para></listitem> + </orderedlist> + See <link xlink:href='https://github.com/astroidmail/astroid/wiki/Customizing-editor' />. + ''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + example = { poll.interval = 0; }; + description = '' + JSON config that will override the default Astroid configuration. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = config.programs.notmuch.maildir.synchronizeFlags; + message = "The astroid module requires" + + " 'programs.notmuch.maildir.synchronizeFlags = true'."; + } + ]; + + home.packages = [ pkgs.astroid ]; + + xdg.configFile."astroid/config".source = + pkgs.runCommand "out.json" + { + json = configFile astroidAccounts; + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + echo -n "$json" | ${pkgs.jq}/bin/jq . > $out + ''; + + xdg.configFile."astroid/poll.sh" = { + executable = true; + text = '' + # Generated by Home Manager + + ${cfg.pollScript} + ''; + }; + }; +} diff --git a/home-manager/modules/programs/autorandr.nix b/home-manager/modules/programs/autorandr.nix new file mode 100644 index 00000000000..4514a5b5d58 --- /dev/null +++ b/home-manager/modules/programs/autorandr.nix @@ -0,0 +1,349 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.autorandr; + + matrixOf = n: m: elemType: mkOptionType rec { + name = "matrixOf"; + description = "${toString n}×${toString m} matrix of ${elemType.description}s"; + check = xss: + let + listOfSize = l: xs: isList xs && length xs == l; + in + listOfSize n xss && all (xs: listOfSize m xs && all elemType.check xs) xss; + merge = mergeOneOption; + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*" "*"]); + getSubModules = elemType.getSubModules; + substSubModules = mod: matrixOf n m (elemType.substSubModules mod); + functor = (defaultFunctor name) // { wrapped = elemType; }; + }; + + profileModule = types.submodule { + options = { + fingerprint = mkOption { + type = types.attrsOf types.str; + description = '' + Output name to EDID mapping. + Use <code>autorandr --fingerprint</code> to get current setup values. + ''; + default = {}; + }; + + config = mkOption { + type = types.attrsOf configModule; + description = "Per output profile configuration."; + default = {}; + }; + + hooks = mkOption { + type = profileHooksModule; + description = "Profile hook scripts."; + default = {}; + }; + }; + }; + + configModule = types.submodule { + options = { + enable = mkOption { + type = types.bool; + description = "Whether to enable the output."; + default = true; + }; + + primary = mkOption { + type = types.bool; + description = "Whether output should be marked as primary"; + default = false; + }; + + position = mkOption { + type = types.str; + description = "Output position"; + default = ""; + example = "5760x0"; + }; + + mode = mkOption { + type = types.str; + description = "Output resolution."; + default = ""; + example = "3840x2160"; + }; + + rate = mkOption { + type = types.str; + description = "Output framerate."; + default = ""; + example = "60.00"; + }; + + gamma = mkOption { + type = types.str; + description = "Output gamma configuration."; + default = ""; + example = "1.0:0.909:0.833"; + }; + + rotate = mkOption { + type = types.nullOr (types.enum ["normal" "left" "right" "inverted"]); + description = "Output rotate configuration."; + default = null; + example = "left"; + }; + + transform = mkOption { + type = types.nullOr (matrixOf 3 3 types.float); + default = null; + example = literalExample '' + [ + [ 0.6 0.0 0.0 ] + [ 0.0 0.6 0.0 ] + [ 0.0 0.0 1.0 ] + ] + ''; + description = '' + Refer to + <citerefentry> + <refentrytitle>xrandr</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for the documentation of the transform matrix. + ''; + }; + + dpi = mkOption { + type = types.nullOr types.ints.positive; + description = "Output DPI configuration."; + default = null; + example = 96; + }; + + scale = mkOption { + type = types.nullOr (types.submodule { + options = { + method = mkOption { + type = types.enum ["factor" "pixel" ]; + description = "Output scaling method."; + default = "factor"; + example = "pixel"; + }; + + x = mkOption { + type = types.either types.float types.ints.positive; + description = "Horizontal scaling factor/pixels."; + }; + + y = mkOption { + type = types.either types.float types.ints.positive; + description = "Vertical scaling factor/pixels."; + }; + }; + }); + description = '' + Output scale configuration. + </para><para> + Either configure by pixels or a scaling factor. When using pixel method the + <citerefentry> + <refentrytitle>xrandr</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + option + <parameter class="command">--scale-from</parameter> + will be used; when using factor method the option + <parameter class="command">--scale</parameter> + will be used. + </para><para> + This option is a shortcut version of the transform option and they are mutually + exclusive. + ''; + default = null; + example = literalExample '' + { + x = 1.25; + y = 1.25; + } + ''; + }; + }; + }; + + hookType = types.lines; + + globalHooksModule = types.submodule { + options = { + postswitch = mkOption { + type = types.attrsOf hookType; + description = "Postswitch hook executed after mode switch."; + default = {}; + }; + + preswitch = mkOption { + type = types.attrsOf hookType; + description = "Preswitch hook executed before mode switch."; + default = {}; + }; + + predetect = mkOption { + type = types.attrsOf hookType; + description = "Predetect hook executed before autorandr attempts to run xrandr."; + default = {}; + }; + }; + }; + + profileHooksModule = types.submodule { + options = { + postswitch = mkOption { + type = hookType; + description = "Postswitch hook executed after mode switch."; + default = ""; + }; + + preswitch = mkOption { + type = hookType; + description = "Preswitch hook executed before mode switch."; + default = ""; + }; + + predetect = mkOption { + type = hookType; + description = "Predetect hook executed before autorandr attempts to run xrandr."; + default = ""; + }; + }; + }; + + hookToFile = folder: name: hook: + nameValuePair + "autorandr/${folder}/${name}" + { source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook"; }; + profileToFiles = name: profile: with profile; mkMerge ([ + { + "autorandr/${name}/setup".text = concatStringsSep "\n" (mapAttrsToList fingerprintToString fingerprint); + "autorandr/${name}/config".text = concatStringsSep "\n" (mapAttrsToList configToString profile.config); + } + (mkIf (hooks.postswitch != "") (listToAttrs [ (hookToFile name "postswitch" hooks.postswitch) ])) + (mkIf (hooks.preswitch != "") (listToAttrs [ (hookToFile name "preswitch" hooks.preswitch) ])) + (mkIf (hooks.predetect != "") (listToAttrs [ (hookToFile name "predetect" hooks.predetect) ])) + ]); + 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 '' + output ${name} + off + ''; + +in + +{ + options = { + programs.autorandr = { + enable = mkEnableOption "Autorandr"; + + hooks = mkOption { + type = globalHooksModule; + description = "Global hook scripts"; + default = {}; + example = literalExample '' + { + postswitch = { + "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart"; + "change-background" = readFile ./change-background.sh; + "change-dpi" = ''' + case "$AUTORANDR_CURRENT_PROFILE" in + default) + DPI=120 + ;; + home) + DPI=192 + ;; + work) + DPI=144 + ;; + *) + echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE" + exit 1 + esac + + echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge + ''' + }; + } + ''; + }; + + profiles = mkOption { + type = types.attrsOf profileModule; + description = "Autorandr profiles specification."; + default = {}; + example = literalExample '' + { + "work" = { + fingerprint = { + eDP1 = "<EDID>"; + DP1 = "<EDID>"; + }; + config = { + eDP1.enable = false; + DP1 = { + enable = true; + primary = true; + position = "0x0"; + mode = "3840x2160"; + gamma = "1.0:0.909:0.833"; + rate = "60.00"; + rotate = "left"; + }; + }; + hooks.postswitch = readFile ./work-postswitch.sh; + }; + } + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = flatten (mapAttrsToList ( + profile: { config, ... }: mapAttrsToList ( + output: opts: { + assertion = opts.scale == null || opts.transform == null; + message = '' + Cannot use the profile output options 'scale' and 'transform' simultaneously. + Check configuration for: programs.autorandr.profiles.${profile}.config.${output} + ''; + }) + config + ) + cfg.profiles); + + home.packages = [ pkgs.autorandr ]; + xdg.configFile = mkMerge ([ + (mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch) + (mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch) + (mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect) + (mkMerge (mapAttrsToList profileToFiles cfg.profiles)) + ]); + }; + + meta.maintainers = [ maintainers.uvnikita ]; +} diff --git a/home-manager/modules/programs/bash.nix b/home-manager/modules/programs/bash.nix new file mode 100644 index 00000000000..82a9fbe8f8b --- /dev/null +++ b/home-manager/modules/programs/bash.nix @@ -0,0 +1,219 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.bash; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.bash = { + enable = mkEnableOption "GNU Bourne-Again SHell"; + + historySize = mkOption { + type = types.int; + default = 10000; + description = "Number of history lines to keep in memory."; + }; + + historyFile = mkOption { + type = types.str; + default = "$HOME/.bash_history"; + description = "Location of the bash history file."; + }; + + historyFileSize = mkOption { + type = types.int; + default = 100000; + description = "Number of history lines to keep on file."; + }; + + historyControl = mkOption { + type = types.listOf (types.enum [ + "erasedups" + "ignoredups" + "ignorespace" + ]); + default = []; + description = "Controlling how commands are saved on the history list."; + }; + + historyIgnore = mkOption { + type = types.listOf types.str; + default = []; + example = [ "ls" "cd" "exit" ]; + description = "List of commands that should not be saved to the history list."; + }; + + shellOptions = mkOption { + type = types.listOf types.str; + default = [ + # Append to history file rather than replacing it. + "histappend" + + # check the window size after each command and, if + # necessary, update the values of LINES and COLUMNS. + "checkwinsize" + + # Extended globbing. + "extglob" + "globstar" + + # Warn if closing shell with running jobs. + "checkjobs" + ]; + description = "Shell options to set."; + }; + + sessionVariables = mkOption { + default = {}; + type = types.attrs; + example = { MAILCHECK = 30; }; + description = '' + Environment variables that will be set for the Bash session. + ''; + }; + + shellAliases = mkOption { + default = {}; + type = types.attrsOf types.str; + example = { 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. + ''; + }; + + enableAutojump = mkOption { + default = false; + type = types.bool; + description = "Enable the autojump navigation tool."; + }; + + profileExtra = mkOption { + default = ""; + type = types.lines; + description = '' + Extra commands that should be run when initializing a login + shell. + ''; + }; + + bashrcExtra = mkOption { + # Hide for now, may want to rename in the future. + visible = false; + default = ""; + type = types.lines; + description = '' + Extra commands that should be added to + <filename>~/.bashrc</filename>. + ''; + }; + + initExtra = mkOption { + default = ""; + type = types.lines; + description = '' + Extra commands that should be run when initializing an + interactive shell. + ''; + }; + + logoutExtra = mkOption { + default = ""; + type = types.lines; + description = '' + Extra commands that should be run when logging out of an + interactive shell. + ''; + }; + }; + }; + + config = ( + let + aliasesStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: "alias ${k}=${escapeShellArg v}") cfg.shellAliases + ); + + shoptsStr = concatStringsSep "\n" ( + map (v: "shopt -s ${v}") cfg.shellOptions + ); + + sessionVarsStr = config.lib.shell.exportAll cfg.sessionVariables; + + historyControlStr = + concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${v}") ( + { + HISTFILE = "\"${cfg.historyFile}\""; + HISTFILESIZE = toString cfg.historyFileSize; + HISTSIZE = toString cfg.historySize; + } + // optionalAttrs (cfg.historyControl != []) { + HISTCONTROL = concatStringsSep ":" cfg.historyControl; + } + // optionalAttrs (cfg.historyIgnore != []) { + HISTIGNORE = concatStringsSep ":" cfg.historyIgnore; + } + )); + in mkIf cfg.enable { + programs.bash.bashrcExtra = '' + # Commands that should be applied only for interactive shells. + if [[ $- == *i* ]]; then + ${historyControlStr} + + ${shoptsStr} + + ${aliasesStr} + + ${cfg.initExtra} + + ${optionalString cfg.enableAutojump + ". ${pkgs.autojump}/share/autojump/autojump.bash"} + fi + ''; + + home.file.".bash_profile".text = '' + # -*- mode: sh -*- + + # include .profile if it exists + [[ -f ~/.profile ]] && . ~/.profile + + # include .bashrc if it exists + [[ -f ~/.bashrc ]] && . ~/.bashrc + ''; + + home.file.".profile".text = '' + # -*- mode: sh -*- + + . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh" + + ${sessionVarsStr} + + ${cfg.profileExtra} + ''; + + home.file.".bashrc".text = '' + # -*- mode: sh -*- + + ${cfg.bashrcExtra} + ''; + + home.file.".bash_logout" = mkIf (cfg.logoutExtra != "") { + text = '' + # -*- mode: sh -*- + + ${cfg.logoutExtra} + ''; + }; + + home.packages = + optional (cfg.enableAutojump) pkgs.autojump; + } + ); +} diff --git a/home-manager/modules/programs/bat.nix b/home-manager/modules/programs/bat.nix new file mode 100644 index 00000000000..860c5e82f54 --- /dev/null +++ b/home-manager/modules/programs/bat.nix @@ -0,0 +1,40 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.bat; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.bat = { + enable = mkEnableOption "bat, a cat clone with wings"; + + config = mkOption { + type = types.attrsOf types.str; + default = {}; + example = { + theme = "TwoDark"; + pager = "less -FR"; + }; + description = '' + Bat configuration. + ''; + }; + + }; + + 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 + ); + }; + }; +} diff --git a/home-manager/modules/programs/beets.nix b/home-manager/modules/programs/beets.nix new file mode 100644 index 00000000000..152bfd304a4 --- /dev/null +++ b/home-manager/modules/programs/beets.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.beets; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.beets = { + enable = mkOption { + type = types.bool; + default = + if versionAtLeast config.home.stateVersion "19.03" + then false + else cfg.settings != {}; + defaultText = "false"; + description = '' + Whether to enable the beets music library manager. This + defaults to <literal>false</literal> for state + version ≥ 19.03. For earlier versions beets is enabled if + <option>programs.beets.settings</option> is non-empty. + ''; + }; + + settings = mkOption { + type = types.attrs; + default = {}; + description = '' + Configuration written to + <filename>~/.config/beets/config.yaml</filename> + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.beets ]; + + xdg.configFile."beets/config.yaml".text = + builtins.toJSON config.programs.beets.settings; + }; +} diff --git a/home-manager/modules/programs/broot.nix b/home-manager/modules/programs/broot.nix new file mode 100644 index 00000000000..f6d3cd7f920 --- /dev/null +++ b/home-manager/modules/programs/broot.nix @@ -0,0 +1,261 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.broot; + + configFile = config: + pkgs.runCommand "conf.toml" + { + buildInputs = [ pkgs.remarshal ]; + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + remarshal -if json -of toml \ + < ${pkgs.writeText "verbs.json" (builtins.toJSON config)} \ + > $out + ''; + + brootConf = { + verbs = + mapAttrsToList + (name: value: value // { invocation = name; }) + cfg.verbs; + skin = cfg.skin; + }; + +in + +{ + meta.maintainers = [ maintainers.aheaume ]; + + options.programs.broot = { + enable = mkEnableOption "Broot, a better way to navigate directories"; + + 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. + ''; + }; + + verbs = mkOption { + type = with types; attrsOf (attrsOf (either bool str)); + default = { + "p" = { execution = ":parent"; }; + "edit" = { shortcut = "e"; execution = "$EDITOR {file}" ; }; + "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; }; + "view" = { execution = "less {file}"; }; + }; + example = literalExample '' + { + "p" = { execution = ":parent"; }; + "edit" = { shortcut = "e"; execution = "$EDITOR {file}" ; }; + "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; }; + "view" = { execution = "less {file}"; }; + "blop {name}\\.{type}" = { + execution = "/bin/mkdir {parent}/{type} && /usr/bin/nvim {parent}/{type}/{name}.{type}"; + from_shell = true; + }; + } + ''; + description = '' + Define new verbs. The attribute name indicates how the verb is + called by the user, with placeholders for arguments. + </para><para> + The possible attributes are: + </para> + + <para> + <variablelist> + <varlistentry> + <term><literal>execution</literal> (mandatory)</term> + <listitem><para>how the verb is executed</para></listitem> + </varlistentry> + <varlistentry> + <term><literal>shortcut</literal> (optional)</term> + <listitem><para>an alternate way to call the verb (without + the arguments part)</para></listitem> + </varlistentry> + <varlistentry> + <term><literal>leave_broot</literal> (optional)</term> + <listitem><para>whether to quit broot on execution + (default: <literal>true</literal>)</para></listitem> + </varlistentry> + <varlistentry> + <term><literal>from_shell</literal> (optional)</term> + <listitem><para>whether the verb must be executed from the + parent shell (default: + <literal>false</literal>)</para></listitem> + </varlistentry> + </variablelist> + ''; + }; + + skin = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + status_normal_fg = "grayscale(18)"; + status_normal_bg = "grayscale(3)"; + status_error_fg = "red"; + status_error_bg = "yellow"; + tree_fg = "red"; + selected_line_bg = "grayscale(7)"; + permissions_fg = "grayscale(12)"; + size_bar_full_bg = "red"; + size_bar_void_bg = "black"; + directory_fg = "lightyellow"; + input_fg = "cyan"; + flag_value_fg = "lightyellow"; + table_border_fg = "red"; + code_fg = "lightyellow"; + } + ''; + description = '' + Color configuration. + </para><para> + Complete list of keys (expected to change before the v1 of broot): + + <itemizedlist> + <listitem><para><literal>char_match</literal></para></listitem> + <listitem><para><literal>code</literal></para></listitem> + <listitem><para><literal>directory</literal></para></listitem> + <listitem><para><literal>exe</literal></para></listitem> + <listitem><para><literal>file</literal></para></listitem> + <listitem><para><literal>file_error</literal></para></listitem> + <listitem><para><literal>flag_label</literal></para></listitem> + <listitem><para><literal>flag_value</literal></para></listitem> + <listitem><para><literal>input</literal></para></listitem> + <listitem><para><literal>link</literal></para></listitem> + <listitem><para><literal>permissions</literal></para></listitem> + <listitem><para><literal>selected_line</literal></para></listitem> + <listitem><para><literal>size_bar_full</literal></para></listitem> + <listitem><para><literal>size_bar_void</literal></para></listitem> + <listitem><para><literal>size_text</literal></para></listitem> + <listitem><para><literal>spinner</literal></para></listitem> + <listitem><para><literal>status_error</literal></para></listitem> + <listitem><para><literal>status_normal</literal></para></listitem> + <listitem><para><literal>table_border</literal></para></listitem> + <listitem><para><literal>tree</literal></para></listitem> + <listitem><para><literal>unlisted</literal></para></listitem> + </itemizedlist></para> + + <para> + Add <literal>_fg</literal> for a foreground color and + <literal>_bg</literal> for a background colors. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.broot ]; + + xdg.configFile."broot/conf.toml".source = configFile brootConf; + + # Dummy file to prevent broot from trying to reinstall itself + xdg.configFile."broot/launcher/installed".text = ""; + + programs.bash.initExtra = + mkIf cfg.enableBashIntegration ( + # 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 + # This function starts broot and executes the command + # it produces, if any. + # It's needed because some shell commands, like `cd`, + # have no useful effect if executed in a subshell. + function br { + f=$(mktemp) + ( + set +e + broot --outcmd "$f" "$@" + code=$? + if [ "$code" != 0 ]; then + rm -f "$f" + exit "$code" + fi + ) + code=$? + if [ "$code" != 0 ]; then + return "$code" + fi + d=$(cat "$f") + rm -f "$f" + eval "$d" + } + '' + ); + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + # This script was automatically generated by the broot function + # More information can be found in https://github.com/Canop/broot + # This function starts broot and executes the command + # it produces, if any. + # It's needed because some shell commands, like `cd`, + # have no useful effect if executed in a subshell. + function br { + f=$(mktemp) + ( + set +e + broot --outcmd "$f" "$@" + code=$? + if [ "$code" != 0 ]; then + rm -f "$f" + exit "$code" + fi + ) + code=$? + if [ "$code" != 0 ]; then + return "$code" + fi + d=$(cat "$f") + rm -f "$f" + eval "$d" + } + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + # This script was automatically generated by the broot function + # More information can be found in https://github.com/Canop/broot + # This function starts broot and executes the command + # it produces, if any. + # It's needed because some shell commands, like `cd`, + # have no useful effect if executed in a subshell. + function br + set f (mktemp) + broot --outcmd $f $argv + if test $status -ne 0 + rm -f "$f" + return "$code" + end + set d (cat "$f") + rm -f "$f" + eval "$d" + end + ''; + }; +} diff --git a/home-manager/modules/programs/browserpass.nix b/home-manager/modules/programs/browserpass.nix new file mode 100644 index 00000000000..7af5e8f8756 --- /dev/null +++ b/home-manager/modules/programs/browserpass.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + browsers = [ + "chrome" + "chromium" + "firefox" + "vivaldi" + ]; +in { + options = { + programs.browserpass = { + enable = mkEnableOption "the browserpass extension host application"; + + browsers = mkOption { + type = types.listOf (types.enum browsers); + default = browsers; + example = [ "firefox" ]; + description = "Which browsers to install browserpass for"; + }; + }; + }; + + config = mkIf config.programs.browserpass.enable { + home.file = builtins.concatLists (with pkgs.stdenv; map (x: + if x == "chrome" then + let dir = if isDarwin + then "Library/Application Support/Google/Chrome/NativeMessagingHosts" + else ".config/google-chrome/NativeMessagingHosts"; + in [ + { + target = "${dir}/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; + } + { + target = "${dir}/../policies/managed/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; + } + ] + else if x == "chromium" then + let dir = if isDarwin + then "Library/Application Support/Chromium/NativeMessagingHosts" + else ".config/chromium/NativeMessagingHosts"; + in [ + { + target = "${dir}/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; + } + { + target = "${dir}/../policies/managed/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; + } + ] + else if x == "firefox" then + [ { + target = (if isDarwin + then "Library/Application Support/Mozilla/NativeMessagingHosts" + else ".mozilla/native-messaging-hosts") + + "/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json"; + } ] + else if x == "vivaldi" then + let dir = if isDarwin + then "Library/Application Support/Vivaldi/NativeMessagingHosts" + else ".config/vivaldi/NativeMessagingHosts"; + in [ + { + target = "${dir}/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json"; + } + { + target = "${dir}/../policies/managed/com.github.browserpass.native.json"; + source = "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json"; + } + ] + else throw "unknown browser ${x}") config.programs.browserpass.browsers); + }; +} diff --git a/home-manager/modules/programs/chromium.nix b/home-manager/modules/programs/chromium.nix new file mode 100644 index 00000000000..83a827a0ed0 --- /dev/null +++ b/home-manager/modules/programs/chromium.nix @@ -0,0 +1,93 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + browserModule = defaultPkg: name: visible: + let + browser = (builtins.parseDrvName defaultPkg.name).name; + in + { + enable = mkOption { + inherit visible; + default = false; + example = true; + description = "Whether to enable ${name}."; + type = lib.types.bool; + }; + + package = mkOption { + inherit visible; + type = types.package; + default = defaultPkg; + defaultText = literalExample "pkgs.${browser}"; + description = "The ${name} package to use."; + }; + + extensions = mkOption { + inherit visible; + type = types.listOf types.str; + default = []; + example = literalExample '' + [ + "chlffgpmiacpedhhbkiomidkjlcfhogd" # pushbullet + "mbniclmhobmnbdlbpiphghaielnnpgdp" # lightshot + "gcbommkclmclpchllfjekcdonpmejbdp" # https everywhere + "cjpalhdlnbpafiamejdnhcphjbkeiagm" # ublock origin + ] + ''; + description = '' + List of ${name} extensions to install. + To find the extension ID, check its URL on the + <link xlink:href="https://chrome.google.com/webstore/category/extensions">Chrome Web Store</link>. + ''; + }; + }; + + browserConfig = cfg: + let + + browser = (builtins.parseDrvName cfg.package.name).name; + + darwinDirs = { + chromium = "Chromium"; + google-chrome = "Google/Chrome"; + google-chrome-beta = "Google/Chrome Beta"; + google-chrome-dev = "Google/Chrome Dev"; + }; + + configDir = if pkgs.stdenv.isDarwin + then "Library/Application Support/${getAttr browser darwinDirs}" + else "${config.xdg.configHome}/${browser}"; + + extensionJson = ext: { + target = "${configDir}/External Extensions/${ext}.json"; + text = builtins.toJSON { + external_update_url = "https://clients2.google.com/service/update2/crx"; + }; + }; + + in + mkIf cfg.enable { + home.packages = [ cfg.package ]; + home.file = map extensionJson cfg.extensions; + }; + +in + +{ + options.programs = { + chromium = browserModule pkgs.chromium "Chromium" true; + google-chrome = browserModule pkgs.google-chrome "Google Chrome" false; + google-chrome-beta = browserModule pkgs.google-chrome-beta "Google Chrome Beta" false; + google-chrome-dev = browserModule pkgs.google-chrome-dev "Google Chrome Dev" false; + }; + + config = mkMerge [ + (browserConfig config.programs.chromium) + (browserConfig config.programs.google-chrome) + (browserConfig config.programs.google-chrome-beta) + (browserConfig config.programs.google-chrome-dev) + ]; +} diff --git a/home-manager/modules/programs/command-not-found/command-not-found.nix b/home-manager/modules/programs/command-not-found/command-not-found.nix new file mode 100644 index 00000000000..0053fe36ad7 --- /dev/null +++ b/home-manager/modules/programs/command-not-found/command-not-found.nix @@ -0,0 +1,57 @@ +# Adapted from Nixpkgs. + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.command-not-found; + commandNotFound = pkgs.substituteAll { + name = "command-not-found"; + dir = "bin"; + src = ./command-not-found.pl; + isExecutable = true; + inherit (pkgs) perl; + inherit (cfg) dbPath; + perlFlags = concatStrings (map (path: "-I ${path}/lib/perl5/site_perl ") + [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite pkgs.perlPackages.StringShellQuote ]); + }; + + shInit = commandNotFoundHandlerName: '' + # This function is called whenever a command is not found. + ${commandNotFoundHandlerName}() { + local p=${commandNotFound}/bin/command-not-found + if [ -x $p -a -f ${cfg.dbPath} ]; then + # Run the helper program. + $p "$@" + else + echo "$1: command not found" >&2 + return 127 + fi + } + ''; + +in + +{ + options.programs.command-not-found = { + enable = mkEnableOption "command-not-found hook for interactive shell"; + + dbPath = mkOption { + default = "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite" ; + description = '' + Absolute path to <filename>programs.sqlite</filename>. By + default this file will be provided by your channel + (nixexprs.tar.xz). + ''; + type = types.path; + }; + }; + + config = mkIf cfg.enable { + programs.bash.initExtra = shInit "command_not_found_handle"; + programs.zsh.initExtra = shInit "command_not_found_handler"; + + home.packages = [ commandNotFound ]; + }; +} diff --git a/home-manager/modules/programs/command-not-found/command-not-found.pl b/home-manager/modules/programs/command-not-found/command-not-found.pl new file mode 100644 index 00000000000..997dfec649b --- /dev/null +++ b/home-manager/modules/programs/command-not-found/command-not-found.pl @@ -0,0 +1,44 @@ +#! @perl@/bin/perl -w @perlFlags@ + +use strict; +use DBI; +use DBD::SQLite; +use String::ShellQuote; +use Config; + +my $program = $ARGV[0]; + +my $dbPath = "@dbPath@"; + +my $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "") + or die "cannot open database `$dbPath'"; +$dbh->{RaiseError} = 0; +$dbh->{PrintError} = 0; + +my $system = $ENV{"NIX_SYSTEM"} // $Config{myarchname}; + +my $res = $dbh->selectall_arrayref( + "select package from Programs where system = ? and name = ?", + { Slice => {} }, $system, $program); + +if (!defined $res || scalar @$res == 0) { + print STDERR "$program: command not found\n"; +} elsif (scalar @$res == 1) { + my $package = @$res[0]->{package}; + if ($ENV{"NIX_AUTO_RUN"} // "") { + exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV)); + } else { + print STDERR <<EOF; +The program ‘$program’ is currently not installed. You can install it by typing: + nix-env -iA nixos.$package +EOF + } +} else { + print STDERR <<EOF; +The program ‘$program’ is currently not installed. It is provided by +several packages. You can install it by typing one of the following: +EOF + print STDERR " nix-env -iA nixos.$_->{package}\n" foreach @$res; +} + +exit 127; diff --git a/home-manager/modules/programs/direnv.nix b/home-manager/modules/programs/direnv.nix new file mode 100644 index 00000000000..e4c17239c58 --- /dev/null +++ b/home-manager/modules/programs/direnv.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.direnv; + 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.rycee ]; + + options.programs.direnv = { + enable = mkEnableOption "direnv, the environment switcher"; + + config = mkOption { + type = types.attrs; + default = {}; + description = '' + Configuration written to + <filename>~/.config/direnv/config.toml</filename>. + </para><para> + See + <citerefentry> + <refentrytitle>direnv.toml</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>. + for the full list of options. + ''; + }; + + stdlib = mkOption { + type = types.lines; + default = ""; + description = '' + Custom stdlib written to + <filename>~/.config/direnv/direnvrc</filename>. + ''; + }; + + 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 = [ pkgs.direnv ]; + + xdg.configFile."direnv/config.toml" = mkIf (cfg.config != {}) { + source = configFile cfg.config; + }; + + xdg.configFile."direnv/direnvrc" = mkIf (cfg.stdlib != "") { + text = cfg.stdlib; + }; + + programs.bash.initExtra = + mkIf cfg.enableBashIntegration ( + # Using mkAfter to make it more likely to appear after other + # manipulations of the prompt. + mkAfter '' + eval "$(${pkgs.direnv}/bin/direnv hook bash)" + '' + ); + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + eval "$(${pkgs.direnv}/bin/direnv hook zsh)" + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + eval (${pkgs.direnv}/bin/direnv hook fish) + ''; + }; +} diff --git a/home-manager/modules/programs/eclipse.nix b/home-manager/modules/programs/eclipse.nix new file mode 100644 index 00000000000..4a432c9fe1a --- /dev/null +++ b/home-manager/modules/programs/eclipse.nix @@ -0,0 +1,54 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.eclipse; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.eclipse = { + enable = mkEnableOption "Eclipse"; + + enableLombok = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to enable the Lombok Java Agent in Eclipse. This is + necessary to use the Lombok class annotations. + ''; + }; + + jvmArgs = mkOption { + type = types.listOf types.str; + default = []; + description = "JVM arguments to use for the Eclipse process."; + }; + + plugins = mkOption { + type = types.listOf types.package; + default = []; + description = "Plugins that should be added to Eclipse."; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ + (pkgs.eclipses.eclipseWithPlugins { + eclipse = pkgs.eclipses.eclipse-platform; + 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 new file mode 100644 index 00000000000..78c136c9868 --- /dev/null +++ b/home-manager/modules/programs/emacs.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + hmTypes = import ../lib/types.nix { inherit lib; }; + + cfg = config.programs.emacs; + + # Copied from all-packages.nix, with modifications to support + # overrides. + emacsPackages = + let + epkgs = pkgs.emacsPackagesNgGen cfg.package; + in + epkgs.overrideScope' cfg.overrides; + emacsWithPackages = emacsPackages.emacsWithPackages; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.emacs = { + enable = mkEnableOption "Emacs"; + + package = mkOption { + type = types.package; + default = pkgs.emacs; + defaultText = literalExample "pkgs.emacs"; + example = literalExample "pkgs.emacs25-nox"; + description = "The Emacs package to use."; + }; + + extraPackages = mkOption { + default = self: []; + type = hmTypes.selectorFunction; + defaultText = "epkgs: []"; + example = literalExample "epkgs: [ epkgs.emms epkgs.magit ]"; + description = '' + Extra packages available to Emacs. To get a list of + available packages run: + <command>nix-env -f '<nixpkgs>' -qaP -A emacsPackagesNg</command>. + ''; + }; + + overrides = mkOption { + default = self: super: {}; + type = hmTypes.overlayFunction; + defaultText = "self: super: {}"; + example = literalExample '' + self: super: rec { + haskell-mode = self.melpaPackages.haskell-mode; + # ... + }; + ''; + description = '' + Allows overriding packages within the Emacs package set. + ''; + }; + + finalPackage = mkOption { + type = types.package; + visible = false; + readOnly = true; + description = '' + The Emacs package including any overrides and extra packages. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.finalPackage ]; + programs.emacs.finalPackage = emacsWithPackages cfg.extraPackages; + }; +} diff --git a/home-manager/modules/programs/feh.nix b/home-manager/modules/programs/feh.nix new file mode 100644 index 00000000000..4342181fa4a --- /dev/null +++ b/home-manager/modules/programs/feh.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.feh; + + disableBinding = func: key: func; + enableBinding = func: key: "${func} ${key}"; + +in + +{ + options.programs.feh = { + enable = mkEnableOption "feh - a fast and light image viewer"; + + keybindings = mkOption { + default = {}; + type = types.attrsOf types.str; + example = { zoom_in = "plus"; zoom_out = "minus"; }; + description = '' + Set keybindings. + See <link xlink:href="https://man.finalrewind.org/1/feh/#x4b455953"/> for + default bindings and available commands. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.feh ]; + + xdg.configFile."feh/keys".text = '' + # Disable default keybindings + ${concatStringsSep "\n" (mapAttrsToList disableBinding cfg.keybindings)} + + # Enable new keybindings + ${concatStringsSep "\n" (mapAttrsToList enableBinding cfg.keybindings)} + ''; + }; +} diff --git a/home-manager/modules/programs/firefox.nix b/home-manager/modules/programs/firefox.nix new file mode 100644 index 00000000000..708b05417d6 --- /dev/null +++ b/home-manager/modules/programs/firefox.nix @@ -0,0 +1,282 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.firefox; + + extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"; + + profiles = + flip mapAttrs' cfg.profiles (_: profile: + nameValuePair "Profile${toString profile.id}" { + Name = profile.name; + Path = profile.path; + IsRelative = 1; + Default = if profile.isDefault then 1 else 0; + } + ) // { + General = { + StartWithLastProfile = 1; + }; + }; + + profilesIni = generators.toINI {} profiles; + + mkUserJs = prefs: extraPrefs: '' + // Generated by Home Manager. + + ${concatStrings (mapAttrsToList (name: value: '' + user_pref("${name}", ${builtins.toJSON value}); + '') prefs)} + + ${extraPrefs} + ''; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.firefox = { + enable = mkEnableOption "Firefox"; + + package = mkOption { + type = types.package; + default = + if versionAtLeast config.home.stateVersion "19.09" + then pkgs.firefox + else pkgs.firefox-unwrapped; + defaultText = literalExample "pkgs.firefox"; + description = '' + The Firefox package to use. If state version ≥ 19.09 then + this should be a wrapped Firefox package. For earlier state + versions it should be an unwrapped Firefox package. + ''; + }; + + extensions = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample '' + with pkgs.nur.repos.rycee.firefox-addons; [ + https-everywhere + privacy-badger + ] + ''; + description = '' + List of Firefox add-on packages to install. Note, it is + necessary to manually enable these extensions inside Firefox + after the first installation. + ''; + }; + + profiles = mkOption { + type = types.attrsOf (types.submodule ({config, name, ...}: { + options = { + name = mkOption { + type = types.str; + default = name; + description = "Profile name."; + }; + + id = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + Profile ID. This should be set to a unique number per profile. + ''; + }; + + settings = mkOption { + type = with types; attrsOf (either bool (either int str)); + default = {}; + example = literalExample '' + { + "browser.startup.homepage" = "https://nixos.org"; + "browser.search.region" = "GB"; + "browser.search.isUS" = false; + "distribution.searchplugins.defaultLocale" = "en-GB"; + "general.useragent.locale" = "en-GB"; + "browser.bookmarks.showMobileBookmarks" = true; + } + ''; + description = "Attribute set of Firefox preferences."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra preferences to add to <filename>user.js</filename>. + ''; + }; + + userChrome = mkOption { + type = types.lines; + default = ""; + description = "Custom Firefox CSS."; + example = '' + /* Hide tab bar in FF Quantum */ + @-moz-document url("chrome://browser/content/browser.xul") { + #TabsToolbar { + visibility: collapse !important; + margin-bottom: 21px !important; + } + + #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header { + visibility: collapse !important; + } + } + ''; + }; + + path = mkOption { + type = types.str; + default = name; + description = "Profile path."; + }; + + isDefault = mkOption { + type = types.bool; + default = config.id == 0; + defaultText = "true if profile ID is 0"; + description = "Whether this is a default profile."; + }; + }; + })); + default = {}; + description = "Attribute set of Firefox profiles."; + }; + + enableAdobeFlash = mkOption { + type = types.bool; + 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. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + ( + let + defaults = + catAttrs "name" (filter (a: a.isDefault) (attrValues cfg.profiles)); + in { + assertion = cfg.profiles == {} || length defaults == 1; + message = + "Must have exactly one default Firefox profile but found " + + toString (length defaults) + + optionalString (length defaults > 1) + (", namely " + concatStringsSep ", " defaults); + } + ) + + ( + let + duplicates = + filterAttrs (_: v: length v != 1) + (zipAttrs + (mapAttrsToList (n: v: { "${toString v.id}" = n; }) + (cfg.profiles))); + + mkMsg = n: v: " - ID ${n} is used by ${concatStringsSep ", " v}"; + in { + assertion = duplicates == {}; + message = + "Must not have Firefox profiles with duplicate IDs but\n" + + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates); + } + ) + ]; + + home.packages = + let + # 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. + browserName = cfg.package.browserName + or (builtins.parseDrvName cfg.package.name).name; + + # The configuration expected by the Firefox wrapper builder. + bcfg = setAttrByPath [browserName] fcfg; + + package = + if versionAtLeast config.home.stateVersion "19.09" + then cfg.package.override { cfg = fcfg; } + else (pkgs.wrapFirefox.override { config = bcfg; }) cfg.package { }; + in + [ package ]; + + home.file = mkMerge ( + [{ + ".mozilla/${extensionPath}" = mkIf (cfg.extensions != []) ( + let + extensionsEnv = pkgs.buildEnv { + name = "hm-firefox-extensions"; + paths = cfg.extensions; + }; + in { + source = "${extensionsEnv}/share/mozilla/${extensionPath}"; + recursive = true; + } + ); + + ".mozilla/firefox/profiles.ini" = mkIf (cfg.profiles != {}) { + text = profilesIni; + }; + }] + ++ flip mapAttrsToList cfg.profiles (_: profile: { + ".mozilla/firefox/${profile.path}/chrome/userChrome.css" = + mkIf (profile.userChrome != "") { + text = profile.userChrome; + }; + + ".mozilla/firefox/${profile.path}/user.js" = + mkIf (profile.settings != {} || profile.extraConfig != "") { + text = mkUserJs profile.settings profile.extraConfig; + }; + }) + ); + }; +} diff --git a/home-manager/modules/programs/fish.nix b/home-manager/modules/programs/fish.nix new file mode 100644 index 00000000000..87a17b85507 --- /dev/null +++ b/home-manager/modules/programs/fish.nix @@ -0,0 +1,191 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.fish; + + abbrsStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: "abbr --add --global ${k} '${v}'") cfg.shellAbbrs + ); + + aliasesStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: "alias ${k}='${v}'") cfg.shellAliases + ); + +in + +{ + options = { + programs.fish = { + enable = mkEnableOption "fish friendly interactive shell"; + + package = mkOption { + 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 = {}; + description = '' + Set of aliases for fish shell. See + <option>environment.shellAliases</option> for an option + format description. + ''; + type = types.attrs; + }; + + shellAbbrs = mkOption { + default = {}; + description = '' + Set of abbreviations for fish shell. + ''; + type = types.attrs; + }; + + shellInit = mkOption { + default = ""; + description = '' + Shell script code called during fish shell initialisation. + ''; + type = types.lines; + }; + + loginShellInit = mkOption { + default = ""; + description = '' + Shell script code called during fish login shell initialisation. + ''; + type = types.lines; + }; + + interactiveShellInit = mkOption { + default = ""; + description = '' + Shell script code called during interactive fish shell initialisation. + ''; + type = types.lines; + }; + + promptInit = mkOption { + default = ""; + description = '' + Shell script code used to initialise fish prompt. + ''; + type = types.lines; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + 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 ? "" + , ... + }: + 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" + { + 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 \ + | xargs python ${cfg.package}/share/fish/tools/create_manpage_completions.py --directory $out \ + > /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); + }; + + 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 + ''; + }; +} diff --git a/home-manager/modules/programs/fzf.nix b/home-manager/modules/programs/fzf.nix new file mode 100644 index 00000000000..832c0bfa3e4 --- /dev/null +++ b/home-manager/modules/programs/fzf.nix @@ -0,0 +1,138 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.fzf; + +in + +{ + options.programs.fzf = { + enable = mkEnableOption "fzf - a command-line fuzzy finder"; + + defaultCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type f"; + description = '' + The command that gets executed as the default source for fzf + when running. + ''; + }; + + defaultOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--height 40%" "--border" ]; + description = '' + Extra command line options given to fzf by default. + ''; + }; + + fileWidgetCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type f"; + description = '' + The command that gets executed as the source for fzf for the + CTRL-T keybinding. + ''; + }; + + fileWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--preview 'head {}'" ]; + description = '' + Command line options for the CTRL-T keybinding. + ''; + }; + + changeDirWidgetCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type d" ; + description = '' + The command that gets executed as the source for fzf for the + ALT-C keybinding. + ''; + }; + + changeDirWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--preview 'tree -C {} | head -200'" ]; + description = '' + Command line options for the ALT-C keybinding. + ''; + }; + + historyWidgetCommand = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The command that gets executed as the source for fzf for the + CTRL-R keybinding. + ''; + }; + + historyWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--sort" "--exact" ]; + description = '' + Command line options for the CTRL-R keybinding. + ''; + }; + + 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. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.fzf ]; + + home.sessionVariables = + mapAttrs (n: v: toString v) ( + filterAttrs (n: v: v != [] && v != null) { + FZF_ALT_C_COMMAND = cfg.changeDirWidgetCommand; + FZF_ALT_C_OPTS = cfg.changeDirWidgetOptions; + FZF_CTRL_R_COMMAND = cfg.historyWidgetCommand; + FZF_CTRL_R_OPTS = cfg.historyWidgetOptions; + FZF_CTRL_T_COMMAND = cfg.fileWidgetCommand; + FZF_CTRL_T_OPTS = cfg.fileWidgetOptions; + FZF_DEFAULT_COMMAND = cfg.defaultCommand; + FZF_DEFAULT_OPTS = cfg.defaultOptions; + } + ); + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then + . ${pkgs.fzf}/share/fzf/completion.bash + . ${pkgs.fzf}/share/fzf/key-bindings.bash + fi + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + if [[ $options[zle] = on ]]; then + . ${pkgs.fzf}/share/fzf/completion.zsh + . ${pkgs.fzf}/share/fzf/key-bindings.zsh + fi + ''; + }; +} diff --git a/home-manager/modules/programs/getmail-accounts.nix b/home-manager/modules/programs/getmail-accounts.nix new file mode 100644 index 00000000000..32e1312dc8f --- /dev/null +++ b/home-manager/modules/programs/getmail-accounts.nix @@ -0,0 +1,49 @@ +{ config, lib, ... }: + +with lib; + +{ + options.getmail = { + enable = mkEnableOption "the getmail mail retriever for this account"; + + destinationCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "\${pkgs.maildrop}/bin/maildrop"; + description = '' + Specify a command delivering the incoming mail to your maildir. + ''; + }; + + mailboxes = mkOption { + type = types.nonEmptyListOf types.str; + default = []; + example = ["INBOX" "INBOX.spam"]; + description = '' + A non-empty list of mailboxes. To download all mail you can + use the <literal>ALL</literal> mailbox. + ''; + }; + + delete = mkOption { + type = types.bool; + default = false; + description = '' + Enable if you want to delete read messages from the server. Most + users should either enable <literal>delete</literal> or disable + <literal>readAll</literal>. + ''; + }; + + readAll = mkOption { + type = types.bool; + default = true; + description = '' + Enable if you want to fetch all, even the read messages from the + server. Most users should either enable <literal>delete</literal> or + disable <literal>readAll</literal>. + ''; + }; + + }; +} diff --git a/home-manager/modules/programs/getmail.nix b/home-manager/modules/programs/getmail.nix new file mode 100644 index 00000000000..8c1ac5e021e --- /dev/null +++ b/home-manager/modules/programs/getmail.nix @@ -0,0 +1,59 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + accounts = filter (a: a.getmail.enable) + (attrValues config.accounts.email.accounts); + + renderAccountConfig = account: with account; + let + passCmd = concatMapStringsSep ", " (x: "'${x}'") passwordCommand; + renderedMailboxes = concatMapStringsSep ", " (x: "'${x}'") getmail.mailboxes; + retrieverType = if imap.tls.enable + then "SimpleIMAPSSLRetriever" + else "SimpleIMAPRetriever"; + destination = if getmail.destinationCommand != null + then + { + destinationType = "MDA_external"; + destinationPath = getmail.destinationCommand; + } + else + { + destinationType = "Maildir"; + destinationPath = "${maildir.absPath}/"; + }; + renderGetmailBoolean = v: if v then "true" else "false"; + in '' + # Generated by Home-Manager. + [retriever] + type = ${retrieverType} + server = ${imap.host} + username = ${userName} + password_command = (${passCmd}) + mailboxes = ( ${renderedMailboxes} ) + + [destination] + type = ${destination.destinationType} + path = ${destination.destinationPath} + + [options] + delete = ${renderGetmailBoolean getmail.delete} + read_all = ${renderGetmailBoolean getmail.readAll} + ''; + getmailEnabled = length (filter (a: a.getmail.enable) accounts) > 0; + # Watch out! This is used by the getmail.service too! + renderConfigFilepath = a: ".getmail/getmail${if a.primary then "rc" else a.name}"; +in + + { + config = mkIf getmailEnabled { + home.file = map (a: + { target = renderConfigFilepath a; + text = renderAccountConfig a; + }) accounts; + + }; + } diff --git a/home-manager/modules/programs/git.nix b/home-manager/modules/programs/git.nix new file mode 100644 index 00000000000..913f86f71ce --- /dev/null +++ b/home-manager/modules/programs/git.nix @@ -0,0 +1,325 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.git; + + # create [section "subsection"] keys from "section.subsection" attrset names + mkSectionName = name: + let + containsQuote = strings.hasInfix ''"'' name; + sections = splitString "." name; + section = head sections; + subsections = tail sections; + subsection = concatStringsSep "." subsections; + in + if containsQuote || subsections == [] + then name + else "${section} \"${subsection}\""; + + # generation for multiple ini values + mkKeyValue = k: v: + let + mkKeyValue = generators.mkKeyValueDefault {} "=" k; + in + concatStringsSep "\n" (map mkKeyValue (toList v)); + + # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI + gitFlattenAttrs = + let + recurse = path: value: + if isAttrs value then + mapAttrsToList (name: value: recurse ([name] ++ path) value) value + else if length path > 1 then + { ${concatStringsSep "." (reverseList (tail path))}.${head path} = value; } + else + { ${head path} = value; }; + in + attrs: foldl recursiveUpdate {} (flatten (recurse [] attrs)); + + gitToIni = attrs: + let + toIni = generators.toINI { inherit mkKeyValue mkSectionName; }; + in + toIni (gitFlattenAttrs attrs); + + gitIniType = with types; + let + primitiveType = either str (either bool int); + multipleType = either primitiveType (listOf primitiveType); + sectionType = attrsOf multipleType; + supersectionType = attrsOf (either multipleType sectionType); + in + attrsOf supersectionType; + + signModule = types.submodule { + options = { + key = mkOption { + type = types.str; + description = "The default GPG signing key fingerprint."; + }; + + signByDefault = mkOption { + type = types.bool; + default = false; + description = "Whether commits should be signed by default."; + }; + + gpgPath = mkOption { + type = types.str; + default = "${pkgs.gnupg}/bin/gpg2"; + defaultText = "\${pkgs.gnupg}/bin/gpg2"; + description = "Path to GnuPG binary to use."; + }; + }; + }; + + includeModule = types.submodule ({ config, ... }: { + options = { + condition = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Include this configuration only when <varname>condition</varname> + matches. Allowed conditions are described in + <citerefentry> + <refentrytitle>git-config</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>. + ''; + }; + + path = mkOption { + type = with types; either str path; + description = "Path of the configuration file to include."; + }; + + contents = mkOption { + type = types.attrs; + default = {}; + description = '' + Configuration to include. If empty then a path must be given. + ''; + }; + }; + + config.path = mkIf (config.contents != {}) ( + mkDefault (pkgs.writeText "contents" (gitToIni config.contents)) + ); + }); + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.git = { + enable = mkEnableOption "Git"; + + package = mkOption { + type = types.package; + default = pkgs.git; + defaultText = literalExample "pkgs.git"; + description = '' + Git package to install. Use <varname>pkgs.gitAndTools.gitFull</varname> + to gain access to <command>git send-email</command> for instance. + ''; + }; + + userName = mkOption { + type = types.nullOr types.str; + default = null; + description = "Default user name to use."; + }; + + userEmail = mkOption { + type = types.nullOr types.str; + default = null; + description = "Default user email to use."; + }; + + aliases = mkOption { + type = types.attrsOf types.str; + default = {}; + example = { co = "checkout"; }; + description = "Git aliases to define."; + }; + + signing = mkOption { + type = types.nullOr signModule; + default = null; + description = "Options related to signing commits using GnuPG."; + }; + + extraConfig = mkOption { + type = types.either types.lines gitIniType; + default = {}; + example = { + core = { whitespace = "trailing-space,space-before-tab"; }; + url."ssh://git@host".insteadOf = "otherhost"; + }; + description = '' + Additional configuration to add. The use of string values is + deprecated and will be removed in the future. + ''; + }; + + iniContent = mkOption { + type = gitIniType; + internal = true; + }; + + ignores = mkOption { + type = types.listOf types.str; + default = []; + example = [ "*~" "*.swp" ]; + description = "List of paths that should be globally ignored."; + }; + + attributes = mkOption { + type = types.listOf types.str; + default = []; + example = [ "*.pdf diff=pdf" ]; + description = "List of defining attributes set globally."; + }; + + includes = mkOption { + type = types.listOf includeModule; + default = []; + example = literalExample '' + [ + { path = "~/path/to/config.inc"; } + { + path = "~/path/to/conditional.inc"; + condition = "gitdir:~/src/dir"; + } + ] + ''; + description = "List of configuration files to include."; + }; + + lfs = { + enable = mkEnableOption "Git Large File Storage"; + + skipSmudge = mkOption { + type = types.bool; + default = false; + description = '' + Skip automatic downloading of objects on clone or pull. + This requires a manual <command>git lfs pull</command> + every time a new commit is checked out on your repository. + ''; + }; + }; + }; + }; + + config = mkIf cfg.enable ( + mkMerge [ + { + home.packages = [ cfg.package ]; + + programs.git.iniContent.user = { + name = mkIf (cfg.userName != null) cfg.userName; + email = mkIf (cfg.userEmail != null) cfg.userEmail; + }; + + xdg.configFile = { + "git/config".text = gitToIni cfg.iniContent; + + "git/ignore" = mkIf (cfg.ignores != []) { + text = concatStringsSep "\n" cfg.ignores + "\n"; + }; + + "git/attributes" = mkIf (cfg.attributes != []) { + text = concatStringsSep "\n" cfg.attributes + "\n"; + }; + }; + } + + { + programs.git.iniContent = + let + hasSmtp = name: account: account.smtp != null; + + genIdentity = name: account: with account; + nameValuePair "sendemail.${name}" ({ + smtpEncryption = if smtp.tls.enable then "tls" else ""; + smtpServer = smtp.host; + smtpUser = userName; + from = address; + } + // optionalAttrs (smtp.port != null) { + smtpServerPort = smtp.port; + }); + in + mapAttrs' genIdentity + (filterAttrs hasSmtp config.accounts.email.accounts); + } + + (mkIf (cfg.signing != null) { + programs.git.iniContent = { + user.signingKey = cfg.signing.key; + commit.gpgSign = cfg.signing.signByDefault; + gpg.program = cfg.signing.gpgPath; + }; + }) + + (mkIf (cfg.aliases != {}) { + programs.git.iniContent.alias = cfg.aliases; + }) + + (mkIf (lib.isAttrs cfg.extraConfig) { + programs.git.iniContent = cfg.extraConfig; + }) + + (mkIf (lib.isString cfg.extraConfig) { + warnings = [ + '' + Using programs.git.extraConfig as a string option is + deprecated and will be removed in the future. Please + change to using it as an attribute set instead. + '' + ]; + + xdg.configFile."git/config".text = cfg.extraConfig; + }) + + (mkIf (cfg.includes != []) { + xdg.configFile."git/config".text = + let + include = i: with i; + if condition != null + then { includeIf.${condition}.path = "${path}"; } + else { include.path = "${path}"; }; + in + mkAfter + (concatStringsSep "\n" + (map gitToIni + (map include cfg.includes))); + }) + + (mkIf cfg.lfs.enable { + home.packages = [ pkgs.git-lfs ]; + + programs.git.iniContent.filter.lfs = + let + skipArg = optional cfg.lfs.skipSmudge "--skip"; + in + { + clean = "git-lfs clean -- %f"; + process = concatStringsSep " " ( + [ "git-lfs" "filter-process" ] ++ skipArg + ); + required = true; + smudge = concatStringsSep " " ( + [ "git-lfs" "smudge" ] ++ skipArg ++ [ "--" "%f" ] + ); + }; + }) + ] + ); +} diff --git a/home-manager/modules/programs/gnome-terminal.nix b/home-manager/modules/programs/gnome-terminal.nix new file mode 100644 index 00000000000..9a44364491d --- /dev/null +++ b/home-manager/modules/programs/gnome-terminal.nix @@ -0,0 +1,242 @@ +{ config, lib, pkgs, ... }: + +with lib; + +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 { + type = types.str; + description = "The foreground color."; + }; + + background = mkOption { + type = types.str; + description = "The background color."; + }; + }; + } + ); + + profileColorsSubModule = types.submodule ( + { ... }: { + options = { + foregroundColor = mkOption { + type = types.str; + description = "The foreground color."; + }; + + backgroundColor = mkOption { + type = types.str; + description = "The background color."; + }; + + boldColor = mkOption { + default = null; + type = types.nullOr types.str; + description = "The bold color, null to use same as foreground."; + }; + + palette = mkOption { + type = types.listOf types.str; + description = "The terminal palette."; + }; + + cursor = mkOption { + default = null; + type = types.nullOr backForeSubModule; + description = "The color for the terminal cursor."; + }; + + highlight = mkOption { + default = null; + type = types.nullOr backForeSubModule; + description = "The colors for the terminal’s highlighted area."; + }; + }; + } + ); + + profileSubModule = types.submodule ( + { name, config, ... }: { + options = { + default = mkOption { + default = false; + type = types.bool; + description = "Whether this should be the default profile."; + }; + + visibleName = mkOption { + type = types.str; + description = "The profile name."; + }; + + colors = mkOption { + default = null; + type = types.nullOr profileColorsSubModule; + description = "The terminal colors, null to use system default."; + }; + + cursorShape = mkOption { + default = "block"; + type = types.enum [ "block" "ibeam" "underline" ]; + description = "The cursor shape."; + }; + + font = mkOption { + default = null; + type = types.nullOr types.str; + description = "The font name, null to use system default."; + }; + + allowBold = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + If <literal>true</literal>, allow applications in the + terminal to make text boldface. + ''; + }; + + scrollOnOutput = mkOption { + default = true; + type = types.bool; + description = "Whether to scroll when output is written."; + }; + + showScrollbar = mkOption { + default = true; + type = types.bool; + description = "Whether the scroll bar should be visible."; + }; + + scrollbackLines = mkOption { + default = 10000; + type = types.nullOr types.int; + description = + '' + The number of scrollback lines to keep, null for infinite. + ''; + }; + }; + } + ); + + buildProfileSet = pcfg: + { + visible-name = pcfg.visibleName; + scrollbar-policy = if pcfg.showScrollbar then "always" else "never"; + scrollback-lines = pcfg.scrollbackLines; + cursor-shape = pcfg.cursorShape; + } + // ( + if (pcfg.font == null) + then { use-system-font = true; } + else { use-system-font = false; font = pcfg.font; } + ) // ( + if (pcfg.colors == null) + then { use-theme-colors = true; } + else ( + { + use-theme-colors = false; + foreground-color = pcfg.colors.foregroundColor; + background-color = pcfg.colors.backgroundColor; + palette = pcfg.colors.palette; + } + // optionalAttrs (pcfg.allowBold != null) { + allow-bold = pcfg.allowBold; + } + // ( + if (pcfg.colors.boldColor == null) + then { bold-color-same-as-fg = true; } + else { + bold-color-same-as-fg = false; + bold-color = pcfg.colors.boldColor; + } + ) + // ( + if (pcfg.colors.cursor != null) + then { + cursor-colors-set = true; + cursor-foreground-color = pcfg.colors.cursor.foreground; + cursor-background-color = pcfg.colors.cursor.background; + } + else { cursor-colors-set = false; } + ) + // ( + if (pcfg.colors.highlight != null) + then { + highlight-colors-set = true; + highlight-foreground-color = pcfg.colors.highlight.foreground; + highlight-background-color = pcfg.colors.highlight.background; + } + else { highlight-colors-set = false; } + ) + ) + ); + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.gnome-terminal = { + enable = mkEnableOption "Gnome Terminal"; + + showMenubar = mkOption { + default = true; + type = types.bool; + description = "Whether to show the menubar by default"; + }; + + themeVariant = mkOption { + default = "default"; + type = types.enum [ "default" "light" "dark" ]; + description = "The theme variation to request"; + }; + + profile = mkOption { + default = {}; + type = types.attrsOf profileSubModule; + description = "A set of Gnome Terminal profiles."; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.gnome3.gnome_terminal ]; + + dconf.settings = + let + dconfPath = "org/gnome/terminal/legacy"; + in + { + "${dconfPath}" = { + default-show-menubar = cfg.showMenubar; + theme-variant = cfg.themeVariant; + schema-version = 3; + }; + + "${dconfPath}/profiles:" = { + default = head (attrNames (filterAttrs (n: v: v.default) cfg.profile)); + list = attrNames cfg.profile; + }; + } + // mapAttrs' (n: v: + nameValuePair ("${dconfPath}/profiles:/:${n}") (buildProfileSet v) + ) cfg.profile; + + programs.bash.initExtra = mkBefore vteInitStr; + programs.zsh.initExtra = vteInitStr; + }; +} diff --git a/home-manager/modules/programs/go.nix b/home-manager/modules/programs/go.nix new file mode 100644 index 00000000000..06c25c9b82a --- /dev/null +++ b/home-manager/modules/programs/go.nix @@ -0,0 +1,75 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.go; + +in + +{ + meta.maintainers = [ maintainers.rvolosatovs ]; + + options = { + programs.go = { + enable = mkEnableOption "Go"; + + package = mkOption { + type = types.package; + default = pkgs.go; + defaultText = literalExample "pkgs.go"; + description = "The Go package to use."; + }; + + packages = mkOption { + type = with types; attrsOf path; + default = {}; + example = literalExample '' + { + "golang.org/x/text" = builtins.fetchGit "https://go.googlesource.com/text"; + "golang.org/x/time" = builtins.fetchGit "https://go.googlesource.com/time"; + } + ''; + description = "Packages to add to GOPATH."; + }; + + goPath = mkOption { + type = with types; nullOr str; + default = null; + example = "go"; + description = "GOPATH relative to HOME"; + }; + + goBin = mkOption { + type = with types; nullOr str; + default = null; + example = ".local/bin.go"; + description = "GOBIN relative to HOME"; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { + home.packages = [ cfg.package ]; + + home.file = + let + goPath = if cfg.goPath != null then cfg.goPath else "go"; + + mkSrc = n: v: { + target = "${goPath}/src/${n}"; + source = v; + }; + in + mapAttrsToList mkSrc cfg.packages; + } + (mkIf (cfg.goPath != null) { + home.sessionVariables.GOPATH = builtins.toPath "${config.home.homeDirectory}/${cfg.goPath}"; + }) + (mkIf (cfg.goBin != null) { + home.sessionVariables.GOBIN = builtins.toPath "${config.home.homeDirectory}/${cfg.goBin}"; + }) + ]); +} diff --git a/home-manager/modules/programs/gpg.nix b/home-manager/modules/programs/gpg.nix new file mode 100644 index 00000000000..4588c59c882 --- /dev/null +++ b/home-manager/modules/programs/gpg.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.gpg; + + cfgText = + concatStringsSep "\n" + (attrValues + (mapAttrs (key: value: + if isString value + then "${key} ${value}" + else optionalString value key) + cfg.settings)); + +in { + options.programs.gpg = { + enable = mkEnableOption "GnuPG"; + + settings = mkOption { + type = types.attrsOf (types.either types.str types.bool); + example = { + no-comments = false; + s2k-cipher-algo = "AES128"; + }; + description = '' + GnuPG configuration options. Available options are described + in the gpg manpage: + <link xlink:href="https://gnupg.org/documentation/manpage.html"/>. + ''; + }; + }; + + config = mkIf cfg.enable { + programs.gpg.settings = { + personal-cipher-preferences = mkDefault "AES256 AES192 AES"; + personal-digest-preferences = mkDefault "SHA512 SHA384 SHA256"; + personal-compress-preferences = mkDefault "ZLIB BZIP2 ZIP Uncompressed"; + default-preference-list = mkDefault "SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed"; + cert-digest-algo = mkDefault "SHA512"; + s2k-digest-algo = mkDefault "SHA512"; + s2k-cipher-algo = mkDefault "AES256"; + charset = mkDefault "utf-8"; + fixed-list-mode = mkDefault true; + no-comments = mkDefault true; + no-emit-version = mkDefault true; + keyid-format = mkDefault "0xlong"; + list-options = mkDefault "show-uid-validity"; + verify-options = mkDefault "show-uid-validity"; + with-fingerprint = mkDefault true; + require-cross-certification = mkDefault true; + no-symkey-cache = mkDefault true; + use-agent = mkDefault true; + }; + + home.packages = [ pkgs.gnupg ]; + + home.file.".gnupg/gpg.conf".text = cfgText; + }; +} diff --git a/home-manager/modules/programs/home-manager.nix b/home-manager/modules/programs/home-manager.nix new file mode 100644 index 00000000000..42e3c8a384f --- /dev/null +++ b/home-manager/modules/programs/home-manager.nix @@ -0,0 +1,42 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.home-manager; + + dag = config.lib.dag; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.home-manager = { + enable = mkEnableOption "Home Manager"; + + path = mkOption { + type = types.nullOr types.str; + default = null; + example = "$HOME/devel/home-manager"; + description = '' + The default path to use for Home Manager. If this path does + not exist then + <filename>$HOME/.config/nixpkgs/home-manager</filename> and + <filename>$HOME/.nixpkgs/home-manager</filename> will be + attempted. + ''; + }; + }; + }; + + config = mkIf (cfg.enable && !config.submoduleSupport.enable) { + home.packages = [ + (pkgs.callPackage ../../home-manager { + inherit (cfg) path; + }) + ]; + }; +} diff --git a/home-manager/modules/programs/htop.nix b/home-manager/modules/programs/htop.nix new file mode 100644 index 00000000000..d700c4855fe --- /dev/null +++ b/home-manager/modules/programs/htop.nix @@ -0,0 +1,327 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.htop; + + list = xs: concatMapStrings (x: "${toString x} ") xs; + + bool = b: if b then "1" else "0"; + + fields = { + PID = 0; + COMM = 1; + STATE = 2; + PPID = 3; + PGRP = 4; + SESSION = 5; + TTY_NR = 6; + TPGID = 7; + MINFLT = 9; + MAJFLT = 11; + PRIORITY = 17; + NICE = 18; + STARTTIME = 20; + PROCESSOR = 37; + M_SIZE = 38; + M_RESIDENT = 39; + ST_UID = 45; + PERCENT_CPU = 46; + PERCENT_MEM = 47; + USER = 48; + TIME = 49; + NLWP = 50; + TGID = 51; + CMINFLT = 10; + CMAJFLT = 12; + UTIME = 13; + STIME = 14; + CUTIME = 15; + CSTIME = 16; + M_SHARE = 40; + M_TRS = 41; + M_DRS = 42; + M_LRS = 43; + M_DT = 44; + CTID = 99; + VPID = 100; + VXID = 102; + RCHAR = 102; + WCHAR = 103; + SYSCR = 104; + SYSCW = 105; + RBYTES = 106; + WBYTES = 107; + CNCLWB = 108; + IO_READ_RATE = 109; + IO_WRITE_RATE = 110; + IO_RATE = 111; + CGROUP = 112; + OOM = 113; + IO_PRIORITY = 114; + }; + + # Mapping from names to defaults + meters = { + Clock = 2; + LoadAverage = 2; + Load = 2; + Memory = 1; + Swap = 1; + Tasks = 2; + Uptime = 2; + Battery = 2; + Hostname = 2; + AllCPUs = 1; + AllCPUs2 = 1; + LeftCPUs = 1; + RightCPUs = 1; + LeftCPUs2 = 1; + RightCPUs2 = 1; + Blank = 2; + CPU = 1; + "CPU(1)"= 1; + "CPU(2)" = 1; + "CPU(3)" = 1; + "CPU(4)" = 1; + }; + + singleMeterType = types.coercedTo + (types.enum (attrNames meters)) + (m: { kind = m; mode = meters.${m}; }) + (types.submodule { + options = { + kind = mkOption { + type = types.enum (attrNames meters); + example = "AllCPUs"; + description = "What kind of meter."; + }; + + mode = mkOption { + type = types.enum [ 1 2 3 4 ]; + example = 2; + description = "Which mode the meter should use, one of 1(Bar) 2(Text) 3(Graph) 4(LED)."; + }; + }; + }); + + meterType = types.submodule { + options = { + left = mkOption { + description = "Meters shown in the left header."; + default = [ "AllCPUs" "Memory" "Swap" ]; + example = [ + "Memory" + "LeftCPUs2" + "RightCPUs2" + { kind = "CPU"; mode = 3; } + ]; + type = types.listOf singleMeterType; + }; + right = mkOption { + description = "Meters shown in the right header."; + default = [ "Tasks" "LoadAverage" "Uptime" ]; + example = [ + { kind = "Clock"; mode = 4; } + "Uptime" + "Tasks" + ]; + type = types.listOf singleMeterType; + }; + }; + }; +in +{ + options.programs.htop = { + enable = mkEnableOption "htop"; + + fields = mkOption { + type = types.listOf (types.enum (attrNames fields)); + default = [ "PID" "USER" "PRIORITY" "NICE" "M_SIZE" "M_RESIDENT" "M_SHARE" "STATE" "PERCENT_CPU" "PERCENT_MEM" "TIME" "COMM" ]; + example = [ "PID" "USER" "PRIORITY" "PERCENT_CPU" "M_RESIDENT" "PERCENT_MEM" "TIME" "COMM" ]; + description = "Active fields shown in the table."; + }; + + sortKey = mkOption { + type = types.enum (attrNames fields); + default = "PERCENT_CPU"; + example = "TIME"; + description = "Which field to use for sorting."; + }; + + sortDescending = mkOption { + type = types.bool; + default = true; + description = "Whether to sort descending or not."; + }; + + hideThreads = mkOption { + type = types.bool; + default = false; + description = "Hide threads."; + }; + + hideKernelThreads = mkOption { + type = types.bool; + default = true; + description = "Hide kernel threads."; + }; + + hideUserlandThreads = mkOption { + type = types.bool; + default = false; + description = "Hide userland process threads."; + }; + + shadowOtherUsers = mkOption { + type = types.bool; + default = false; + description = "Shadow other users' processes."; + }; + + showThreadNames = mkOption { + type = types.bool; + default = false; + description = "Show custom thread names."; + }; + + showProgramPath = mkOption { + type = types.bool; + default = true; + description = "Show program path."; + }; + + highlightBaseName = mkOption { + type = types.bool; + default = false; + description = "Highlight program <quote>basename</quote>."; + }; + + highlightMegabytes = mkOption { + type = types.bool; + default = true; + description = "Highlight large numbers in memory counters."; + }; + + highlightThreads = mkOption { + type = types.bool; + default = true; + description = "Display threads in a different color."; + }; + + treeView = mkOption { + type = types.bool; + default = false; + description = "Tree view."; + }; + + headerMargin = mkOption { + type = types.bool; + default = true; + description = "Leave a margin around header."; + }; + + detailedCpuTime = mkOption { + type = types.bool; + default = false; + description = "Detailed CPU time (System/IO-Wait/Hard-IRQ/Soft-IRQ/Steal/Guest)."; + }; + + cpuCountFromZero = mkOption { + type = types.bool; + default = false; + description = "Count CPUs from 0 instead of 1."; + }; + + updateProcessNames = mkOption { + type = types.bool; + default = false; + description = "Update process names on every refresh."; + }; + + accountGuestInCpuMeter = mkOption { + type = types.bool; + default = false; + description = "Add guest time in CPU meter percentage."; + }; + + colorScheme = mkOption { + type = types.enum [ 0 1 2 3 4 5 6 ]; + default = 0; + example = 6; + description = "Which color scheme to use."; + }; + + delay = mkOption { + type = types.int; + default = 15; + example = 2; + description = "Set the delay between updates, in tenths of seconds."; + }; + + meters = mkOption { + description = "Meters shown in the header."; + default = { + left = [ "AllCPUs" "Memory" "Swap" ]; + right = [ "Tasks" "LoadAverage" "Uptime" ]; + }; + example = { + left = [ + "Memory" + "CPU" + "LeftCPUs2" + "RightCPUs2" + { kind = "CPU"; mode = 3; } + ]; + right = [ + { kind = "Clock"; mode = 4; } + "Uptime" + "Tasks" + "LoadAverage" + { kind = "Battery"; mode = 1; } + ]; + }; + type = meterType; + }; + + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.htop ]; + + xdg.configFile."htop/htoprc".text = let + leftMeters = map (m: m.kind) cfg.meters.left; + leftModes = map (m: m.mode) cfg.meters.left; + rightMeters = map (m: m.kind) cfg.meters.right; + rightModes = map (m: m.mode) cfg.meters.right; + in '' + # This file is regenerated by home-manager + # when options are changed in the config + fields=${list (map (n: fields.${n}) cfg.fields)} + sort_key=${toString (fields.${cfg.sortKey})} + sort_direction=${bool cfg.sortDescending} + hide_threads=${bool cfg.hideThreads} + hide_kernel_threads=${bool cfg.hideKernelThreads} + hide_userland_threads=${bool cfg.hideUserlandThreads} + shadow_other_users=${bool cfg.shadowOtherUsers} + show_thread_names=${bool cfg.showThreadNames} + show_program_path=${bool cfg.showProgramPath} + highlight_base_name=${bool cfg.highlightBaseName} + highlight_megabytes=${bool cfg.highlightMegabytes} + highlight_threads=${bool cfg.highlightThreads} + tree_view=${bool cfg.treeView} + header_margin=${bool cfg.headerMargin} + detailed_cpu_time=${bool cfg.detailedCpuTime} + cpu_count_from_zero=${bool cfg.cpuCountFromZero} + update_process_names=${bool cfg.updateProcessNames} + account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter} + color_scheme=${toString cfg.colorScheme} + delay=${toString cfg.delay} + left_meters=${list leftMeters} + left_meter_modes=${list leftModes} + right_meters=${list rightMeters} + right_meter_modes=${list rightModes} + ''; + }; +} diff --git a/home-manager/modules/programs/info.nix b/home-manager/modules/programs/info.nix new file mode 100644 index 00000000000..93dcaf474af --- /dev/null +++ b/home-manager/modules/programs/info.nix @@ -0,0 +1,78 @@ +# info.nix -- install texinfo, set INFOPATH, 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. + +# 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. + +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.info; + + dag = config.lib.dag; + + # 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"; + + homeInfoDirLocation = mkOption { + default = "\${XDG_CACHE_HOME:-$HOME/.cache}/info"; + description = '' + Directory in which to store the info <filename>dir</filename> + file within your home. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.sessionVariables.INFOPATH = + "${cfg.homeInfoDirLocation}\${INFOPATH:+:}\${INFOPATH}"; + + home.activation.createHomeInfoDir = 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 ]; + + home.extraOutputsToInstall = [ "info" ]; + }; +} diff --git a/home-manager/modules/programs/irssi.nix b/home-manager/modules/programs/irssi.nix new file mode 100644 index 00000000000..fc8fa8e6132 --- /dev/null +++ b/home-manager/modules/programs/irssi.nix @@ -0,0 +1,211 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.irssi; + + boolStr = b: if b then "yes" else "no"; + quoteStr = s: escape ["\""] s; + + assignFormat = set: + concatStringsSep "\n" + (mapAttrsToList (k: v: " ${k} = \"${quoteStr v}\";") set); + + chatnetString = + concatStringsSep "\n" + (flip mapAttrsToList cfg.networks + (k: v: '' + ${k} = { + type = "${v.type}"; + nick = "${quoteStr v.nick}"; + autosendcmd = "${concatMapStringsSep ";" quoteStr v.autoCommands}"; + }; + '')); + + serversString = + concatStringsSep ",\n" + (flip mapAttrsToList cfg.networks + (k: v: '' + { + chatnet = "${k}"; + address = "${v.server.address}"; + port = "${toString v.server.port}"; + use_ssl = "${boolStr v.server.ssl.enable}"; + ssl_verify = "${boolStr v.server.ssl.verify}"; + autoconnect = "${boolStr v.server.autoConnect}"; + } + '')); + + channelString = + concatStringsSep ",\n" + (flip mapAttrsToList cfg.networks + (k: v: + concatStringsSep ",\n" + (flip mapAttrsToList v.channels + (c: cv: '' + { + chatnet = "${k}"; + name = "${c}"; + autojoin = "${boolStr cv.autoJoin}"; + } + '')))); + + channelType = types.submodule { + options = { + name = mkOption { + type = types.nullOr types.str; + visible = false; + default = null; + description = "Name of the channel."; + }; + + autoJoin = mkOption { + type = types.bool; + default = false; + description = "Whether to join this channel on connect."; + }; + }; + }; + + networkType = types.submodule ({ name, ...}: { + options = { + name = mkOption { + visible = false; + default = name; + type = types.str; + }; + + nick = mkOption { + type = types.str; + description = "Nickname in that network."; + }; + + type = mkOption { + type = types.str; + description = "Type of the network."; + default = "IRC"; + }; + + autoCommands = mkOption { + type = types.listOf types.str; + default = []; + description = "List of commands to execute on connect."; + }; + + server = { + address = mkOption { + type = types.str; + description = "Address of the chat server."; + }; + + port = mkOption { + type = types.port; + default = 6667; + description = "Port of the chat server."; + }; + + ssl = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether SSL should be used."; + }; + + verify = mkOption { + type = types.bool; + default = true; + description = "Whether the SSL certificate should be verified."; + }; + }; + + autoConnect = mkOption { + type = types.bool; + default = false; + description = "Whether Irssi connects to the server on launch."; + }; + }; + + channels = mkOption { + description = "Channels for the given network."; + type = types.attrsOf channelType; + default = {}; + }; + }; + }); + +in + +{ + + options = { + programs.irssi = { + enable = mkEnableOption "the Irssi chat client"; + + extraConfig = mkOption { + default = ""; + description = "These lines are appended to the Irssi configuration."; + type = types.str; + }; + + aliases = mkOption { + default = {}; + example = { J = "join"; BYE = "quit";}; + description = "An attribute set that maps aliases to commands."; + type = types.attrsOf types.str; + }; + + networks = mkOption { + default = {}; + example = literalExample '' + { + freenode = { + nick = "hmuser"; + server = { + address = "chat.freenode.net"; + port = 6697; + autoConnect = true; + }; + channels = { + nixos.autoJoin = true; + }; + }; + } + ''; + description = "An attribute set of chat networks."; + type = types.attrsOf networkType; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.irssi ]; + + home.file.".irssi/config".text = '' + settings = { + core = { + settings_autosave = "no"; + }; + }; + + aliases = { + ${assignFormat cfg.aliases} + }; + + chatnets = { + ${chatnetString} + }; + + servers = ( + ${serversString} + ); + + channels = ( + ${channelString} + ); + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/jq.nix b/home-manager/modules/programs/jq.nix new file mode 100644 index 00000000000..56c3adf0654 --- /dev/null +++ b/home-manager/modules/programs/jq.nix @@ -0,0 +1,76 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.jq; + + colorType = mkOption { + type = types.str; + description = "ANSI color definition"; + example = "1;31"; + visible = false; + }; + + colorsType = types.submodule { + options = { + null = colorType; + false = colorType; + true = colorType; + numbers = colorType; + strings = colorType; + arrays = colorType; + objects = colorType; + }; + }; + +in + +{ + options = { + programs.jq = { + enable = mkEnableOption "the jq command-line JSON processor"; + + colors = mkOption { + description = '' + The colors used in colored JSON output.</para> + + <para>See <link xlink:href="https://stedolan.github.io/jq/manual/#Colors"/>. + ''; + + example = literalExample '' + { + null = "1;30"; + false = "0;31"; + true = "0;32"; + numbers = "0;36"; + strings = "0;33"; + arrays = "1;35"; + objects = "1;37"; + } + ''; + + default = { + null = "1;30"; + false = "0;39"; + true = "0;39"; + numbers = "0;39"; + strings = "0;32"; + arrays = "1;39"; + objects = "1;39"; + }; + + type = colorsType; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.jq ]; + + home.sessionVariables = let c = cfg.colors; in { + JQ_COLORS = "${c.null}:${c.false}:${c.true}:${c.numbers}:${c.strings}:${c.arrays}:${c.objects}"; + }; + }; +} diff --git a/home-manager/modules/programs/kakoune.nix b/home-manager/modules/programs/kakoune.nix new file mode 100644 index 00000000000..e48f0e295a8 --- /dev/null +++ b/home-manager/modules/programs/kakoune.nix @@ -0,0 +1,574 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.kakoune; + + hook = types.submodule { + options = { + name = mkOption { + type = types.enum [ + "NormalBegin" "NormalIdle" "NormalEnd" "NormalKey" + "InsertBegin" "InsertIdle" "InsertEnd" "InsertKey" + "InsertChar" "InsertDelete" "InsertMove" "WinCreate" + "WinClose" "WinResize" "WinDisplay" "WinSetOption" + "BufSetOption" "BufNewFile" "BufOpenFile" "BufCreate" + "BufWritePre" "BufWritePost" "BufReload" "BufClose" + "BufOpenFifo" "BufReadFifo" "BufCloseFifo" "RuntimeError" + "ModeChange" "PromptIdle" "GlobalSetOption" "KakBegin" + "KakEnd" "FocusIn" "FocusOut" "RawKey" + "InsertCompletionShow" "InsertCompletionHide" + "InsertCompletionSelect" + ]; + example = "SetOption"; + description = '' + The name of the hook. For a description, see + <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/hooks.asciidoc#default-hooks"/>. + ''; + }; + + once = mkOption { + type = types.bool; + default = false; + description = '' + Remove the hook after running it once. + ''; + }; + + group = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Add the hook to the named group. + ''; + }; + + option = mkOption { + type = types.nullOr types.str; + default = null; + example = "filetype=latex"; + description = '' + Additional option to pass to the hook. + ''; + }; + + commands = mkOption { + type = types.lines; + default = ""; + example = "set-option window indentwidth 2"; + description = '' + Commands to run when the hook is activated. + ''; + }; + }; + }; + + keyMapping = types.submodule { + options = { + mode = mkOption { + type = types.enum [ + "insert" + "normal" + "prompt" + "menu" + "user" + "goto" + "view" + "object" + ]; + example = "user"; + description = '' + The mode in which the mapping takes effect. + ''; + }; + + docstring = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Optional documentation text to display in info boxes. + ''; + }; + + key = mkOption { + type = types.str; + example = "<a-x>"; + description = '' + The key to be mapped. See + <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/mapping.asciidoc#mappable-keys"/> + for possible values. + ''; + }; + + effect = mkOption { + type = types.str; + example = ":wq<ret>"; + description = '' + The sequence of keys to be mapped. + ''; + }; + }; + }; + + configModule = types.submodule { + options = { + colorScheme = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Set the color scheme. To see available schemes, enter + <command>colorscheme</command> at the kakoune prompt. + ''; + }; + + tabStop = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = '' + The width of a tab in spaces. The kakoune default is + <literal>6</literal>. + ''; + }; + + indentWidth = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = '' + The width of an indentation in spaces. + The kakoune default is <literal>4</literal>. + If <literal>0</literal>, a tab will be used instead. + ''; + }; + + incrementalSearch = mkOption { + type = types.bool; + default = true; + description = '' + Execute a search as it is being typed. + ''; + }; + + alignWithTabs = mkOption { + type = types.bool; + default = false; + description = '' + Use tabs for the align command. + ''; + }; + + autoInfo = mkOption { + type = types.nullOr (types.listOf (types.enum [ "command" "onkey" "normal" ])); + default = null; + example = [ "command" "normal" ]; + description = '' + Contexts in which to display automatic information box. + The kakoune default is <literal>[ "command" "onkey" ]</literal>. + ''; + }; + + autoComplete = mkOption { + type = types.nullOr(types.listOf (types.enum [ "insert" "prompt" ])); + default = null; + description = '' + Modes in which to display possible completions. + The kakoune default is <literal>[ "insert" "prompt" ]</literal>. + ''; + }; + + autoReload = mkOption { + type = types.nullOr (types.enum [ "yes" "no" "ask" ]); + default = null; + description = '' + Reload buffers when an external modification is detected. + The kakoune default is <literal>"ask"</literal>. + ''; + }; + + scrollOff = mkOption { + type = types.nullOr (types.submodule { + options = { + lines = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + The number of lines to keep visible around the cursor. + ''; + }; + + columns = mkOption { + type = types.ints.unsigned; + default = 0; + description = '' + The number of columns to keep visible around the cursor. + ''; + }; + }; + }); + default = null; + description = '' + How many lines and columns to keep visible around the cursor. + ''; + }; + + ui = mkOption { + type = types.nullOr (types.submodule { + options = { + setTitle = mkOption { + type = types.bool; + default = false; + description = '' + Change the title of the terminal emulator. + ''; + }; + + statusLine = mkOption { + type = types.enum [ "top" "bottom" ]; + default = "bottom"; + description = '' + Where to display the status line. + ''; + }; + + assistant = mkOption { + type = types.enum [ "clippy" "cat" "dilbert" "none" ]; + default = "clippy"; + description = '' + The assistant displayed in info boxes. + ''; + }; + + enableMouse = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable mouse support. + ''; + }; + + changeColors = mkOption { + type = types.bool; + default = true; + description = '' + Change color palette. + ''; + }; + + wheelDownButton = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Button to send for wheel down events. + ''; + }; + + wheelUpButton = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Button to send for wheel up events. + ''; + }; + + shiftFunctionKeys = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = '' + Amount by which shifted function keys are offset. That + is, if the terminal sends F13 for Shift-F1, this + should be <literal>12</literal>. + ''; + }; + + useBuiltinKeyParser = mkOption { + type = types.bool; + default = false; + description = '' + Bypass ncurses key parser and use an internal one. + ''; + }; + }; + }); + default = null; + description = '' + Settings for the ncurses interface. + ''; + }; + + showMatching = mkOption { + type = types.bool; + default = false; + description = '' + Highlight the matching char of the character under the + selections' cursor using the <literal>MatchingChar</literal> + face. + ''; + }; + + wrapLines = mkOption { + type = types.nullOr (types.submodule { + options = { + enable = mkEnableOption "the wrap lines highlighter"; + + word = mkOption { + type = types.bool; + default = false; + description = '' + Wrap at word boundaries instead of codepoint boundaries. + ''; + }; + + indent = mkOption { + type = types.bool; + default = false; + description = '' + Preserve line indentation when wrapping. + ''; + }; + + maxWidth = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + description = '' + Wrap text at maxWidth, even if the window is wider. + ''; + }; + + marker = mkOption { + type = types.nullOr types.str; + default = null; + example = "⏎"; + description = '' + Prefix wrapped lines with marker text. + If not <literal>null</literal>, + the marker text will be displayed in the indentation if possible. + ''; + }; + }; + }); + default = null; + description = '' + Settings for the wrap lines highlighter. + ''; + }; + + numberLines = mkOption { + type = types.nullOr (types.submodule { + options = { + enable = mkEnableOption "the number lines highlighter"; + + relative = mkOption { + type = types.bool; + default = false; + description = '' + Show line numbers relative to the main cursor line. + ''; + }; + + highlightCursor = mkOption { + type = types.bool; + default = false; + description = '' + Highlight the cursor line with a separate face. + ''; + }; + + separator = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + String that separates the line number column from the + buffer contents. The kakoune default is + <literal>"|"</literal>. + ''; + }; + }; + }); + default = null; + description = '' + Settings for the number lines highlighter. + ''; + }; + + showWhitespace = mkOption { + type = types.nullOr (types.submodule { + options = { + enable = mkEnableOption "the show whitespace highlighter"; + + lineFeed = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The character to display for line feeds. + The kakoune default is <literal>"¬"</literal>. + ''; + }; + + space = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The character to display for spaces. + The kakoune default is <literal>"·"</literal>. + ''; + }; + + nonBreakingSpace = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The character to display for non-breaking spaces. + The kakoune default is <literal>"⍽"</literal>. + ''; + }; + + tab = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The character to display for tabs. + The kakoune default is <literal>"→"</literal>. + ''; + }; + + tabStop = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The character to append to tabs to reach the width of a tabstop. + The kakoune default is <literal>" "</literal>. + ''; + }; + }; + }); + default = null; + description = '' + Settings for the show whitespaces highlighter. + ''; + }; + + keyMappings = mkOption { + type = types.listOf keyMapping; + default = []; + description = '' + User-defined key mappings. For documentation, see + <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/mapping.asciidoc"/>. + ''; + }; + + hooks = mkOption { + type = types.listOf hook; + default = []; + description = '' + Global hooks. For documentation, see + <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/hooks.asciidoc"/>. + ''; + }; + }; + }; + + configFile = + let + wrapOptions = with cfg.config.wrapLines; concatStrings [ + "${optionalString word " -word"}" + "${optionalString indent " -indent"}" + "${optionalString (marker != null) " -marker ${marker}"}" + "${optionalString (maxWidth != null) " -width ${toString maxWidth}"}" + ]; + + numberLinesOptions = with cfg.config.numberLines; concatStrings [ + "${optionalString relative " -relative "}" + "${optionalString highlightCursor " -hlcursor"}" + "${optionalString (separator != null) " -separator ${separator}"}" + ]; + + uiOptions = with cfg.config.ui; concatStringsSep " " [ + "ncurses_set_title=${if setTitle then "true" else "false"}" + "ncurses_status_on_top=${if (statusLine == "top") then "true" else "false"}" + "ncurses_assistant=${assistant}" + "ncurses_enable_mouse=${if enableMouse then "true" else "false"}" + "ncurses_change_colors=${if changeColors then "true" else "false"}" + "${optionalString (wheelDownButton != null) + "ncurses_wheel_down_button=${wheelDownButton}"}" + "${optionalString (wheelUpButton != null) + "ncurses_wheel_up_button=${wheelUpButton}"}" + "${optionalString (shiftFunctionKeys != null) + "ncurses_shift_function_key=${toString shiftFunctionKeys}"}" + "ncurses_builtin_key_parser=${if useBuiltinKeyParser then "true" else "false"}" + ]; + + keyMappingString = km: concatStringsSep " " [ + "map global" + "${km.mode} ${km.key} '${km.effect}'" + "${optionalString (km.docstring != null) "-docstring '${km.docstring}'"}" + ]; + + hookString = h: concatStringsSep " " [ + "hook" "${optionalString (h.group != null) "-group ${group}"}" + "${optionalString (h.once) "-once"}" "global" + "${h.name}" "${optionalString (h.option != null) h.option}" + "%{ ${h.commands} }" + ]; + + cfgStr = with cfg.config; concatStringsSep "\n" ( + [ "# Generated by home-manager" ] + ++ optional (colorScheme != null) "colorscheme ${colorScheme}" + ++ optional (tabStop != null) "set-option global tabstop ${toString tabStop}" + ++ optional (indentWidth != null) "set-option global indentwidth ${toString indentWidth}" + ++ optional (!incrementalSearch) "set-option global incsearch false" + ++ optional (alignWithTabs) "set-option global aligntab true" + ++ optional (autoInfo != null) "set-option global autoinfo ${concatStringsSep "|" autoInfo}" + ++ optional (autoComplete != null) "set-option global autocomplete ${concatStringsSep "|" autoComplete}" + ++ optional (autoReload != null) "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 (scrollOff != null) + "set-option global scrolloff ${toString scrollOff.lines},${toString scrollOff.columns}" + + ++ [ "# UI options" ] + ++ optional (ui != null) "set-option global ui_options ${uiOptions}" + + ++ [ "# Key mappings" ] + ++ map keyMappingString keyMappings + + ++ [ "# Hooks" ] + ++ map hookString hooks + ); + in + pkgs.writeText "kakrc" ( + optionalString (cfg.config != null) cfgStr + + cfg.extraConfig + ); + +in + +{ + options = { + programs.kakoune = { + enable = mkEnableOption "the kakoune text editor"; + + config = mkOption { + type = types.nullOr configModule; + default = {}; + description = "kakoune configuration options."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration lines to add to + <filename>~/.config/kak/kakrc</filename>. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.kakoune ]; + xdg.configFile."kak/kakrc".source = configFile; + }; +} diff --git a/home-manager/modules/programs/keychain.nix b/home-manager/modules/programs/keychain.nix new file mode 100644 index 00000000000..6dbf83a872e --- /dev/null +++ b/home-manager/modules/programs/keychain.nix @@ -0,0 +1,88 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.keychain; + + flags = cfg.extraFlags + ++ optional (cfg.agents != []) "--agents ${concatStringsSep "," cfg.agents}" + ++ optional (cfg.inheritType != null) "--inherit ${cfg.inheritType}"; + + shellCommand = '' + eval "$(${cfg.package}/bin/keychain --eval ${concatStringsSep " " flags} ${concatStringsSep " " cfg.keys})" + ''; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.keychain = { + enable = mkEnableOption "keychain"; + + package = mkOption { + type = types.package; + default = pkgs.keychain; + defaultText = literalExample "pkgs.keychain"; + description = '' + Keychain package to install. + ''; + }; + + keys = mkOption { + type = types.listOf types.str; + default = [ "id_rsa" ]; + description = '' + Keys to add to keychain. + ''; + }; + + agents = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Agents to add. + ''; + }; + + inheritType = mkOption { + type = types.nullOr (types.enum ["local" "any" "local-once" "any-once"]); + default = null; + description = '' + Inherit type to attempt from agent variables from the environment. + ''; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = [ "--quiet" ]; + description = '' + Extra flags to pass to keychain. + ''; + }; + + 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. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + programs.bash.initExtra = mkIf cfg.enableBashIntegration shellCommand; + programs.zsh.initExtra = mkIf cfg.enableZshIntegration shellCommand; + }; +} diff --git a/home-manager/modules/programs/lesspipe.nix b/home-manager/modules/programs/lesspipe.nix new file mode 100644 index 00000000000..a7a51ffe2a2 --- /dev/null +++ b/home-manager/modules/programs/lesspipe.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: + +with lib; + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.lesspipe = { + enable = mkEnableOption "lesspipe preprocessor for less"; + }; + }; + + config = mkIf config.programs.lesspipe.enable { + home.sessionVariables = { + LESSOPEN = "|${pkgs.lesspipe}/bin/lesspipe.sh %s"; + }; + }; +} diff --git a/home-manager/modules/programs/lsd.nix b/home-manager/modules/programs/lsd.nix new file mode 100644 index 00000000000..5e145e8c69b --- /dev/null +++ b/home-manager/modules/programs/lsd.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.lsd; + + aliases = { + ls = "${pkgs.lsd}/bin/lsd"; + ll = "ls -l"; + la = "ls -a"; + lt = "ls --tree"; + lla ="ls -la"; + }; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.lsd = { + enable = mkEnableOption "lsd"; + + enableAliases = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable recommended lsd aliases. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.lsd ]; + + programs.bash.shellAliases = mkIf cfg.enableAliases aliases; + + programs.zsh.shellAliases = mkIf cfg.enableAliases aliases; + }; +} diff --git a/home-manager/modules/programs/man.nix b/home-manager/modules/programs/man.nix new file mode 100644 index 00000000000..0ed376780d4 --- /dev/null +++ b/home-manager/modules/programs/man.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, ... }: + +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>. + ''; + }; + }; + + config = mkIf config.programs.man.enable { + home.packages = [ pkgs.man ]; + home.extraOutputsToInstall = [ "man" ]; + }; +} diff --git a/home-manager/modules/programs/matplotlib.nix b/home-manager/modules/programs/matplotlib.nix new file mode 100644 index 00000000000..48ff6e60d68 --- /dev/null +++ b/home-manager/modules/programs/matplotlib.nix @@ -0,0 +1,64 @@ +{ config, lib, ... }: + +with lib; + +let + + cfg = config.programs.matplotlib; + + formatLine = o: n: v: + let + formatValue = v: + if isBool v then (if v then "True" else "False") + else toString v; + in + if isAttrs v + then concatStringsSep "\n" (mapAttrsToList (formatLine "${o}${n}.") v) + else (if v == "" then "" else "${o}${n}: ${formatValue v}"); + +in + +{ + meta.maintainers = [ maintainers.rprospero ]; + + options.programs.matplotlib = { + enable = mkEnableOption "matplotlib, a plotting library for python"; + + config = mkOption { + default = { }; + type = types.attrs; + description = '' + Add terms to the <filename>matplotlibrc</filename> file to + control the default matplotlib behavior. + ''; + example = literalExample '' + { + backend = "Qt5Agg"; + axes = { + grid = true; + facecolor = "black"; + edgecolor = "FF9900"; + }; + grid.color = "FF9900"; + } + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional commands for matplotlib that will be added to the + <filename>matplotlibrc</filename> file. + ''; + }; + }; + + config = mkIf cfg.enable { + xdg.configFile."matplotlib/matplotlibrc".text = + concatStringsSep "\n" ([] + ++ mapAttrsToList (formatLine "") cfg.config + ++ optional (cfg.extraConfig != "") cfg.extraConfig + ) + "\n"; + }; +} diff --git a/home-manager/modules/programs/mbsync-accounts.nix b/home-manager/modules/programs/mbsync-accounts.nix new file mode 100644 index 00000000000..c586481df4d --- /dev/null +++ b/home-manager/modules/programs/mbsync-accounts.nix @@ -0,0 +1,107 @@ +{ lib, ... }: + +with lib; + +let + + extraConfigType = with lib.types; attrsOf (either (either str int) bool); + +in + +{ + options.mbsync = { + enable = mkEnableOption "synchronization using mbsync"; + + flatten = mkOption { + type = types.nullOr types.str; + default = null; + example = "."; + description = '' + If set, flattens the hierarchy within the maildir by + substituting the canonical hierarchy delimiter + <literal>/</literal> with this value. + ''; + }; + + create = mkOption { + type = types.enum [ "none" "maildir" "imap" "both" ]; + default = "none"; + example = "maildir"; + description = '' + Automatically create missing mailboxes within the + given mail store. + ''; + }; + + remove = mkOption { + type = types.enum [ "none" "maildir" "imap" "both" ]; + default = "none"; + example = "imap"; + description = '' + Propagate mailbox deletions to the given mail store. + ''; + }; + + expunge = mkOption { + type = types.enum [ "none" "maildir" "imap" "both" ]; + default = "none"; + example = "both"; + description = '' + Permanently remove messages marked for deletion from + the given mail store. + ''; + }; + + patterns = mkOption { + type = types.listOf types.str; + default = [ "*" ]; + description = '' + Pattern of mailboxes to synchronize. + ''; + }; + + extraConfig.channel = mkOption { + type = extraConfigType; + default = {}; + example = literalExample '' + { + MaxMessages = 10000; + MaxSize = "1m"; + }; + ''; + description = '' + Per channel extra configuration. + ''; + }; + + extraConfig.local = mkOption { + type = extraConfigType; + default = {}; + description = '' + Local store extra configuration. + ''; + }; + + extraConfig.remote = mkOption { + type = extraConfigType; + default = {}; + description = '' + Remote store extra configuration. + ''; + }; + + extraConfig.account = mkOption { + type = extraConfigType; + default = {}; + example = literalExample '' + { + PipelineDepth = 10; + Timeout = 60; + }; + ''; + description = '' + Account section extra configuration. + ''; + }; + }; +} diff --git a/home-manager/modules/programs/mbsync.nix b/home-manager/modules/programs/mbsync.nix new file mode 100644 index 00000000000..908a1add715 --- /dev/null +++ b/home-manager/modules/programs/mbsync.nix @@ -0,0 +1,188 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + dag = config.lib.dag; + + cfg = config.programs.mbsync; + + # Accounts for which mbsync is enabled. + mbsyncAccounts = + filter (a: a.mbsync.enable) (attrValues config.accounts.email.accounts); + + genTlsConfig = tls: { + SSLType = + if !tls.enable then "None" + else if tls.useStartTls then "STARTTLS" + else "IMAPS"; + } + // + optionalAttrs (tls.enable && tls.certificatesFile != null) { + CertificateFile = toString tls.certificatesFile; + }; + + masterSlaveMapping = { + none = "None"; + imap = "Master"; + maildir = "Slave"; + both = "Both"; + }; + + genSection = header: entries: + let + escapeValue = escape [ "\"" ]; + hasSpace = v: builtins.match ".* .*" v != null; + genValue = n: v: + if isList v + then concatMapStringsSep " " (genValue n) v + else if isBool v then (if v then "yes" else "no") + else if isInt v then toString v + else if isString v && hasSpace v then "\"${escapeValue v}\"" + else if isString v then v + else + let prettyV = lib.generators.toPretty {} v; + in throw "mbsync: unexpected value for option ${n}: '${prettyV}'"; + in + '' + ${header} + ${concatStringsSep "\n" + (mapAttrsToList (n: v: "${n} ${genValue n v}") entries)} + ''; + + genAccountConfig = account: with account; + genSection "IMAPAccount ${name}" ( + { + Host = imap.host; + User = userName; + PassCmd = toString passwordCommand; + } + // genTlsConfig imap.tls + // optionalAttrs (imap.port != null) { Port = toString imap.port; } + // mbsync.extraConfig.account + ) + + "\n" + + genSection "IMAPStore ${name}-remote" ( + { + Account = name; + } + // mbsync.extraConfig.remote + ) + + "\n" + + genSection "MaildirStore ${name}-local" ( + { + Path = "${maildir.absPath}/"; + Inbox = "${maildir.absPath}/${folders.inbox}"; + SubFolders = "Verbatim"; + } + // optionalAttrs (mbsync.flatten != null) { Flatten = mbsync.flatten; } + // mbsync.extraConfig.local + ) + + "\n" + + genSection "Channel ${name}" ( + { + Master = ":${name}-remote:"; + Slave = ":${name}-local:"; + Patterns = mbsync.patterns; + Create = masterSlaveMapping.${mbsync.create}; + Remove = masterSlaveMapping.${mbsync.remove}; + Expunge = masterSlaveMapping.${mbsync.expunge}; + SyncState = "*"; + } + // mbsync.extraConfig.channel + ) + + "\n"; + + genGroupConfig = name: channels: + let + genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}"; + in + concatStringsSep "\n" ( + [ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels + ); + +in + +{ + options = { + programs.mbsync = { + enable = mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer"; + + package = mkOption { + type = types.package; + default = pkgs.isync; + defaultText = literalExample "pkgs.isync"; + example = literalExample "pkgs.isync"; + description = "The package to use for the mbsync binary."; + }; + + groups = mkOption { + type = types.attrsOf (types.attrsOf (types.listOf types.str)); + default = {}; + example = literalExample '' + { + inboxes = { + account1 = [ "Inbox" ]; + account2 = [ "Inbox" ]; + }; + } + ''; + description = '' + Definition of groups. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration lines to add to the mbsync configuration. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = + let + checkAccounts = pred: msg: + let + badAccounts = filter pred mbsyncAccounts; + in { + assertion = badAccounts == []; + message = "mbsync: ${msg} for accounts: " + + concatMapStringsSep ", " (a: a.name) badAccounts; + }; + in + [ + (checkAccounts (a: a.maildir == null) "Missing maildir configuration") + (checkAccounts (a: a.imap == null) "Missing IMAP configuration") + (checkAccounts (a: a.passwordCommand == null) "Missing passwordCommand") + (checkAccounts (a: a.userName == null) "Missing username") + ]; + + home.packages = [ cfg.package ]; + + programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ]; + + home.file.".mbsyncrc".text = + let + accountsConfig = map genAccountConfig mbsyncAccounts; + groupsConfig = mapAttrsToList genGroupConfig cfg.groups; + in + concatStringsSep "\n" ( + [ "# Generated by Home Manager.\n" ] + ++ optional (cfg.extraConfig != "") cfg.extraConfig + ++ accountsConfig + ++ groupsConfig + ) + "\n"; + + home.activation.createMaildir = + dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] '' + $DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${ + concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts + } + ''; + }; +} diff --git a/home-manager/modules/programs/mercurial.nix b/home-manager/modules/programs/mercurial.nix new file mode 100644 index 00000000000..fa6e7b3e5ba --- /dev/null +++ b/home-manager/modules/programs/mercurial.nix @@ -0,0 +1,102 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.mercurial; + +in + +{ + + options = { + programs.mercurial = { + enable = mkEnableOption "Mercurial"; + + package = mkOption { + type = types.package; + default = pkgs.mercurial; + defaultText = literalExample "pkgs.mercurial"; + description = "Mercurial package to install."; + }; + + userName = mkOption { + type = types.str; + description = "Default user name to use."; + }; + + userEmail = mkOption { + type = types.str; + description = "Default user email to use."; + }; + + aliases = mkOption { + type = types.attrs; + default = {}; + description = "Mercurial aliases to define."; + }; + + extraConfig = mkOption { + type = types.either types.attrs types.lines; + default = {}; + description = "Additional configuration to add."; + }; + + iniContent = mkOption { + type = types.attrsOf types.attrs; + internal = true; + }; + + ignores = mkOption { + type = types.listOf types.str; + default = []; + example = [ "*~" "*.swp" ]; + description = "List of globs for files to be globally ignored."; + }; + + ignoresRegexp = mkOption { + type = types.listOf types.str; + default = []; + example = [ "^.*~$" "^.*\\.swp$" ]; + description = + "List of regular expressions for files to be globally ignored."; + }; + }; + }; + + config = mkIf cfg.enable ( + mkMerge [ + { + home.packages = [ cfg.package ]; + + programs.mercurial.iniContent.ui = { + username = cfg.userName + " <" + cfg.userEmail + ">"; + }; + + xdg.configFile."hg/hgrc".text = generators.toINI {} cfg.iniContent; + } + + (mkIf (cfg.ignores != [] || cfg.ignoresRegexp != []) { + programs.mercurial.iniContent.ui.ignore = + "${config.xdg.configHome}/hg/hgignore_global"; + + xdg.configFile."hg/hgignore_global".text = + "syntax: glob\n" + concatStringsSep "\n" cfg.ignores + "\n" + + "syntax: regexp\n" + concatStringsSep "\n" cfg.ignoresRegexp + "\n"; + }) + + (mkIf (cfg.aliases != {}) { + programs.mercurial.iniContent.alias = cfg.aliases; + }) + + (mkIf (lib.isAttrs cfg.extraConfig) { + programs.mercurial.iniContent = cfg.extraConfig; + }) + + (mkIf (lib.isString cfg.extraConfig) { + xdg.configFile."hg/hgrc".text = cfg.extraConfig; + }) + ] + ); +} diff --git a/home-manager/modules/programs/mpv.nix b/home-manager/modules/programs/mpv.nix new file mode 100644 index 00000000000..1051f71ccd6 --- /dev/null +++ b/home-manager/modules/programs/mpv.nix @@ -0,0 +1,152 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + inherit (builtins) typeOf stringLength; + + cfg = config.programs.mpv; + + mpvOption = with types; either str (either int (either bool float)); + mpvOptions = with types; attrsOf mpvOption; + mpvProfiles = with types; attrsOf mpvOptions; + mpvBindings = with types; attrsOf str; + + renderOption = option: + rec { + int = toString option; + float = int; + + bool = if option then "yes" else "no"; + + string = option; + }.${typeOf option}; + + renderOptions = options: + concatStringsSep "\n" + (mapAttrsToList + (name: value: + let + rendered = renderOption value; + length = toString (stringLength rendered); + in + "${name}=%${length}%${rendered}") + options); + + renderProfiles = profiles: + concatStringsSep "\n" + (mapAttrsToList + (name: value: '' + [${name}] + ${renderOptions value} + '') + profiles); + + renderBindings = bindings: + concatStringsSep "\n" + (mapAttrsToList + (name: value: + "${name} ${value}") + bindings); + +in { + options = { + programs.mpv = { + enable = mkEnableOption "mpv"; + + scripts = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "[ pkgs.mpvScripts.mpris ]"; + description = '' + List of scripts to use with mpv. + ''; + }; + + config = mkOption { + description = '' + Configuration written to + <filename>~/.config/mpv/mpv.conf</filename>. See + <citerefentry> + <refentrytitle>mpv</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for the full list of options. + ''; + type = mpvOptions; + default = {}; + example = literalExample '' + { + profile = "gpu-hq"; + force-window = "yes"; + ytdl-format = "bestvideo+bestaudio"; + cache-default = 4000000; + } + ''; + }; + + profiles = mkOption { + description = '' + Sub-configuration options for specific profiles written to + <filename>~/.config/mpv/mpv.conf</filename>. See + <option>programs.mpv.config</option> for more information. + ''; + type = mpvProfiles; + default = {}; + example = literalExample '' + { + fast = { + vo = "vdpau"; + }; + "protocol.dvd" = { + profile-desc = "profile for dvd:// streams"; + alang = "en"; + }; + } + ''; + }; + + bindings = mkOption { + description = '' + Input configuration written to + <filename>~/.config/mpv/input.conf</filename>. See + <citerefentry> + <refentrytitle>mpv</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + for the full list of options. + ''; + type = mpvBindings; + default = {}; + example = literalExample '' + { + WHEEL_UP = "seek 10"; + WHEEL_DOWN = "seek -10"; + "Alt+0" = "set window-scale 0.5"; + } + ''; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + { + home.packages = [( + if cfg.scripts == [] + then pkgs.mpv + else pkgs.mpv-with-scripts.override { scripts = cfg.scripts; } + )]; + } + (mkIf (cfg.config != {} || cfg.profiles != {}) { + xdg.configFile."mpv/mpv.conf".text = '' + ${optionalString (cfg.config != {}) (renderOptions cfg.config)} + ${optionalString (cfg.profiles != {}) (renderProfiles cfg.profiles)} + ''; + }) + (mkIf (cfg.bindings != {}) { + xdg.configFile."mpv/input.conf".text = renderBindings cfg.bindings; + }) + ]); + + meta.maintainers = with maintainers; [ tadeokondrak ]; +} diff --git a/home-manager/modules/programs/msmtp-accounts.nix b/home-manager/modules/programs/msmtp-accounts.nix new file mode 100644 index 00000000000..277710f4cba --- /dev/null +++ b/home-manager/modules/programs/msmtp-accounts.nix @@ -0,0 +1,47 @@ +{ config, lib, ... }: + +with lib; + +{ + options.msmtp = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable msmtp. + </para><para> + If enabled then it is possible to use the + <parameter class="command">--account</parameter> command line + option to send a message for a given account using the + <command>msmtp</command> or <command>msmtpq</command> tool. + For example, <command>msmtp --account=private</command> would + send using the account defined in + <option>accounts.email.accounts.private</option>. If the + <parameter class="command">--account</parameter> option is not + given then the primary account will be used. + ''; + }; + + tls.fingerprint = mkOption { + type = types.nullOr (types.strMatching "([[:alnum:]]{2}\:)+[[:alnum:]]{2}"); + default = null; + example = "my:SH:a2:56:ha:sh"; + description = '' + Fingerprint of a trusted TLS certificate. + The fingerprint can be obtained by executing + <command>msmtp --serverinfo --tls --tls-certcheck=off</command>. + ''; + }; + + extraConfig = mkOption { + type = types.attrsOf types.str; + default = { }; + example = { auth = "login"; }; + description = '' + Extra configuration options to add to <filename>~/.msmtprc</filename>. + See <link xlink:href="https://marlam.de/msmtp/msmtprc.txt"/> for + examples. + ''; + }; + }; +} diff --git a/home-manager/modules/programs/msmtp.nix b/home-manager/modules/programs/msmtp.nix new file mode 100644 index 00000000000..1ff3139ef36 --- /dev/null +++ b/home-manager/modules/programs/msmtp.nix @@ -0,0 +1,79 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.msmtp; + + msmtpAccounts = filter (a: a.msmtp.enable) + (attrValues config.accounts.email.accounts); + + onOff = p: if p then "on" else "off"; + + accountStr = account: with account; + concatStringsSep "\n" ( + [ "account ${name}" ] + ++ mapAttrsToList (n: v: n + " " + v) ( + { + host = smtp.host; + from = address; + auth = "on"; + user = userName; + tls = onOff smtp.tls.enable; + tls_starttls = onOff smtp.tls.useStartTls; + tls_trust_file = smtp.tls.certificatesFile; + } + // optionalAttrs (msmtp.tls.fingerprint != null) { + tls_fingerprint = msmtp.tls.fingerprint; + } + // optionalAttrs (smtp.port != null) { + port = toString smtp.port; + } + // optionalAttrs (passwordCommand != null) { + # msmtp requires the password to finish with a newline. + passwordeval = ''${pkgs.bash}/bin/bash -c "${toString passwordCommand}; echo"''; + } + // msmtp.extraConfig + ) + ++ optional primary "\naccount default : ${name}" + ); + + configFile = mailAccounts: '' + # Generated by Home Manager. + + ${cfg.extraConfig} + + ${concatStringsSep "\n\n" (map accountStr mailAccounts)} + ''; + +in + +{ + + options = { + programs.msmtp = { + enable = mkEnableOption "msmtp"; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration lines to add to <filename>~/.msmtprc</filename>. + See <link xlink:href="https://marlam.de/msmtp/msmtprc.txt"/> for examples. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.msmtp ]; + + xdg.configFile."msmtp/config".text = configFile msmtpAccounts; + + home.sessionVariables = { + MSMTP_QUEUE = "${config.xdg.dataHome}/msmtp/queue"; + MSMTP_LOG = "${config.xdg.dataHome}/msmtp/queue.log"; + }; + }; +} diff --git a/home-manager/modules/programs/neovim.nix b/home-manager/modules/programs/neovim.nix new file mode 100644 index 00000000000..dadda2c7118 --- /dev/null +++ b/home-manager/modules/programs/neovim.nix @@ -0,0 +1,212 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.neovim; + + extraPythonPackageType = mkOptionType { + name = "extra-python-packages"; + description = "python packages in python.withPackages format"; + check = with types; (x: if isFunction x + then isList (x pkgs.pythonPackages) + else false); + merge = mergeOneOption; + }; + + extraPython3PackageType = mkOptionType { + name = "extra-python3-packages"; + description = "python3 packages in python.withPackages format"; + check = with types; (x: if isFunction x + then isList (x pkgs.python3Packages) + else false); + merge = mergeOneOption; + }; + + moduleConfigure = + optionalAttrs (cfg.extraConfig != "") { + customRC = cfg.extraConfig; + } + // optionalAttrs (cfg.plugins != []) { + packages.home-manager.start = cfg.plugins; + }; + +in + +{ + options = { + programs.neovim = { + enable = mkEnableOption "Neovim"; + + viAlias = mkOption { + type = types.bool; + default = false; + description = '' + Symlink `vi` to `nvim` binary. + ''; + }; + + vimAlias = mkOption { + type = types.bool; + default = false; + description = '' + Symlink `vim` to `nvim` binary. + ''; + }; + + withNodeJs = mkOption { + type = types.bool; + default = false; + description = '' + Enable node provider. Set to <literal>true</literal> to + use Node plugins. + ''; + }; + + withPython = mkOption { + type = types.bool; + default = true; + description = '' + Enable Python 2 provider. Set to <literal>true</literal> to + use Python 2 plugins. + ''; + }; + + extraPythonPackages = mkOption { + type = with types; either extraPythonPackageType (listOf package); + default = (_: []); + defaultText = "ps: []"; + example = literalExample "(ps: with ps; [ pandas jedi ])"; + description = '' + A function in python.withPackages format, which returns a + list of Python 2 packages required for your plugins to work. + ''; + }; + + withRuby = mkOption { + type = types.nullOr types.bool; + default = true; + description = '' + Enable ruby provider. + ''; + }; + + withPython3 = mkOption { + type = types.bool; + default = true; + description = '' + Enable Python 3 provider. Set to <literal>true</literal> to + use Python 3 plugins. + ''; + }; + + extraPython3Packages = mkOption { + type = with types; either extraPython3PackageType (listOf package); + default = (_: []); + defaultText = "ps: []"; + example = literalExample "(ps: with ps; [ python-language-server ])"; + description = '' + A function in python.withPackages format, which returns a + list of Python 3 packages required for your plugins to work. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.neovim-unwrapped; + defaultText = literalExample "pkgs.neovim-unwrapped"; + description = "The package to use for the neovim binary."; + }; + + finalPackage = mkOption { + type = types.package; + visible = false; + readOnly = true; + description = "Resulting customized neovim package."; + }; + + configure = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + configure = { + customRC = $'''' + " here your custom configuration goes! + $''''; + packages.myVimPackage = with pkgs.vimPlugins; { + # loaded on launch + start = [ fugitive ]; + # manually loadable by calling `:packadd $plugin-name` + opt = [ ]; + }; + }; + ''; + description = '' + Generate your init file from your list of plugins and custom commands, + and loads it from the store via <command>nvim -u /nix/store/hash-vimrc</command> + + </para><para> + + This option is deprecated. Please use the options <varname>extraConfig</varname> + and <varname>plugins</varname> which are mutually exclusive with this option. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + set nocompatible + set nobackup + ''; + description = '' + Custom vimrc lines. + + </para><para> + + This option is mutually exclusive with <varname>configure</varname>. + ''; + }; + + plugins = mkOption { + type = with types; listOf package; + default = [ ]; + example = literalExample "[ pkgs.vimPlugins.yankring ]"; + description = '' + List of vim plugins to install. + + </para><para> + + This option is mutually exclusive with <varname>configure</varname>. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.configure == { } || moduleConfigure == { }; + message = "The programs.neovim option configure is mutually exclusive" + + " with extraConfig and plugins."; + } + ]; + + warnings = optional (cfg.configure != {}) '' + The programs.neovim.configure option is deprecated. Please use + extraConfig and package option. + ''; + + home.packages = [ cfg.finalPackage ]; + + programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package { + inherit (cfg) + extraPython3Packages withPython3 + extraPythonPackages withPython + withNodeJs withRuby viAlias vimAlias; + + configure = cfg.configure // moduleConfigure; + }; + }; +} diff --git a/home-manager/modules/programs/newsboat.nix b/home-manager/modules/programs/newsboat.nix new file mode 100644 index 00000000000..84c64dfa607 --- /dev/null +++ b/home-manager/modules/programs/newsboat.nix @@ -0,0 +1,98 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.newsboat; + wrapQuote = x: "\"${x}\""; + +in + +{ + options = { + programs.newsboat = { + enable = mkEnableOption "the Newsboat feed reader"; + + urls = mkOption { + type = types.listOf types.attrs; + default = []; + example = [{url = "http://example.com"; tags = ["foo" "bar"];}]; + description = "List of urls and tokens."; + }; + + maxItems = mkOption { + type = types.int; + default = 0; + description = "Maximum number of items per feed, 0 for infinite."; + }; + + reloadThreads = mkOption { + type = types.int; + default = 5; + description = "How many threads to use for updating the feeds."; + }; + + autoReload = mkOption { + type = types.bool; + default = false; + description = "Whether to enable automatic reloading while newsboat is running."; + }; + + reloadTime = mkOption { + type = types.nullOr types.int; + default = 60; + description = "Time in minutes between reloads."; + }; + + browser = mkOption { + type = types.str; + default = "${pkgs.xdg_utils}/bin/xdg-open"; + description = "External browser to use."; + }; + + queries = mkOption { + type = types.attrsOf types.str; + default = {}; + example = { + "foo" = "rssurl =~ \"example.com\""; + }; + description = "A list of queries to use."; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Extra configuration values that will be appended to the end."; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.newsboat ]; + home.file.".newsboat/urls".text = + let + urls = builtins.concatStringsSep "\n" ( + map (u: builtins.concatStringsSep " " ([u.url] ++ (map wrapQuote u.tags))) + cfg.urls); + queries = builtins.concatStringsSep "\n" ( + mapAttrsToList (n: v: "\"query:${n}:${escape ["\""] v}\"") cfg.queries); + + in + + '' + ${urls} + + ${queries} + ''; + home.file.".newsboat/config".text = '' + max-items ${toString cfg.maxItems} + browser ${cfg.browser} + reload-threads ${toString cfg.reloadThreads} + auto-reload ${if cfg.autoReload then "yes" else "no"} + ${optionalString (cfg.reloadTime != null) (toString "reload-time ${toString cfg.reloadTime}")} + prepopulate-query-feeds yes + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/noti.nix b/home-manager/modules/programs/noti.nix new file mode 100644 index 00000000000..476c2eb1978 --- /dev/null +++ b/home-manager/modules/programs/noti.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + + cfg = config.programs.noti; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.noti = { + enable = mkEnableOption "Noti"; + + settings = mkOption { + type = types.attrsOf (types.attrsOf types.str); + default = {}; + description = '' + Configuration written to + <filename>~/.config/noti/noti.yaml</filename>. + </para><para> + See + <citerefentry> + <refentrytitle>noti.yaml</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry>. + for the full list of options. + ''; + example = literalExample '' + { + say = { + voice = "Alex"; + }; + slack = { + token = "1234567890abcdefg"; + channel = "@jaime"; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.noti ]; + + xdg.configFile."noti/noti.yaml" = mkIf (cfg.settings != {}) { + text = generators.toYAML {} cfg.settings; + }; + }; + +} diff --git a/home-manager/modules/programs/notmuch-accounts.nix b/home-manager/modules/programs/notmuch-accounts.nix new file mode 100644 index 00000000000..7c9c93d3f95 --- /dev/null +++ b/home-manager/modules/programs/notmuch-accounts.nix @@ -0,0 +1,7 @@ +{ lib, ... }: + +{ + options.notmuch = { + enable = lib.mkEnableOption "notmuch indexing"; + }; +} diff --git a/home-manager/modules/programs/notmuch.nix b/home-manager/modules/programs/notmuch.nix new file mode 100644 index 00000000000..cd0b1384ad9 --- /dev/null +++ b/home-manager/modules/programs/notmuch.nix @@ -0,0 +1,211 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.notmuch; + + mkIniKeyValue = key: value: + let + tweakVal = v: + if isString v then v + else if isList v then concatMapStringsSep ";" tweakVal v + else if isBool v then (if v then "true" else "false") + else toString v; + in + "${key}=${tweakVal value}"; + + notmuchIni = + recursiveUpdate + { + database = { + path = config.accounts.email.maildirBasePath; + }; + + maildir = { + synchronize_flags = cfg.maildir.synchronizeFlags; + }; + + new = { + ignore = cfg.new.ignore; + tags = cfg.new.tags; + }; + + user = + let + accounts = + filter (a: a.notmuch.enable) + (attrValues config.accounts.email.accounts); + primary = filter (a: a.primary) accounts; + secondaries = filter (a: !a.primary) accounts; + in { + name = catAttrs "realName" primary; + primary_email = catAttrs "address" primary; + other_email = catAttrs "aliases" primary + ++ catAttrs "address" secondaries + ++ catAttrs "aliases" secondaries; + }; + + search = { + exclude_tags = cfg.search.excludeTags; + }; + } + cfg.extraConfig; + +in + +{ + options = { + programs.notmuch = { + enable = mkEnableOption "Notmuch mail indexer"; + + new = mkOption { + type = types.submodule { + options = { + ignore = mkOption { + type = types.listOf types.str; + default = []; + description = '' + A list to specify files and directories that will not be + searched for messages by <command>notmuch new</command>. + ''; + }; + + tags = mkOption { + type = types.listOf types.str; + default = [ "unread" "inbox" ]; + example = [ "new" ]; + description = '' + A list of tags that will be added to all messages + incorporated by <command>notmuch new</command>. + ''; + }; + }; + }; + default = {}; + description = '' + Options related to email processing performed by + <command>notmuch new</command>. + ''; + }; + + extraConfig = mkOption { + type = types.attrsOf (types.attrsOf types.str); + default = {}; + description = '' + Options that should be appended to the notmuch configuration file. + ''; + }; + + hooks = { + preNew = mkOption { + type = types.lines; + default = ""; + example = "mbsync --all"; + description = '' + Bash statements run before scanning or importing new + messages into the database. + ''; + }; + + postNew = mkOption { + type = types.lines; + default = ""; + example = '' + notmuch tag +nixos -- tag:new and from:nixos1@discoursemail.com + ''; + description = '' + Bash statements run after new messages have been imported + into the database and initial tags have been applied. + ''; + }; + + postInsert = mkOption { + type = types.lines; + default = ""; + description = '' + Bash statements run after a message has been inserted + into the database and initial tags have been applied. + ''; + }; + }; + + maildir = { + synchronizeFlags = mkOption { + type = types.bool; + default = true; + description = '' + Whether to synchronize Maildir flags. + ''; + }; + }; + + search = { + excludeTags = mkOption { + type = types.listOf types.str; + default = [ "deleted" "spam" ]; + example = [ "trash" "spam" ]; + description = '' + A list of tags that will be excluded from search results by + default. Using an excluded tag in a query will override that + exclusion. + ''; + }; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = notmuchIni.user.name != []; + message = "notmuch: Must have a user name set."; + } + { + assertion = notmuchIni.user.primary_email != []; + message = "notmuch: Must have a user primary email address set."; + } + ]; + + home.packages = [ pkgs.notmuch ]; + + home.sessionVariables = { + NOTMUCH_CONFIG = "${config.xdg.configHome}/notmuch/notmuchrc"; + NMBGIT = "${config.xdg.dataHome}/notmuch/nmbug"; + }; + + xdg.configFile."notmuch/notmuchrc".text = + let + toIni = generators.toINI { mkKeyValue = mkIniKeyValue; }; + in + "# Generated by Home Manager.\n\n" + + toIni notmuchIni; + + home.file = + let + hook = name: cmds: + { + target = "${notmuchIni.database.path}/.notmuch/hooks/${name}"; + source = pkgs.writeScript name '' + #!${pkgs.runtimeShell} + + export PATH="${pkgs.notmuch}/bin''${PATH:+:}$PATH" + export NOTMUCH_CONFIG="${config.xdg.configHome}/notmuch/notmuchrc" + export NMBGIT="${config.xdg.dataHome}/notmuch/nmbug" + + ${cmds} + ''; + executable = true; + }; + in + optional (cfg.hooks.preNew != "") + (hook "pre-new" cfg.hooks.preNew) + ++ + optional (cfg.hooks.postNew != "") + (hook "post-new" cfg.hooks.postNew) + ++ + optional (cfg.hooks.postInsert != "") + (hook "post-insert" cfg.hooks.postInsert); + }; +} diff --git a/home-manager/modules/programs/obs-studio.nix b/home-manager/modules/programs/obs-studio.nix new file mode 100644 index 00000000000..f0dfecb63cc --- /dev/null +++ b/home-manager/modules/programs/obs-studio.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.obs-studio; + package = pkgs.obs-studio; + + mkPluginEnv = packages: + let + pluginDirs = map (pkg: "${pkg}/share/obs/obs-plugins") packages; + plugins = concatMapStringsSep " " (p: "${p}/*") pluginDirs; + in + pkgs.runCommand "obs-studio-plugins" + { + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + mkdir $out + [[ '${plugins}' ]] || exit 0 + for plugin in ${plugins}; do + ln -s "$plugin" $out/ + done + ''; + +in + +{ + meta.maintainers = [ maintainers.adisbladis ]; + + options = { + programs.obs-studio = { + enable = mkEnableOption "obs-studio"; + + plugins = mkOption { + default = []; + example = literalExample "[ pkgs.obs-linuxbrowser ]"; + description = "Optional OBS plugins."; + type = types.listOf types.package; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ package ]; + + xdg.configFile."obs-studio/plugins" = mkIf (cfg.plugins != []) { + source = mkPluginEnv cfg.plugins; + }; + }; +} diff --git a/home-manager/modules/programs/offlineimap-accounts.nix b/home-manager/modules/programs/offlineimap-accounts.nix new file mode 100644 index 00000000000..015a5974ab3 --- /dev/null +++ b/home-manager/modules/programs/offlineimap-accounts.nix @@ -0,0 +1,57 @@ +{ config, lib, ... }: + +with lib; + +let + + extraConfigType = with types; attrsOf (either (either str int) bool); + +in + +{ + options.offlineimap = { + enable = mkEnableOption "OfflineIMAP"; + + extraConfig.account = mkOption { + type = extraConfigType; + default = {}; + example = { + autorefresh = 20; + }; + description = '' + Extra configuration options to add to the account section. + ''; + }; + + extraConfig.local = mkOption { + type = extraConfigType; + default = {}; + example = { + sync_deletes = true; + }; + description = '' + Extra configuration options to add to the local account + section. + ''; + }; + + extraConfig.remote = mkOption { + type = extraConfigType; + default = {}; + example = { + maxconnections = 2; + expunge = false; + }; + description = '' + Extra configuration options to add to the remote account + section. + ''; + }; + + postSyncHookCommand = mkOption { + type = types.lines; + default = ""; + description = "Command to run after fetching new mails."; + }; + }; +} diff --git a/home-manager/modules/programs/offlineimap.nix b/home-manager/modules/programs/offlineimap.nix new file mode 100644 index 00000000000..82143b630ad --- /dev/null +++ b/home-manager/modules/programs/offlineimap.nix @@ -0,0 +1,207 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.offlineimap; + + accounts = filter (a: a.offlineimap.enable) + (attrValues config.accounts.email.accounts); + + toIni = generators.toINI { + mkKeyValue = key: value: + let + value' = + if isBool value then (if value then "yes" else "no") + else toString value; + in + "${key} = ${value'}"; + }; + + # Generates a script to fetch only a specific account. + # + # Note, these scripts are not actually created and installed at the + # moment. It will need some thinking on whether this is a good idea + # and whether other modules should have some similar functionality. + # + # Perhaps have a single tool `email` that wraps the command? + # Something like + # + # $ email <account name> <program name> <program args> + genOfflineImapScript = account: with account; + pkgs.writeShellScriptBin "offlineimap-${name}" '' + exec ${pkgs.offlineimap}/bin/offlineimap -a${account.name} "$@" + ''; + + accountStr = account: with account; + let + postSyncHook = optionalAttrs (offlineimap.postSyncHookCommand != "") { + postsynchook = + pkgs.writeShellScriptBin + "postsynchook" + offlineimap.postSyncHookCommand + + "/bin/postsynchook"; + }; + + localType = + if account.flavor == "gmail.com" + then "GmailMaildir" + else "Maildir"; + + remoteType = + if account.flavor == "gmail.com" + then "Gmail" + else "IMAP"; + + remoteHost = optionalAttrs (imap.host != null) { + remotehost = imap.host; + }; + + remotePort = optionalAttrs ((imap.port or null) != null) { + remoteport = imap.port; + }; + + ssl = + if imap.tls.enable + then + { + ssl = true; + sslcacertfile = imap.tls.certificatesFile; + starttls = imap.tls.useStartTls; + } + else + { + ssl = false; + }; + + remotePassEval = + let + arglist = concatMapStringsSep "," (x: "'${x}'") passwordCommand; + in + optionalAttrs (passwordCommand != null) { + remotepasseval = ''get_pass("${name}", [${arglist}])''; + }; + in + toIni { + "Account ${name}" = { + localrepository = "${name}-local"; + remoterepository = "${name}-remote"; + } + // postSyncHook + // offlineimap.extraConfig.account; + + "Repository ${name}-local" = { + type = localType; + localfolders = maildir.absPath; + } + // offlineimap.extraConfig.local; + + "Repository ${name}-remote" = { + type = remoteType; + remoteuser = userName; + } + // remoteHost + // remotePort + // remotePassEval + // ssl + // offlineimap.extraConfig.remote; + }; + + extraConfigType = with types; attrsOf (either (either str int) bool); + +in + +{ + options = { + programs.offlineimap = { + enable = mkEnableOption "OfflineIMAP"; + + pythonFile = mkOption { + type = types.lines; + default = '' + import subprocess + + def get_pass(service, cmd): + return subprocess.check_output(cmd, ) + ''; + description = '' + Python code that can then be used in other parts of the + configuration. + ''; + }; + + extraConfig.general = mkOption { + type = extraConfigType; + default = {}; + example = { + maxage = 30; + ui = "blinkenlights"; + }; + description = '' + Extra configuration options added to the + <option>general</option> section. + ''; + }; + + extraConfig.default = mkOption { + type = extraConfigType; + default = {}; + example = { + gmailtrashfolder = "[Gmail]/Papierkorb"; + }; + description = '' + Extra configuration options added to the + <option>DEFAULT</option> section. + ''; + }; + + extraConfig.mbnames = mkOption { + type = extraConfigType; + default = {}; + example = literalExample '' + { + filename = "~/.config/mutt/mailboxes"; + header = "'mailboxes '"; + peritem = "'+%(accountname)s/%(foldername)s'"; + sep = "' '"; + footer = "'\\n'"; + } + ''; + description = '' + Extra configuration options added to the + <code>mbnames</code> section. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.offlineimap ]; + + xdg.configFile."offlineimap/get_settings.py".text = cfg.pythonFile; + + xdg.configFile."offlineimap/config".text = + '' + # Generated by Home Manager. + # See https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf + # for an exhaustive list of options. + '' + + toIni ({ + general = { + accounts = concatMapStringsSep "," (a: a.name) accounts; + pythonfile = "${config.xdg.configHome}/offlineimap/get_settings.py"; + metadata = "${config.xdg.dataHome}/offlineimap"; + } + // cfg.extraConfig.general; + } + // optionalAttrs (cfg.extraConfig.mbnames != {}) { + mbnames = { enabled = true; } // cfg.extraConfig.mbnames; + } + // optionalAttrs (cfg.extraConfig.default != {}) { + DEFAULT = cfg.extraConfig.default; + }) + + "\n" + + concatStringsSep "\n" (map accountStr accounts); + }; +} diff --git a/home-manager/modules/programs/opam.nix b/home-manager/modules/programs/opam.nix new file mode 100644 index 00000000000..4de2e82da55 --- /dev/null +++ b/home-manager/modules/programs/opam.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.opam; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.opam = { + enable = mkEnableOption "Opam"; + + package = mkOption { + type = types.package; + default = pkgs.opam; + defaultText = literalExample "pkgs.opam"; + description = "Opam package to install."; + }; + + 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. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + eval "$(${cfg.package}/bin/opam env --shell=bash)" + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + eval "$(${cfg.package}/bin/opam env --shell=zsh)" + ''; + }; +} diff --git a/home-manager/modules/programs/pidgin.nix b/home-manager/modules/programs/pidgin.nix new file mode 100644 index 00000000000..8dcb2122172 --- /dev/null +++ b/home-manager/modules/programs/pidgin.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.pidgin; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.pidgin = { + enable = mkEnableOption "Pidgin messaging client"; + + package = mkOption { + type = types.package; + default = pkgs.pidgin; + defaultText = literalExample "pkgs.pidgin"; + description = "The Pidgin package to use."; + }; + + plugins = mkOption { + default = []; + example = literalExample "[ pkgs.pidgin-otr pkgs.pidgin-osd ]"; + description = "Plugins that should be available to Pidgin."; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ (cfg.package.override { inherit (cfg) plugins; }) ]; + }; +} diff --git a/home-manager/modules/programs/rofi.nix b/home-manager/modules/programs/rofi.nix new file mode 100644 index 00000000000..e64e5d4782e --- /dev/null +++ b/home-manager/modules/programs/rofi.nix @@ -0,0 +1,339 @@ +{ config, lib, pkgs, ... }: + +with lib; +with builtins; + +let + + cfg = config.programs.rofi; + + colorOption = description: mkOption { + type = types.str; + description = description; + }; + + rowColorSubmodule = types.submodule { + options = { + background = colorOption "Background color"; + foreground = colorOption "Foreground color"; + backgroundAlt = colorOption "Alternative background color"; + highlight = mkOption { + type = types.submodule { + options = { + background = colorOption "Highlight background color"; + foreground = colorOption "Highlight foreground color"; + }; + }; + description = "Color settings for highlighted row."; + }; + }; + }; + + windowColorSubmodule = types.submodule { + options = { + background = colorOption "Window background color"; + border = colorOption "Window border color"; + separator = colorOption "Separator color"; + }; + }; + + colorsSubmodule = types.submodule { + options = { + window = mkOption { + default = null; + type = windowColorSubmodule; + description = "Window color settings."; + }; + rows = mkOption { + default = null; + type = types.submodule { + options = { + normal = mkOption { + default = null; + type = types.nullOr rowColorSubmodule; + description = "Normal row color settings."; + }; + active = mkOption { + default = null; + type = types.nullOr rowColorSubmodule; + description = "Active row color settings."; + }; + urgent = mkOption { + default = null; + type = types.nullOr rowColorSubmodule; + description = "Urgent row color settings."; + }; + }; + }; + description = "Rows color settings."; + }; + }; + }; + + valueToString = value: + if isBool value + then (if value then "true" else "else") + else toString value; + + windowColorsToString = window: concatStringsSep ", " (with window; [ + background + border + separator + ]); + + rowsColorsToString = rows: '' + ${optionalString + (rows.normal != null) + (setOption "color-normal" (rowColorsToString rows.normal))} + ${optionalString + (rows.active != null) + (setOption "color-active" (rowColorsToString rows.active))} + ${optionalString + (rows.urgent != null) + (setOption "color-urgent" (rowColorsToString rows.urgent))} + ''; + + rowColorsToString = row: concatStringsSep ", " (with row; [ + background + foreground + backgroundAlt + highlight.background + highlight.foreground + ]); + + setOption = name: value: + optionalString (value != null) "rofi.${name}: ${valueToString value}"; + + setColorScheme = colors: optionalString (colors != null) '' + ${optionalString + (colors.window != null) + setOption "color-window" (windowColorsToString colors.window)} + ${optionalString + (colors.rows != null) + (rowsColorsToString colors.rows)} + ''; + + locationsMap = { + center = 0; + top-left = 1; + top = 2; + top-right = 3; + right = 4; + bottom-right = 5; + bottom = 6; + bottom-left = 7; + left = 8; + }; + + themeName = + if (cfg.theme == null) then null + else if (lib.isString cfg.theme) then cfg.theme + else lib.removeSuffix ".rasi" (baseNameOf cfg.theme); + + themePath = if (lib.isString cfg.theme) then null else cfg.theme; + +in + +{ + options.programs.rofi = { + enable = mkEnableOption "Rofi: A window switcher, application launcher and dmenu replacement"; + + width = mkOption { + default = null; + type = types.nullOr types.int; + description = "Window width"; + example = 100; + }; + + lines = mkOption { + default = null; + type = types.nullOr types.int; + description = "Number of lines"; + example = 10; + }; + + borderWidth = mkOption { + default = null; + type = types.nullOr types.int; + description = "Border width"; + example = 1; + }; + + rowHeight = mkOption { + default = null; + type = types.nullOr types.int; + description = "Row height (in chars)"; + example = 1; + }; + + padding = mkOption { + default = null; + type = types.nullOr types.int; + description = "Padding"; + example = 400; + }; + + font = mkOption { + default = null; + type = types.nullOr types.str; + example = "Droid Sans Mono 14"; + description = "Font to use."; + }; + + scrollbar = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Whether to show a scrollbar."; + }; + + terminal = mkOption { + default = null; + type = types.nullOr types.str; + description = '' + Path to the terminal which will be used to run console applications + ''; + example = "\${pkgs.gnome3.gnome_terminal}/bin/gnome-terminal"; + }; + + separator = mkOption { + default = null; + type = types.nullOr (types.enum [ "none" "dash" "solid" ]); + description = "Separator style"; + example = "solid"; + }; + + cycle = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Whether to cycle through the results list."; + }; + + fullscreen = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Whether to run rofi fullscreen."; + }; + + location = mkOption { + default = "center"; + type = types.enum (builtins.attrNames locationsMap); + description = "The location rofi appears on the screen."; + }; + + xoffset = mkOption { + default = 0; + type = types.int; + description = '' + Offset in the x-axis in pixels relative to the chosen location. + ''; + }; + + yoffset = mkOption { + default = 0; + type = types.int; + description = '' + Offset in the y-axis in pixels relative to the chosen location. + ''; + }; + + colors = mkOption { + default = null; + type = types.nullOr colorsSubmodule; + description = '' + Color scheme settings. Colors can be specified in CSS color + formats. This option may become deprecated in the future and + therefore the <varname>programs.rofi.theme</varname> option + should be used whenever possible. + ''; + example = literalExample '' + colors = { + window = { + background = "argb:583a4c54"; + border = "argb:582a373e"; + separator = "#c3c6c8"; + }; + + rows = { + normal = { + background = "argb:58455a64"; + foreground = "#fafbfc"; + backgroundAlt = "argb:58455a64"; + highlight = { + background = "#00bcd4"; + foreground = "#fafbfc"; + }; + }; + }; + }; + ''; + }; + + theme = mkOption { + default = null; + type = with types; nullOr (either str path); + example = "Arc"; + description = '' + Name of theme or path to theme file in rasi format. Available + named themes can be viewed using the + <command>rofi-theme-selector</command> tool. + ''; + }; + + configPath = mkOption { + default = "${config.xdg.configHome}/rofi/config"; + defaultText = "$XDG_CONFIG_HOME/rofi/config"; + type = types.str; + description = "Path where to put generated configuration file."; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = "Additional configuration to add."; + }; + + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.theme == null || cfg.colors == null; + message = '' + Cannot use the rofi options 'theme' and 'colors' simultaneously. + ''; + } + ]; + + home.packages = [ pkgs.rofi ]; + + home.file."${cfg.configPath}".text = '' + ${setOption "width" cfg.width} + ${setOption "lines" cfg.lines} + ${setOption "font" cfg.font} + ${setOption "bw" cfg.borderWidth} + ${setOption "eh" cfg.rowHeight} + ${setOption "padding" cfg.padding} + ${setOption "separator-style" cfg.separator} + ${setOption "hide-scrollbar" ( + if (cfg.scrollbar != null) + then (! cfg.scrollbar) + else cfg.scrollbar + )} + ${setOption "terminal" cfg.terminal} + ${setOption "cycle" cfg.cycle} + ${setOption "fullscreen" cfg.fullscreen} + ${setOption "location" (builtins.getAttr cfg.location locationsMap)} + ${setOption "xoffset" cfg.xoffset} + ${setOption "yoffset" cfg.yoffset} + + ${setColorScheme cfg.colors} + ${setOption "theme" themeName} + + ${cfg.extraConfig} + ''; + + xdg.dataFile = mkIf (themePath != null) { + "rofi/themes/${themeName}.rasi".source = themePath; + }; + }; +} diff --git a/home-manager/modules/programs/rtorrent.nix b/home-manager/modules/programs/rtorrent.nix new file mode 100644 index 00000000000..6300969a519 --- /dev/null +++ b/home-manager/modules/programs/rtorrent.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.rtorrent; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.rtorrent = { + enable = mkEnableOption "rTorrent"; + + settings = mkOption { + type = types.lines; + default = ""; + description = '' + Configuration written to + <filename>~/.config/rtorrent/rtorrent.rc</filename>. See + <link xlink:href="https://github.com/rakshasa/rtorrent/wiki/Config-Guide" /> + for explanation about possible values. + ''; + }; + + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.rtorrent ]; + + xdg.configFile."rtorrent/rtorrent.rc" = mkIf (cfg.settings != "") { + text = cfg.settings; + }; + }; +} diff --git a/home-manager/modules/programs/skim.nix b/home-manager/modules/programs/skim.nix new file mode 100644 index 00000000000..de1bff30fce --- /dev/null +++ b/home-manager/modules/programs/skim.nix @@ -0,0 +1,128 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.skim; + +in + +{ + options.programs.skim = { + enable = mkEnableOption "skim - a command-line fuzzy finder"; + + defaultCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type f"; + description = '' + The command that gets executed as the default source for skim + when running. + ''; + }; + + defaultOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--height 40%" "--prompt ⟫" ]; + description = '' + Extra command line options given to skim by default. + ''; + }; + + fileWidgetCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type f"; + description = '' + The command that gets executed as the source for skim for the + CTRL-T keybinding. + ''; + }; + + fileWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--preview 'head {}'" ]; + description = '' + Command line options for the CTRL-T keybinding. + ''; + }; + + changeDirWidgetCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "fd --type d" ; + description = '' + The command that gets executed as the source for skim for the + ALT-C keybinding. + ''; + }; + + changeDirWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--preview 'tree -C {} | head -200'" ]; + description = '' + Command line options for the ALT-C keybinding. + ''; + }; + + historyWidgetOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--tac" "--exact" ]; + description = '' + Command line options for the CTRL-R keybinding. + ''; + }; + + 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. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.skim ]; + + home.sessionVariables = + mapAttrs (n: v: toString v) ( + filterAttrs (n: v: v != [] && v != null) { + SKIM_ALT_C_COMMAND = cfg.changeDirWidgetCommand; + SKIM_ALT_C_OPTS = cfg.changeDirWidgetOptions; + SKIM_CTRL_R_OPTS = cfg.historyWidgetOptions; + SKIM_CTRL_T_COMMAND = cfg.fileWidgetCommand; + SKIM_CTRL_T_OPTS = cfg.fileWidgetOptions; + SKIM_DEFAULT_COMMAND = cfg.defaultCommand; + SKIM_DEFAULT_OPTIONS = cfg.defaultOptions; + } + ); + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then + . ${pkgs.skim}/share/skim/completion.bash + . ${pkgs.skim}/share/skim/key-bindings.bash + fi + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + if [[ $options[zle] = on ]]; then + . ${pkgs.skim}/share/skim/completion.zsh + . ${pkgs.skim}/share/skim/key-bindings.zsh + fi + ''; + }; +} diff --git a/home-manager/modules/programs/ssh.nix b/home-manager/modules/programs/ssh.nix new file mode 100644 index 00000000000..ab61c0dcbc4 --- /dev/null +++ b/home-manager/modules/programs/ssh.nix @@ -0,0 +1,457 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.ssh; + + isPath = x: builtins.substring 0 1 (toString x) == "/"; + + addressPort = entry: + if isPath entry.address + then " ${entry.address}" + else " [${entry.address}]:${toString entry.port}"; + + yn = flag: if flag then "yes" else "no"; + + unwords = builtins.concatStringsSep " "; + + bindOptions = { + address = mkOption { + type = types.str; + default = "localhost"; + example = "example.org"; + description = "The address where to bind the port."; + }; + + port = mkOption { + type = types.port; + example = 8080; + description = "Specifies port number to bind on bind address."; + }; + }; + + dynamicForwardModule = types.submodule { + options = bindOptions; + }; + + forwardModule = types.submodule { + options = { + bind = bindOptions; + + host = { + address = mkOption { + type = types.str; + example = "example.org"; + description = "The address where to forward the traffic to."; + }; + + port = mkOption { + type = types.port; + example = 80; + description = "Specifies port number to forward the traffic to."; + }; + }; + }; + }; + + matchBlockModule = types.submodule ({ name, ... }: { + options = { + host = mkOption { + type = types.str; + example = "*.example.org"; + description = '' + The host pattern used by this conditional block. + ''; + }; + + port = mkOption { + type = types.nullOr types.port; + default = null; + description = "Specifies port number to connect on remote host."; + }; + + forwardAgent = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Whether the connection to the authentication agent (if any) + will be forwarded to the remote machine. + ''; + }; + + forwardX11 = mkOption { + type = types.bool; + default = false; + description = '' + Specifies whether X11 connections will be automatically redirected + over the secure channel and <envar>DISPLAY</envar> set. + ''; + }; + + forwardX11Trusted = mkOption { + type = types.bool; + default = false; + description = '' + Specifies whether remote X11 clients will have full access to the + original X11 display. + ''; + }; + + identitiesOnly = mkOption { + type = types.bool; + default = false; + description = '' + Specifies that ssh should only use the authentication + identity explicitly configured in the + <filename>~/.ssh/config</filename> files or passed on the + ssh command-line, even if <command>ssh-agent</command> + offers more identities. + ''; + }; + + identityFile = mkOption { + type = with types; either (listOf str) (nullOr str); + default = []; + apply = p: + if p == null then [] + else if isString p then [p] + else p; + description = '' + Specifies files from which the user identity is read. + Identities will be tried in the given order. + ''; + }; + + user = mkOption { + type = types.nullOr types.str; + default = null; + description = "Specifies the user to log in as."; + }; + + hostname = mkOption { + type = types.nullOr types.str; + default = null; + description = "Specifies the real host name to log into."; + }; + + serverAliveInterval = mkOption { + type = types.int; + default = 0; + description = + "Set timeout in seconds after which response will be requested."; + }; + + sendEnv = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Environment variables to send from the local host to the + server. + ''; + }; + + compression = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Specifies whether to use compression. Omitted from the host + block when <literal>null</literal>. + ''; + }; + + checkHostIP = mkOption { + type = types.bool; + default = true; + description = '' + Check the host IP address in the + <filename>known_hosts</filename> file. + ''; + }; + + proxyCommand = mkOption { + type = types.nullOr types.str; + default = null; + description = "The command to use to connect to the server."; + }; + + proxyJump = mkOption { + type = types.nullOr types.str; + default = null; + description = "The proxy host to use to connect to the server."; + }; + + certificateFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Specifies a file from which the user certificate is read. + ''; + }; + + addressFamily = mkOption { + default = null; + type = types.nullOr (types.enum ["any" "inet" "inet6"]); + description = '' + Specifies which address family to use when connecting. + ''; + }; + + localForwards = mkOption { + type = types.listOf forwardModule; + default = []; + example = literalExample '' + [ + { + bind.port = 8080; + host.address = "10.0.0.13"; + host.port = 80; + } + ]; + ''; + description = '' + Specify local port forwardings. See + <citerefentry> + <refentrytitle>ssh_config</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> for <literal>LocalForward</literal>. + ''; + }; + + remoteForwards = mkOption { + type = types.listOf forwardModule; + default = []; + example = literalExample '' + [ + { + bind.port = 8080; + host.address = "10.0.0.13"; + host.port = 80; + } + ]; + ''; + description = '' + Specify remote port forwardings. See + <citerefentry> + <refentrytitle>ssh_config</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> for <literal>RemoteForward</literal>. + ''; + }; + + dynamicForwards = mkOption { + type = types.listOf dynamicForwardModule; + default = []; + example = literalExample '' + [ { port = 8080; } ]; + ''; + description = '' + Specify dynamic port forwardings. See + <citerefentry> + <refentrytitle>ssh_config</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> for <literal>DynamicForward</literal>. + ''; + }; + + extraOptions = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Extra configuration options for the host."; + }; + }; + + config.host = mkDefault name; + }); + + matchBlockStr = cf: concatStringsSep "\n" ( + ["Host ${cf.host}"] + ++ optional (cf.port != null) " Port ${toString cf.port}" + ++ optional (cf.forwardAgent != null) " ForwardAgent ${yn cf.forwardAgent}" + ++ optional cf.forwardX11 " ForwardX11 yes" + ++ optional cf.forwardX11Trusted " ForwardX11Trusted yes" + ++ optional cf.identitiesOnly " IdentitiesOnly yes" + ++ optional (cf.user != null) " User ${cf.user}" + ++ optional (cf.certificateFile != null) " CertificateFile ${cf.certificateFile}" + ++ optional (cf.hostname != null) " HostName ${cf.hostname}" + ++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}" + ++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}" + ++ optional (cf.serverAliveInterval != 0) + " ServerAliveInterval ${toString cf.serverAliveInterval}" + ++ optional (cf.compression != null) " Compression ${yn cf.compression}" + ++ optional (!cf.checkHostIP) " CheckHostIP no" + ++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}" + ++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}" + ++ map (file: " IdentityFile ${file}") cf.identityFile + ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards + ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards + ++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards + ++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions + ); + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options.programs.ssh = { + enable = mkEnableOption "SSH client configuration"; + + forwardAgent = mkOption { + default = false; + type = types.bool; + description = '' + Whether the connection to the authentication agent (if any) + will be forwarded to the remote machine. + ''; + }; + + compression = mkOption { + default = false; + type = types.bool; + description = "Specifies whether to use compression."; + }; + + serverAliveInterval = mkOption { + type = types.int; + default = 0; + description = '' + Set default timeout in seconds after which response will be requested. + ''; + }; + + hashKnownHosts = mkOption { + default = false; + type = types.bool; + description = '' + Indicates that + <citerefentry> + <refentrytitle>ssh</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry> + should hash host names and addresses when they are added to + the known hosts file. + ''; + }; + + userKnownHostsFile = mkOption { + type = types.str; + default = "~/.ssh/known_hosts"; + description = '' + Specifies one or more files to use for the user host key + database, separated by whitespace. The default is + <filename>~/.ssh/known_hosts</filename>. + ''; + }; + + controlMaster = mkOption { + default = "no"; + type = types.enum ["yes" "no" "ask" "auto" "autoask"]; + description = '' + Configure sharing of multiple sessions over a single network connection. + ''; + }; + + controlPath = mkOption { + type = types.str; + default = "~/.ssh/master-%r@%n:%p"; + description = '' + Specify path to the control socket used for connection sharing. + ''; + }; + + controlPersist = mkOption { + type = types.str; + default = "no"; + example = "10m"; + description = '' + Whether control socket should remain open in the background. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration. + ''; + }; + + extraOptionOverrides = mkOption { + type = types.attrsOf types.str; + default = {}; + description = '' + Extra SSH configuration options that take precedence over any + host specific configuration. + ''; + }; + + matchBlocks = mkOption { + type = types.loaOf matchBlockModule; + default = {}; + example = literalExample '' + { + "john.example.com" = { + hostname = "example.com"; + user = "john"; + }; + foo = { + hostname = "example.com"; + identityFile = "/home/john/.ssh/foo_rsa"; + }; + }; + ''; + description = '' + Specify per-host settings. Note, if the order of rules matter + then this must be a list. See + <citerefentry> + <refentrytitle>ssh_config</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry>. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = + let + # `builtins.any`/`lib.lists.any` does not return `true` if there are no elements. + any' = pred: items: if items == [] then true else any pred items; + # Check that if `entry.address` is defined, and is a path, that `entry.port` has not + # been defined. + noPathWithPort = entry: entry ? address && isPath entry.address -> !(entry ? port); + checkDynamic = block: any' noPathWithPort block.dynamicForwards; + checkBindAndHost = fwd: noPathWithPort fwd.bind && noPathWithPort fwd.host; + 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); + message = "Forwarded paths cannot have ports."; + } + ]; + + home.file.".ssh/config".text = '' + ${concatStringsSep "\n" ( + mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)} + + ${concatStringsSep "\n\n" ( + map matchBlockStr ( + builtins.attrValues cfg.matchBlocks))} + + Host * + ForwardAgent ${yn cfg.forwardAgent} + Compression ${yn cfg.compression} + ServerAliveInterval ${toString cfg.serverAliveInterval} + HashKnownHosts ${yn cfg.hashKnownHosts} + UserKnownHostsFile ${cfg.userKnownHostsFile} + ControlMaster ${cfg.controlMaster} + ControlPath ${cfg.controlPath} + ControlPersist ${cfg.controlPersist} + + ${replaceStrings ["\n"] ["\n "] cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/starship.nix b/home-manager/modules/programs/starship.nix new file mode 100644 index 00000000000..81793c7a6f6 --- /dev/null +++ b/home-manager/modules/programs/starship.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.starship; + + 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.marsam ]; + + options.programs.starship = { + enable = mkEnableOption "starship"; + + settings = mkOption { + type = types.attrs; + default = {}; + description = '' + Configuration written to + <filename>~/.config/starship.toml</filename>. + </para><para> + See <link xlink:href="https://starship.rs/config/" /> for the full list + of options. + ''; + }; + + 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 = [ pkgs.starship ]; + + xdg.configFile."starship.toml" = mkIf (cfg.settings != {}) { + source = configFile cfg.settings; + }; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + if [[ -z $INSIDE_EMACS ]]; then + eval "$(${pkgs.starship}/bin/starship init bash)" + fi + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + if [ -z "$INSIDE_EMACS" ]; then + eval "$(${pkgs.starship}/bin/starship init zsh)" + fi + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + if test -z "$INSIDE_EMACS" + eval (${pkgs.starship}/bin/starship init fish) + end + ''; + }; +} diff --git a/home-manager/modules/programs/taskwarrior.nix b/home-manager/modules/programs/taskwarrior.nix new file mode 100644 index 00000000000..eeacc77da29 --- /dev/null +++ b/home-manager/modules/programs/taskwarrior.nix @@ -0,0 +1,112 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.taskwarrior; + + themePath = theme: "${pkgs.taskwarrior}/share/doc/task/rc/${theme}.theme"; + + includeTheme = location: + if location == null then "" + else if isString location then "include ${themePath location}" + else "include ${location}"; + + formatValue = value: + if isBool value then if value then "true" else "false" + else if isList value then concatMapStringsSep "," formatValue value + else toString value; + + formatLine = key: value: + "${key}=${formatValue value}"; + + formatSet = key: values: + (concatStringsSep "\n" + (mapAttrsToList + (subKey: subValue: formatPair "${key}.${subKey}" subValue) + values)); + + formatPair = key: value: + if isAttrs value then formatSet key value + else formatLine key value; + +in + +{ + options = { + programs.taskwarrior = { + enable = mkEnableOption "Task Warrior"; + + config = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + confirmation = false; + report.minimal.filter = "status:pending"; + report.active.columns = [ "id" "start" "entry.age" "priority" "project" "due" "description" ]; + report.active.labels = [ "ID" "Started" "Age" "Priority" "Project" "Due" "Description" ]; + taskd = { + certificate = "/path/to/cert"; + key = "/path/to/key"; + ca = "/path/to/ca"; + server = "host.domain:53589"; + credentials = "Org/First Last/cf31f287-ee9e-43a8-843e-e8bbd5de4294"; + }; + } + ''; + description = '' + Key-value configuration written to + <filename>~/.taskrc</filename>. + ''; + }; + + dataLocation = mkOption { + type = types.str; + default = "${config.xdg.dataHome}/task"; + defaultText = "$XDG_DATA_HOME/task"; + description = '' + Location where Task Warrior will store its data. + </para><para> + Home Manager will attempt to create this directory. + ''; + }; + + colorTheme = mkOption { + type = with types; nullOr (either str path); + default = null; + example = "dark-blue-256"; + description = '' + Either one of the default provided theme as string, or a + path to a theme configuration file. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional content written at the end of + <filename>~/.taskrc</filename>. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.taskwarrior ]; + + home.file."${cfg.dataLocation}/.keep".text = ""; + + home.file.".taskrc".text = '' + data.location=${cfg.dataLocation} + ${includeTheme cfg.colorTheme} + + ${concatStringsSep "\n" ( + mapAttrsToList formatPair cfg.config)} + + ${cfg.extraConfig} + ''; + }; +} diff --git a/home-manager/modules/programs/termite.nix b/home-manager/modules/programs/termite.nix new file mode 100644 index 00000000000..6eab39edb3a --- /dev/null +++ b/home-manager/modules/programs/termite.nix @@ -0,0 +1,378 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.termite; + + vteInitStr = '' + # See https://github.com/thestinger/termite#id1 + if [[ $TERM == xterm-termite ]]; then + . ${pkgs.gnome3.vte-ng}/etc/profile.d/vte.sh + fi + ''; + +in + +{ + options = { + programs.termite = { + enable = mkEnableOption "Termite VTE-based terminal"; + + allowBold = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Allow the output of bold characters when the bold escape sequence appears. + ''; + }; + + audibleBell = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Have the terminal beep on the terminal bell."; + }; + + clickableUrl = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Auto-detected URLs can be clicked on to open them in your browser. + Only enabled if a browser is configured or detected. + ''; + }; + + dynamicTitle = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Settings dynamic title allows the terminal and the shell to + update the terminal's title. + ''; + }; + + fullscreen = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Enables entering fullscreen mode by pressing F11."; + }; + + mouseAutohide = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Automatically hide the mouse pointer when you start typing."; + }; + + scrollOnOutput = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Scroll to the bottom when the shell generates output."; + }; + + scrollOnKeystroke = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Scroll to the bottom automatically when a key is pressed."; + }; + + searchWrap = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Search from top again when you hit the bottom."; + }; + + urgentOnBell = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Sets the window as urgent on the terminal bell."; + }; + + font = mkOption { + default = null; + example = "Monospace 12"; + type = types.nullOr types.str; + description = "The font description for the terminal's font."; + }; + + geometry = mkOption { + default = null; + example = "640x480"; + type = types.nullOr types.str; + description = "The default window geometry for new terminal windows."; + }; + + iconName = mkOption { + default = null; + example = "terminal"; + type = types.nullOr types.str; + description = "The name of the icon to be used for the terminal process."; + }; + + scrollbackLines = mkOption { + default = null; + example = 10000; + type = types.nullOr types.int; + description = "Set the number of lines to limit the terminal's scrollback."; + }; + + browser = mkOption { + default = null; + type = types.nullOr types.str; + example = "${pkgs.xdg_utils}/xdg-open"; + description = '' + Set the default browser for opening links. If its not set, $BROWSER is read. + If that's not set, url hints will be disabled. + ''; + }; + + cursorBlink = mkOption { + default = null; + example = "system"; + type = types.nullOr (types.enum [ "system" "on" "off" ]); + description = '' + Specify the how the terminal's cursor should behave. + Accepts system to respect the gtk global configuration, + on and off to explicitly enable or disable them. + ''; + }; + + cursorShape = mkOption { + default = null; + example = "block"; + type = types.nullOr (types.enum [ "block" "underline" "ibeam" ]); + description = '' + Specify how the cursor should look. Accepts block, ibeam and underline. + ''; + }; + + filterUnmatchedUrls = mkOption { + default = null; + type = types.nullOr types.bool; + description = "Whether to hide url hints not matching input in url hints mode."; + }; + + modifyOtherKeys = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Emit escape sequences for extra keys, + like the modifyOtherKeys resource for + <citerefentry> + <refentrytitle>xterm</refentrytitle> + <manvolnum>1</manvolnum> + </citerefentry>. + ''; + }; + + sizeHints = mkOption { + default = null; + type = types.nullOr types.bool; + description = '' + Enable size hints. Locks the terminal resizing + to increments of the terminal's cell size. + Requires a window manager that respects scroll hints. + ''; + }; + + scrollbar = mkOption { + default = null; + type = types.nullOr (types.enum [ "off" "left" "right" ]); + description = "Scroll to the bottom when the shell generates output."; + }; + + backgroundColor = mkOption { + default = null; + example = "rgba(63, 63, 63, 0.8)"; + type = types.nullOr types.str; + description = "Background color value."; + }; + + cursorColor = mkOption { + default = null; + example = "#dcdccc"; + type = types.nullOr types.str; + description = "Cursor color value."; + }; + + cursorForegroundColor = mkOption { + default = null; + example = "#dcdccc"; + type = types.nullOr types.str; + description = "Cursor foreground color value."; + }; + + foregroundColor = mkOption { + default = null; + example = "#dcdccc"; + type = types.nullOr types.str; + description = "Foreground color value."; + }; + + foregroundBoldColor = mkOption { + default = null; + example = "#ffffff"; + type = types.nullOr types.str; + description = "Foreground bold color value."; + }; + + highlightColor = mkOption { + default = null; + example = "#2f2f2f"; + type = types.nullOr types.str; + description = "highlight color value."; + }; + + hintsActiveBackgroundColor = mkOption { + default = null; + example = "#3f3f3f"; + type = types.nullOr types.str; + description = "Hints active background color value."; + }; + + hintsActiveForegroundColor = mkOption { + default = null; + example = "#e68080"; + type = types.nullOr types.str; + description = "Hints active foreground color value."; + }; + + hintsBackgroundColor = mkOption { + default = null; + example = "#3f3f3f"; + type = types.nullOr types.str; + description = "Hints background color value."; + }; + + hintsForegroundColor = mkOption { + default = null; + example = "#dcdccc"; + type = types.nullOr types.str; + description = "Hints foreground color value."; + }; + + hintsBorderColor = mkOption { + default = null; + example = "#3f3f3f"; + type = types.nullOr types.str; + description = "Hints border color value."; + }; + + hintsBorderWidth = mkOption { + default = null; + example = "0.5"; + type = types.nullOr types.str; + description = "Hints border width."; + }; + + hintsFont = mkOption { + default = null; + example = "Monospace 12"; + type = types.nullOr types.str; + description = "The font description for the hints font."; + }; + + hintsPadding = mkOption { + default = null; + example = 2; + type = types.nullOr types.int; + description = "Hints padding."; + }; + + hintsRoundness = mkOption { + default = null; + example = "0.2"; + type = types.nullOr types.str; + description = "Hints roundness."; + }; + + optionsExtra = mkOption { + default = ""; + example = "fullscreen = true"; + type = types.lines; + description = "Extra options that should be added to [options] section."; + }; + + colorsExtra = mkOption { + default = ""; + example = '' + color0 = #3f3f3f + color1 = #705050 + color2 = #60b48a + ''; + type = types.lines; + description = "Extra colors options that should be added to [colors] section."; + }; + + hintsExtra = mkOption { + default = ""; + example = "border = #3f3f3f"; + type = types.lines; + description = "Extra hints options that should be added to [hints] section."; + }; + }; + }; + + config = ( + let + boolToString = v: if v then "true" else "false"; + optionalBoolean = name: val: lib.optionalString (val != null) "${name} = ${boolToString val}"; + optionalInteger = name: val: lib.optionalString (val != null) "${name} = ${toString val}"; + optionalString = name: val: lib.optionalString (val != null) "${name} = ${val}"; + in mkIf cfg.enable { + home.packages = [ pkgs.termite ]; + xdg.configFile."termite/config".text = '' + [options] + ${optionalBoolean "allow_bold" cfg.allowBold} + ${optionalBoolean "audible_bell" cfg.audibleBell} + ${optionalString "browser" cfg.browser} + ${optionalBoolean "clickable_url" cfg.clickableUrl} + ${optionalString "cursor_blink" cfg.cursorBlink} + ${optionalString "cursor_shape" cfg.cursorShape} + ${optionalBoolean "dynamic_title" cfg.dynamicTitle} + ${optionalBoolean "filter_unmatched_urls" cfg.filterUnmatchedUrls} + ${optionalString "font" cfg.font} + ${optionalBoolean "fullscreen" cfg.fullscreen} + ${optionalString "geometry" cfg.geometry} + ${optionalString "icon_name" cfg.iconName} + ${optionalBoolean "modify_other_keys" cfg.modifyOtherKeys} + ${optionalBoolean "mouse_autohide" cfg.mouseAutohide} + ${optionalBoolean "scroll_on_keystroke" cfg.scrollOnKeystroke} + ${optionalBoolean "scroll_on_output" cfg.scrollOnOutput} + ${optionalInteger "scrollback_lines" cfg.scrollbackLines} + ${optionalString "scrollbar" cfg.scrollbar} + ${optionalBoolean "search_wrap" cfg.searchWrap} + ${optionalBoolean "size_hints" cfg.sizeHints} + ${optionalBoolean "urgent_on_bell" cfg.urgentOnBell} + + ${cfg.optionsExtra} + + [colors] + ${optionalString "background" cfg.backgroundColor} + ${optionalString "cursor" cfg.cursorColor} + ${optionalString "cursor_foreground" cfg.cursorForegroundColor} + ${optionalString "foreground" cfg.foregroundColor} + ${optionalString "foregroundBold" cfg.foregroundBoldColor} + ${optionalString "highlight" cfg.highlightColor} + + ${cfg.colorsExtra} + + [hints] + ${optionalString "active_background" cfg.hintsActiveBackgroundColor} + ${optionalString "active_foreground" cfg.hintsActiveForegroundColor} + ${optionalString "background" cfg.hintsBackgroundColor} + ${optionalString "border" cfg.hintsBorderColor} + ${optionalInteger "border_width" cfg.hintsBorderWidth} + ${optionalString "font" cfg.hintsFont} + ${optionalString "foreground" cfg.hintsForegroundColor} + ${optionalInteger "padding" cfg.hintsPadding} + ${optionalInteger "roundness" cfg.hintsRoundness} + + ${cfg.hintsExtra} + ''; + + programs.bash.initExtra = vteInitStr; + programs.zsh.initExtra = vteInitStr; + } + ); +} diff --git a/home-manager/modules/programs/texlive.nix b/home-manager/modules/programs/texlive.nix new file mode 100644 index 00000000000..0f8953e9f91 --- /dev/null +++ b/home-manager/modules/programs/texlive.nix @@ -0,0 +1,50 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.texlive; + + texlivePkgs = cfg.extraPackages pkgs.texlive; + +in + +{ + meta.maintainers = [ maintainers.rycee ]; + + options = { + programs.texlive = { + enable = mkEnableOption "Texlive"; + + extraPackages = mkOption { + default = tpkgs: { inherit (tpkgs) collection-basic; }; + defaultText = "tpkgs: { inherit (tpkgs) collection-basic; }"; + example = literalExample '' + tpkgs: { inherit (tpkgs) collection-fontsrecommended algorithms; } + ''; + description = "Extra packages available to Texlive."; + }; + + package = mkOption { + type = types.package; + description = "Resulting customized Texlive package."; + readOnly = true; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = texlivePkgs != {}; + message = "Must provide at least one extra package in" + + " 'programs.texlive.extraPackages'."; + } + ]; + + home.packages = [ cfg.package ]; + + programs.texlive.package = pkgs.texlive.combine texlivePkgs; + }; +} diff --git a/home-manager/modules/programs/tmux.nix b/home-manager/modules/programs/tmux.nix new file mode 100644 index 00000000000..766bc6238ba --- /dev/null +++ b/home-manager/modules/programs/tmux.nix @@ -0,0 +1,320 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.tmux; + + pluginName = p: if types.package.check p then p.name else p.plugin.name; + + pluginModule = types.submodule { + options = { + plugin = mkOption { + type = types.package; + description = "Path of the configuration file to include."; + }; + + extraConfig = mkOption { + type = types.lines; + description = "Additional configuration for the associated plugin."; + default = ""; + }; + }; + }; + + defaultKeyMode = "emacs"; + defaultResize = 5; + defaultShortcut = "b"; + defaultTerminal = "screen"; + + boolToStr = value: if value then "on" else "off"; + + tmuxConf = '' + set -g default-terminal "${cfg.terminal}" + set -g base-index ${toString cfg.baseIndex} + setw -g pane-base-index ${toString cfg.baseIndex} + + ${optionalString cfg.newSession "new-session"} + + ${optionalString cfg.reverseSplit '' + bind v split-window -h + bind s split-window -v + ''} + + set -g status-keys ${cfg.keyMode} + set -g mode-keys ${cfg.keyMode} + + ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) '' + bind h select-pane -L + bind j select-pane -D + bind k select-pane -U + bind l select-pane -R + + bind -r H resize-pane -L ${toString cfg.resizeAmount} + bind -r J resize-pane -D ${toString cfg.resizeAmount} + bind -r K resize-pane -U ${toString cfg.resizeAmount} + bind -r L resize-pane -R ${toString cfg.resizeAmount} + ''} + + ${optionalString (cfg.shortcut != defaultShortcut) '' + # rebind main key: C-${cfg.shortcut} + unbind C-${defaultShortcut} + set -g prefix C-${cfg.shortcut} + bind ${cfg.shortcut} send-prefix + bind C-${cfg.shortcut} last-window + ''} + + ${optionalString cfg.disableConfirmationPrompt '' + bind-key & kill-window + bind-key x kill-pane + ''} + + setw -g aggressive-resize ${boolToStr cfg.aggressiveResize} + 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} + ''; + +in + +{ + options = { + programs.tmux = { + aggressiveResize = mkOption { + default = false; + type = types.bool; + description = '' + Resize the window to the size of the smallest session for + which it is the current window. + ''; + }; + + baseIndex = mkOption { + default = 0; + example = 1; + type = types.ints.unsigned; + description = "Base index for windows and panes."; + }; + + clock24 = mkOption { + default = false; + type = types.bool; + description = "Use 24 hour clock."; + }; + + customPaneNavigationAndResize = mkOption { + default = false; + type = types.bool; + description = '' + Override the hjkl and HJKL bindings for pane navigation and + resizing in VI mode. + ''; + }; + + disableConfirmationPrompt = mkOption { + default = false; + type = types.bool; + description = '' + Disable confirmation prompt before killing a pane or window + ''; + }; + + enable = mkEnableOption "tmux"; + + escapeTime = mkOption { + default = 500; + example = 0; + type = types.ints.unsigned; + description = '' + Time in milliseconds for which tmux waits after an escape is + input. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional configuration to add to + <filename>tmux.conf</filename>. + ''; + }; + + historyLimit = mkOption { + default = 2000; + example = 5000; + type = types.ints.positive; + description = "Maximum number of lines held in window history."; + }; + + keyMode = mkOption { + default = defaultKeyMode; + example = "vi"; + type = types.enum [ "emacs" "vi" ]; + description = "VI or Emacs style shortcuts."; + }; + + newSession = mkOption { + default = false; + type = types.bool; + description = '' + Automatically spawn a session if trying to attach and none + are running. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.tmux; + defaultText = literalExample "pkgs.tmux"; + example = literalExample "pkgs.tmux"; + description = "The tmux package to install"; + }; + + reverseSplit = mkOption { + default = false; + type = types.bool; + description = "Reverse the window split shortcuts."; + }; + + resizeAmount = mkOption { + default = defaultResize; + example = 10; + type = types.ints.positive; + description = "Number of lines/columns when resizing."; + }; + + sensibleOnTop = mkOption { + type = types.bool; + default = true; + description = '' + Run the sensible plugin at the top of the configuration. It + is possible to override the sensible settings using the + <option>programs.tmux.extraConfig</option> option. + ''; + }; + + shortcut = mkOption { + default = defaultShortcut; + example = "a"; + type = types.str; + description = '' + CTRL following by this key is used as the main shortcut. + ''; + }; + + terminal = mkOption { + default = defaultTerminal; + example = "screen-256color"; + type = types.str; + description = "Set the $TERM variable."; + }; + + secureSocket = mkOption { + default = true; + type = types.bool; + description = '' + Store tmux socket under <filename>/run</filename>, which is more + secure than <filename>/tmp</filename>, but as a downside it doesn't + survive user logout. + ''; + }; + + tmuxp.enable = mkEnableOption "tmuxp"; + + tmuxinator.enable = mkEnableOption "tmuxinator"; + + plugins = mkOption { + type = with types; + listOf (either package pluginModule) + // { description = "list of plugin packages or submodules"; }; + description = '' + List of tmux plugins to be included at the end of your tmux + configuration. The sensible plugin, however, is defaulted to + run at the top of your configuration. + ''; + default = [ ]; + example = literalExample '' + with pkgs; [ + tmuxPlugins.cpu + { + plugin = tmuxPlugins.resurrect; + extraConfig = "set -g @resurrect-strategy-nvim 'session'"; + } + { + plugin = tmuxPlugins.continuum; + extraConfig = ''' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '60' # minutes + '''; + } + ] + ''; + }; + }; + }; + + config = mkIf cfg.enable ( + 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)} + # ============================================= # + ''; + }) + ] + ); +} diff --git a/home-manager/modules/programs/urxvt.nix b/home-manager/modules/programs/urxvt.nix new file mode 100644 index 00000000000..6f4eb3ff7ba --- /dev/null +++ b/home-manager/modules/programs/urxvt.nix @@ -0,0 +1,156 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.urxvt; + +in + +{ + options.programs.urxvt = { + enable = mkEnableOption "rxvt-unicode terminal emulator"; + + package = mkOption { + type = types.package; + default = pkgs.rxvt_unicode; + defaultText = literalExample "pkgs.rxvt_unicode"; + description = "rxvt-unicode package to install."; + }; + + fonts = mkOption { + type = types.listOf types.str; + default = []; + description = "List of fonts to be used."; + example = [ "xft:Droid Sans Mono Nerd Font:size=9" ]; + }; + + keybindings = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Mapping of keybindings to actions"; + example = literalExample '' + { + "Shift-Control-C" = "eval:selection_to_clipboard"; + "Shift-Control-V" = "eval:paste_clipboard"; + } + ''; + }; + + iso14755 = mkOption { + type = types.bool; + default = true; + description = "ISO14755 support for viewing and entering unicode characters."; + }; + + scroll = { + bar = mkOption { + type = types.submodule { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether to enable the scrollbar"; + }; + + style = mkOption { + type = types.enum [ "rxvt" "plain" "next" "xterm" ]; + default = "plain"; + description = "Scrollbar style."; + }; + + align = mkOption { + type = types.enum [ "top" "bottom" "center" ]; + default = "center"; + description = "Scrollbar alignment."; + }; + + position = mkOption { + type = types.enum [ "left" "right" ]; + default = "right"; + description = "Scrollbar position."; + }; + + floating = mkOption { + type = types.bool; + default = true; + description = "Whether to display an rxvt scrollbar without a trough."; + }; + }; + }; + default = {}; + description = "Scrollbar settings."; + }; + + lines = mkOption { + type = types.ints.unsigned; + default = 10000; + description = "Number of lines to save in the scrollback buffer."; + }; + + keepPosition = mkOption { + type = types.bool; + default = true; + description = "Whether to keep a scroll position when TTY receives new lines."; + }; + + scrollOnKeystroke = mkOption { + type = types.bool; + default = true; + description = "Whether to scroll to bottom on keyboard input."; + }; + + scrollOnOutput = mkOption { + type = types.bool; + default = false; + description = "Whether to scroll to bottom on TTY output."; + }; + }; + + transparent = mkOption { + type = types.bool; + default = false; + description = "Whether to enable pseudo-transparency."; + }; + + shading = mkOption { + type = types.ints.between 0 200; + default = 100; + description = "Darken (0 .. 99) or lighten (101 .. 200) the transparent background."; + }; + + extraConfig = mkOption { + default = {}; + type = types.attrs; + description = "Additional configuration to add."; + example = { "shading" = 15; }; + }; + + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + xresources.properties = { + "URxvt.scrollBar" = cfg.scroll.bar.enable; + "URxvt.scrollstyle" = cfg.scroll.bar.style; + "URxvt.scrollBar_align" = cfg.scroll.bar.align; + "URxvt.scrollBar_right" = cfg.scroll.bar.position == "right"; + "URxvt.scrollBar_floating" = cfg.scroll.bar.floating; + "URxvt.saveLines" = cfg.scroll.lines; + "URxvt.scrollWithBuffer" = cfg.scroll.keepPosition; + "URxvt.scrollTtyKeypress" = cfg.scroll.scrollOnKeystroke; + "URxvt.scrollTtyOutput" = cfg.scroll.scrollOnOutput; + "URxvt.transparent" = cfg.transparent; + "URxvt.shading" = cfg.shading; + "URxvt.iso14755" = cfg.iso14755; + } // flip mapAttrs' cfg.keybindings (kb: action: + nameValuePair "URxvt.keysym.${kb}" action + ) // optionalAttrs (cfg.fonts != []) { + "URxvt.font" = concatStringsSep "," cfg.fonts; + } // flip mapAttrs' cfg.extraConfig (k: v: + nameValuePair "URxvt.${k}" v + ); + }; +} diff --git a/home-manager/modules/programs/vim.nix b/home-manager/modules/programs/vim.nix new file mode 100644 index 00000000000..a14a9562ac1 --- /dev/null +++ b/home-manager/modules/programs/vim.nix @@ -0,0 +1,191 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.vim; + defaultPlugins = [ pkgs.vimPlugins.sensible ]; + + knownSettings = { + background = types.enum [ "dark" "light" ]; + backupdir = types.listOf types.str; + copyindent = types.bool; + directory = types.listOf types.str; + expandtab = types.bool; + hidden = types.bool; + history = types.int; + ignorecase = types.bool; + modeline = types.bool; + mouse = types.enum [ "n" "v" "i" "c" "h" "a" "r" ]; + mousefocus = types.bool; + mousehide = types.bool; + mousemodel = types.enum [ "extend" "popup" "popup_setpos" ]; + number = types.bool; + relativenumber = types.bool; + shiftwidth = types.int; + smartcase = types.bool; + tabstop = types.int; + undodir = types.listOf types.str; + undofile = types.bool; + }; + + vimSettingsType = types.submodule { + options = + let + opt = name: type: mkOption { + type = types.nullOr type; + default = null; + visible = false; + }; + in + mapAttrs opt knownSettings; + }; + + setExpr = name: value: + let + v = + if isBool value then (if value then "" else "no") + name + else + "${name}=${ + if isList value + then concatStringsSep "," value + else toString value + }"; + in + optionalString (value != null) ("set " + v); + + plugins = + let + vpkgs = pkgs.vimPlugins; + getPkg = p: + if isDerivation p + then [ p ] + else optional (isString p && hasAttr p vpkgs) vpkgs.${p}; + in + concatMap getPkg cfg.plugins; + +in + +{ + options = { + programs.vim = { + enable = mkEnableOption "Vim"; + + plugins = mkOption { + type = with types; listOf (either str package); + default = defaultPlugins; + example = literalExample ''[ pkgs.vimPlugins.YankRing ]''; + description = '' + List of vim plugins to install. To get a list of supported plugins run: + <command>nix-env -f '<nixpkgs>' -qaP -A vimPlugins</command>. + + </para><para> + + Note: String values are deprecated, please use actual packages. + ''; + }; + + settings = mkOption { + type = vimSettingsType; + default = {}; + example = literalExample '' + { + expandtab = true; + history = 1000; + background = "dark"; + } + ''; + description = '' + At attribute set of Vim 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 Vim documentation for detailed descriptions of these + options. Note, use <varname>extraConfig</varname> to + manually set any options not listed above. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + set nocompatible + set nobackup + ''; + description = "Custom .vimrc lines"; + }; + + package = mkOption { + type = types.package; + description = "Resulting customized vim package"; + readOnly = true; + }; + }; + }; + + config = ( + let + customRC = '' + ${concatStringsSep "\n" ( + filter (v: v != "") ( + mapAttrsToList setExpr ( + builtins.intersectAttrs knownSettings cfg.settings)))} + + ${cfg.extraConfig} + ''; + + vim = pkgs.vim_configurable.customize { + name = "vim"; + vimrcConfig = { + inherit customRC; + + packages.home-manager.start = plugins; + }; + }; + in + mkIf cfg.enable { + assertions = + let + packagesNotFound = filter (p: isString p && (!hasAttr p pkgs.vimPlugins)) cfg.plugins; + in + [ + { + assertion = packagesNotFound == []; + message = "Following VIM plugin not found in pkgs.vimPlugins: ${ + concatMapStringsSep ", " (p: ''"${p}"'') packagesNotFound + }"; + } + ]; + + warnings = + let + stringPlugins = filter isString cfg.plugins; + in + optional (stringPlugins != []) '' + Specifying VIM plugins using strings is deprecated, found ${ + concatMapStringsSep ", " (p: ''"${p}"'') stringPlugins + } as strings. + ''; + + home.packages = [ cfg.package ]; + + programs.vim = { + package = vim; + plugins = defaultPlugins; + }; + } + ); +} diff --git a/home-manager/modules/programs/vscode.nix b/home-manager/modules/programs/vscode.nix new file mode 100644 index 00000000000..3a82816f588 --- /dev/null +++ b/home-manager/modules/programs/vscode.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.vscode; + + configFilePath = + if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/Code/User/settings.json" + else + "${config.xdg.configHome}/Code/User/settings.json"; + +in + +{ + options = { + programs.vscode = { + enable = mkEnableOption "Visual Studio Code"; + + userSettings = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + "update.channel" = "none"; + "[nix]"."editor.tabSize" = 2; + } + ''; + description = '' + Configuration written to Visual Studio Code's + <filename>settings.json</filename>. + ''; + }; + + extensions = mkOption { + type = types.listOf types.package; + default = []; + example = literalExample "[ pkgs.vscode-extensions.bbenoist.Nix ]"; + description = '' + The extensions Visual Studio Code should be started with. + These will override but not delete manually installed ones. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ + (pkgs.vscode-with-extensions.override { + vscodeExtensions = cfg.extensions; + }) + ]; + + home.file."${configFilePath}".text = builtins.toJSON cfg.userSettings; + }; +} diff --git a/home-manager/modules/programs/vscode/haskell.nix b/home-manager/modules/programs/vscode/haskell.nix new file mode 100644 index 00000000000..c8ea10d473e --- /dev/null +++ b/home-manager/modules/programs/vscode/haskell.nix @@ -0,0 +1,66 @@ +{ pkgs, config, lib, ... }: + +with lib; + +let + + cfg = config.programs.vscode.haskell; + + defaultHieNixExe = hie-nix.hies + "/bin/hie-wrapper"; + defaultHieNixExeText = literalExample + "\"\${pkgs.hie-nix.hies}/bin/hie-wrapper\""; + + hie-nix = pkgs.hie-nix or (abort '' + vscode.haskell: pkgs.hie-nix missing. Please add an overlay such as: + ${exampleOverlay} + ''); + + exampleOverlay = '' + nixpkgs.overlays = [ + (self: super: { hie-nix = import ~/src/hie-nix {}; }) + ] + ''; + +in + +{ + options.programs.vscode.haskell = { + enable = mkEnableOption "Haskell integration for Visual Studio Code"; + + hie.enable = mkOption { + type = types.bool; + default = true; + description = "Whether to enable Haskell IDE engine integration."; + }; + + hie.executablePath = mkOption { + type = types.path; + default = defaultHieNixExe; + defaultText = defaultHieNixExeText; + description = '' + The path to the Haskell IDE Engine executable. + </para><para> + Because hie-nix is not packaged in Nixpkgs, you need to add it as an + overlay or set this option. Example overlay configuration: + <programlisting language="nix">${exampleOverlay}</programlisting> + ''; + example = literalExample '' + (import ~/src/haskell-ide-engine {}).hies + "/bin/hie-wrapper"; + ''; + }; + }; + + config = mkIf cfg.enable { + programs.vscode.userSettings = mkIf cfg.hie.enable { + "languageServerHaskell.enableHIE" = true; + "languageServerHaskell.hieExecutablePath" = cfg.hie.executablePath; + }; + + programs.vscode.extensions = + [ + pkgs.vscode-extensions.justusadam.language-haskell + ] + ++ lib.optional cfg.hie.enable + pkgs.vscode-extensions.alanz.vscode-hie-server; + }; +} diff --git a/home-manager/modules/programs/z-lua.nix b/home-manager/modules/programs/z-lua.nix new file mode 100644 index 00000000000..245eff6a51e --- /dev/null +++ b/home-manager/modules/programs/z-lua.nix @@ -0,0 +1,86 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.z-lua; + + aliases = { + zz = "z -c"; # restrict matches to subdirs of $PWD + zi = "z -i"; # cd with interactive selection + zf = "z -I"; # use fzf to select in multiple matches + zb = "z -b"; # quickly cd to the parent directory + zh = "z -I -t ."; # fzf + }; + +in + +{ + meta.maintainers = [ maintainers.marsam ]; + + options.programs.z-lua = { + enable = mkEnableOption "z.lua"; + + options = mkOption { + type = types.listOf types.str; + default = []; + example = [ "enhanced" "once" "fzf" ]; + description = '' + List of options to pass to z.lua. + ''; + }; + + 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. + ''; + }; + + enableAliases = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable recommended z.lua aliases. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.z-lua ]; + + programs.bash.initExtra = mkIf cfg.enableBashIntegration '' + eval "$(${pkgs.z-lua}/bin/z --init bash ${concatStringsSep " " cfg.options})" + ''; + + programs.zsh.initExtra = mkIf cfg.enableZshIntegration '' + eval "$(${pkgs.z-lua}/bin/z --init zsh ${concatStringsSep " " cfg.options})" + ''; + + programs.fish.shellInit = mkIf cfg.enableFishIntegration '' + source (${pkgs.z-lua}/bin/z --init fish ${concatStringsSep " " cfg.options} | psub) + ''; + + programs.bash.shellAliases = mkIf cfg.enableAliases aliases; + + programs.zsh.shellAliases = mkIf cfg.enableAliases aliases; + }; +} diff --git a/home-manager/modules/programs/zathura.nix b/home-manager/modules/programs/zathura.nix new file mode 100644 index 00000000000..f01bd501c39 --- /dev/null +++ b/home-manager/modules/programs/zathura.nix @@ -0,0 +1,61 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + + cfg = config.programs.zathura; + + formatLine = n: v: + let + formatValue = v: + if isBool v then (if v then "true" else "false") + else toString v; + in + "set ${n}\t\"${formatValue v}\""; + +in + +{ + meta.maintainers = [ maintainers.rprospero ]; + + options.programs.zathura = { + enable = mkEnableOption '' + Zathura, a highly customizable and functional document viewer + focused on keyboard interaction''; + + options = mkOption { + default = {}; + type = with types; attrsOf (either str (either bool int)); + description = '' + Add <option>:set</option> command options to zathura and make + them permanent. See + <citerefentry> + <refentrytitle>zathurarc</refentrytitle> + <manvolnum>5</manvolnum> + </citerefentry> + for the full list of options. + ''; + example = { default-bg = "#000000"; default-fg = "#FFFFFF"; }; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Additional commands for zathura that will be added to the + <filename>zathurarc</filename> file. + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.zathura ]; + + xdg.configFile."zathura/zathurarc".text = + concatStringsSep "\n" ([] + ++ optional (cfg.extraConfig != "") cfg.extraConfig + ++ mapAttrsToList formatLine cfg.options + ) + "\n"; + }; +} diff --git a/home-manager/modules/programs/zsh.nix b/home-manager/modules/programs/zsh.nix new file mode 100644 index 00000000000..ffe5f4960b6 --- /dev/null +++ b/home-manager/modules/programs/zsh.nix @@ -0,0 +1,439 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.zsh; + + relToDotDir = file: (optionalString (cfg.dotDir != null) (cfg.dotDir + "/")) + file; + + pluginsDir = if cfg.dotDir != null then + relToDotDir "plugins" else ".zsh/plugins"; + + envVarsStr = config.lib.zsh.exportAll cfg.sessionVariables; + localVarsStr = config.lib.zsh.defineAll cfg.localVariables; + + aliasesStr = concatStringsSep "\n" ( + mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") cfg.shellAliases + ); + + zdotdir = "$HOME/" + cfg.dotDir; + + bindkeyCommands = { + emacs = "bindkey -e"; + viins = "bindkey -v"; + vicmd = "bindkey -a"; + }; + + historyModule = types.submodule ({ config, ... }: { + options = { + size = mkOption { + type = types.int; + default = 10000; + description = "Number of history lines to keep."; + }; + + save = mkOption { + type = types.int; + defaultText = 10000; + default = config.size; + description = "Number of history lines to save."; + }; + + path = mkOption { + type = types.str; + default = relToDotDir ".zsh_history"; + defaultText = ".zsh_history"; + description = "History file location"; + }; + + ignoreDups = mkOption { + type = types.bool; + default = true; + description = '' + Do not enter command lines into the history list + if they are duplicates of the previous event. + ''; + }; + + expireDuplicatesFirst = mkOption { + type = types.bool; + default = false; + description = "Expire duplicates first."; + }; + + extended = mkOption { + type = types.bool; + default = false; + description = "Save timestamp into the history file."; + }; + + share = mkOption { + type = types.bool; + default = true; + description = "Share command history between zsh sessions."; + }; + }; + }); + + pluginModule = types.submodule ({ config, ... }: { + options = { + src = mkOption { + type = types.path; + description = '' + Path to the plugin folder. + + Will be added to <envar>fpath</envar> and <envar>PATH</envar>. + ''; + }; + + name = mkOption { + type = types.str; + description = '' + The name of the plugin. + + Don't forget to add <option>file</option> + if the script name does not follow convention. + ''; + }; + + file = mkOption { + type = types.str; + description = "The plugin script to source."; + }; + }; + + config.file = mkDefault "${config.name}.plugin.zsh"; + }); + + ohMyZshModule = types.submodule { + options = { + enable = mkEnableOption "oh-my-zsh"; + + plugins = mkOption { + default = []; + example = [ "git" "sudo" ]; + type = types.listOf types.str; + description = '' + List of oh-my-zsh plugins + ''; + }; + + custom = mkOption { + default = ""; + type = types.str; + example = "$HOME/my_customizations"; + description = '' + Path to a custom oh-my-zsh package to override config of + oh-my-zsh. See <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization"/> + for more information. + ''; + }; + + theme = mkOption { + default = ""; + example = "robbyrussell"; + type = types.str; + description = '' + Name of the theme to be used by oh-my-zsh. + ''; + }; + }; + }; + +in + +{ + options = { + programs.zsh = { + enable = mkEnableOption "Z shell (Zsh)"; + + autocd = mkOption { + default = null; + description = '' + Automatically enter into a directory if typed directly into shell. + ''; + type = types.nullOr types.bool; + }; + + dotDir = mkOption { + default = null; + example = ".config/zsh"; + description = '' + Directory where the zsh configuration and more should be located, + relative to the users home directory. The default is the home + directory. + ''; + type = types.nullOr types.str; + }; + + shellAliases = mkOption { + default = {}; + example = { 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. + ''; + type = types.attrsOf types.str; + }; + + enableCompletion = mkOption { + default = true; + description = '' + Enable zsh completion. Don't forget to add + <programlisting language="nix"> + environment.pathsToLink = [ "/share/zsh" ]; + </programlisting> + to your system configuration to get completion for system packages (e.g. systemd). + ''; + type = types.bool; + }; + + enableAutosuggestions = mkOption { + default = false; + description = "Enable zsh autosuggestions"; + }; + + history = mkOption { + type = historyModule; + default = {}; + description = "Options related to commands history configuration."; + }; + + defaultKeymap = mkOption { + type = types.nullOr (types.enum (attrNames bindkeyCommands)); + default = null; + example = "emacs"; + description = "The default base keymap to use."; + }; + + sessionVariables = mkOption { + default = {}; + type = types.attrs; + example = { MAILCHECK = 30; }; + description = "Environment variables that will be set for zsh session."; + }; + + initExtraBeforeCompInit = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zshrc</filename> before compinit."; + }; + + initExtra = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zshrc</filename>."; + }; + + envExtra = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zshenv</filename>."; + }; + + profileExtra = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zprofile</filename>."; + }; + + loginExtra = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zlogin</filename>."; + }; + + logoutExtra = mkOption { + default = ""; + type = types.lines; + description = "Extra commands that should be added to <filename>.zlogout</filename>."; + }; + + plugins = mkOption { + type = types.listOf pluginModule; + default = []; + example = literalExample '' + [ + { + # will source zsh-autosuggestions.plugin.zsh + name = "zsh-autosuggestions"; + src = pkgs.fetchFromGitHub { + owner = "zsh-users"; + repo = "zsh-autosuggestions"; + rev = "v0.4.0"; + sha256 = "0z6i9wjjklb4lvr7zjhbphibsyx51psv50gm07mbb0kj9058j6kc"; + }; + } + { + name = "enhancd"; + file = "init.sh"; + src = pkgs.fetchFromGitHub { + owner = "b4b4r07"; + repo = "enhancd"; + rev = "v2.2.1"; + sha256 = "0iqa9j09fwm6nj5rpip87x3hnvbbz9w9ajgm6wkrd5fls8fn8i5g"; + }; + } + ] + ''; + description = "Plugins to source in <filename>.zshrc</filename>."; + }; + + oh-my-zsh = mkOption { + type = ohMyZshModule; + default = {}; + description = "Options to configure oh-my-zsh."; + }; + + localVariables = mkOption { + type = types.attrs; + default = {}; + example = { POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=["dir" "vcs"]; }; + description = '' + Extra local variables defined at the top of <filename>.zshrc</filename>. + ''; + }; + }; + }; + + config = mkIf cfg.enable (mkMerge [ + (mkIf (cfg.envExtra != "") { + home.file."${relToDotDir ".zshenv"}".text = cfg.envExtra; + }) + + (mkIf (cfg.profileExtra != "") { + home.file."${relToDotDir ".zprofile"}".text = cfg.profileExtra; + }) + + (mkIf (cfg.loginExtra != "") { + home.file."${relToDotDir ".zlogin"}".text = cfg.loginExtra; + }) + + (mkIf (cfg.logoutExtra != "") { + home.file."${relToDotDir ".zlogout"}".text = cfg.logoutExtra; + }) + + (mkIf cfg.oh-my-zsh.enable { + home.file."${relToDotDir ".zshenv"}".text = '' + ZSH="${pkgs.oh-my-zsh}/share/oh-my-zsh"; + ZSH_CACHE_DIR="${config.xdg.cacheHome}/oh-my-zsh"; + ''; + }) + + (mkIf (cfg.dotDir != null) { + home.file."${relToDotDir ".zshenv"}".text = '' + ZDOTDIR=${zdotdir} + ''; + + # When dotDir is set, only use ~/.zshenv to source ZDOTDIR/.zshenv, + # This is so that if ZDOTDIR happens to be + # already set correctly (by e.g. spawning a zsh inside a zsh), all env + # vars still get exported + home.file.".zshenv".text = '' + source ${zdotdir}/.zshenv + ''; + }) + + { + home.packages = with pkgs; [ zsh ] + ++ optional cfg.enableCompletion nix-zsh-completions + ++ optional cfg.oh-my-zsh.enable oh-my-zsh; + + home.file."${relToDotDir ".zshrc"}".text = '' + typeset -U path cdpath fpath manpath + + 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 + + HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help" + + ${optionalString (cfg.defaultKeymap != null) '' + # Use ${cfg.defaultKeymap} keymap as the default. + ${getAttr cfg.defaultKeymap bindkeyCommands} + ''} + + ${localVarsStr} + + ${cfg.initExtraBeforeCompInit} + + ${concatStrings (map (plugin: '' + path+="$HOME/${pluginsDir}/${plugin.name}" + fpath+="$HOME/${pluginsDir}/${plugin.name}" + '') cfg.plugins)} + + # Oh-My-Zsh calls compinit during initialization, + # calling it twice causes sight start up slowdown + # as all $fpath entries will be traversed again. + ${optionalString (cfg.enableCompletion && !cfg.oh-my-zsh.enable) + "autoload -U compinit && compinit" + } + + ${optionalString cfg.enableAutosuggestions + "source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh" + } + + # Environment variables + . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh" + ${envVarsStr} + + ${optionalString cfg.oh-my-zsh.enable '' + # oh-my-zsh configuration generated by NixOS + ${optionalString (cfg.oh-my-zsh.plugins != []) + "plugins=(${concatStringsSep " " cfg.oh-my-zsh.plugins})" + } + ${optionalString (cfg.oh-my-zsh.custom != "") + "ZSH_CUSTOM=\"${cfg.oh-my-zsh.custom}\"" + } + ${optionalString (cfg.oh-my-zsh.theme != "") + "ZSH_THEME=\"${cfg.oh-my-zsh.theme}\"" + } + source $ZSH/oh-my-zsh.sh + ''} + + ${concatStrings (map (plugin: '' + if [ -f "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" ]; then + source "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" + fi + '') cfg.plugins)} + + # History options should be set in .zshrc and after oh-my-zsh sourcing. + # See https://github.com/rycee/home-manager/issues/177. + HISTSIZE="${toString cfg.history.size}" + HISTFILE="$HOME/${cfg.history.path}" + SAVEHIST="${toString cfg.history.save}" + + setopt HIST_FCNTL_LOCK + ${if cfg.history.ignoreDups then "setopt" else "unsetopt"} HIST_IGNORE_DUPS + ${if cfg.history.expireDuplicatesFirst then "setopt" else "unsetopt"} HIST_EXPIRE_DUPS_FIRST + ${if cfg.history.share then "setopt" else "unsetopt"} SHARE_HISTORY + ${if cfg.history.extended then "setopt" else "unsetopt"} EXTENDED_HISTORY + ${if cfg.autocd != null then "${if cfg.autocd then "setopt" else "unsetopt"} autocd" else ""} + + ${cfg.initExtra} + + # Aliases + ${aliasesStr} + ''; + } + + (mkIf cfg.oh-my-zsh.enable { + # Make sure we create a cache directory since some plugins expect it to exist + # See: https://github.com/rycee/home-manager/issues/761 + home.file."${config.xdg.cacheHome}/oh-my-zsh/.keep".text = ""; + }) + + (mkIf (cfg.plugins != []) { + # Many plugins require compinit to be called + # but allow the user to opt out. + programs.zsh.enableCompletion = mkDefault true; + + home.file = map (plugin: { + target = "${pluginsDir}/${plugin.name}"; + source = plugin.src; + }) cfg.plugins; + }) + ]); +} |