diff options
Diffstat (limited to 'modules/communication/neomutt')
| -rw-r--r-- | modules/communication/neomutt/assertions.nix | 34 | ||||
| -rw-r--r-- | modules/communication/neomutt/default.nix | 427 | ||||
| -rw-r--r-- | modules/communication/neomutt/theme.nix | 130 |
3 files changed, 591 insertions, 0 deletions
diff --git a/modules/communication/neomutt/assertions.nix b/modules/communication/neomutt/assertions.nix new file mode 100644 index 0000000..989d273 --- /dev/null +++ b/modules/communication/neomutt/assertions.nix @@ -0,0 +1,34 @@ +{ config, ... }: + +{ + assertions = [ + { + assertion = !config.programs.neomutt.enable || config.programs.less.enable; + message = '' + programs.neomutt is enabled but programs.less.enable is not. + neomutt requires less as its pager. Please enable programs.less. + ''; + } + { + assertion = !config.programs.neomutt.enable || config.programs.bat.enable; + message = '' + programs.neomutt is enabled but programs.bat.enable is not. + neomutt requires bat for text/html filtering. Please enable programs.bat. + ''; + } + { + assertion = !config.programs.neomutt.enable || config.programs.email.enable; + message = '' + programs.neomutt is enabled but programs.email.enable is not. + neomutt requires the email module (mbsync + notmuch) to be enabled. + ''; + } + { + assertion = !config.programs.neomutt.enable || config.programs.kitty.enable; + message = '' + programs.neomutt is enabled but programs.kitty.enable is not. + neomutt's desktop entry requires kitty as the terminal launcher. Please enable programs.kitty. + ''; + } + ]; +} diff --git a/modules/communication/neomutt/default.nix b/modules/communication/neomutt/default.nix new file mode 100644 index 0000000..3626341 --- /dev/null +++ b/modules/communication/neomutt/default.nix @@ -0,0 +1,427 @@ +# neomutt - Terminal email client +{ + config, + lib, + pkgs, + ... +}: +let + neomuttDesktopItem = pkgs.makeDesktopItem { + name = "neomutt"; + desktopName = "NeoMutt"; + exec = "${lib.getExe config.programs.kitty.package} -e ${lib.getExe config.programs.neomutt.package}"; + icon = "kitty"; + categories = [ + "Network" + "Email" + ]; + comment = "Terminal email client"; + terminal = false; + type = "Application"; + }; +in +{ + imports = [ + ./assertions.nix + ./theme.nix + ]; + config = lib.mkIf config.programs.neomutt.enable { + xdg.mimeApps.defaultApplications = { + "x-scheme-handler/mailto" = "neomutt.desktop"; + }; + + home.packages = [ + pkgs.urlscan + pkgs.w3m + neomuttDesktopItem + ]; + + xdg.configFile."neomutt/mailcap".text = '' + text/html; w3m -dump -T text/html '%s'; copiousoutput; nametemplate=%s.html + text/html; $BROWSER '%s'; nametemplate=%s.html + image/*; xdg-open '%s' & + application/pdf; xdg-open '%s'; nametemplate=%s.pdf + video/*; xdg-open '%s' & + audio/*; xdg-open '%s' & + application/*; xdg-open '%s' & + ''; + + xdg.configFile."neomutt/flag_legend.txt".text = '' + NeoMutt Index Flags + =================== + + Message state ($flag_chars) -- unchanged defaults + N New - unread, arrived since you last checked + O Old - unread, but seen in a previous session + D Deleted - marked for deletion (pending expunge) + d Att-del - has attachments marked for deletion + ! Flagged - important (toggle with F) + * Tagged - selected for a bulk operation (toggle with t) + r Replied - you've replied to this message + (blank) - read, nothing else notable + + Cryptography ($crypt_chars) + Verified - signed, and the signature checks out + Encrypted - PGP/SMIME encrypted + Signed - PGP/SMIME signed (unverified) + PGP key - message contains a PGP public key + (blank) - no cryptography info + + Addressing ($to_chars) + To you only - sent to you and only you + To (with others) - you're in the To: list, among others + Cc only - you're only in the Cc: list + From you - sent by you + Mailing list - sent to a list you're subscribed to + (blank) - not in To/Cc (e.g. Bcc, unlisted) + + Press q to close. + ''; + + programs.neomutt = { + sidebar.enable = true; + sort = "reverse-threads"; + vimKeys = true; + unmailboxes = true; + checkStatsInterval = 20; + + extraConfig = '' + set menu_scroll = yes + + # cancel any prompt/command-line entry with Esc, vim-style + set abort_key = "<Esc>" + + # attachments: use our mailcap for dedicated openers, + # and auto-render html/images inline where sensible + set mailcap_path = "~/.config/neomutt/mailcap" + auto_view text/html image/png image/jpeg image/gif application/pdf + ''; + + binds = [ + # --- sidebar navigation (vim: k=up, j=down) --- + { + map = [ + "index" + "pager" + ]; + key = "\\CK"; + action = "sidebar-prev"; + } + { + map = [ + "index" + "pager" + ]; + key = "\\CJ"; + action = "sidebar-next"; + } + { + # select the highlighted mailbox directly, no Ctrl+O needed + map = [ + "index" + "pager" + ]; + key = "o"; + action = "sidebar-open"; + } + + # --- open / go deeper (vim: l = right/forward) --- + { + map = [ "index" ]; + key = "<Return>"; + action = "display-message"; + } + { + map = [ "index" ]; + key = "l"; + action = "display-message"; + } + { + map = [ "browser" ]; + key = "<Return>"; + action = "select-entry"; + } + { + map = [ "browser" ]; + key = "l"; + action = "select-entry"; + } + { + map = [ "attach" ]; + key = "<Return>"; + action = "view-attach"; + } + { + map = [ "attach" ]; + key = "l"; + action = "view-attach"; + } + { + map = [ + "alias" + "query" + ]; + key = "<Return>"; + action = "select-entry"; + } + + # --- back / exit (vim: h = left/back) --- + { + map = [ "pager" ]; + key = "h"; + action = "exit"; + } + { + map = [ "browser" ]; + key = "h"; + action = "exit"; + } + { + map = [ "attach" ]; + key = "h"; + action = "exit"; + } + { + map = [ "index" ]; + key = "h"; + action = "collapse-thread"; + } + + # --- pager line scrolling (vim: j/k move by line) --- + { + map = [ "pager" ]; + key = "j"; + action = "next-line"; + } + { + map = [ "pager" ]; + key = "k"; + action = "previous-line"; + } + { + # kept from before: jump straight to the next unread message, + # extends the h/l "back/forward" metaphor already in use + map = [ "pager" ]; + key = "l"; + action = "next-unread"; + } + + # --- top/bottom of buffer (vim: gg/G -- NOT 0/$, which mean + # start/end of *line* in vim and would be a false friend here) --- + { + map = [ "index" ]; + key = "gg"; + action = "first-entry"; + } + { + map = [ "index" ]; + key = "G"; + action = "last-entry"; + } + { + map = [ "pager" ]; + key = "gg"; + action = "top"; + } + { + map = [ "pager" ]; + key = "G"; + action = "bottom"; + } + { + map = [ "browser" ]; + key = "gg"; + action = "first-entry"; + } + { + map = [ "browser" ]; + key = "G"; + action = "last-entry"; + } + + # --- page scrolling (vim: Ctrl-f/b full page, Ctrl-d/u half page) --- + { + map = [ "pager" ]; + key = "\\Cf"; + action = "next-page"; + } + { + map = [ "pager" ]; + key = "\\Cb"; + action = "previous-page"; + } + { + map = [ "pager" ]; + key = "\\Cd"; + action = "half-down"; + } + { + map = [ "pager" ]; + key = "\\Cu"; + action = "half-up"; + } + { + # index has no half-page concept, so Ctrl-d/u fall back to + # the same full-page scroll as Ctrl-f/b + map = [ "index" ]; + key = "\\Cf"; + action = "next-page"; + } + { + map = [ "index" ]; + key = "\\Cb"; + action = "previous-page"; + } + { + map = [ "index" ]; + key = "\\Cd"; + action = "half-down"; + } + { + map = [ "index" ]; + key = "\\Cu"; + action = "half-up"; + } + + # --- search (vim: / forward, ? backward, n/N repeat) --- + { + map = [ + "index" + "pager" + ]; + key = "/"; + action = "search"; + } + { + map = [ + "index" + "pager" + ]; + key = "?"; + action = "search-reverse"; + } + { + map = [ + "index" + "pager" + ]; + key = "n"; + action = "search-next"; + } + { + map = [ + "index" + "pager" + ]; + key = "N"; + action = "search-opposite"; + } + + { + map = [ + "index" + "pager" + ]; + key = "u"; + action = "undelete-message"; + } + + # --- copy (vim: yy yank -- non-destructive, unlike save-message + # which moves/deletes the original) --- + { + map = [ "index" ]; + key = "y"; + action = "noop"; + } + { + map = [ "index" ]; + key = "yy"; + action = "copy-message"; + } + + # --- tagging (vim: v/V visual select single/whole block) --- + # NOTE: this overrides neomutt's default "v" (view-attachments). + # Attachments are still reachable from the pager/index via the + # mailcap auto-view or the attach menu, but if you want plain + # "v" back for view-attachments, just delete these two binds. + { + map = [ "index" ]; + key = "v"; + action = "tag-entry"; + } + { + map = [ "index" ]; + key = "V"; + action = "tag-thread"; + } + + # --- mailbox switching (vim: gt = next tab) --- + { + map = [ "index" ]; + key = "gt"; + action = "next-unread-mailbox"; + } + + # --- filter/limit (vim: gf to 'find' or 'filter') --- + { + map = [ "index" ]; + key = "gf"; + action = "limit"; + } + + # --- filter/limit (vim: gf to 'find' or 'filter') --- + { + map = [ "index" ]; + key = "gF"; + action = "limit"; + } + + # --- help message to show functions and keybindings --- + { + map = [ + "index" + "pager" + "browser" + "attach" + ]; + key = "g?"; + action = "help"; + } + ]; + + macros = [ + { + map = [ + "index" + "pager" + ]; + key = "O"; + action = "<sync-mailbox><shell-escape>mbsync -a<enter><sync-mailbox>"; + } + { + # gx: extract links from the current message and open one, vim-netrw style + map = [ + "index" + "pager" + ]; + key = "gx"; + action = "<pipe-message>urlscan<Enter>"; + } + { + map = [ + "index" + "pager" + ]; + key = "gl"; + action = "<shell-escape>less ~/.config/neomutt/flag_legend.txt<enter>"; + } + { + # ZZ: save and quit, vim-style (sync mailbox, then quit) + map = [ "index" ]; + key = "ZZ"; + action = "<sync-mailbox><quit>"; + } + ]; + }; + }; +} diff --git a/modules/communication/neomutt/theme.nix b/modules/communication/neomutt/theme.nix new file mode 100644 index 0000000..52845ee --- /dev/null +++ b/modules/communication/neomutt/theme.nix @@ -0,0 +1,130 @@ +{ config, lib, ... }: + +# Colorscheme - True base16 OneDark colors for neomutt +# Leverages Kitty's precise mapping to base16 (color0-15) +{ + config.programs.neomutt.extraConfig = lib.mkIf config.programs.neomutt.enable '' + # vim: filetype=muttrc + # + # Base16 OneDark Dark terminal colors for neomutt + # Each column in the index receives a distinct color to keep the UI organized. + + # Nerd Font icon-based flag/attachment/crypto indicators + # order: new, old, deleted, flagged, replied, old(unused), tagged, encrypt, ... + set flag_chars = "" + set to_chars = " " + set crypt_chars = " " + set attach_format = "%u%D%I %t%4n %T%.40d %> [%m/%M, %e%?C?, %C?, %s] " + + set index_format = " %5C │ %Z │ %{%d/%m/%y %H:%M} │ %-25.25a │ %s%* " + + # Hide the keybindings help bar at the top to save vertical space + set help = no + + # Status bar with Nerd Font icons per field + set status_format = " Unread: %5u │ New: %5n │ Tagged: %5t │ Search: %f │ Filter: %v │ Sorted By: %s │" + + # --- Basic UI Colors -------------------------------------------------- + # target foreground background + color normal white default # base05 (Standard text) + color error red default # base08 (Red) + color tilde brightblack default # base03 (Dark Grey) + color message cyan default # base0C (Cyan) + color markers red default # base08 (Red) + color attachment magenta default # base0E (Magenta) + color search magenta default # base0E (Magenta) + color prompt cyan default # base0C (Questions/prompts) + color progress black yellow # base0A bg (Progress bar) + + # Selected row and status bar + color status white brightblack # base05 on base03 + color indicator default brightblack # default on base03 (Selected email) + color tree magenta default # base03 (Thread arrows) + + # Whole-line fallback for the index (base for anything not caught below) + color index white default # base05 + + # --- Index Columns (The Multi-Color Table) ---------------------------- + # Applies distinct colors to specific columns for ALL messages (~A) + color index_number cyan default "~A" # base05 (Fixed: was brightblack) + color index_date green default "~A" # base0B (Green) + color index_flags red default "~A" # base08 (Red) + color index_size cyan default "~A" # base0C (Cyan) + color index_author yellow default "~A" # base0A (Yellow) + color index_subject white default "~A" # base05 (Standard text for Read messages) + color index_collapsed yellow default # base0A (Collapsed thread count, %M) + color index_label magenta default # base0E (Message label, %y %Y) + color index_tag cyan default # base0C (Message tags, %G) + color index_tags cyan default # base0C (Transformed tags, %g %J) + + # --- Index Message States --------------------------------------------- + # By targeting *only* `index_subject`, we preserve your multi-color + # columns while still easily identifying unread or flagged emails. + color index_subject blue default "~U" # Unread: Blue (base0D) + color index_subject blue default "~N" # New: Blue (base0D) + color index_subject red default "~F" # Flagged: Red (base08) + color index_subject magenta default "~Q" # Replied: Magenta (base0E) + color index_subject black red "~D" # Deleted + + # This rule now colors the "email (name)" column as it is considered the 'author' + color index_author yellow default "~A" + color index_subject white default "~A" # %s + + # We removed the ~R (Read) override so it stays white. + # For Deleted (~D), if brightblack is still too dark to see, we can use a red background instead: + color index_subject black red "~D" # Deleted: Black on Red background + + # Thread collapsing states + color index_subject yellow default "~v" # Collapsed thread + color index_subject blue default "~v~(~N)" # Collapsed with unread inside + color index_subject red default "~v~(~F)" # Collapsed with flagged inside + + # Subtle zebra striping between adjacent index rows (barely-there contrast) + color stripe_odd default default + color stripe_even default default + + # --- Message Headers -------------------------------------------------- + color hdrdefault cyan default # base0C + color header yellow default "^(From|To|Cc|Bcc)" # base0A + color header blue default "^(Subject)" # base0D + color header green default "^(Date)" # base03 + + # Attachment header lines (shown above each MIME part in the pager) + color attach_headers cyan default # + + # --- Message Body ----------------------------------------------------- + color quoted blue default # base0D + color quoted1 cyan default # base0C + color quoted2 yellow default # base0A + color quoted3 red default # base08 + color quoted4 magenta default # base0E + color quoted5 blue default # base0D (cycle repeats) + color quoted6 cyan default # base0C + color quoted7 yellow default # base0A + color quoted8 red default # base08 + color quoted9 magenta default # base0E + + color signature green default # base0B + color bold white default # base05 + color underline white default # base05 + + # URLs + color body blue default "([a-z][a-z0-9+-]*://[^ ]+)" + + # PGP + color body green default "(Good signature)" # verified + color body red default "(BAD signature)" # bad + color body brightblack default "^gpg: " # + + # --- Sidebar (was completely unset despite sidebar.enable = true) ----- + set sidebar_format = " %D%* %?N? %N?%?F? %F? " + color sidebar_background default default # transparent, blends with terminal bg + color sidebar_divider white default # base03 (matches old tree color) + color sidebar_ordinary white default # base05 (normal mailbox entries) + color sidebar_new green default # base0B (mailboxes with new mail) + color sidebar_flagged red default # base08 (mailboxes with flagged mail) + color sidebar_spool_file yellow default # base0A (your inbox/spool) + color sidebar_highlight white brightblack # base05 on base03 (cursor position) + color sidebar_indicator default brightblack # default on base03 (currently open mailbox) + ''; +} |
