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
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
|
#+TITLE: Cool Emacs @ mms site
#+AUTHOR: Michał Sapka
#+URL: https://michal.sapka.me
#+STARTUP: show2levels indent logdone
#+HUGO_BASE_DIR: ~/ghq/michal.sapka.me/mms/site
#+HUGO_WEIGHT: auto
#+HUGO_SECTION: emacs
* CE :@emacs:@emacs:
:PROPERTIES:
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :image_dir "emacs" :image_max_width 480
:EXPORT_HUGO_PAIRED_SHORTCODES: image tableofcontent menu
:END:
** DONE Cool Emacs
CLOSED: [2024-06-15 Sat 21:52]
:PROPERTIES:
:EXPORT_FILE_NAME: _index
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :primary_menu emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :shortname Cool usages of Emacs
:EXPORT_HUGO_MENU: :menu emacs :post "Emacs usage that is not text editing"
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/cool-emacs/)
:END:
#+attr_shortcode: :file cool-macs.png
#+attr_shortcode: :alt An Emacs logo in cool glassess doing a skateboard flip
#+attr_shortcode: :class pull-right
#+attr_shortcode: :forced_width 350
#+attr_shortcode: :transparency yes
#+begin_image
noop
#+end_image
#+begin_quote
/Let me tell you: Emacs is not a text editor./
-- Emerald McS., PhD
#+end_quote
Even though most of what Emacs is known for is /editing text/, it can do so much more.
It's the most flexible application out there, so when you start to adjust the basics for yourself, you want to use it everywhere.
Here, I will present /Emacs/ as a general purpose interface.
*** Cool ways to use Emacs
#+attr_shortcode: "cool-emacs-ways"
#+begin_menu
Dune
#+end_menu
*** Appendix
#+attr_shortcode: "cool-emacs-appendix"
#+begin_menu
Dune
#+end_menu
*** Coolmacs
The mascot, Coolmacs, was drawn by [[https://drewsh.com/][Drew]].
** Ways
:PROPERTIES:
:EXPORT_HUGO_MENU: :menu cool-emacs-ways
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :primary_menu cool-emacs-ways
:END:
*** DONE Read RSS with Elfeed
CLOSED: [2023-05-19 Wed 23:00]
:PROPERTIES:
:EXPORT_FILE_NAME: elfeed
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract Setting up config inside an org file
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/2023/moving-my-rss-reading-to-emacs-with-elfeed/ /emacs/moving-my-rss-reading-to-emacs-with-elfeed/ /cool-emacs/elfeed-literate-config/)
:END:
Since Emacs became my shell of choice[fn:1], I am abandoning more and more dedicated applications in favor of different packages.
As it turns out, Emacs packages are very feature rich.
This time: I moved my RSS reading from newsboat[fn:2] to elfeed[fn:3].
Elfeed has very simple keybindings:
- g will refresh the items list
- G will refresh the items list and fetch new items
- r will mark currently selected item is read (remove unread tag)[^4]
- b will open item in the browser
One huge upside of elfeed compared to newsboat is image support.
Emacs is a GUI application, so all images are present in their glory!
#+attr_shortcode: :file elfeed-list.png
#+attr_shortcode: :alt An Emacs screenshot showing a list using a dark mode
#+attr_shortcode: :class centered
#+begin_image
List of articles
#+end_image
#+attr_shortcode: :file elfeed-details.png
#+attr_shortcode: :alt An Emacs screenshot showing Elfeed in a dark mode with a vintage PC case in the middle
#+attr_shortcode: :class centered
#+begin_image
Images!
#+end_image
My setup is near stock.
I have a few dozen feeds that are auto-tagged.
Three essential tags are "important", "news", and "company".
I want to read each "important", then I want to see all normal, and finally I can just skim "news" and "company".
Adding auto-tagging is very simple: just define the tag when defining the RSS feed list:
#+BEGIN_SRC emacs-lisp
("https://rubenerd.com/feed/" blog important)
("https://www.pine64.org/feed/" company)
#+END_SRC
Now, each new article will be tagged with matching tags.
Elfeed allows to define of custom faces that will be applied to items matching tag[fn:6]:
#+BEGIN_SRC emacs-lisp
(defface important-elfeed-entry
'((t :foreground "#f77"))
"Marks an important Elfeed entry."
:group 'elfeed)
(defface nonimportant-elfeed-entry
'((t :foreground "#C0C0C0"))
"Marks an nonimportant Elfeed entry."
:group 'elfeed)
(push '(important important-elfeed-entry)
elfeed-search-face-alist)
(push '(company nonimportant-elfeed-entry)
elfeed-search-face-alist)
(push '(news nonimportant-elfeed-entry)
elfeed-search-face-alist)
#+END_SRC
Now important items will be dark red, while company & news will be dark gray
#+attr_shortcode: "elfeed-list.png"
#+begin_img-c
No important things to read at this moment.
#+end_img-c
Elfeed has a few packages expanding its functionality, but I found the default behavior to be exactly right.
[fn:1] [[/2023/emacs-as-a-shell/][Emacs as a Shell]]
[fn:2] [[https://newsboat.org/][Newsboat homepage]]
[fn:3] [[https://github.com/skeeto/elfeed][Elfeed repository on Github]]
[fn:4] The "r" key messes with my muscle memory, as notmuch[^5] uses "ku" for the same action while "r" will start composing a reply.
[fn:5] [[https://d-s.sh/tags/notmuch][Posts tagged about notmuch]]
[fn:6] my elisp-fu not good
**** Elfeed-org
One of the packages expanding capabilities of elfeed is elfeed-org[fn:elf-org].
It allows configuring the list of feeds with a standard org tree.
Since my config is now also an org file, nothing stops me from adding the list as an org-tree inside my config org-file! I set it up via:
{{<highlight lisp "linenos=table,linenostart=199,hl_lines=7">}}
#+BEGIN_SRC org
,*** elfeed-org
,#+BEGIN_SRC emacs-lisp
(use-package elfeed-org
:ensure t
:config
(setq rmh-elfeed-org-files (list "~/.emacs.d/config.org"))
(elfeed-org))
,#+END_SRC
#+END_SRC
Therefore, I am now pointing at the same file to become the data source for elfeed-org as the rest of my config.
Just a few lines down, I start to define my list of subscriptions:
#+BEGIN_SRC org
,*** Feeds :elfeed:
,**** Blogs
,***** https://gideonwolfe.com/index.xml :important:
,***** https://fabiensanglard.net/rss.xml. :important:
,**** Emacs
,***** https://protesilaos.com/master.xml :important:
#+END_SRC
Much more readable! Elfeed-org will ignore the entire outer tree and extract the feeds from leaves under the =:elfeed:= tag.
[fn:elf-org] https://github.com/remyhonig/elfeed-org
*** DONE Read email with Notmuch
CLOSED: [2023-07-03 Wed 23:00]
:PROPERTIES:
:EXPORT_FILE_NAME: notmuch
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :abstract My email based workflow for GitHub Pull Review Requests
:EXPORT_HUGO_MENU_OVERRIDE: :name "Reading and automating email using Notmuch"
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/2023/notmuch/ /emacs/notmuch/ /cool-emacs/read-email-notmuch)
:END:
Web email interfaces have taken over the world a long time ago.
Except for Outlook users, only a few people even consider using an actual application for it.
This is one of the primary reasons for our low opinion of electronic mail today, since with limited tools, you get limited capabilities.
While there are options for automatic filtering, tagging, or folding with most clients, those capabilities are barebones.
Luckily there are still compelling alternatives.
**** Managing email locally
Back in the day of POP, this was the standard: your computer downloaded new messages from the server, and you had local copies from this point.
Then IMAP came, and after it, Exchange.
Now you have a fast and convenient option to sync all changes with other clients and not bother with hard drive space.
But this also puts us at the mercy of email providers.
If a given service doesn't support tags, folders, or saved searches, you cannot use it.
I tried to reverse this direction and never looked back.
I want all my email to be on my machine, available at all times, and accessible instantly.
Mbsync(1)[fn:mbs] is a popular application to populate local ~/Mail folder with remote messages fetched via IMAP.
The config is quite simple. For Gmail[fn:gmail][fn:frosty]:
#+BEGIN_SRC shell -r -n
MaildirStore gmail-local (ref:local-start)
Path ~/Mail/gmail/
Inbox ~/Mail/gmail/INBOX
Subfolders Verbatim (ref:local-end)
IMAPStore gmail-remote (ref:remote-start)
Host imap.gmail.com
SSLType IMAPS
AuthMechs LOGIN
User <username>
PassCmd <password> (ref:remote-end)
Channel gmail (ref:chan)
Far :gmail-remote:
Near :gmail-local:
Create Both
Expunge
Patterns * !"[Gmail]/All Mail" !"[Gmail]/Important" !"[Gmail]/Starred" !"[Gmail]/Bin"
SyncState *
#+END_SRC
Then type =mbsybc -a=, wait for a few (hundred? depending on the mailbox size) minutes, and voila.
Your =
~/Mail/gmail/= is now populated with all your messages. Let's break down the config.
Mbsync(1) assumes two stores exist: local (on your computer) and remote (the IMAP server).
A store is a place where mail exists.
We have then configured in lines [[(local-start)]]-[[(local-end)]] and [[(remote-start)]]-[[(remote-end)]].
The remote one is self-explanatory.
One thing to remember: some providers will require you to use an app-specific password and reject auth attempts with the normal one.
Our password in line 11 can be either a string with the password or an arbitrary command (think =cat ~/my-secret-password= or a CLI password manager).
The local store is just a definition of local folders to use.
It can be anywhere, but =~/Mail= is the standard, anxd many mail clients will assume that you store your email there.
On line [[(chan)]], we start to define a channel, which is how mbsync(1) works.
One store is =far= (remote), while the other is =near= (on your machine).
The rest of the config defines behavior.
Refer to the manual, but in my case:
- it will create non-matching mailboxes
- it will delete messages in a store if a message was deleted on the other
- it will sync all messages except a few matching the pattern.
- it will store the synchronization state file in the Mail dir.
One last thing to add is a simple cron rule.
You can force mail fetching manually, but I opted for the automatic option.
Therefore, my crontab(1) has:
#+BEGIN_SRC cron
1/5 * * * * killall mbsync &>/dev/null; <msync_dir>/mbsync -a -V 2>&1 > ~/.mbsync_log
#+END_SRC
We will now fetch new messages every 5 minutes.
[fn:mbs] [[https://isync.sourceforge.io/mbsync.html][mbsync on sourceforge]]
[fn:gmail] I have my gripes with Gmail, but it is still the standard.
While this article assumes Gmail, you can easily use mbsync(1) with any IMAP service.
[fn:frosty] Originally I took the config from [[https://frostyx.cz/posts/synchronize-your-2fa-gmail-with-mbsync][Frosty]]
**** Local clients
Now we need to choose a local email client to use.
There is a lot to choose from! Thunderbird[fn:thunderbrid] seems to be the most popular option for GUI users.
For terminal users, we have Mutt(1)[fn:mutt], its successor Neomutt(1)[fn:neomutt], and many, many more.
I used Neomutt(1)[fn:luke] for a while, and it was a pleasure compared to web clients.
However, ever since I started to use Emacs more, I wanted to move my Email inside Emacs.
And to little surprise, we also have a lot to choose from.
By default, Emacs comes with Gnus[fn:gnus], a newsgroups reader with an email client capability.
However, the two most popular packages are Mu4e[fn:mu4e] and notmuch(1)[fn:notmuch]
The last two are based on fast email indexing and searching but assume different workflows.
Mu4e is based on filtering, while notmuch around tagging.
A friend[fn:alex] recommended Notmuch(1) to me.
I have never tried mu4e as notmuch fully satisfies my needs.
[fn:thunderbrid] [[https://www.thunderbird.net/][Thunderbird website]]
[fn:mutt] [[https://www.mutt.org/][Mutt website]]
[fn:neomutt] [[https://neomutt.org/][Neomutt website]]
[fn:luke] Luke Smith has a few excellent vlogs about Mutt/NeoMutt like [[https://www.youtube.com/watch?v=2jMInHnpNfQ&t=683s)"][Email on the terminal with mutt]]
[fn:gnus] [[https://www.gnu.org/software/emacs/manual/html_node/gnus/][Gnus on gnu.org]]
[fn:mu4e] [[https://www.djcbsoftware.nl/code/mu/][mu4e website]]
[fn:notmuch] [[https://notmuchmail.org/][notmuch website]]
[fn:alex] Thanks, [[https://se30.xyz/][Alex]]
**** Setting up notmuch(1)
Notmuch(1) is not a client but an indexer.
It indexes and tags existing email and allows one to search messages.
It should be present on most systems package management, so install it.
They run =notmuch=, answer a few questions, and you've got yourself a ready notmuch.
Whenever a new mail arrives, it won't be known to notmuch before indexing.
You can manually run =notmuch new= or make a cron definition for it.
One killer feature of notmuch is its sheer speed.
The name comes from the fact that it can work on gigantic mailboxes very swiftly - oh, you have one million messages? That's not much!
Let's try a simple search:
#+BEGIN_SRC shell
$ notmuch search 'from:*@github.com'
#+END_SRC
You can search based on sender, receiver, dates, attachments, contents, titles, etc.
Refer to man pages for =notmuch-search-terms(7)=.
However, to get the most out of notmuch, we need to learn about tags.
Let's add a "gh" tag for messages from Git Hub.
#+BEGIN_SRC shell
$ notmuch search 'from:*@github.com'
$ notmuch tag "+gh" -- "from:@github.com"
#+END_SRC
Now, we can search for such messages
#+BEGIN_SRC shell
$ notmuch search "tag:gh"
#+END_SRC
We can also join multiple search criteria with "and", "or" and other boolean operators.
We now have a fully working local email reader - however we can not send email.
I will not discuss sending email here as it's a separate subject. Notmuch(1) is not for sending email.
Using CLI for reading email is far from pleasant.
All those commands will come in handy, but first, let's add a user interface.
**** Notmuch(1) in Emacs(1)
Notmuch(1) can be used with different UIs, like Mutt(1).
However, it comes with an Emacs(1) package, so let's enable it.
#+BEGIN_SRC emacs-lisp
(use-package notmuch
:commands notmuch-hello
:bind (("C-c m" . notmuch-hello)))
#+END_SRC
The key binding "C-c m" is very popular, but you can use whatever you want.
After running =notmuch-hello=, we are not greeted with a list of messages but with a search interface.
You've got access to saved searches, recent searches, and a list of tags.
This tells us that we are dealing with a completely different beast than webmail, and the user needs to think of new workflows.
The way we are thought of thinking of email is a list of messages.
Some clients can mark messages that are more important, favorite, tag them, move them into folders, etc.
But everywhere I know, the primary interface is just a list - unread email on top, read on the bottom.
The behavior I always expect is to open (or mark as read) all incoming messages and then ignore most of them.
If you spent some time on configuration, maybe you have automatic rules - like moving all newsletters to a "newsletter" folder and removing them from your inbox.
Hey[^hey] is even based on separating all incoming messages into three tiers: important mail, newsletters, and paper trial.
[^hey]: [Hey email website](https://www.hey.com/). It's the best email service I know, but it doesn't allow any external clients, and the pricing is unacceptable for me.
But back to notmuch.
Look at saved searches - we will use them later.
Open "unread," and we see a semi-normal list of messages.
Use "n" and "p" to select email, enter to open it, and so on - standard Emacs stuff.
One thing to remember is that by default, reading an email will not mark it as read.
You need to manually remove the tag via =k u= - either one by one, or on all messages in a selected region (C-spc, it's still a buffer, after all).
"Unread" here is just another tag.
We can be much smarter with marking actionable items.
**** Automatic Github Pull Review workflow
What we've seen before is nothing more than a normal email client with extra steps.
We read emails in Emacs(1), which is great, but we don't get anything extra.
It's time for a real-world example.
I am a software engineer forced to work with GitHub.
One thing I do is to review pull requests.
The primary problem here is knowing that someone wants me to review something.
The review itself is the easy part :-)
I rely solely on email for this information, ostensibly ignoring all nudges on Slack[^slack].
[^slack]: Back when Slack was first sold, it was proposed not only as a chat tool but also as a single place for all information.
We see it now: we connect Slack to everything - GitHub, jira, Data Dog, or pager duty.
The general idea is great, but Slack is a pretty mediocre application.
The only way to manage what you receive is to leave a channel.
But then you lose all other messages sent there, so the price may be high.
First of all, we need to enable email notifications from GitHub.
Remember to mark that you want to get emails about your own actions.
Now, let's think about what we want to achieve.
For me it is "I want to know about all the pull requests I should look at without opening browser[fn:gh-cli]".
This means I want to see all the review requests I was assigned (personally or by being part of a team) that I have not yet reviewed.
And it can achieve the same.
However, let's ignore it for now, as the same model of email-based dashboards can be expanded to many other things.
Luckily, GitHub allows us to get that from email:
1. When you are first assigned a review, you get a dedicated email
2. When you approve or reject a PR, you get an email
3. When someone asks you to re-review an email, you get the same email as it was the first request for this PR.
We now know that we can use Notmuch(1).
There are two ways: we can use =notmuch-hooks(5)= and place a shell script in =~/Mail/.notmuch/hooks/post-new, but it never worked reliably for me.
Instead, I have a cron job that runs a script:
#+BEGIN_SRC shell -n
#!/usr/bin/env sh
notmuch tag +gh -unread -- '(from:notifications@github.com)'
# Mark new review requests
for thread in $(notmuch search --sort=oldest-first --output=threads -- "\"requested your review on\" and tag:gh and -tag:gh-pr-done"); do
for msg in $(notmuch search --sort=oldest-first --output=messages -- "$thread"); do
txt=$(notmuch show "$msg")
(echo "$txt" | grep "requested your review on") && notmuch tag +gh-pr-todo -- "$thread"
(echo "$txt" | grep "@michalsapka approved this pull request") && notmuch tag -gh-pr-todo -- "$thread"
(echo "$txt" | grep "@michalsapka requested changes on this pull request") && notmuch tag -gh-pr-todo -- "$thread"
(echo "$txt" | grep "Merged.*into") && notmuch tag -gh-pr-todo +gh-pr-done -- "$thread"
done
done
#+END_SRC
Let's break it down:
1. First, we add a "gh" tag to all notification emails from GitHub and remove the "unread" tag.
I don't need to be notified about all such emails, but I can still look at the "gh" tag if needed.
2. Then we search for threads where an email informs me about a review request.
I limit the search to emails from GitHub via the tag from #1 and those without "gh-pr-done" tag. More on the second one in a moment
3. Then I search for all messages in such threads.
I force order as oldest-first to make it possible to reason about.
In normal PR, all actions happen with a significant delay between them, so this should be enough not to get lost in the timeframe.
If I ask for a change in review, the re-request will not happen instantly.
Note that I get the email body as a variable on line 8.
4. Then comes the meat.
I will tag the entire thread multiple times based on the body of the message.
When a request comes, a "gh-pr-todo" is added.
I need to look at it.
When I approve or reject a PR, the tag is removed.
If someone asks for a review, logic from line 10 will be triggered, and the tag will be added again.
This means that I want to handle all email threads with the "gh-pr-todo" tag.
6. Lastly, when a PR is merged, I ensure that the "gh-pr-todo" tag is removed, and I add the "gh-pr-done" tag so this thread will not be found in step 2 in the future.
There are other ways to tag, like afew(1)[fn:afew], but keeping it to simple shell script working with notmuch(1) directly gives us the greatest amount of freedom and made it easier for me to tell this story.
[fn:afew] [[https://github.com/afewmail/afew][Afew on Github]]
[fn:gh-cli] There is the GitHub CLI which is amazing by itself - one of the best things that GitHub has done in the last few years.
**** Making it more visible
This alone would be a challenge to manage.
An email with a tag would be easily missed.
Notmuch has us covered yet again! My emacs config has a few dedicated lines:
#+BEGIN_SRC emacs-lisp -n
(setq notmuch-search-line-faces
'(("gh-pr-todo" . ((t :foreground "#f77"))))
notmuch-saved-searches
;; other saved searches omitted
( :name "GitHub[reviews]"
:query "tag:gh-pr-todo"
:sort-order newest-first))
#+END_SRC
This makes two changes.
1. Firstly, all messages with "gh-pr-todo" will be shown in red in any email list.
All red items are actionable since we remove this tag in the workflow.
2. Secondly, amongst other saved searches, I have one dedicated to PRs.
With those two things, every time I enter =notmuch-hello= screen, I get instant information about the work I need to do.
**** Making it extra visible
But we can go one step further.
Prot's[fn:prot] excellent notmuch-indicator[fn: not-ind] allows us to add saved searches to the mode line.
After installing it, the configuration is straightforward:
#+BEGIN_SRC emacs-lisp
(notmuch-indicator-mode 1)
(setq notmuch-indicator-refresh-count 60
notmuch-indicator-hide-empty-counters t
notmuch-indicator-args
'((:terms "tag:gh-pr-todo":label "pr:")))
#+END_SRC
This adds a "pr:<count>" to the mode line.
The count is the number of messages, not threads, but frankly, I want it to be 0.
The counter will be refreshed every 60 seconds.
And lastly, if the count is 0, the label will not be added to the mode line.
[fn:prot] [Prot's website](https://protesilaos.com/)
[fn:not-ind] [notmuch-indicator repository](https://git.sr.ht/~protesilaos/notmuch-indicator)
**** The downsides
Notmuch comes with one significant downside: lack of multi-device support.
It's 2023, and most of us have more than one computer and those pesky mobile phones.
As for the mobile - I have no solution.
The read statuses will sync via mbsync(1), but not much else.
I try to purge myself from phone addiction, so maybe that's a plus?
As for the other computers, we have muchsync(1)[fn:muchsync].
It's an external application designed to sync entire mailboxes and tags between devices over ssh.
I have not tried it yet[fn:boundries], but it looks promising.
[fn:muchsync] [[https://www.muchsync.org/][Muchsync homepage]]
[fn:boundries] my work computer gets all work messages, and my private one gets all private ones—complete separation. When I get a second personal machine, I will set it up, but for now, there is no use case for me.
**** Summary
With local email and tools like notmuch(1), we are not at the mercy of external tools for even sophisticated workflows. If you get transactional emails, you can extract actionable data. It can be JIRA tickets, Pager Duty alerts, heck - even Amazon deliveries. Here I have demonstrated how easy it is to leverage notmuch(1), simple shell script, and emacs(1) to have a fully automated notification setup. It does not try to hijack your attention (like mobile notifications do) and is not hidden on some webpage (like GitHub notification), but it still gives actionable results. And all that without leaving the comfort of Emacs.
One cool thing I plan to apply soon is integrating notmuch(1) with Org-mode with the ol-notmich[oln] package. But for now, I am in the process of moving as many external services to a similar workflow as possible.
[^oln]: [ol-notmuch on sourcehut](https://git.sr.ht/~tarsius/ol-notmuch)
*** DONE Watch YouTube with Yeetube and mpv
CLOSED: [2024-02-23 Fri 16:16]
:PROPERTIES:
:EXPORT_FILE_NAME: yeetube
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract Let's use YouTube from the comfort of Emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/emacs/watching-youtube-with-emacs/ /cool-emacs/watch-youtube)
:END:
I may hate YouTube as a service, but I even I can't deny the quality of vlogs there.
Even though I would strongly prefer to follow folks on PeerTube or Odysee, this will not happen anytime soon for most of the channels.
Luckily, with the popularity of YouTube comes quite a few ways to use it in a better way
**** Yeetube
/Yeetube/[fn:yeetube] is an Emacs wrapper around searching and viewing videos on Youtube.
The watching part happens via =mpv=[fn:mpv].
You simply pass the video link (not the page) in the shell and =mpv= will handle the rest.
/Yeetube/ handles everything /before/ we have the actual file, and running =mpv=.
First, let's install it:
#+begin_src emacs-lisp
(use-package yeetube)
#+end_src
And, assuming =mpv= is already installed, you are ready to go.
Run =yeetube-search=, type in whatever you want and press enter.
A table with top results will show up.
Now, select the one that interests you, run =yeetube-play=, wait a few seconds and mpv window will show.
#+attr_shortcode: :file emacs-yeetube-search.png
#+attr_shortcode: :alt An Emacs screenshot showing a list using a dark mode
#+attr_shortcode: :class centered
#+begin_image
Yeetube search for Emacs Elements
#+end_image
#+attr_shortcode: :file emacs-yeetube-play.png
#+attr_shortcode: :alt A screenshot of a DWM windows manager. Left of the screen is ocupied by MPV, the right one with Emacs.
#+attr_shortcode: :class centered
#+begin_image
mpv playing a movie next to Emacs with Yeetube search.
#+end_image
/Yeetube/ also supports downloading videos via =yt-dl= and saving videos for future reference.
My full config, with evil keybindings looks like:
#+begin_src emacs-lisp
(use-package yeetube
:general
(:states 'normal
:keymaps 'yeetube-mode-map
"RET" 'yeetube-play
"d" 'yeetube-download-video
"b" 'yeetube-play-saved-video
"B" 'yeetube-save-video
"x" 'yeetube-remove-saved-video
"/" 'yeetube-search
"0" 'yeetube-toggle-video
))
#+end_src
Note that this comes with no ads, and less tracking, but also less revenue to the creator.
Being a patron is a good way to feel better about it.
[fn:yeetube] [[https://thanosapollo.org/post/yeetube/]["yeetube | Emacs Front-End for YouTube"]] blog post from the author
[fn:mpv] [[https://mpv.io/][mpv official website]]
**** Link handler
This is nice, but we can make it /extra-nice/.
I subscribe to quite a few YouTube channels via RSS[fn:ytrss] and want to use /Yeetube/ fast.
We can write a very simple /elisp/ function:
#+begin_src emacs-lisp
(defun mms-open-yt-under-point ()
(interactive)
(setq url (thing-at-point 'url))
(string-match "youtube.com" url)
#+end_src
Now, move the pointer on a YT[fn:providers] link and call this function.
/Yeetube/ interface will open, so just play the top result.
My (current) full function in use is:
#+begin_src emacs-lisp
(defun mms-open-link-under-point ()
(interactive)
(let (url (thing-at-point 'url))
(cond
((string-match "youtube.com" url) (yeetube-search url))
(t (eww url)))
))
#+end_src
So it will use =eww= for anything that is not a YT link, and I can expand it further whenever I want.
Then, just add a simple keyboard navigation, and you're done
#+begin_src emacs-lisp
(mms-leader-keys
"RET RET" '(lambda () (interactive) (mms-open-link-under-point) :wk "follow link"))
#+end_src
**** Errata
2024-02-26: Dave pointed me that using =let= inside custom method is preferable to =setq=
[fn:ytrss] The secret URL: =https://www.youtube.com/feeds/videos.xml?channel_id=<channel_id>=.
You can find the channel ID using different online services, as it is not as straight forward as it should be.
[fn:providers] Only if the package registers itself as a provider for =thing-at-point=.
In Elfeed it will for main item URL, but not for items embedded in the body.
We need to use =eww= to open the page and we can get the URL from there.
*** DONE Play Interactive Fiction with Malyon
CLOSED: [2024-06-20 Thu 22:38]
:PROPERTIES:
:EXPORT_FILE_NAME: malyon
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract Play classic text adventures with Emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :alias '(/cool-emacs/interactive-fiction-malyon)
:END:
#+begin_quote
The IFTF defines interactive fiction—IF, for short—as a kind of video game where the player’s interactions primarily involve text.
Under this broad definition, we can find decades of IF work taking many interesting and innovative forms.
Parser-based IF, also known as the “text adventure” genre, represents one of the oldest and best-known forms of interactive fiction.
Some early examples are digital games from the 70s and 80s like Zork and Enchanter.
In a parser game, players type natural-language commands into a simulated world, and the game interprets them as actions for the story’s main character to carry out.
Parser games often involve puzzles that the player must solve to move forward in the story.
-- [[https://iftechfoundation.org/frequently-asked-questions/][Interactive Fiction Technology Foundation]]
#+end_quote
Games?
With text?
And thinking?
Sounds like a great match for Emacs!
Text and your favorite color scheme and keybindings.
**** Z-machine
Interactive Fiction (aka /IF/) is overengineered since it's inception.
/Infocom/, the company behind Zork, created a virtual machine (/Z/), just to make it easier to port their games to different architectures.
The machine is stil alive today, with vibrant community pushing new titles all the time.
It was also ported to Emacs, with the /malyon/ package.
#+attr_shortcode: :file malyon-zork.png
#+attr_shortcode: :alt An Emacs screenshot showing first few moves in Zork games
#+attr_shortcode: :class centered
#+begin_image
Zork in Emacs
#+end_image
Intallation is very straight forward, just
#+begin_src emacs-lisp
(use-package malyon
:ensure t)
#+end_src
And we're ready to play!
Call =malyon= to open a games, =malyon-restore-file= to restore a previously saved game, and =malyon-quit= if the package breaks.
=malyon-restore= will activate buffer with game in progress.
You can save the gamy any time issuing the =save= command to the interpreter - that is in /the game/, not in =M-x=.
**** Formats
Malon supports Z-machine based games only, and only three versions (z3, z5, z8).
You can determine version of Z-machine of a game, as the extension will match the version.
The extremey popular =gblorb=, which supports things like embedded graphics, unfortunatelly is not supported.
**** Getting games
You can a lot (over 8,5k) Interactive Fiction titles on [[https://ifdb.org/][Interactive Fiction Database]].
Be sure to check compatibility!
**** Other resources
- [[http://www.brasslantern.org/beginners/index.html][Beginner Resources]] on Brass Lantern. Great place to start.
- [[https://archive.org/details/getlamp-interviews][Get Lamp]] on Internet Archive. An amazing documetary on the early days of IF.
** Appendix
:PROPERTIES:
:EXPORT_HUGO_MENU: :menu cool-emacs-appendix
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :primary_menu cool-emacs-appendix
:END:
*** DONE My "whys" for Emacs
CLOSED: [2023-12-26 Tue 22:12]
:PROPERTIES:
:EXPORT_FILE_NAME: whys-of-emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract My reasons for using Emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/cool-emacs/whys-of-emacs/)
:END:
I have used many editors over the years.
I've started with Notepad, then moved to Notepad++, Sublime Text, IntelliJ, and VS Code.
Heck, I even Nova at some point.
But only two really stayed with me for longer - the first being Vim, the second Emacs.
Choosing Vim[fn:nvim] seems like the logical option[fn:popular].
It's an extremely popular editor with a huge community.
But first: I *strongly* believe, that everyone working with computer, owe to themselves to spend some quality time with both, Vim, and Emacs.
Vim is amazing.
Modal editing is second to none when it comes to natural text processing.
They keybindings are close to perfect.
It requires some time to get used to, but let's not overstate that.
You can get semi-fluent in Vim in 2 weeks.
And then there's Emacs.
I've tried it for a year.
I like it, but something felt off[fn:off].
Then, for a few months I have returned to using Neovim.
But here I am.
Using Emacs once again.
Why is that?
[fn:popular] amongst specific crowd at least.
But since you are here, it's quite possible that you are one of us.
[fn:nvim] I don't distinguish between Vim and Neovim here.
[fn:off] mostly keybindings. I am now armed with Evil mode, the best replication of Vim in any editor.
**** License
VSCode is released under non-free license[fn:vsclicense], and therefore the code you can see is not necessary what you run.
In fact we know this, as VSCode spies on users[fn:vsctelemetry], so it's an instant No for me.
[[https://vscodium.com/][Codium]] exists as /libre/ version of VSCode, so it's an option.
However, it is not the same program as VSCode as incompatibilities exist, and the presence of plugins is limited[fn:codium].
Any advancement of Codium is to the benefit of VSCode, and I hove problems with this.
IntelliJ is completely closed-source, so it's also a no-go for me.
Emacs[fn:gnu] is released on GPL[fn:emacsgpl]
Vim is released under custom /libre/ license[fn:vimlicense].
Neovim is released under Apache license[fn:nvimlicense]
This means that the software is truly free.
I can see the entirety of codebase, not just the Lite version (like with VSCode).
I can redistribute it, I can copy, and share it without any limitations.
I can fork it, I can change it.
I can do almost whatever the hell I want it - as this was the basis of FSF.
This may be not important to you, but it is for me.
I believe /libre/ software is a necessity.
Some people prefer /proprietary/ programs - some even don't see problems with /software as a service/.
But only /libre/ software allows for betterment of our /craft/.
Only by being able to freely modify and share software are we able to make the /craft/ better for future generations[fn:libre].
I am paying for free software without any second thoughts by donating to some foundations.
Yes, I make my living creating /properties/ software, but I try to give back.
I refuse to /choose/ non-free software.
I am forced to use some, but whenever there is a choice, I will choose /free/.
This leaves Vim and Emacs as two the two /important/ and /libre/ editors.
And with this, I have quickly removed almost all competition.
Now, how did I choose between those two?
[fn:gnu] I refer to "GNU Emacs" as "Emacs".
[fn:vsclicense] https://code.visualstudio.com/license
[fn:vsctelemetry] "The software may collect information about you and your use of the software, and send that to Microsoft"[fn:vsclicense]
[fn:codium] https://ruky.me/2022/06/11/im-switching-form-vs-code-to-vs-codium/
[fn:emacsgpl] https://www.gnu.org/philosophy/free-sw.html
[fn:vimlicense] https://github.com/vim/vim/blob/master/LICENSE
[fn:nvimlicense] https://github.com/neovim/neovim/blob/master/LICENSE.txt
[fn:libre] I will need to write a dedicated article about it at some point, as all that would only mud this one.
**** Different visions of computing
Vim and Emacs represent two, vastly different visions of computing.
Vim is a precompiled program with plugin capabilities.
This is the vision of computing that has won.
You get a binary (or, worse, a webpage) which makes your program do its thing.
You can /expand/ it using different plugins and configuration, but the /program/ is an unmovable object[fn:os]
Emacs is different.
We've got a very small /core/ written in C.
It handles basic functionalities, but by itself is close to useless.
But then we've got Lisp.
Almost all user-facing functionalities are written in Emacs specific dialect Lisp.
And Lisp is the keyword here.
All of the codebase is exposed to the user, ready to be not only read (as with Vim) but also modified during runtime.
You can /change/ Emacs at a whim.
VSCode tries the same, as it's a JavaScript-backed browser dressed as an editor.
Emacs therefore can be though as a Lisp-backed virtual machine dressed as an editor.
But the former was already rejected on the basis of being non-free (and I don't to run more Chromes that it's absolutely necessary), so it leaves me with Emacs.
I am not a Lisp programmer, but I started learning it.
For now I like it more than JavaScript.
[fn:os] I know you can modify the source code, but then you are running a fork and not the original program.
**** Community
I am not a people person, but people are what drive me.
And this another aspect which drives me to Emacs.
There are folks who are happy with all-batteries-included distributions, like Doom Emacs.
But there's a huge community of personalization maniacs.
People change every possible aspect of Emacs, because the program allows it.
Lisp is right there, and Emacs seems to be the last standing in the good fight to preserve /Literate Coding/ with /Literate Configuration/ people share.
To name just a few, who are my personal inspiration:
- [[https://se30.xyz/conf.html][Alex Maestas]]
- [[https://sqrtminusone.xyz/configs/readme/][Pavel Korytov]]
- [[https://protesilaos.com/emacs/dotemacs][Protesilaos Stavrou]]
- [[https://gitlab.com/dwt1/dotfiles/-/blob/master/.config/emacs/config.org?ref_type=heads][Derek Taylor]]
- [[https://sachachua.com/dotemacs/index.html][Sacha Chua]]
But at the same time the community is /small/ and I have a thing for smaller communities.
There are Vim users everywhere, but it's not that easy to find an Emacsian[fn:bsd].
But when you do, there are huge chances you've met someone at least interesting.
All Emacs users who I've ever meet in real life became my mentors to some degree.
Also, what is *very* important to mention: despite popular joke, there is very little hate in the Emacs community.
People into Vim tend to tell mean joke about Emacs users[fn:vimjoke] - and part of this joke is that the feeling is mutual.
It couldn't be further from the truth.
We all have great respect for other /libre/ editors and their users.
In fact, a lot of us move between Emacs and Vim on a daily basis.
Maybe this is partly due to age[fn:paah]?
Emacs users tend to be, well, older.
Some have their configs evolving since the 90s!
They've already got a lot to show, so they don't need to be mean spirited?
I have no idea, but what I know is that they are a lovely bunch!
[fn:bsd] this also applies my other love, [[/bsd/][BSD]]
[fn:vimjoke] sadly, this was one of the biggest reasons I stopped following certain hyperactive Vim evangelist.
Let him become nameless.
[fn:paah] there is a very funny movie about this https://www.youtube.com/watch?v=urcL86UpqZc
**** Stability
Lastly, stability.
I use [[/bsd/][BSD]] because it doesn't change for the sake of change.
No SystemD will ruin a perfectly good workflow.
Emacs is the same.
It's badge of honor - very rarely were there any bigger breaking changes.
When I used NeoVim, every few weeks something broke due to unfortunate update.
In Emacs on the other hand?
Not even once has something broke not from my own volition.
Emacs comes with multiple competing solutions for the same problem, as people may rely on one of those.
If a new one comes along, the old ones are not replaced.
This increases the confusion of a new user, but it's very much appreciated.
**** Summary
Emacs is the only empowering, libre and limitless editor I know.
It has an amazing community which values I share, and I want to participate in.
Also, it does not require constant maintenance because someone changed something.
Using Emacs /sparks joy/.
*** DONE Emacs as a Shell
CLOSED: [2023-04-13 Wed 23:00]
:PROPERTIES:
:EXPORT_FILE_NAME: emacs-as-a-shell
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract My current understanding of Emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/2023/emacs-as-a-shell/ /cool-emacs/emacs-as-a-shell/)
:END:
Pavel Korytov writes in his [[https://sqrtminusone.xyz/posts/2023-04-13-emacs/][recent post]];
#+BEGIN_QUOTE
So over time, Emacs has become my programming environment, email client, window manager, knowledge base, and a lot more. I think I ended up using Emacs for almost as many things as possible;
#+END_QUOTE
This is where I want to be in the near future.
So far I've moved my development environment and email to Emacs.
Next up are notes, RSS reading, and music listening.
What I love about Emacs is the consistency between modes/packages.
They accomplish widely different things, but the general control scheme is the same.
It's great since all TUI programs I use tend to support Vim's way of doing things.
Having it all inside Emacs changes the dynamic.
I'm trying to think of Emacs as a shell rather than an editor.
What Emacs really is, is a virtual machine running LISP code.
Some say that Emacs violates Linux philosophy.
I don't see it this way.
Does shell violate it?
It's also a way to run different programs.
Emacs is an abstraction over real shell which adds some calm to it.
It's a way to have an interactive layer over OS... which also does text editing.
So, when you look at it this way, Emacs makes a lot of sense:
- It runs programs.
Bigger packages, like Magit, are nothing short of real programs.
- It's scriptable.
Elisp all the way!
- It allows for interoperability between programs.
- It runs above basic OS.
You can replace your window manager with Emacs, but you need some sort of kernel.
- You can live entirely inside Emacs, just like you can live entirely inside a terminal.
*** DONE Multiprocess Emacs environment
CLOSED: [2024-06-17 Mon 17:46]
:PROPERTIES:
:EXPORT_FILE_NAME: multi-process-emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract Running dedicated Emacs processes
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :alias '(/cool-emacs/multi-process-emacs)
:END:
The more you move into Emacs, the happier you may become.
But at the same time, Emacs is not a *real* shell, but a text editor.
This means there is no real way to manage functionality similarly one would manage applications.
It's all a buffer - the file you opened, each IRC channel, email list, and so on.
There are ways to manage it, like dedicated "workspace" plugins[fn:perspective] or simple tabs.
But all those don't fit my mental model.
I am "Editing a file" or "chatting" or "writing a web page".
Those are separate concerns, so I want to have dedicated spaces for them.
At the same time I want them in Emacs, as it gives me a unified interface.
I don't need to think how to change IRC channel, how to spell check, or how to actually write in my selected keybindings.
**** Dedicated Emacs instances
This led me to use multiple, dedicated Emacs instances.
This way, I've a got a dedicated IRC client, Code editor, Notepad, Email client, and so on.
I have unified interface, but at the same time it's still akin to dedicated programs.
This has the added benefit of fault protection.
Tramp session hanging entire Emacs doesn't disconnect me from IRC, as those are separate processes.
Therefore, I have functions which I call /ultimate modes/[fn:music] to configure Emacs for given use case.
A simple ultimate mode for IRC would look like:
#+begin_src emacs-lisp
(defun mms-irc-mode()
"use this instance of Emacs for IRC"
(interactive)
(load-theme 'ef-bio)
(erc-tls :server "irc.tilde.chat" :port "6697"
:nick "mms" :full-name "https://michal.sapka.me")
(erc-tls :server "irc.libera.chat" :port "6697"
:nick "mms" :full-name "https://michal.sapka.me")
)
#+end_src
This simple function:
- Loads a dedicated theme, so I won't get lost. IRC is a happy place, therefore green.
- Connects me to my servers
ERC is configured elsewhere, so all auto-joins are there, just redacted, but nothing limits the number of things the ultimate mode setup up.
Want to defer loading of bigger package?
Want to reconfigure Ispell language?
Or maybe you want to load parts of Emacs configurations only when they make sense?
Shy is the limit!
Now, I can either run =Emacs= and call =mms-irc-mode= or have a dedicated OS level key binding to run Emacs in this mode via:
#+begin_src shell
emacs --eval='(mms-irc-mode)'
#+end_src
This method could easily be expanded to run dedicated =emacs servers= and connected clients, but I wanted a simpler way.
[fn:perspective] Like [[https://github.com/nex3/perspective-el][perspective.el]]
[fn:music] Yes, it breaks the musical theory naming scheme.
I could have used "tonic" there to run the =erc= and pla play with the wordplay, but I decided against it.
Dammit Jim, I'm an engineer, not a musician!
Also, diatonic modes don't fit the "larger than major" mode.
*** DONE Input Completiton
CLOSED: [2023-05-26 Wed 23:00]
:PROPERTIES:
:EXPORT_FILE_NAME: input-completition-in-emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract Icomplete, IDO and FIDO
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/2023/input-completition-in-emacs/ /cool-emacs/input-completition-in-emacs/)
:END:
Emacs consists of a massive set of tools with a long history.
Therefore, whatever the problem is, someone likely has already created a package for it.
In many cases, this package is already baked in Emacs.
This is great for veterans but very confusing to newcomers - like me.
**** The three modes
Emacs comes with three modes for input completion: Icomplete, IDO, and FIDO.
Input completion works with whatever you select in the Minibuffer section.
For text competition, you must use a different solution, like Company mode[fn:company].
The oldest one of those is /icomplete/[fn:icomplete] mode.
It allows you to select from a list of choices incrementally[fn:hist], so you need to type the beginning, and icomplete will narrow the list of propositions.
For example, when searching for "magit", the user needs to type "m" first as omitting it and starting with "i" will instead narrow to options beginning with "i".
Similarly, "mgt" will not limit to "magit" as we're missing"a".
Newer Emacs versions allowallow us to use "flex" matching with icomplete, but more on this later.
Incomplete work for all lists using a mini buffer, like filenames or imenu.
Then /IDO/[fn:ido] mode (Interactively Do) came in.
It uses the aforementioned "flex" matching, where you can search for any part of the word.
"Magit" would be found with "agit", "mgt" or just "git".
IDO, however, works only with files and buffers, so `M-x` would still fall back to icomplete.
Starting with Emacs 27, we've got /fido/ mode (Fake Ido).
It combines the best things about icomplete (works anywhere) with flex matching.
In fact, all Fido does under the hood is to enable Icomplete with flex matching.
There are also other solutions for competition, not baked into emacs, with the most popular being Helm[fn:helm] and Ivy[fn:ivy].
Personally, I always try the default option, and only if their limits become annoying do I look at the alternatives.
So, I am now a happy FIDO mode user.
[fn:company] [[https://company-mode.github.io/][Company-mode website]]
[fn:hist] According to a [[https://www.reddit.com/r/emacs/comments/13szol7/comment/jltmaud/?utm_source=reddit&utm_medium=web2x&context=3][Reddit comment]] this behavior is relatively new.
Until recently, Icompelete only showed the narrowed selection in a dedicated buffer, similar to using `Tab-Tab` now.
The actual input was still up to the user to type in. Thanks for the tidbit
[fn:icomplete] [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html][Icomplete documentation]]
[fn:ido] [[https://www.gnu.org/software/emacs/manual/html_mono/ido.html][IDO documentation]]
[fn:helm] [[https://emacs-helm.github.io/helm/][Helm website]]
[fn:ivy] [[https://oremacs.com/swiper/][Ivy website]]
**** Using FIDO
To start FIDO mode run
#+BEGIN_SRC emacs-lisp
fido-mode
#+END_SRC
or, if you find a vertical list to be more readable, use
#+BEGIN_SRC emacs-lisp
fido-veritcal-mode
#+END_SRC
You can also customize variables to have it auto-load in your `init.el`:
#+BEGIN_SRC emacs-lisp
(fido-mode t)
;; or
(fido-vertical-mode t)
#+END_SRC
As I've stated above, FIDO is nothing more than flex-enabled Icomplete, so all keybindings work the same.
For vertical mode:
- Typing a string will narrow the selection
- `C-n` and `C-p` will select the next/prev element on the list
- `Tab` will display a buffer with a list of narrowed elements
- `enter` or `C-j` will select the option
One cool thing you can do with FIDO is file deletion (when selecting files) or closing buffers (when selecting buffers) using `C-k`.
**** Customizing the Minibuffer
We can, of course, customize how Icomplete looks works[^savanah].
- /icompleteatches-format/ (default "%s/%s") - how the number of filtered and total options is presented
- /complete-first-match/ (default (t :weight bold)) - how the first filtered option is presented
- /icomplete-selected-match/ (default (t :inherit highlight)) - the same as above, but for vertical mode
- /icomplete-section/ (default (t :inherit shadow :slant italic)) - how the section title for vertical mode is presented
- /icomplete-compute-delay/ (default .15) - how fast the filtering will start after a keypress when a larger number of options is presented
- /icomplete-delay-completions-threshold/ (default 400) - defines the "large number" for icomplete-compute-delay
- /icomplete-max-delay-chars/ (default 2) - maximum number of initial characters before applying /icomplete-compute-delay/. I have no idea what it means, nor have I the knowledge of Elisp to dig into it.
- /icomplete-in-buffer/ (default nil) - enables usage of Icomplete outside of the Minibuffer. I have not tested it.
We can also use /icomplete-minibuffer-setup-hook/ hook if needed.
[^savanah]: list based on [[https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/icomplete.el][Icomplete source code]]
**** Using Completions from Elisp
The great thing about FIDO is that it, like Icomplete, uses Minibuffer API[fn:minibuffer], so you can simply:
#+BEGIN_SRC emacs-lisp
(completing-read
"prompt: "
'(("option1" 1) ("option" 2))
nil t "default query")
#+END_SRC
[fn:minibuffer] [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html][Guide on Minibuffer completition]]
*** DONE Introduction to Literate programming
CLOSED: [2024-01-30 Tue 19:10]
:PROPERTIES:
:EXPORT_FILE_NAME: literate-programing-in-emacs
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract A short introduction into the idea of literate programming
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/cool-emacs/literate-programing-in-emacs/)
:END:
**** Abstract
In this article I give a short, theoretical introduction to the idea of Literate programming
**** Introduction
Literate programming is not a subject that comes out often, except if you are talking with an Emacs enthusiast.
There is a significant chance that most programmers don't even know the term.
Let's fix that with a quote:
#+begin_quote
Literate programming is an approach to programming which emphasises that programs should be written to be read by people as well as compilers.
From a purist standpoint, a program could be considered a publishable-quality document that argues mathematically for its own correctness.
A different approach is that a program could be a document that teaches programming to the reader through its own example.
A more casual approach to literate programming would be that a program should be documented at least well enough that someone could maintain the code properly and make informed changes in a reasonable amount of time without direct help from the author.
At the most casual level, a literate program should at least make its own workings plain to the author of the program so that at least the author can easily maintain the code over its lifetime.
-- Christopher Lee[fn:lee]
#+end_quote
The idea is therefore a conversion of an entity out of which one can extract code or documentation.
A semi-abstract in-between called "web[fn:web]".
The process of creating code is called "tangling", and generation of document is a "weave".
**** An example
Let's say we want to show the reader how to install DWM.
We can create a document in a style:
#+begin_quote
DWM is a window manager that can be changed only via source code modification.
Here, we will fetch and compile it.
First, we need to download the tarball:
#+begin_src shell
wget https://dl.suckless.org/dwm/dwm-6.4.tar.gz
#+end_src
then, simply extract it
#+begin_src shell
tar - xvzf dwm-6.4.tar.gz
#+end_src
And then we compile it
#+begin_src shell
cd dwm-6.4
doas make clean install
#+end_src
After the compilation finishes, add executable to your .xinit
#+begin_src shell
echo "exec dwm" >> ~/.xinit
#+end_src
#+end_quote
So yeah, it's a blog post.
A blog post which one can execute.
The example assumes shell, but the actual language can be anything.
We can /tangle/ C code without any problems.
**** Literate programming
This is *not* the way we do programming.
We smack spaghetti code together, add a random sentence here and there, commit is as "bug fix" and voilà!
In a few months no one knows what's going on.
Success, up to the next JIRA task.
Very often code comments are treated as an harmful or (at best) a necessary evil.
We think that code should be self-documenting.
And this is completely valid.
A developer needs to understand what given code does, just by reading it.
If your function is so convoluted, nested and complicated that it's impossible to comprehend without a descriptive comment.
But this is not the whole story.
A function may be very simple, but there is always /context/ in which it is used.
Literate Programming promotes telling story to the reader.
You are free to do narration giving all extra info in one place.
Since the code coexists with documentation, the reader gets the whole picture.
[fn:lee] [[https://web.archive.org/web/20170603045917/http://vasc.ri.cmu.edu:80/old_help/Programming/Literate/literate.html]["Literate Programming -- Propaganda and Tools", Christopher Lee, 1997]]
[fn:babel] [[https://orgmode.org/worg/org-contrib/babel/intro.html][Org Babel]]
[fn:jupyter] I know that Jupyter is not strictly a literate program, but it's close enough.
[fn:web] this name was choosen, because at the time it was not in use related to computing.
We're dealing with history here!
**** Conclusion
Now, this is not a generic fix for all programs.
We work on massive systems with hundreds of intertwined, moving parts.
It is impossible to create a cohesive narrative when the program jumps all over the place.
Literate programing, however, found a different home.
It is loved by scientists (just look at Jupyter Notebooks[fn:jupyter]) who use it for reproducible resarch.
We, amongts Emacs crowed, use it extensively for literate configuration of our environments.
It could be used for scripts, runbooks, debugging logs and so on.
Wherever one can see a logical A, B and C points, we can explain the interconnections.
You can learn more (including much better example) by reading the [[https://michal.sapka.me/papers/literate_programming_knuth_1984.pdf][original Knuth's paper]].
*** DONE Executing code in Org files with Babel
CLOSED: [2024-02-07 Wed 21:23]
:PROPERTIES:
:EXPORT_FILE_NAME: org-babel
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :abstract A short introduction into the world if Org Babel
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :aliases '(/cool-emacs/org-babel/)
:END:
*Abstract*: a very short introduction into the word of Org Babel.
No other package seems to have had such huge impact on Emacs community as the introduction of Org Babel[fn:magit].
It made folks not only love their Org Mode, but /live/ in it.
In short: Babel allows for [[/emacs/literate-programing-in-emacs/][literate programming]] form within an Org document.
[fn:magit] you may argue and say that =magit= had bigger impact.
I disagree.
Magit made using git nicer and easier, but it is still the old, old git.
Babel changed the way we use the edit code, the way we think and how we work.
**** Source code basics
Babel comes baked into any modern version of =Emacs=, so you've already got everything you need.
Open any =org= document, add the code to the document within source code blocks:
#+BEGIN_SRC emacs-lisp
regular text
,#+BEGIN_SRC emacs-lisp
(+ 1 1)
,#+END_SRC
and more regular text
#+END_SRC
Now, move the pointer to with the blocks, tap =C-c C-c= and the code is evaluated.
The result will be appended just below the source code, in a =RESULT= block.
You now know how to do literate programming in Emacs.
Create an Org document, add your narrative, add your source code and execute.
Simple, isn't it?
By default, =Babel= allows only for execution of =emacs-lisp= mode, but it supports many more.
You can enable all supported via:
#+begin_src emacs-lisp
(org-babel-do-load-languages 'org-babel-load-languages
'((shell . t)
(js . t)
(emacs-lisp . t)
(clojure . t)
(python . t)
(ruby . t)
(dot . t)
(css . t)
(plantuml . t)))
#+end_src
There are even more provided as dedicated packages.
**** Being faster
Typing =#+BEGIN_SRC= can become tedious pretty quick.
We can use the =org-tempo= package to make it much faster.
#+begin_src emacs-lisp
(require 'org-tempo)
(setq org-structure-template-alist
'(("s" . "src")))
#+end_src
Now, we can create new block simply by taping =<s= and pressing tab.
Org tempo supports many more blocks, you can refer to [[https://orgmode.org/manual/Structure-Templates.html][the official manual]].
**** Joining the blocks together
In /strict/ Literate Programming sense, your /web// should not be where you execute code.
Instead, you should extract programmatic parts and run it separately - the process is called /tangling/.
Babel bypasses this limitation with =noweb= mode, in which code blocks can reference each other.
First, you need to name your code blocks:
#+begin_src emacs-lisp
,#+NAME: two
,#+BEGIN_SRC emacs-lisp
(+ 1 1)
,#+END_SRC
#+end_src
Then we can use it the result other code blocks
#+begin_src emacs-lisp
,#+NAME: three
,#+BEGIN_SRC emacs-lisp :noweb yes
(+ <<two>> 1)
,#+END_SRC
#+end_src
Here, the =<<two>>= will be replaced with the result of block named =two=.
This makes the block =three= return the number =3=.
This mode gives quite a lot more option.
You can read about them inside [[https://orgmode.org/manual/Noweb-Reference-Syntax.html][manual]].
**** Running on remote machines
To make it even /cooler/, Babel integrates with =Tramp= mode which allows for execution over ssh!
You can add a tramp-style header argument as =:dir= and the code will be executed over the established connection.
#+begin_src emacs-lisp
,#+BEGIN_SRC shell :dir /user@host:~/
rm -rf /
,#+END_SRC
#+END_SRC
And boom - we've got a bomb.
**** Babel as source for Emacs configuration
The most popular use case of Babel however is Emacs configuration.
You can have as many org files as you want, have the configuration inside =emacs-lisp= code blocks and use that as config.
***** Loading files directly
If your config isn't /very/ complicated, you can force Emacs to parse the files on boot.
To achieve that, add
#+begin_src emacs-lisp
(org-babel-load-file "~/.emacs.d/config.org")
#+END_SRC
To your =init.el=.
What happens is, that Emacs will create a corresponding =config.el= (/tangle/) file and load it as a elisp file.
Just remember not to name your config as =init.org= as there will be naming conflict with =init.el=.
***** Pre-tangling
If you don't want /tangling/ on boot time, but rather want to have it ready you can manually /org-babel-tangle/.
To achieve it, add a =tangle= header argument to all you code blocks:
#+begin_src emacs-lisp
,#+BEGIN_SRC emacs-lisp :tangle ~/.emacs.d/init.el
(+ 1 1)
,#+END_SRC
#+END_SRC
And then, after any changes of the file run =org-babel-tangle= and your =init.el= will be up-to-date.
This is the approach [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Prot]] uses, but his config consists of over /17_000/ [sic!] lines of org file, so your mileage may vary.
**** Links
If you are interested in the subject, you can look at much more details sources:
- [[https://orgmode.org/worg/org-contrib/babel/intro.html][Official website]]
- [[https://howardism.org/Technical/Emacs/literate-devops.html][Literate devops]] on Howardism
- [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration]] on Prot's website.
- [[https://org-babel.readthedocs.io/en/latest/][Cheat sheet]]
|