bf6b85136b
At the moment, only the inbox of each mail account is added to neomutt. This inbox is always called "Inbox", so if you configure multiple accounts, it is hard to know which one is which. This change allows the user to specify a display name per account that uses `named-mailboxes` under the hood. Additionally this change now allows to add other folders than the inbox, for example the Trash, Spam or Drafts folders to be added on a per-account basis. Using extraOptions is not possible here, as those are lazily loaded on mailbox open and thus would appear at the bottom and not sorted by account. This commit also changes the default sidebar format string to use %D instead of %B because %B will ignore named mailboxes and show the folder name instead.
364 lines
10 KiB
Nix
364 lines
10 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.programs.neomutt;
|
|
|
|
neomuttAccounts =
|
|
filter (a: a.neomutt.enable) (attrValues config.accounts.email.accounts);
|
|
|
|
sidebarModule = types.submodule {
|
|
options = {
|
|
enable = mkEnableOption "sidebar support";
|
|
|
|
width = mkOption {
|
|
type = types.int;
|
|
default = 22;
|
|
description = "Width of the sidebar";
|
|
};
|
|
|
|
shortPath = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
By default sidebar shows the full path of the mailbox, but
|
|
with this enabled only the relative name is shown.
|
|
'';
|
|
};
|
|
|
|
format = mkOption {
|
|
type = types.str;
|
|
default = "%D%?F? [%F]?%* %?N?%N/?%S";
|
|
description = ''
|
|
Sidebar format. Check neomutt documentation for details.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
sortOptions = [
|
|
"date"
|
|
"date-received"
|
|
"from"
|
|
"mailbox-order"
|
|
"score"
|
|
"size"
|
|
"spam"
|
|
"subject"
|
|
"threads"
|
|
"to"
|
|
];
|
|
|
|
bindModule = types.submodule {
|
|
options = {
|
|
map = mkOption {
|
|
type = let
|
|
menus = [
|
|
"alias"
|
|
"attach"
|
|
"browser"
|
|
"compose"
|
|
"editor"
|
|
"generic"
|
|
"index"
|
|
"mix"
|
|
"pager"
|
|
"pgp"
|
|
"postpone"
|
|
"query"
|
|
"smime"
|
|
];
|
|
in with types; either (enum menus) (listOf (enum menus));
|
|
default = "index";
|
|
description = "Select the menu to bind the command to.";
|
|
};
|
|
|
|
key = mkOption {
|
|
type = types.str;
|
|
example = "<left>";
|
|
description = "The key to bind.";
|
|
};
|
|
|
|
action = mkOption {
|
|
type = types.str;
|
|
example = "<enter-command>toggle sidebar_visible<enter><refresh>";
|
|
description = "Specify the action to take.";
|
|
};
|
|
};
|
|
};
|
|
|
|
yesno = x: if x then "yes" else "no";
|
|
setOption = n: v: if v == null then "unset ${n}" else "set ${n}=${v}";
|
|
escape = replaceStrings [ "%" ] [ "%25" ];
|
|
|
|
accountFilename = account: config.xdg.configHome + "/neomutt/" + account.name;
|
|
|
|
genCommonFolderHooks = account:
|
|
with account; {
|
|
from = "'${address}'";
|
|
realname = "'${realName}'";
|
|
spoolfile = "'+${folders.inbox}'";
|
|
record = if folders.sent == null then null else "'+${folders.sent}'";
|
|
postponed = "'+${folders.drafts}'";
|
|
trash = "'+${folders.trash}'";
|
|
};
|
|
|
|
mtaSection = account:
|
|
with account;
|
|
let passCmd = concatStringsSep " " passwordCommand;
|
|
in if neomutt.sendMailCommand != null then {
|
|
sendmail = "'${neomutt.sendMailCommand}'";
|
|
} else
|
|
let
|
|
smtpProto = if smtp.tls.enable then "smtps" else "smtp";
|
|
smtpPort = if smtp.port != null then ":${toString smtp.port}" else "";
|
|
smtpBaseUrl =
|
|
"${smtpProto}://${escape userName}@${smtp.host}${smtpPort}";
|
|
in {
|
|
smtp_url = "'${smtpBaseUrl}'";
|
|
smtp_pass = ''"`${passCmd}`"'';
|
|
};
|
|
|
|
genMaildirAccountConfig = account:
|
|
with account;
|
|
let
|
|
folderHook = mapAttrsToList setOption (genCommonFolderHooks account
|
|
// optionalAttrs cfg.changeFolderWhenSourcingAccount {
|
|
folder = "'${account.maildir.absPath}'";
|
|
});
|
|
in ''
|
|
${concatStringsSep "\n" folderHook}
|
|
'';
|
|
|
|
registerAccount = account:
|
|
let
|
|
mailboxes = if account.neomutt.mailboxName == null then
|
|
"mailboxes"
|
|
else
|
|
''named-mailboxes "${account.neomutt.mailboxName}"'';
|
|
extraMailboxes = concatMapStringsSep "\n" (extra:
|
|
if isString extra then
|
|
''mailboxes "${account.maildir.absPath}/${extra}"''
|
|
else if extra.name == null then
|
|
''mailboxes "${account.maildir.absPath}/${extra.mailbox}"''
|
|
else
|
|
''
|
|
named-mailboxes "${extra.name}" "${account.maildir.absPath}/${extra.mailbox}"'')
|
|
account.neomutt.extraMailboxes;
|
|
in with account; ''
|
|
# register account ${name}
|
|
${mailboxes} "${maildir.absPath}/${folders.inbox}"
|
|
${extraMailboxes}
|
|
folder-hook ${maildir.absPath}/ " \
|
|
source ${accountFilename account} "
|
|
'';
|
|
|
|
mraSection = account:
|
|
with account;
|
|
if account.maildir != null then
|
|
genMaildirAccountConfig account
|
|
else
|
|
throw "Only maildir is supported at the moment";
|
|
|
|
optionsStr = attrs: concatStringsSep "\n" (mapAttrsToList setOption attrs);
|
|
|
|
sidebarSection = ''
|
|
# Sidebar
|
|
set sidebar_visible = yes
|
|
set sidebar_short_path = ${yesno cfg.sidebar.shortPath}
|
|
set sidebar_width = ${toString cfg.sidebar.width}
|
|
set sidebar_format = '${cfg.sidebar.format}'
|
|
'';
|
|
|
|
genBindMapper = bindType:
|
|
concatMapStringsSep "\n" (bind:
|
|
''
|
|
${bindType} ${
|
|
concatStringsSep "," (toList bind.map)
|
|
} ${bind.key} "${bind.action}"'');
|
|
|
|
bindSection = (genBindMapper "bind") cfg.binds;
|
|
|
|
macroSection = (genBindMapper "macro") cfg.macros;
|
|
|
|
mailCheckSection = ''
|
|
set mail_check_stats
|
|
set mail_check_stats_interval = ${toString cfg.checkStatsInterval}
|
|
'';
|
|
|
|
notmuchSection = account:
|
|
with account; ''
|
|
# notmuch section
|
|
set nm_default_uri = "notmuch://${config.accounts.email.maildirBasePath}"
|
|
virtual-mailboxes "My INBOX" "notmuch://?query=tag:inbox"
|
|
'';
|
|
|
|
accountStr = account:
|
|
with account;
|
|
''
|
|
# Generated by Home Manager.
|
|
set ssl_force_tls = yes
|
|
set certificate_file=${toString config.accounts.email.certificatesFile}
|
|
|
|
# GPG section
|
|
set crypt_use_gpgme = yes
|
|
set crypt_autosign = ${yesno (gpg.signByDefault or false)}
|
|
set crypt_opportunistic_encrypt = ${yesno (gpg.encryptByDefault or false)}
|
|
set pgp_use_gpg_agent = yes
|
|
set mbox_type = ${if maildir != null then "Maildir" else "mbox"}
|
|
set sort = "${cfg.sort}"
|
|
|
|
# MTA section
|
|
${optionsStr (mtaSection account)}
|
|
|
|
${optionalString (cfg.checkStatsInterval != null) mailCheckSection}
|
|
|
|
${optionalString cfg.sidebar.enable sidebarSection}
|
|
|
|
# MRA section
|
|
${mraSection account}
|
|
|
|
# Extra configuration
|
|
${account.neomutt.extraConfig}
|
|
'' + optionalString (account.signature.showSignature != "none") ''
|
|
set signature = ${pkgs.writeText "signature.txt" account.signature.text}
|
|
'' + optionalString account.notmuch.enable (notmuchSection account);
|
|
|
|
in {
|
|
options = {
|
|
programs.neomutt = {
|
|
enable = mkEnableOption "the NeoMutt mail client";
|
|
|
|
sidebar = mkOption {
|
|
type = sidebarModule;
|
|
default = { };
|
|
description = "Options related to the sidebar.";
|
|
};
|
|
|
|
binds = mkOption {
|
|
type = types.listOf bindModule;
|
|
default = [ ];
|
|
description = "List of keybindings.";
|
|
};
|
|
|
|
macros = mkOption {
|
|
type = types.listOf bindModule;
|
|
default = [ ];
|
|
description = "List of macros.";
|
|
};
|
|
|
|
sort = mkOption {
|
|
# allow users to choose any option from sortOptions, or any option prefixed with "reverse-"
|
|
type = types.enum
|
|
(sortOptions ++ (map (option: "reverse-" + option) sortOptions));
|
|
default = "threads";
|
|
description = "Sorting method on messages.";
|
|
};
|
|
|
|
vimKeys = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable vim-like bindings.";
|
|
};
|
|
|
|
checkStatsInterval = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
example = 60;
|
|
description = "Enable and set the interval of automatic mail check.";
|
|
};
|
|
|
|
editor = mkOption {
|
|
type = types.str;
|
|
default = "$EDITOR";
|
|
description = "Select the editor used for writing mail.";
|
|
};
|
|
|
|
settings = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = { };
|
|
description = "Extra configuration appended to the end.";
|
|
};
|
|
|
|
changeFolderWhenSourcingAccount =
|
|
mkEnableOption "changing the folder when sourcing an account" // {
|
|
default = true;
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
description = "Extra configuration appended to the end.";
|
|
};
|
|
};
|
|
|
|
accounts.email.accounts = mkOption {
|
|
type = with types; attrsOf (submodule (import ./neomutt-accounts.nix));
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
home.packages = [ pkgs.neomutt ];
|
|
home.file = let
|
|
rcFile = account: {
|
|
"${accountFilename account}".text = accountStr account;
|
|
};
|
|
in foldl' (a: b: a // b) { } (map rcFile neomuttAccounts);
|
|
|
|
xdg.configFile."neomutt/neomuttrc" = mkIf (neomuttAccounts != [ ]) {
|
|
text = let
|
|
# Find the primary account, if it has neomutt enabled;
|
|
# otherwise use the first neomutt account as primary.
|
|
primary =
|
|
head (filter (a: a.primary) neomuttAccounts ++ neomuttAccounts);
|
|
in ''
|
|
# Generated by Home Manager.
|
|
set header_cache = "${config.xdg.cacheHome}/neomutt/headers/"
|
|
set message_cachedir = "${config.xdg.cacheHome}/neomutt/messages/"
|
|
set editor = "${cfg.editor}"
|
|
set implicit_autoview = yes
|
|
|
|
alternative_order text/enriched text/plain text
|
|
|
|
set delete = yes
|
|
|
|
# Binds
|
|
${bindSection}
|
|
|
|
# Macros
|
|
${macroSection}
|
|
|
|
${optionalString cfg.vimKeys
|
|
"source ${pkgs.neomutt}/share/doc/neomutt/vim-keys/vim-keys.rc"}
|
|
|
|
# Register accounts
|
|
${concatMapStringsSep "\n" registerAccount neomuttAccounts}
|
|
|
|
# Source primary account
|
|
source ${accountFilename primary}
|
|
|
|
# Extra configuration
|
|
${optionsStr cfg.settings}
|
|
|
|
${cfg.extraConfig}
|
|
'';
|
|
};
|
|
|
|
assertions = [{
|
|
assertion =
|
|
((filter (b: (length (toList b.map)) == 0) (cfg.binds ++ cfg.macros))
|
|
== [ ]);
|
|
message =
|
|
"The 'programs.neomutt.(binds|macros).map' list must contain at least one element.";
|
|
}];
|
|
|
|
warnings =
|
|
let hasOldBinds = binds: (filter (b: !(isList b.map)) binds) != [ ];
|
|
in mkIf (hasOldBinds (cfg.binds ++ cfg.macros)) [
|
|
"Specifying 'programs.neomutt.(binds|macros).map' as a string is deprecated, use a list of strings instead. See https://github.com/nix-community/home-manager/pull/1885."
|
|
];
|
|
};
|
|
}
|