aboutsummaryrefslogtreecommitdiffstats
path: root/modules/communication/neomutt
diff options
context:
space:
mode:
Diffstat (limited to 'modules/communication/neomutt')
-rw-r--r--modules/communication/neomutt/assertions.nix34
-rw-r--r--modules/communication/neomutt/default.nix427
-rw-r--r--modules/communication/neomutt/theme.nix130
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)
+ '';
+}