1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
|
{ pkgs, ... }:
let
# Nix store paths are world-readable, so accounts.conf (IMAP/SMTP passwords)
# stays hand-managed in ~/.config/aerc and out of this module.
opener = if pkgs.stdenv.isDarwin then "open" else "xdg-open";
# Renders the markdown body of a composed mail into the text/html half of a
# multipart/alternative message (see [multipart-converters] below).
mdToHtml = pkgs.writeShellScriptBin "aerc-md2html" ''
body=$(${pkgs.cmark-gfm}/bin/cmark-gfm \
--extension autolink \
--extension table \
--extension strikethrough \
--extension tasklist)
cat <<HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<style>
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 15px; line-height: 1.5; color: #1a1a1a; background: #ffffff;
max-width: 40em; margin: 0; padding: 0; }
a { color: #2b5cd9; }
h1, h2, h3 { line-height: 1.25; margin: 1.2em 0 0.5em; }
h1 { font-size: 1.4em; }
h2 { font-size: 1.2em; }
h3 { font-size: 1.05em; }
p, ul, ol, table { margin: 0 0 1em; }
blockquote { margin: 0 0 1em; padding: 0 0 0 1em; border-left: 3px solid #d0d0d0; color: #555; }
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em; }
code { background: #f2f2f2; padding: 0.1em 0.3em; border-radius: 3px; }
pre { background: #f2f2f2; padding: 0.8em; border-radius: 4px; overflow-x: auto; }
pre code { background: none; padding: 0; }
table { border-collapse: collapse; }
th, td { border: 1px solid #d0d0d0; padding: 0.3em 0.6em; text-align: left; }
hr { border: none; border-top: 1px solid #d0d0d0; margin: 1.5em 0; }
@media (prefers-color-scheme: dark) {
body { color: #e6e6e6; background: #1a1a1a; }
a { color: #7aa2f7; }
blockquote { border-left-color: #444; color: #aaa; }
code, pre { background: #262626; }
th, td, hr { border-color: #444; }
}
</style>
</head>
<body>
$body
</body>
</html>
HTML
'';
notify =
if pkgs.stdenv.isDarwin then
# -activate focuses Ghostty when the banner is clicked.
"${pkgs.terminal-notifier}/bin/terminal-notifier"
+ " -group \"aerc-$AERC_ACCOUNT\""
+ " -title \"$AERC_ACCOUNT/$AERC_FOLDER\""
+ " -subtitle \"$AERC_FROM_NAME\""
+ " -message \"$AERC_SUBJECT\""
+ " -activate com.mitchellh.ghostty"
else
"${pkgs.libnotify}/bin/notify-send"
+ " \"[$AERC_ACCOUNT/$AERC_FOLDER] $AERC_FROM_NAME\""
+ " \"$AERC_SUBJECT\"";
in
{
home.file = {
".config/aerc/aerc.conf".text = ''
[general]
default-save-path=~/Downloads
pgp-provider=auto
default-menu-cmd=fzf --reverse --prompt='> '
[ui]
styleset-name=tokyonight
mouse-enabled=true
threading-enabled=true
fuzzy-complete=true
keyhint=true
keyhint-delay=300ms
sidebar-width=24
index-columns=flags:4,name<20%,subject,date>=
timestamp-format=2006-01-02 15:04
this-day-time-format=15:04
this-week-time-format=Mon 15:04
this-year-time-format=02 Jan
empty-message=(no messages)
empty-dirlist=(no folders)
[statusline]
column-left=[{{.Account}}] {{.StatusInfo}}
column-center={{.PendingKeys}}
column-right={{.TrayInfo}}
[viewer]
pager=less -Rc
alternatives=text/plain,text/html
show-headers=false
header-layout=From|To,Cc|Bcc,Date,Subject
parse-http-links=true
[compose]
editor=nvim -c 'set ft=mail' -c 'set textwidth=72' -c 'set nolist'
header-layout=To|From,Subject
focus-body=true
empty-subject-warning=true
no-attachment-warning=^[^>]*attach(ed|ment)
[multipart-converters]
text/html=${mdToHtml}/bin/aerc-md2html
[hooks]
mail-received=${notify}
[filters]
text/plain=colorize
text/calendar=calendar
message/delivery-status=colorize
message/rfc822=colorize
text/html=! html
.headers=colorize
[openers]
x-scheme-handler/http*=${opener} {}
x-scheme-handler/mailto=${opener} {}
text/html=${opener} {}
application/pdf=${opener} {}
image/*=${opener} {}
'';
".config/aerc/binds.conf".text = ''
# Part/field navigation is bound twice: C-j/C-k as aerc ships them, plus
# ]p/[p and Alt-j/Alt-k (right Option = Alt, see modules/home/ghostty.nix)
# which keep working if vim-tmux-navigator's tmux side is ever installed,
# since it claims C-h/j/k/l for any pane that is not running vim.
<C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
\[t = :prev-tab<Enter> # Previous tab
\]t = :next-tab<Enter> # Next tab
<C-t> = :term<Enter>
? = :help keys<Enter>
<C-q> = :prompt 'Quit?' quit<Enter>
<C-z> = :suspend<Enter>
[messages]
q = :prompt 'Quit?' quit<Enter>
. = :repeat
j = :next<Enter>
<Down> = :next<Enter>
k = :prev<Enter>
<Up> = :prev<Enter>
<C-d> = :next 50%<Enter>
<C-u> = :prev 50%<Enter>
<C-f> = :next 100%<Enter>
<C-b> = :prev 100%<Enter>
<PgDn> = :next 100%<Enter>
<PgUp> = :prev 100%<Enter>
gg = :select 0<Enter> # First message
G = :select -1<Enter>
zz = :align center<Enter>
zt = :align top<Enter>
zb = :align bottom<Enter>
J = :next-folder<Enter>
K = :prev-folder<Enter>
gi = :cf INBOX<Enter> # Inbox
ga = :cf Archive<Enter> # Archive
gd = :cf Drafts<Enter> # Drafts
gs = :cf Sent<Enter> # Sent
gj = :cf Junk<Enter> # Junk
gt = :cf Trash<Enter> # Trash
gf = :menu -dc fzf :cf<Enter> # Find folder (fzf)
c = :cf<space>
<Enter> = :view<Enter>
v = :mark -t<Enter>
<Space> = :mark -t<Enter>:next<Enter>
V = :mark -v<Enter>
tt = :toggle-threads<Enter> # Threads
ts = :toggle-sidebar<Enter> # Sidebar
tf = :toggle-folder<Enter> # Folder subscription
tc = :toggle-thread-context<Enter> # Thread context
zc = :fold<Enter>
zo = :unfold<Enter>
za = :fold -t<Enter>
zM = :fold -a<Enter>
zR = :unfold -a<Enter>
<tab> = :fold -t<Enter>
C = :compose<Enter>
r = :reply -aq<Enter>
R = :reply -q<Enter>
f = :forward<Enter>
b = :bounce<space>
d = :choose -o y 'Really delete this message' delete-message<Enter>
D = :delete<Enter>
a = :archive flat<Enter>
A = :unmark -a<Enter>:mark -T<Enter>:archive flat<Enter>
M = :move<space>
mm = :move<space> # Move to folder
mc = :copy<space> # Copy to folder
mr = :read<Enter> # Mark read
mu = :unread<Enter> # Mark unread
mf = :flag -t -x flagged<Enter> # Toggle flagged
mU = :unsubscribe<Enter> # Unsubscribe from list
s = :split<Enter>
S = :vsplit<Enter>
| = :pipe<space>
$ = :term<space>
/ = :search<space>
\ = :filter<space>
n = :next-result<Enter>
N = :prev-result<Enter>
<Esc> = :clear<Enter>
<C-c> = :clear<Enter>
Fu = :filter -u<Enter> # Unread
Ff = :filter -x flagged<Enter> # Flagged
Fs = :filter -f<space> # From sender
Ft = :filter -t<space> # To recipient
Fd = :filter -d<space> # Date range
Fc = :clear<Enter> # Clear filter
F/ = :filter<space> # Filter prompt
[messages:folder=Drafts]
<Enter> = :recall<Enter>
[view]
# The viewer forwards every key it does not bind itself to the pager, so
# globals like ? and : never arrive: bind them again here.
$ex = :
? = :help keys<Enter>
/ = :toggle-key-passthrough<Enter>/
q = :close<Enter>
<Esc> = :close<Enter>
o = :open<Enter>
O = :open<space>
S = :save<space>
s = :save -fpa ~/Downloads<Enter>
| = :pipe<space>
d = :choose -o y 'Really delete this message' delete-message<Enter>
D = :delete<Enter>
a = :archive flat<Enter>
f = :forward<Enter>
r = :reply -aq<Enter>
R = :reply -q<Enter>
H = :toggle-headers<Enter>
th = :toggle-headers<Enter> # Headers
<C-j> = :next-part<Enter>
<C-Down> = :next-part<Enter>
<C-k> = :prev-part<Enter>
<C-Up> = :prev-part<Enter>
\]p = :next-part<Enter> # Next part
\[p = :prev-part<Enter> # Previous part
\]m = :next<Enter> # Next message
\[m = :prev<Enter> # Previous message
J = :next<Enter>
K = :prev<Enter>
gx = :menu -lc fzf :open-link<Enter> # Open link (fzf)
gy = :copy-link<space>
[view::passthrough]
$noinherit = true
$ex = <C-x>
<Esc> = :toggle-key-passthrough<Enter>
[compose]
$noinherit = true
$ex = <C-x>
$complete = <C-o>
<tab> = :next-field<Enter>
<backtab> = :prev-field<Enter>
<C-j> = :next-field<Enter>
<C-Down> = :next-field<Enter>
<C-k> = :prev-field<Enter>
<C-Up> = :prev-field<Enter>
<A-j> = :next-field<Enter>
<A-k> = :prev-field<Enter>
<A-p> = :switch-account -p<Enter>
<A-n> = :switch-account -n<Enter>
<C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
[compose::editor]
$noinherit = true
$ex = <C-x>
<C-j> = :next-field<Enter>
<C-Down> = :next-field<Enter>
<C-k> = :prev-field<Enter>
<C-Up> = :prev-field<Enter>
<A-j> = :next-field<Enter>
<A-k> = :prev-field<Enter>
<C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
[compose::review]
# aerc has no "always send HTML" setting, so the send key adds the
# text/html alternative first; Y sends the plain text part on its own.
y = :multipart text/html<Enter>:send<Enter> # Send (HTML + plain text)
Y = :send<Enter> # Send plain text only
h = :multipart text/html<Enter> # Add HTML alternative
H = :multipart -d text/html<Enter> # Drop HTML alternative
n = :abort<Enter> # Abort (discard message, no confirmation)
s = :sign<Enter> # Toggle signing
x = :encrypt<Enter> # Toggle encryption to all recipients
v = :preview<Enter> # Preview message
p = :postpone<Enter> # Postpone
q = :choose -o d discard abort -o p postpone postpone<Enter> # Abort or postpone
e = :edit<Enter> # Edit (body and headers)
a = :attach<space> # Add attachment
A = :menu -c 'fzf --multi' :attach<Enter> # Add attachment (fzf)
d = :detach<space> # Remove attachment
[terminal]
$noinherit = true
$ex = <C-x>
<C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
'';
# tokyonight-storm, matching Ghostty (modules/home/ghostty.nix). Background
# is left at the terminal default so the panes blend with the window.
".config/aerc/stylesets/tokyonight".text = ''
*.default=true
*.normal=true
default.fg=#c0caf5
error.fg=#f7768e
warning.fg=#ff9e64
success.fg=#9ece6a
title.fg=#1f2335
title.bg=#7aa2f7
title.bold=true
header.fg=#7aa2f7
header.bold=true
realname.fg=#c0caf5
email.fg=#565f89
subject.fg=#c0caf5
subject.bold=true
date.fg=#565f89
tab.fg=#545c7e
tab.bg=#1f2335
tab.selected.fg=#c0caf5
tab.selected.bg=#292e42
tab.selected.bold=true
border.fg=#292e42
border.bg=#292e42
spinner.fg=#7dcfff
msglist_default.fg=#a9b1d6
msglist_unread.fg=#c0caf5
msglist_unread.bold=true
msglist_read.fg=#737aa2
msglist_answered.fg=#bb9af7
msglist_forwarded.fg=#9d7cd8
msglist_flagged.fg=#e0af68
msglist_flagged.bold=true
msglist_deleted.fg=#565f89
msglist_deleted.dim=true
msglist_result.fg=#9ece6a
msglist_result.bold=true
msglist_thread_folded.fg=#7dcfff
msglist_thread_context.fg=#565f89
msglist_thread_orphan.fg=#ff9e64
msglist_marked.fg=#1f2335
msglist_marked.bg=#7dcfff
msglist_gutter.fg=#292e42
msglist_pill.fg=#1f2335
msglist_pill.bg=#7aa2f7
msglist_*.selected.bg=#292e42
msglist_*.selected.bold=true
dirlist_default.fg=#a9b1d6
dirlist_unread.fg=#7aa2f7
dirlist_unread.bold=true
dirlist_recent.fg=#7dcfff
dirlist_*.selected.bg=#292e42
dirlist_*.selected.bold=true
statusline_default.fg=#c0caf5
statusline_default.bg=#1f2335
statusline_error.fg=#f7768e
statusline_error.bold=true
statusline_warning.fg=#ff9e64
statusline_success.fg=#9ece6a
statusline_success.bold=true
completion_default.fg=#a9b1d6
completion_default.selected.bg=#292e42
completion_description.fg=#565f89
completion_gutter.bg=#1f2335
completion_pill.bg=#7aa2f7
keyhint_default.bg=#1f2335
keyhint_key.fg=#7dcfff
keyhint_key.bold=true
keyhint_desc.fg=#a9b1d6
part_switcher.bg=#1f2335
part_filename.fg=#7dcfff
part_mimetype.fg=#565f89
part_*.selected.bg=#292e42
selector_default.fg=#a9b1d6
selector_focused.fg=#c0caf5
selector_focused.bg=#292e42
selector_chooser.bold=true
[viewer]
url.fg=#7dcfff
url.underline=true
header.fg=#7aa2f7
header.bold=true
signature.fg=#565f89
signature.dim=true
diff_meta.fg=#c0caf5
diff_meta.bold=true
diff_chunk.fg=#7dcfff
diff_chunk_func.fg=#7dcfff
diff_chunk_func.bold=true
diff_add.fg=#9ece6a
diff_del.fg=#f7768e
diff_whitespace.bg=#f7768e
quote_1.fg=#7aa2f7
quote_2.fg=#bb9af7
quote_3.fg=#7dcfff
quote_4.fg=#565f89
quote_x.fg=#565f89
quote_x.dim=true
code.fg=#9ece6a
'';
};
}
|