summary refs log tree commit diff
path: root/config.org
blob: 2c218c6107f3f95d3f06ab52acf97bcd2de07a16 (plain)
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
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
#+TITLE: My configurations
#+SUBTITLE: Version 0.2
#+DATE: 2024-06-23
#+PROPERTY: header-args :tangle ~/.config/emacs/init.el
#+OPTIONS: toc:2

* Introduction
This is an experiment in using literate programming to generate my computer configurations.  I want to try it out because my emacs configuration already has a large number of comments, and it seems more fitting to make them first class citizens.  Furthermore, i want to reorder some parts of the configurations to put relevant information close together, rather than having to arbitrarily split configuration from different programs into different files.

The system should be generated using this command:

#+begin_src sh :tangle no
  emacs config.org --batch -f org-babel-tangle
#+end_src

** Inspiration
I have consulted [[https://raw.githubusercontent.com/rasendubi/dotfiles/master/README.org][Oleksii's configuration]] heavily to get used to how to work with literate programming.  Lots of literate configuration attempts are disappointing in that they feature almost no commentary and are strictly linear.  In my eyes, these are the two biggest advantages of the literate programming model to begin with.  Oleksii's configuration is nice in that it has both lots of commentary, and is non-linear.  It also features configuration for more than one program, mixed together with the parts that matter.

For this initial test, my configuration fails on both counts.  All i've done is put every section of elisp into a code block and turned the comments into normal org text.  Over time, i hope to expand it into something which ticks both of those boxes.

** Emacs installation
What libraries do we need?  Quite a few:

#+begin_src sh :tangle no
  sudo apt install libmagickwand-6.q16-dev libwebkit2gtk-4.0-dev libtree-sitter-dev
#+end_src

#+begin_src sh :tangle no
  git clone https://github.com/emacs-mirror/emacs
  cd emacs
  git checkout emacs-30
#+end_src

#+begin_src sh :tangle no
  BUILD_OPTS=$(emacs \
		   --batch \
		   --eval "(prin1 system-configuration-options)")

  ./autogen.sh
  echo "$BUILD_OPTS" | sed 's/^"\(.*\)"$/\1/' \
      | xargs ./configure
  make bootstrap
  sudo make install
#+end_src

#+begin_src sh :tangle no
  ./autogen.sh
  ./configure --with-x-toolkit=gtk3 --with-imagemagick --with-tree-sitter --with-xwidgets --with-native-compilation=aot --with-mailutils
#+end_src

We should use all the cores we have to compile.

#+begin_src sh :tangle no
  make --jobs=$(nproc)
#+end_src

** What is the purpose of a computer?
I think it's a good exercise to consider what exactly i want to use the computer for.  This not only gives me some areas to build this configuration around, it also helps me not to lose track of computers as tools, as bicycles for the mind, rather than as an end in themselves.

* Configuration
I can easily insert new source blocks with ~C-c C-,~.

#+begin_src elisp
  ;;; init.el --- my emacs configuration  -*- lexical-binding: t; -*-
  ;; This file is auto-generated.  Please edit ~/data/share/config.org
#+end_src

#+begin_src elisp
(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp")))
#+end_src

I like to see garbage collection messages.  I keep meaning to set up something like the [[https://akrl.sdf.org/#orgc9536b4][garbage collector magic hack]] so that emacs hangs less when i'm running a macro or opening a big web page or whatever.  But for now, the default values are adequate.

#+begin_src elisp
  (setq garbage-collection-messages t)
  #+end_src

We set the ~gc-cons-threshold~ and ~gc-cons-percentage~ to be really high in the ~early-init.el~ file.  This makes loading emacs a little bit faster.  But obviously i don't want that in general usage, so once emacs has finished starting up, i make sure to lower the values again.

#+begin_src elisp
  (add-hook 'emacs-startup-hook
	    #'(lambda ()
		(message (format "Initialised in %s seconds with %s garbage collections." (emacs-init-time) gcs-done))
		  ;;; reset garbage collector
		(setq
		 gc-cons-threshold 800000
		 gc-cons-percentage 0.2)))
#+end_src

#+begin_src elisp
  (setopt user-full-name "noa")
  (setopt user-mail-address "noa@noa.pub")
#+end_src

Properly distinguish these chords from their ascii legacy

#+begin_src elisp
(define-key input-decode-map [?\C-m] [C-m])
#+end_src

* Packages
For a long time i used use-package to manage emacs package configuration.  Over time, i decided it was a bit too magic for me.  The syntax is quite different to everything else in the configuration, and most of the options i was using were fairly simple.

I had a very brief excursion into using ~package-vc-install~ to manage packages directly from source, but i found this was too fragile, especially without relying on use-package.  For now, i've modified a little defun from acdw i used for that purpose to simply install packages using package.el.

#+begin_src elisp
  (defun package-ensure (pkg)
    (unless (package-installed-p pkg)
      (package-install pkg)))
#+end_src

* Executing commands
I make my caps lock a menu key, so i can open the command palette with it:

#+begin_src elisp
  (define-key context-menu-mode-map (kbd "<menu>") nil)
  (global-set-key (kbd "<menu>") 'execute-extended-command)
#+end_src

I also unbind ~M-x~ so i can use it as another keybinding.

#+begin_src elisp
  (global-unset-key (kbd "M-x"))
#+end_src

It is often useful to be able to run a command while i am already in the process of running a command in the minibuffer.  This is rarely two extended commands; usually it is completion.

#+begin_src elisp
(setopt minibuffer-depth-indicate-mode t)
#+end_src

* Minibuffer candidate completion
Vertico is a package for a nice minibuffer completion experience.  It displays a vertical list of candidates.  It integrates well with the emacs ecosystem and lets me use other packages that also play nicely.

#+begin_src elisp
  (package-ensure 'vertico)
  (setopt vertico-mode t)
#+end_src

We want vertico to take up a maximum of 12 lines on the display.  My screen is quite small, so that's fine, but if i had a bigger screen, i might want to look into setting a percentage or increasing this.

#+begin_src elisp
  (setopt vertico-count 12)
#+end_src

By default, vertico uses a font-face trick to put a horizontal line across group titles.  It looks quite nice, but doesn't really conform to my design sensibilities, so here i redefine the group format to not have this.  Because we no longer have the line, we also align the group name to the left edge.

#+begin_src elisp
(setopt vertico-group-format #("%s " 0 3 (face vertico-group-title)))
#+end_src

We also want to be able to jump to the bottom of the list by moving up from the top of the list, and the opposite.  I've rarely made use of this functionality and i don't know if it's actually a best practice from an interaction perspective, but i'm going to keep it on until it causes an issue for me.

#+begin_src elisp
(setopt vertico-cycle t)
#+end_src

And of course, i want to be able to interact with vertico with the mouse.

#+begin_src elisp
  (with-eval-after-load 'vertico
    (setopt vertico-mouse-mode t))
#+end_src

When completing a filename, i want to be able to easily delete directories in one fell swoop, instead of character by character or word by word.  Usually C-<backspace> would be fine, but if directories have a hyphen or space in their name, i have to press multiple times, which is almost never desirable.

#+begin_src elisp
  (with-eval-after-load 'vertico
    (bind-key (kbd "RET") #'vertico-directory-enter 'vertico-map)
    (bind-key (kbd "<backspace>") #'vertico-directory-delete-char 'vertico-map)
    (bind-key (kbd "<C-<backspace>") #'vertico-directory-delete-word 'vertico-map))
#+end_src

If i type ~/ etc in a find-file prompt, get rid of the preceding directory names for a cleaner look.

#+begin_src elisp
(add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
#+end_src

** Better default completion

Some settings for nicer completion with the default emacs completion buffer.  I don't use this, because i use vertico.

#+begin_src elisp :tangle no
  (setopt completion-auto-help 'lazy
	  completion-auto-select 'second-tab
	  completion-show-help nil
	  completions-sort nil
	  completions-header-format nil)
#+end_src

* Completion styles
When given a prompt to select from a list of candidates, there are quite a lot of things we can tweak to improve the experience.

The first thing we do is to ignore case, which in these cases is rarely useful.  I find that thinking about the case of a candidate is slower than just typing more to narrow down the options.  I don't actually know if these make any difference when i've specified a different completion style.

#+begin_src elisp
(setopt read-buffer-completion-ignore-case t)
(setopt read-file-name-completion-ignore-case t)
(setopt completion-ignore-case t)
#+end_src

Next, we want to set orderless and basic as the two completion style.  Basic matches candidates with the same text before the point, and the text after the point as a substring.  Orderless takes any number of space separated components and displays candidates that much every component in any order.  We specify basic first.  What this means in practice is that first we will try and complete exactly what i've input, and if that fails, widen the search with orderless to pick up more options.

#+begin_src elisp
  (package-ensure 'orderless)
  (setopt completion-styles '(basic orderless))
#+end_src

By default, emacs overrides the completion styles for email address, but i'm happy with my configuration above.

#+begin_src elisp
  (setopt completion-category-defaults nil)
#+end_src

* Aesthetic changes
** Colour theme
This is my emacs theme.  It's a monochrome theme which, unlike most monochrome themes, really does have only two colours.  I define a few faces, and set every other face as one of them.  There are a few things i want to do with it before i make it properly public: make the colours configurable and able to update on the fly, and in general iron out some of the janky parts.  A few things defined it are quite specific to this configuration, like the way i define the borders for the tab and header bars, and there is no mode line configuration because i don't use it.

#+begin_src elisp
  (require 'tubthumping-theme)
  (load-theme 'tubthumping t)
#+end_src

** Fonts
My current favourite font is sn pro, which feels like comic sans for grown ups.  It's friendly but consistent and well thought out.  However, it's also a proportional font, which obviously is the right way to do things, but emacs is very old and comes from a time before the innovation of legibility.  As a result, there are some things that require a monospaced font, so i set one here.  I chose go mono for two reasons: the first is because i think it looks really nice; the second is because it has serifs and is very visually distinct from sn pro, so i can notice and shame those buffers which require a fixed width font to operate properly.

#+begin_src elisp
(custom-set-faces
 '(fixed-pitch ((t (:family "Go Mono" :height 110))))
 '(variable-pitch ((t (:family "SN Pro" :height 110)))))
#+end_src

For some frustrating reason, emacs does not respect fontconfig font settings.  What this means in practice is that emacs by default draws cjk characters with the korean variant.  Luckily emacs has its own obscure and poorly documented way of doing things, so i can iterate over the relevant charsets and set the font specifically for those characters.

#+begin_src elisp
  (dolist (charset '(han cjk-misc))
    (set-fontset-font t charset (font-spec :family "Noto Sans CJK SC")))
#+end_src

Similar to the above, we have to manually set the font we want to be used for emoji.  I like the cute style of the emoji in fsd emoji, but it doesn't have very good coverage, so we also set noto emoji as the backup.  Note that noto emoji is not the same as noto color emoji, which uses coloured emoji.  That's clearly against the vibe of this emacs!

#+begin_src elisp
(set-fontset-font t 'emoji (font-spec :family "FSD Emoji") nil 'prepend)
(set-fontset-font t 'emoji (font-spec :family "Noto Emoji") nil 'append)
#+end_src

While we're here, let's set up emoji input.

#+begin_src elisp
  (global-set-key (kbd "C-.") #'emoji-search)
#+end_src

Describe a key based on a string like "C-SPC"

#+begin_src elisp
(defun describe-key-shortcut (shortcut)
  (interactive "MShortcut: ")
  (describe-key (kbd shortcut)))
#+end_src

Update the calendar.  We want weeks to start on a monday, the first day of the week.  Holidays should be highlighted, and the date format should put the year first.

#+begin_src elisp
(setopt calendar-week-start-day 1
        calendar-mark-holidays-flag t
        calendar-date-style 'iso)
#+end_src

*** Monospace font fallback

This is my own version of fixed-pitch, which has some changes to it.  No hooks are added by default.  Updating the whitelist automatically applies the hooks.  And there is no functionality for changing the cursor type.

#+begin_src elisp
  (use-package fixed-pitch)
  (setopt fixed-pitch-whitelist-hooks '(calendar-mode-hook
  				      dired-mode-hook
  				      ibuffer-mode-hook
  				      magit-mode-hook
  				      profiler-report-mode-hook
  				      jabber-roster-mode-hook
  				      mu4e-headers-mode-hook
  				      proced-mode-hook
                                        rmail-summary-mode-hook))
#+end_src

*** Transient variable font

While we're at it, ensure that transients look nice with a variable pitch font.

#+begin_src elisp
(setopt transient-align-variable-pitch t)
#+end_src

** Replace the mode line with a header line
First, we set the mode line to nil.  On my graphical display, this collapses it so all i get is a thin black line separating the buffer from the echo area.  But the mode line still holds some useful information that i want to see.  I would rather that be in the header line, because to me it makes sense for this kind of metadata to be /above/ the buffer it is describing.

First, in white on black text, i want the information about the state of the file.  This will show three hyphens in the top left corner of the header line.  The first two hyphens mean that the file is both writable and unchanged.  If the buffer has been changed, they will change to two asterisks.  If the buffer is read only, they will change two percentage symbols.  And if the buffer is read only and has been changed, the first will change to a percentage symbol, and the second will change to an asterisk.  The final hyphen represents that the file is local, specifically that the default-directory variable is local.  If it is remote, an at symbol will be displayed instead.

Next, we want to display the buffer name.  For buffers which belong to files, this will usually be the file name, but it is likely to be something more informative for special buffers.

Below that, show a line and column coördinate.  There are special minor modes that will enable or disable this for the default mode line, but i ignore that and put the formatting code here directly.  The docstring for the mode-line-format variable suggests that the column might not be displayed correctly in some situations without enabling the minor mode, but i haven't noticed that yet so i don't bother.  This column number is zero-indexed; a capital c would make it one-indexed.  For now i stick with zero-indexed as that's the emacs default and i'm not sure which is better.  I guess it makes a bit more sense that the first character on a line is labeled "1".

I don't know exactly what the final variable covers, so i keep it here so that if something shows up i know that it gets put here.  Because i have a global mode line in my tab bar, some of the things that would otherwise be here (like the time, battery percentage, and notifications for chat buffers) don't show up.

#+begin_src elisp
  (setq-default mode-line-format nil)
  (setq-default header-line-format
                '("%1*%1+%1@ "
                  "%b:%l,%c "
                  mode-line-format-right-align
                  "♥️"))
#+end_src

** Flash the current line
This function was taken from a karthinks blog post.  Previously i was using an external package, [[https://github.com/Malabarba/beacon][beacon]], and i still think that functions better.  But this is adequate enough that i'm happy.

I'd like to change the face, but ~pulse-highlight-face~ has *DO NOT CUSTOMIZE* in the docstring so what am i meant to do!?

#+begin_src elisp
(defun pulse-line (&rest _)
  "Pulse the current line."
  (pulse-momentary-highlight-one-line (point)))
(dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window))
  (advice-add command :after #'pulse-line))
#+end_src

** Make wide windows narrow with visual-fill-column
Reading prose with long lines is a chore.  Luckily there are several packages in emacs to make the windows squeeze text into a more legible sliver.  Previously i used olivetti, which is a really nice package, but it's quite heavyweight and broke on me with no explanation more frequently than i appreciated.  I've switched to visual-fill-column and been quite satisfied.

#+begin_src elisp
  (package-ensure 'visual-fill-column)
  (setopt visual-fill-column-enable-sensible-window-split t)
  (setopt visual-fill-column-center-text t)
  (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)

  (add-hook 'text-mode-hook #'visual-line-fill-column-mode)
  (add-hook 'eww-after-render-hook #'visual-line-fill-column-mode)
  (add-hook 'nov-post-html-render-hook #'visual-line-fill-column-mode)
#+end_src

The fringes of a window can show useful icons relating to the text in the buffer.  For example, when using org modern, blocks get a line in the left fringe to show their content more clearly,  Because of this, i prefer for them to be right next to the buffer text.

#+begin_src elisp
  (setopt visual-fill-column-fringes-outside-margins nil)
#+end_src

Adaptive wrap will indent visually wrapped text to match the indent at the start of the line, for example in lists.  This works... fine.  However it's adapting the prefix, it doesn't indent nicely with proportional fonts, but you can't win them all.

#+begin_src elisp
  ;; (use-package adaptive-wrap :ensure t)
  ;; (add-hook 'visual-fill-column-mode-hook #'adaptive-wrap-prefix-mode)
#+end_src

** Interface
I want to make sure that various bits of the interface are hidden.  but this isn't an "all gui chrome is useless" rampage.  I personally think the scrollbar is useful, i like the visual indication it gives of how far i am through a file.

At the moment, explicitly disabling the menu bar and tool bar does nothing, because i already set there to be no lines displayed for the tool and menu bars in my early-init.el file.

#+begin_src elisp :tangle no
  (setopt menu-bar-mode nil
	  tool-bar-mode nil)
#+end_src

Tooltips are little popups next to the mouse cursor.  I think this information is helpful, but i like it to appear in a more consistent position, because i find it frustrating when popups cover parts of the ui that i wanted to see.  By disabling tooltip-mode, the contents that would be in a popup is instead shown in the echo area.

#+begin_src elisp
(setopt tooltip-mode nil)
#+end_src

I see no reason not to immediately show which chords in a key sequence i have already pressed.  Emacs does, however, and instead of letting me set the value of echo-keystrokes to zero to wait zero seconds to show that information, it repurposes zero as a method of disabling the functionality altogether, and provides no special functionality for setting it to nil that would explain why that's not an acceptable method of disabling a feature.  Instead, i have to deal with setting it to nearly zero, and luckily i can't tell the difference.

#+begin_src elisp
(setopt echo-keystrokes 0.1)
#+end_src

A useful feature when programming is to show matching parentheses.  Show-paren-mode is a global mode.  By default it runs in all buffers except those inheriting from special mode.

#+begin_src elisp
(setopt show-paren-mode t)
#+end_src

This variable means that if there is no non-whitespace character in between the point and the paren, it will be highlighted.  It's useful to highlight parentheses if the point is at the start of the line and the paren is indented.

#+begin_src elisp
(setopt show-paren-when-point-in-periphery t)
#+end_src

By default, the point has to be after a paren for it to be highlighted.  But often the point will be just inside, in which case it's also helpful for the pair to be highlighted.

#+begin_src elisp
(setopt show-paren-when-point-inside-paren t)
#+end_src

#+begin_src elisp
  (setopt tab-bar-mode t)
  (setopt tab-bar-format '(tab-bar-format-menu-bar
                           tab-bar-format-history
                           tab-bar-format-tabs
                           tab-bar-separator
                           tab-bar-format-add-tab
                           tab-bar-format-align-right
                           tab-bar-format-global))
#+end_src

#+begin_src elisp
(setopt global-font-lock-mode t)
(setopt font-lock-maximum-decoration nil)
#+end_src

#+begin_src elisp
  (setopt inhibit-startup-screen t
	  mouse-drag-and-drop-region nil
	  mouse-yank-at-point t
	  ;; deleting should be an explicit action
	  delete-selection-mode nil)
#+end_src

#+begin_src elisp
(global-set-key (kbd "C-t") 'tab-new)
#+end_src

Shift click to select region with the mouse.  This annoyingly rings the bell for an error.  It also interferes with my input method switcher, which doesn't notice the mouse click and thinks i've pressed shift with no other keys.

#+begin_src elisp
(global-unset-key (kbd "S-<down-mouse-1>"))
(global-set-key (kbd "S-<down-mouse-1>") 'mouse-save-then-kill)
#+end_src

* Xmpp
Jabber.el is an xmpp client in emacs.  I don't actually use xmpp as xmpp that much.  But i do use it to connect to irc, and this package lets me do that.  Unfortunately, it's not a particularly well-behaved package; by default it clobbers some keybindings and floods the echo area with unhelpful messages.

#+begin_src elisp
    (package-ensure 'jabber)
    (setopt jabber-account-list '(("noa@noa.pub")))
#+end_src

So now what we're going to do is get it to stop showing a bunch of channels in the mode line, because there will always be new activity and i want to drop in when i feel like it, and not always have a reminder of that fact.  The defun below is copied from jabber-activity-show-p-default but with an extra condition plopped in.

#+begin_src elisp
  (defcustom noa/jabber-activity-dont-show
    '("#tildetown%town@irc.hmm.st"
      "#meta%tilde.chat@irc.hmm.st"
      "hmm@conference.hmm.st")
    "List of JIDs not to show in the modeline."
    :group 'jabber-activity)(defun noa/jabber-activity-show-p (jid)
    "Return non-nil if JID should be hidden.
  A JID should be hidden when there is an invisible buffer for JID,
  when JID is not in `noa/jabber-activity-dont-show',
  and when JID is not in `jabber-activity-banned'."
    (let ((buffer (jabber-activity-find-buffer-name jid)))
      (and (buffer-live-p buffer)
	   (not (get-buffer-window buffer 'visible))
	   (not (cl-dolist (entry jabber-activity-banned)
		  (when (string-match entry jid)
		    (cl-return t))))
	   (not (cl-dolist (entry noa/jabber-activity-dont-show)
		  (when (string-match entry jid)
		    (cl-return t)))))))(setopt jabber-activity-show-p #'noa/jabber-activity-show-p)
#+end_src

Because i have a lot of irc channels shown, a lot of the jids are quite long and ugly.  Mostly i know what irc server a channel belongs to, so i want to cut off the parts after the percentage symbol.

I might want to look at the jabber-jid-username function which seems to do this.

#+begin_src elisp
  (defun noa/jabber-activity-make-string (jid)
    "Return the nick of the JID.
  If no nick is available, return the user name part of the JID. In
  private MUC conversations, return the user's nickname."
    (if (jabber-muc-sender-p jid)
	(jabber-jid-resource jid)
      (let ((nick (jabber-jid-displayname jid))
	    (user (jabber-jid-user jid))
	    (username (jabber-jid-username jid)))
	(if (and username (string= nick user))
	    username
	  nick))))
  (setopt jabber-activity-make-string #'noa/jabber-activity-make-string)
#+end_src

I'm on a laptop, so whenever i shut it i get disconnected.  Jabber can query auth-source for my password, so automatically reconnecting is useful and doesn't need me to do anything.

#+begin_src elisp
(setopt jabber-auto-reconnect t)
#+end_src

Because my xmpp server supports message history, i don't need to worry about exiting without seeing all messages, as they'll be there when i get back.

#+begin_src elisp
(setopt jabber-activity-query-unread nil)
#+end_src

The default buffer names are a bit ugly to look at, so i change them to a similar format as eww.  Which is still pretty ugly, welcome to emacs

#+begin_src elisp
(setopt jabber-chat-buffer-format "*%n | jabber*"
        jabber-groupchat-buffer-format "*%n | jabber*")
#+end_src

As alluded to above, jabber.el also has a terrible terrible habit of sending a message to the echo area for every change in online state of my contacts, and every single message in any channel.  Obviously this gets annoying fast, especially if i'm using the minibuffer at that time.  Thank you to acdw for pointing me towards these helpful hooks to remove.

#+begin_src elisp
(remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo)
(remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo)
#+end_src

Also stop jabber from clobbering the dired-jump binding and instead use something on C-c like a good child.

#+begin_src elisp
(with-eval-after-load 'jabber
  (keymap-global-set "C-x C-j" #'dired-jump))
#+end_src

Finally, have a binding to jump to a buffer in which there's been some new activity.  Better than always using switch-to-buffer to get there.

#+begin_src elisp
(keymap-global-set "C-c C-j" #'jabber-activity-switch-to)
#+end_src

* Nov.el
#+begin_src elisp
  (package-ensure 'nov)
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
#+end_src

* Writing prose
The majority of the work i do in emacs is writing documents, so it's nice to know that the words coming out of my fingers are the ones i expect them to be.

** Org mode

I thought it would be a good idea to have the ability to set headings in my notes as things i wanted to recall later, as i store most of the things i think about in an org file.  I still do think that, but the package isn't in melpa right now and i'm lazy and i'm not in education, so i haven't got around to it yet.

#+begin_src elisp :tangle no
  (use-package org-fc
    :disabled
    :ensure t
    :custom
    (org-fc-directories (expand-file-name "~/data/notes/")))
#+end_src

If we enable this, emphasis markers will be hidden for a more word processor feel.  This has the downside of meaning you have to delete a hidden character to get rid of bold or italic text.  I don't have much of a problem with seeing the emphasis markers so i'm willing to put up with any aesthetic shortcomings for a better user experience.  The package org-appear solves this by hiding them, but showing them when the point is over them, but i don't think having a whole package just for that is worth it.

#+begin_src elisp
(setopt org-hide-emphasis-markers nil)
#+end_src

#+begin_src elisp
  (setopt org-startup-with-inline-images t)
  (setopt org-image-actual-width '(300))
  (setopt org-auto-align-tags nil)
  (setopt org-tags-column 0)
  (setopt org-catch-invisible-edits 'show-and-error)
  (setopt org-special-ctrl-a/e t)
  (setopt org-insert-heading-respect-content t)
  (setopt org-ellipsis "…")
  (setopt org-display-custom-times t)
  (setopt org-time-stamp-custom-formats '("%Y-%m-%d" . "%Y-%m-%d %H:%M"))
  ;; (setopt org-time-stamp-formats '("<%Y-%m-%d>" . "<%Y-%m-%d %H:%M>"))
  (setopt org-extend-today-until 4)
  (setopt org-adapt-indentation nil)
  (setopt org-log-done 'time)
  (setopt org-return-follows-link t)
  (setopt org-agenda-files '("~/data/notes/notes.org"))
  (setopt org-capture-bookmark nil)
  (setopt org-capture-templates '(("j" "Journal" entry (file+olp+datetree "~/data/notes/notes.org") "* %?\n" :empty-lines 1)
                                  ("w" "Website" entry (file+olp+datetree "~/data/notes/notes.org") "* %a\n%?\n" :empty-lines 1)
                                  ("c" "Contact" entry (file+olp+datetree "~/data/notes/notes.org")
                                   "* %^{Name}
  :PROPERTIES:
  :ADDRESS: %^{Address}
  :BIRTHDAY: %^{yyyy-mm-dd}
  :EMAIL: %^{EMAIL}p
  :NOTE: %^{NOTE}
  :END:"
                                   :empty-lines 1)
                                  ))
#+end_src

#+begin_src elisp
  (setopt org-agenda-block-separator nil)
  (setopt org-agenda-include-deadlines t)
  ;; Always start the weekly agenda for a week from today
  (setopt org-agenda-start-on-weekday nil)
  ;; Show week of agenda by default
  (setopt org-agenda-span 'week)
  (setopt org-agenda-prefix-format '((agenda . " %i %?-12t% s")
				     (todo . " %i ")
				     (tags . " %i ")
				     (search . " %i ")))
#+end_src

The default agenda time grid has thick lines which i don't think add anything to the output.  Similarly, the arrow pointing to the current time has a long line which helps me to find it more easily, but by getting rid of the excess of colour and making it bold, i never struggle to find it anyway.

#+begin_src elisp
  (setopt org-agenda-time-grid '((daily today require-timed)
				 (800 1000 1200 1400 1600 1800 2000)
				 "       " "               "))
  (setopt org-agenda-current-time-string "◀ you are here")
  (setopt org-support-shift-select 'always)
#+end_src

#+begin_src elisp
  ;; Make org html export more pleasant by default
  (setopt org-export-with-smart-quotes t)
  (setopt org-export-with-entities nil)
  (setopt org-export-headline-levels 5)
  (setopt org-export-with-toc nil)
  (setopt org-export-with-section-numbers nil)
  (setopt org-html-doctype "html5")
  (setopt org-html-html5-fancy t)
  (setopt org-html-container-element "section")
  (setopt org-html-divs '((preamble  "header" "preamble")
			  (content   "main" "content")
			  (postamble "footer" "postamble")))
  (setopt org-export-with-sub-superscripts t)
  (setopt org-html-head-include-default-style nil
	  org-html-head-include-scripts nil
	  org-html-validation-link "")
#+end_src

This doesn't work with code blocks at the moment.

#+begin_src elisp
  (setopt org-html-indent nil)
   #+end_src

#+begin_src elisp
(global-set-key (kbd "C-c c") #'org-capture)
(global-set-key (kbd "C-c a") #'org-agenda)
(global-set-key (kbd "C-c l") #'org-store-link)
#+end_src

#+begin_src elisp :tangle no
  ;; (package-ensure 'helm-org-ql)
  ;; (package-ensure 'org-super-links)
  ;; (setopt org-super-links-search-function #'helm-org-ql)
#+end_src

#+begin_src elisp :tangle no
  (package-ensure 'org-modern)
  (add-hook 'org-mode-hook #'org-modern-mode)
  ;; (add-hook 'org-agenda-finalize-hook #'org-modern-agenda)
#+end_src

There are three ways to make bullet lists in org mode, which seems a bit excessive to me.  I almost always only use the hyphen, but i like my bullet points to look like bullets, so here i overwrite the hyphen display to show a bullet point.  While i'm at it, i overwrite the others too, because they are functionally identical so should probably look the same too.

#+begin_src elisp
(setopt org-modern-list '((?+ . " • ")
		          (?- . " • ")
		          (?* . " • ")))
			  #+end_src

This doesn't respect my timestamp custom format configured above.

#+begin_src elisp
(setopt org-modern-timestamp nil)
#+end_src

#+begin_src elisp
  (setopt org-modern-star nil)
  (setopt org-modern-keyword nil)
  (setopt org-modern-checkbox '((88 . "☑")
				(45 . #("□–" 0 2
					(composition
					 ((2)))))
				(32 . "□")))
  (setopt org-modern-table nil)
  (setopt org-modern-block-name nil)
  (setopt org-modern-internal-target nil)
  (setopt org-modern-tag nil)
#+end_src

#+begin_src elisp
(setopt org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
#+end_src

*** Nicer org-mode tables

#+begin_src elisp
  (package-ensure 'valign)
  (add-hook 'markdown-mode-hook #'valign-mode)
  (add-hook 'org-mode-hook #'valign-mode)

  (setopt valign-fancy-bar t)
  (setopt valign-max-table-size 0)
#+end_src

** Website generation

#+begin_src elisp
(defun read-file-as-string (filename)
  "Read file contents from FILENAME."
  (with-temp-buffer
    (insert-file-contents filename)
    (buffer-string)))
#+end_src

#+begin_src elisp
(setq noa/website-header (read-file-as-string "/home/noa/projects/org-website/templates/header.html"))
(setq noa/website-footer (read-file-as-string "/home/noa/projects/org-website/templates/footer.html"))
#+end_src

The index page generation functions were taken from Dennis Ogbe.  Thank you!

#+begin_src elisp
(defun my-blog-parse-sitemap-list (l)
  "Convert the sitemap list in to a list of filenames."
  ;; LIST looks like:
  ;; (unordered ("[[file:uses.org][Things i use]]") ("[[file:media.org][Media Diary]]") ("[[file:tanklobsters.org][Tank lobsters]]"))
  (mapcar #'(lambda (i)
              (let ((link (with-temp-buffer
                            (let ((org-inhibit-startup nil))
                              (insert (car i))
                              (org-mode)
                              (goto-char (point-min))
                              (org-element-link-parser)))))
                (when link
                  (plist-get (cadr link) :path))))
          (cdr l)))
#+end_src

#+begin_src elisp
  (defun my-blog-sort-article-list (l p)
    "sort the article list anti-chronologically."
    (sort l #'(lambda (a b)
                (let ((date-a (org-publish-find-date a p))
                      (date-b (org-publish-find-date b p)))
                  (not (time-less-p date-a date-b))))))
#+end_src

#+begin_src elisp
(defun noa/naive-org-first-paragraph (file)
  "Naively returns the first paragraph of FILE.

	The way that the first paragraph is determined is to assume that there will be an org metadata block beforehand, so look for the first two consecutive newlines and mark the following paragraph."
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (re-search-forward "\n\n")
    (mark-paragraph)
    (let ((beg (mark))
          (end (point)))
      (buffer-substring beg end))))
#+end_src

#+begin_src elisp
  (defun noa/website-sitemap (title list)
    "Generate the index page for my website."
    ;; LIST looks like:
    ;; (unordered ("[[file:uses.org][Things i use]]") ("[[file:media.org][Media Diary]]") ("[[file:tanklobsters.org][Tank lobsters]]"))
    (with-temp-buffer
      ;; mangle the parsed list given to us into a plain lisp list of files
      (let* ((filenames (my-blog-parse-sitemap-list list))
	     (project-plist (assoc "website-pages" org-publish-project-alist))
	     (articles (my-blog-sort-article-list filenames project-plist)))
	(message (concat "PLIST: " (plist-get project-plist :base-directory)))
      
	(insert "Several parts of this website are broken as i wrangle with the monstrosity that is programming in emacs lisp.  The content should still be fine, but for further cosmetics please hold <3\n\n")
	(dolist (file filenames)
	  (let* ((abspath (file-name-concat "/home/noa/data/share" file))
		 ;; (abspath (file-name-concat (plist-get project-plist :base-directory) file))
		 (relpath (file-relative-name abspath "/home/noa/data/share"))
		 (title (org-publish-find-title file project-plist))
		 (date (format-time-string (car org-time-stamp-custom-formats) (org-publish-find-date file project-plist)))
		 (preview (noa/naive-org-first-paragraph abspath)))
	    (insert (concat "* [[file:" relpath "][" title "]]\n"))
	    (insert (concat
		     "*" date ":*"
		     preview))
	    (insert "\n")))
	;; insert a title and save
	(insert "#+TITLE: noa.pub\n")
	(buffer-string))))
      #+end_src

#+begin_src elisp
  (defun noa/org-publish-sitemap-entry (entry style project)
    "Default format for site map ENTRY, as a string.
  ENTRY is a file name.  STYLE is the style of the sitemap.
  PROJECT is the current project."
    (cond ((not (directory-name-p entry))
	   (format "%s: [[file:%s][%s]]"
		   (format-time-string (car org-time-stamp-custom-formats) (org-publish-find-date entry project))
		   entry
		   (org-publish-find-title entry project)))
	  ((eq style 'tree)
	   ;; Return only last subdir.
	   (file-name-nondirectory (directory-file-name entry)))
	  (t entry)))

  (setq org-publish-project-alist
	`(("website"
	   :components ("website-pages" "website-assets"))
	  ("website-pages"
	   :publishing-function org-html-publish-to-html
	   :base-directory "/home/noa/data/share"
	   :publishing-directory "/home/noa/projects/org-website"
	   :base-extension "org"
	   :with-drawers t
	   :html-link-home "/"
	   :html-head-include-default-style nil
	   :html-head-include-scripts nil
	   :html-doctype "html5"
	   ;; :html-validation-link nil
	   :html-preamble ""
	   :html-postamble ,noa/website-footer
	   :html-home/up-format ""
	   :html-link-up ""
	   :html-html5-fancy t
	   :html-indent nil
	   :html-head "<link rel=\"icon\" href=\"data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>𰻝</text></svg>\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"love.css\" />
  <meta name=\"color-scheme\" content=\"light dark\">"
	   :auto-sitemap t
	   :sitemap-filename "/home/noa/projects/org-website/index.org"
	   :sitemap-title "noa.pub"
	   :sitemap-style list
	   :sitemap-format-entry noa/org-publish-sitemap-entry
	   ;; :sitemap-function noa/website-sitemap
	   :sitemap-sort-folders ignore
	   :sitemap-sort-files anti-chronologically
	   :sitemap-ignore-case t)
	  ("website-assets"
	   :publishing-function org-publish-attachment
	   :base-directory "/home/noa/data/share"
	   :publishing-directory "/home/noa/projects/org-website"
	   :base-extension "css\\|js\\|png|\\jpg"
	   :recursive t)))
#+end_src

** Markdown

#+begin_src elisp
  (package-ensure 'markdown-mode)
  (add-to-list 'auto-mode-alist
	       '("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . markdown-mode))

#+end_src

** Autocorrection
Abbrev mode expands one string into another string.  I use it as a simple autocorrect mode.  If i misspell a word, i run C-x a i g which will prompt me for what to expand the previous word into.  I type the correct spelling, and whenever i make that mistake again, it will automatically be corrected.  It's important to be careful not to set something that could be a typo for two words though, because otherwise it gets even more annoying.  Luckily it's easy to update the abbrevs which are stored in ~/.config/emacs/abbrev_defs.  M-x list-abbrevs is also a nice command which shows all the saved abbrevs and how many times they've been expanded.

#+begin_src elisp
(add-hook 'text-mode-hook #'abbrev-mode)
#+end_src

** Spellcheck
Jinx is a package for spellchecking.  Previously i used [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Spelling.html][flyspell]], which is built in, and [[https://codeberg.org/ideasman42/emacs-spell-fu][spell-fu]].  Flyspell is not ideal because it only checks the word under the point.  Furthermore, the correction interface is not pleasant to look at with a proportional font as it uses spaces to align the candidates.  Spell-fu checks all the words that are visible, thereby behaving much more like a traditional spell checker.  Jinx improves on spell-fu by interacting with the spellcheck process in a more efficient way, and has a nicer interface to corrections.

#+begin_src elisp
  (package-ensure 'jinx)
  (setopt global-jinx-mode t)
#+end_src

Replace the default spellcheck binding with jinx

#+begin_src elisp
(keymap-global-set "M-$" #'jinx-correct)
(keymap-global-set "C-M-$" #'jinx-languages)
#+end_src

*** Add corrected words to abbrev

This is a cool function i took from the jinx wiki.  It automatically creates an abbrev for words i correct, so if i make the same error again, it gets fixed without me having to do anything!

#+begin_src elisp
(defun jinx--add-to-abbrev (overlay word)
  "Add abbreviation to `global-abbrev-table'.
The misspelled word is taken from OVERLAY.  WORD is the corrected word."
  (let ((abbrev (buffer-substring-no-properties
                 (overlay-start overlay)
                 (overlay-end overlay))))
    (message "Abbrev: %s -> %s" abbrev word)
    (define-abbrev global-abbrev-table abbrev word)))
(advice-add 'jinx--correct-replace :before #'jinx--add-to-abbrev)
#+end_src

*** Consistent face on hover
Almost everywhere else, the ~highlight~ face is used for interactive text on mouse over.  However, jinx reuses its own ~jinx-highlight~ face, which i don't really like.  This face clashes with the face i use for the active region, which means selecting text across a misspelled word can be a bit confusing.  If i change the ~jinx-highlight~ face, it looks wrong when i've activated jinx to correct the word.

#+begin_src elisp
  (put 'jinx-overlay 'mouse-face '(jinx-misspelled highlight))
#+end_src

I mentioned that i thought this wasn't the best default behaviour [[https://github.com/minad/jinx/discussions/184][in the jinx repository]], but Daniel seems happy with it.  I appreciate that he's made it so easy to change the behaviour to something i prefer.

* Completing-read everywhere with consult
Consult is a package to provide navigation commands that take advantage of completing-read.  I set up a nice completing-read environment earlier with vertico.  There are a lot of commands built in to consult, and it's possible to define more.  But i use it very simply.

#+begin_src elisp
  (package-ensure 'consult)
  (package-activate 'consult)
#+end_src

Consult buffer can be used instead of the default buffer menu.  It lists recently used files and bookmarks as well as open buffers.

#+begin_src elisp
  (autoload #'consult-buffer "consult" nil t)
  (global-set-key [remap switch-to-buffer] #'consult-buffer)
#+end_src

These are some other almost default functions but with extra interactivity.

#+begin_src elisp
  (global-set-key [remap yank-pop] #'consult-yank-pop)
  (global-set-key [remap goto-line] #'consult-goto-line)
  (global-set-key [remap imenu] #'consult-imenu)
  (with-eval-after-load 'org
    (bind-key [remap imenu] #'consult-org-heading #'org-mode-map))
  (global-set-key [remap info] #'consult-info)
#+end_src

* Annotations for completing-read
Marginalia provides us with annotations for candidates in completing read functions.  This is things like docstrings for functions, file permissions in find-file, and so on.  It's a small quality of life improvement.

#+begin_src elisp
  (package-ensure 'marginalia)
  (setopt marginalia-mode t)
#+end_src

We want to always show the relative age of a file.  By default, files which haven't been modified for more than two weeks will display an absolute date.

#+begin_src elisp
(setopt marginalia-max-relative-age most-positive-fixnum)
#+end_src

My keyboard has a tab key and an i key.  For legacy reasons, by default emacs converts C-i to mean the same thing as the tab key, but i don't really want that.  The tab key is called <tab> and it gets translated to TAB.  C-i is TAB, but i'd rather it by C-i.  That's what this decode line does.

#+begin_src elisp
(define-key input-decode-map [?\C-i] [C-i])
#+end_src

Now that tab and C-i are properly distinguished, i can bind C-i to completion at point.

#+begin_src elisp
(global-set-key (kbd "<C-i>") 'completion-at-point)
#+end_src

I also want to make the completion at point function a bit more friendly than the default, so i ask consult to provide the completion functionality.

#+begin_src elisp
(setopt completion-in-region-function 'consult-completion-in-region)
#+end_src

* Getting help
Emacs is great because of its great built in help system!

** Close help buffers more easily
By default, opening a help buffer keeps the original window active, but this means that i have to go into the help buffer to close it.  Some people might like to always have the help buffer hanging around, but i like my screen empty of distractions.  Luckily there's a simple setting to select the help window so that i can easily close it again with q.

#+begin_src elisp
(setopt help-window-select t)
#+end_src

Also to that end, we use the same window as the help buffer to open source files.

#+begin_src elisp
  (setopt help-window-keep-selected t)
#+end_src

** Better aesthetics
Help buffers put quotes around links in the buffer.  I don't think this adds anything to the output.

#+begin_src elisp
  (setopt help-clean-buttons t)
	  #+end_src

The arguments are shown in upper case.  I don't want to be shouted at!  I already define them to be bold, this is all the attention they need.

#+begin_src elisp
  (setopt help-downcase-arguments t)
	  #+end_src

** Edit variables from help buffers
Often, i will look at the documentation for a variable and immediately want to play with it.  This removes one step between wanting that and being able to do that.

#+begin_src elisp
  (setopt help-enable-variable-value-editing t)
#+end_src

* Web browsing in emacs
I hear it's now possible to run a full fat browser inside emacs.  But this is surely quite heavyweight, and doesn't get to take advantage of things like ublock origin.  When it comes to alternative browsers, it's always the same story; i like the concept, but most websites are a horrific experience without a good ad blocker.

Instead, i use eww, a browser more closely aligned with browsers for the terminal.  Despite the name, eww is a delight to use for text-heavy websites.  If a website doesn't render well in it, because it uses fancy layout tricks or lots of javascript, we can press ~&~ to open the url in firefox.

#+begin_src elisp
  (setopt browse-url-browser-function 'eww-browse-url
	  browse-url-secondary-browser-function 'browse-url-default-browser)
#+end_src

For the kind of sites i use eww to visit, i've not had a use for cookies.  We can tell emacs that we don't trust cookies from any sites, we don't trust cookies from all sites, and frankly, we don't want to use cookies.

#+begin_src elisp
  (setopt url-cookie-trusted-urls '()
	  url-cookie-untrusted-urls '(".*")
	  shr-cookie-policy nil)
#+end_src

Eww has rudimentary support for colours.  But i don't want web pages to be able to specify their own colours, because i like the colours i already have set.

#+begin_src elisp
  (setopt shr-use-colors nil)
#+end_src

Shr has the ability to break paragraphs to fit on the screen.  Instead of this, we set it to not break any lines, and use visual-fill-column-mode to do this for us instead.

#+begin_src elisp
  (setopt shr-max-width nil)
#+end_src

We can set what the maximum size of an image in a window should be.  This is a fraction of the total window width or height, and if the image would be bigger than this, it'll be resized to fit.  It's useful to have it smaller because emacs still sort of chokes on scrolling when there are large images in a buffer.  This is the default value of this option.

#+begin_src elisp
  (setopt shr-max-image-proportion 0.9
	  shr-discard-aria-hidden t)
	#+end_src

The default name for the eww buffer is *eww*. This is unhelpful because it makes having more than one eww buffer open a bit of a chore to navigate. We can set it to 'url, 'title, or a function. I set it to 'title because marginalia already shows me the url. However, this means that i can't search for a url name when switching buffers. See the help for this variable for an example of a function which gives the page title and the url.

#+begin_src elisp
(setopt eww-auto-rename-buffer 'title)
#+end_src

Goto address mode makes urls and email address in a buffer clickable.  I want these clickable links to look like links, because that's what they are.  The two mouse face variables are what face is used on hover, which at the moment i ignore.  It might also be worth setting them to 'highlight.

#+begin_src elisp
(setopt global-goto-address-mode t)
(setopt goto-address-mail-face 'link)
(setopt goto-address-mail-mouse-face 'highlight)
(setopt goto-address-url-face 'link)
(setopt goto-address-url-mouse-face 'highlight)
#+end_src

#+begin_src elisp
(setopt global-eldoc-mode t)
#+end_src

Use a bar cursor and blink it and don't stop blinking it.  i don't know how i feel about this yet to be honest, but it helps me know which window is active so for now i'm keeping it

#+begin_src elisp
(setopt
 cursor-type 'bar
 blink-cursor-mode t
 blink-cursor-interval 0.7)
#+end_src

* Dired
Dired is a really nice package which, as with a lot of emacs, has some dodgy defaults.  Here we round off some of the sharp edges to make it more enjoyable to use.

By default, dired permanently deletes files.  But i have quite a bit of storage and also make bad decisions regularly, so it seems fitting to make use of the wonderful invention that is the trash.  People who have used systems from the last forty years or so will likely be familiar with this innovation.

#+begin_src elisp
(setopt delete-by-moving-to-trash t)
#+end_src

It's not fun to be asked every time whether we want to delete a directory recursively.  It's an understandable default for safety reasons, but because we are not deleting permanently but rather just moving to the trash, it's not such a concern.

#+begin_src elisp
(setopt dired-recursive-deletes 'always)
#+end_src

Recursive copying isn't even destructive, so i definitely don't want to be asked about that.

#+begin_src elisp
(setopt dired-recursive-copies 'always)
#+end_src

After we delete some files or directories, it makes sense to get rid of any buffers which are looking at those files or directories.

#+begin_src elisp
(setopt dired-clean-up-buffers-too nil)
#+end_src

With this set, if we have two dired buffers open next to one another, a rename operation in one will default to the directory shown in the other.  In this way, we can pretend we are using some kind of norton commander like file browser instead of slumming it in emacs.

#+begin_src elisp
(setopt dired-dwim-target t)
#+end_src

These are some useful ls switches.  We have to keep -l.  To show dotfiles as well, we use -a.  To sort numbers by number order instead of lumping together ones, twos, and so on, we use -v.  Because we don't have colour, it's nice to have a clear indicator of what is a file and what is a directory, as well as other different things like symlinks which i never remember.  By using -F, a forward slash is appended to every directory.  And to get more easily understandable file sizes, we use -h, which will tell us the file size in kilobytes or megabytes rather than a huge number that means nothing to me.  I won't explain the meaning of the long flag.

#+begin_src elisp
(setopt dired-listing-switches "-alvFh --group-directories-first")
#+end_src

By default, don't show dired details

#+begin_src elisp
(add-hook 'dired-mode-hook #'dired-hide-details-mode)
#+end_src

I find it useful to see the recursive sizes of directories.  This can be a little slow, so setting it as always on might not be the best idea, but the longest i've had to wait is about a second, and that's only if i run it on my home directory, so i think it's worth it at the moment.

#+begin_src elisp
  (package-ensure 'dired-du)
  (setopt dired-du-size-format t)
  (add-hook 'dired-mode-hook #'dired-du-mode)
#+end_src

* Behaviour
** Switching buffers

There is a distinction in emacs between manual buffer switching that i initiate, and automatic buffer switching when emacs wants to show a buffer.  In practice this means that any rules i write for where to display buffers get ignored if i try to show that buffer myself.  This line changes that behaviour.

#+begin_src elisp
  (setopt switch-to-buffer-obey-display-actions t)
#+end_src

** History
#+begin_src elisp
(setopt history-length 250
        kill-ring-max 25)
#+end_src

#+begin_src elisp
(setopt savehist-file "~/.config/emacs/savehist")
(setopt savehist-additional-variables
        '(kill-ring
          command-history
          set-variable-value-history
          custom-variable-history
          query-replace-history
          read-expression-history
          minibuffer-history
          read-char-history
          face-name-history
          bookmark-history
          file-name-history))
(setopt savehist-mode t)
#+end_src

#+begin_src elisp
(setopt window-divider-mode t)
(setopt window-divider-default-right-width 1)
(setopt window-divider-default-bottom-width 1)
(setopt window-divider-default-places t)
#+end_src

Taken from configuration for the vertico stack:
Add prompt indicator to `completing-read-multiple'.
We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
#+begin_src elisp
(defun crm-indicator (args)
  (cons (format "[CRM%s] %s"
		(replace-regexp-in-string
		 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
		 crm-separator)
		(car args))
	(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
#+end_src

Do not allow the cursor in the minibuffer prompt

#+begin_src elisp
  (setopt minibuffer-prompt-properties
	  '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
  (setopt battery-mode-line-format "🔋%p%b ")
  (setopt display-battery-mode t)
  (setopt display-time-default-load-average nil)
  (setopt display-time-24hr-format t)
  (setopt display-time-mode t)
#+end_src

Support opening new minibuffers from inside existing minibuffers.

#+begin_src elisp
  (setopt enable-recursive-minibuffers t)
#+end_src

Whether to drop into the debugger on any error.  This seems cool, but in practice is a bit annoying.

#+begin_src elisp
  (setopt debug-on-error nil)
#+end_src

Hide commands in M-x which do not work in the current mode.

#+begin_src elisp
(setopt read-extended-command-predicate 'command-completion-default-include-p)
#+end_src

#+begin_src elisp
(setopt recentf-max-menu-items 25
        recentf-save-file "~/.config/emacs/recentf"
        recentf-mode t
        bookmark-default-file "~/.config/emacs/bookmarks")
#+end_src

** Undo

Undo is on C-/ and redo is on C-S-/.  It's not standard, but these bindings are easier to remember.  And with this setting, it behaves for the most part like undo in other programs, which isn't as good as i'd really want, but is something i can reason about much more easily than the default undo.

#+begin_src elisp
(setopt undo-no-redo t)
#+end_src

** Saving
Backups are pointless in long emacs sessions imo, but autosaves are useful.

#+begin_src elisp
  (setopt remote-file-name-inhibit-auto-save t)
  (setopt remote-file-name-inhibit-auto-save-visited t)
  (setopt backup-directory-alist '(("." . "~/.config/emacs/backups/"))
	  make-backup-files nil
	  backup-by-copying t
	  create-lockfiles nil
	  auto-save-mode t
	  auto-save-interval 6  ;; every six keystrokes
	  auto-save-timeout 5 ;; every 5 seconds
	  auto-save-default t
	  auto-save-no-message t
	  save-silently t
	  version-control t
	  ;; this will auto save to the current file
	  auto-save-visited-mode t)
  (add-hook 'focus-out-hook (lambda () (interactive) (save-some-buffers t)))
  (add-hook 'mouse-leave-buffer-hook (lambda () (interactive) (save-some-buffers t)))
#+end_src

** Unfill commands

#+begin_src elisp
(defun unfill-paragraph ()
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive)
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))
(global-set-key (kbd "M-Q") #'unfill-paragraph)
#+end_src

The following functions were written by acdw for use with buffers like dict and help, where it's often ugly to read them with their hard wrapping

#+begin_src elisp
;; unfill/refill a buffer

;; unfill makes all paragraphs 1 line
(defun unfill-region (beg end)
  (let ((fill-column most-positive-fixnum))
    (fill-region beg end)))

;; this command is what you'd run in a hook
;; visual-line-mode makes it so it doesn't look shit
(defun unfill-buffer-force ()
  (interactive)
  (let ((buffer-read-only nil))
    (unfill-region (point-min) (point-max))
    (visual-line-mode)))

;; refill makes the width equal to the window-width minus 2
;; (you could change it ofc)
(defun refill-region (beg end)
  (let ((fill-column (- (window-width) 2)))
    (fill-region beg end)))

;; this command is what you'd run in a hook or w/e
(defun refill-buffer-force ()
  (interactive)
  (let ((buffer-read-only nil))
    (refill-region (point-min) (point-max))))
#+end_src

** Better control l
C-l goes in order, rather than first centering the cursor.  This is particularly pleasant with a ~scroll-margin~ greater than the default of zero, which serves to keep a line of context at each edge of the screen, as well as triggering a scroll when the point is that far away from the screen edge.

#+begin_src elisp
  (setopt recenter-positions '(top middle bottom))
  (setopt scroll-margin 1)
#+end_src

** Smooth scrolling

Emacs uses choppy scrolling by default.  If i scoll with my trackpad, it's nice to have it move tiny amounts at the same time as my fingers, which pixel-scroll-precision-mode allows for.  This also has the benefit of making scrolling over images a little bit of a nicer experience.

#+begin_src elisp
(setopt pixel-scroll-precision-mode t
        pixel-scroll-precision-use-momentum t)
#+end_src

** Remember my position in files

#+begin_src elisp
(setopt save-place-mode t)
#+end_src

** More pleasant prompts

The former means that when given a list of choices, we can use single character abbreviations to answer.  The latter is a fancy way of defaliasing yes-or-no-p to y-or-n-p.

#+begin_src elisp
(setopt read-answer-short t)
(setopt use-short-answers t)
#+end_src

** Disable disabled commands

#+begin_src elisp
  (setq disabled-command-function nil)
#+end_src

** Don't save changes in the customize interface

#+begin_src elisp
  (setopt custom-file (make-temp-file "custom"))
#+end_src

** Scrolling in compilation

Scroll along with text in compilation mode, and stop scrolling at the first error.

#+begin_src elisp
  (setopt compilation-scroll-output 'first-error)
#+end_src

** Don't advertise gnu on startup

#+begin_src elisp
  (setq inhibit-startup-echo-area-message "noa")  ;; #userfreedom
#+end_src

** Better buffer naming

#+begin_src elisp
  (setopt uniquify-after-kill-buffer-p t
	  uniquify-buffer-name-style 'forward
	  uniquify-ignore-buffers-re "^\\*"
	  uniquify-separator "/")
#+end_src

** Emacs server

#+begin_src elisp :tangle no
(unless (server-running-p) (server-start)))
#+end_src

** Ignore the bell

Ignore the bell.

#+begin_src elisp
  (setopt ring-bell-function 'ignore)
#+end_src

** Automatic help at point

This puts some help in the minibuffer when we leave the point on some interactive text.

#+begin_src elisp
(setopt help-at-pt-display-when-idle 'never)
#+end_src

** Long lines

Better support for long lines.

#+begin_src elisp
  (setopt global-so-long-mode t)
#+end_src

** Kill processes without asking

This will stop us being prompted before killing a buffer with a running process:

#+begin_src elisp
  (setopt kill-buffer-query-functions
	  (remq 'process-kill-buffer-query-function
		kill-buffer-query-functions))
#+end_src

** Automatically revert buffers

Automatically revert buffers when they change on disk.  This doesn't apply to tramp.

#+begin_src elisp
  (setopt global-auto-revert-mode t)
#+end_src

** Visit symlinks

This behaviour changes how we visit symlinks.

#+begin_src elisp
  (setopt find-file-visit-truename t)
  (setopt vc-follow-symlinks 'ask)
#+end_src

** Ibuffer

Use ibuffer instead of list-buffers

#+begin_src elisp
  (global-set-key [remap list-buffers] 'ibuffer)
#+end_src

* Writing code
** Indentation: tabs and whitespace settings
In general, my rules for inserting tabs are that the tab key should insert tabs.  I personally prefer tabs to spaces, because tabs work reasonably well whatever font or tab width one chooses to set, whereas spaces are the same width for everyone, except when someone uses a proportional font in which case they are narrower than expected.  Furthermore, people tend to use spaces for alignment, which looks bad when you can't rely on every character being the same width.

However, i'm in the minority, and fighting with the very complicated emacs indentation systems is simply not fun.  That said, i refuse to use a monospaced font.  Luckily the minority is more than one and someone has already done the hard work for me of writing a mode to make spaces for indentation work reasonably well with a proportional font.  That mode is elastic-indent-mode, and it very simply makes leading whitespace characters the same width as the characters on the line above.  It's a simple solution but most of the time it does what i want.

#+begin_src elisp
(require 'elastic-indent)
(add-hook 'prog-mode-hook #'elastic-indent-mode)
#+end_src

Elastic-table-mode is similar; for tab characters within lines, ensure that they change width to make subsequent lines form a table-like layout.

#+begin_src elisp
  (require 'elastic-table)
  (add-hook 'prog-mode-hook #'elastic-table-mode)
#+end_src

Previously i used a function to naïvely copy the whitespace from the line above.  This is the way that vi, nano, and acme all implement auto-indentation.  However, for now i'm experimenting with using the built-in indentation functions again.  I'm leaving this defun here for posterity.

#+begin_src elisp :tangle no
(defun noa/naive-return-and-indent ()
  "Insert a newline and copy the indentation of the previous line, vi/nano style."
  (interactive)
  (open-line 1)
  (let* ((start (progn (beginning-of-line) (point)))
	 (indent (progn (back-to-indentation) (point)))
	 (end (progn (end-of-line) (point)))
	 (whitespace (buffer-substring start indent)))
    (delete-trailing-whitespace start end)
    (beginning-of-line 2)
    (insert whitespace)))
#+end_src

We will only be trying to indent at the start of a line, and sometimes we will want to insert a standard tab character.  We can also set this option to 'complete, which will run completion at point if the region is already indented.

#+begin_src elisp
(setopt tab-always-indent nil)
#+end_src

Usually, we want indentation to be done with tabs.  Some modes make more sense to use spaces to indent.  Lisp is a particular example, and emacs's default behaviour of converting tabs into spaces is frankly horrific.  I've taken the below code from acdw to use spaces in these modes.

#+begin_src elisp
(defvar space-indent-modes '(emacs-lisp-mode
                             lisp-interaction-mode
                             lisp-mode
                             scheme-mode
                             python-mode)
  "Modes to indent with spaces, not tabs.")
(add-hook 'prog-mode-hook
          (defun indent-tabs-mode-maybe ()
            (setq indent-tabs-mode
                  (if (apply #'derived-mode-p space-indent-modes) nil t))))
#+end_src

I want to ensure that indentation is always correct.  The builtin electric indent mode works /sometimes/, but the aggressive indent mode package is more reliable.

#+begin_src elisp
  (package-ensure 'aggressive-indent)
  (setopt global-aggressive-indent-mode t)
#+end_src

* Sentences
I prefer to double space sentences.  But it seems that most other people do not, and the sentence navigation commands still work for my sentences with this set to nil, but don't work for other people's with it set to t.  There are of course some little errors with this, like ending a title with a full stop, but for the most part it's fine.

#+begin_src elisp
(setopt sentence-end-double-space nil)
#+end_src

If i write a script, i will always run chmod +x after saving it.  This command means i don't have to do that.

#+begin_src elisp
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
#+end_src

We are on a unix system, so it makes sense to end files in the unix system way.  I'm surprised this isn't the default.

#+begin_src elisp
(setopt require-final-newline t)
#+end_src

#+begin_src elisp
(setopt window-min-height 1
        window-combination-resize t
        window-resize-pixelwise t
        frame-resize-pixelwise t)
#+end_src elisp

* Tramp

Some tramp settings.

#+begin_src elisp
  (setopt remote-file-name-inhibit-locks t)
  (setopt tramp-inline-compress-start-size 1000)
  (setopt tramp-verbose 3)
  ;; (add-to-list 'tramp-remote-path 'tramp-own-remote-path)
#+end_src

The version control system will try each of these methods in order.  Because almost everything source controlled i do uses git, i put it first in the list.  But at the moment, because i don't think i actually use any of the other methods, i remove the rest of them from the list.

#+begin_src elisp
  (setopt vc-handled-backends '(Git))
  ;; (setopt vc-handled-backends '(Git RCS CVS SVN SCCS SRC Bzr Hg))
#+end_src

  It seems that tramp can also be made faster with these .ssh/config settings.

#+begin_src text :tangle no
  Host *
       ControlMaster auto
       ControlPath ~/.ssh/master-%h:%p
       ControlPersist 10m
       ForwardAgent yes
       ServerAliveInterval 60
#+end_src

* I-ching
I don't know why but it seems cool

#+begin_src elisp
  ;; (package-ensure 'i-ching)
#+end_src

* Keybindings
** Zap up to char

#+begin_src elisp
(global-set-key (kbd "M-z") 'zap-up-to-char)
#+end_src

* Search in buffer
Isearch is good, but it has some rough edges.  The easiest way forward was just to use ctrlf, which fixes most of them.  But i still had some gripes with ctrlf, like that it doesn't play well with a lot of other commands and packages and the general ecosystem built around isearch.  So i've tried to fix as many of the issues as i can while keeping real isearch.

It makes more sense to go to the start of the match, because i start searching where i want to be.

#+begin_src elisp
(defun isearch-exit-at-front ()
  "always exit isearch, at the front of search match."
  (interactive)
  (isearch-exit)
  (when isearch-forward
    (goto-char isearch-other-end)))
(defun isearch-exit-at-end ()
  "Always exit isearch, at the end of search match."
  (interactive)
  (isearch-exit)
  (when (not isearch-forward)
    (goto-char isearch-other-end)))
#+end_src

My preferred behaviour is for the point to be at the start of the match.  Because the search is incremental, usually i won't finish typing something useful before exiting the search, but i always start searching at a place i can reason about.  However, i can't figure out how to get this to work along with isearch-mb

#+begin_src elisp
;; (define-key isearch-mb-minibuffer-map (kbd "<return>") #'isearch-exit-at-front)
;; (define-key isearch-mb-minibuffer-map (kbd "C-<return>") #'isearch-exit-at-end)
;; Make isearch always quit on C-g
(define-key isearch-mode-map (kbd "C-g") #'isearch-cancel)
(define-key isearch-mode-map (kbd "C-o") #'isearch-occur)
(setopt search-whitespace-regexp ".*?")
(setopt isearch-lax-whitespace t)
(setopt isearch-lazy-count t)
(setopt isearch-allow-motion t)
(setopt isearch-repeat-on-direction-change t)
(setopt isearch-wrap-pause 'no)
#+end_src

Okay, i lied about being vanilla.  I want isearch to use the minibuffer like a good normal part of emacs

#+begin_src elisp
  (package-ensure 'isearch-mb)
  (setopt isearch-mb-mode t)
#+end_src

#+begin_src elisp
(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "C-x k") 'kill-this-buffer)
#+end_src

** Smart scan
This is a nice package to easily jump between identical things at point, like the * operator in vim.  I disabled it because i'm trying out using the same keybindings to jump between compile mode errors.

#+begin_src elisp :tangle no
  (use-package smartscan)
  (global-set-key (kbd "M-n") #'smartscan-symbol-go-forward)
  (global-set-key (kbd "M-p") #'smartscan-symbol-go-backward)
  (global-set-key (kbd "M-n") #'next-error)
  (global-set-key (kbd "M-p") #'previous-error)
#+end_src

** Jef Raskin's leap
As a concept, i really like the canon cat.  As described [[https://news.ycombinator.com/item?id=33286408][here]], it has two leap keys to navigate the buffer.  [[https://dercuano.github.io/notes/eink-design.html][According to Kragen Javier Sitaker]], you could move side to side with the arrow keys, but not up and down.  The idea was that any up and down movement would be better served by incremental search.  This is something that i want to explore.

#+begin_quote
If you press and release the LEAP key, it advances the cursor one character forward (or backwards if you hit the left leap key.)

If you press down (but do not release) the LEAP key you enter a search semi-mode. As you type a search term in this semi-mode, the cursor moves to the first instance of that search term it finds. After moving to the first instance of the search term, you release the leap key to exit the search semi-mode.

If you want to move the cursor to a subsequent instance of the search term, you press (and do not release) the "USE FRONT" key and press the leap key again (whose key front is labeled "Leap Again.")
#+end_quote

Semi-modes seem hard to implement in emacs, although in a lot of ways they are already here.  I am not a fan of modes in the vi-sense at all, and isearch in emacs is a mode like this.  That is, keybindings work differently when isearching, and you have to press C-g or RET to go back to the normal editing flow.

** Replace
Anzu provides real-time updates to the replacement string when running query-replace.

#+begin_src elisp
  (package-ensure 'anzu)
  (global-set-key [remap query-replace] 'anzu-query-replace)
  (global-set-key [remap query-replace-regexp] 'anzu-query-replace-regexp)
#+end_src

My computer has a small screen, so i find that it's more beneficial for me to split the frame into columns, so i get more context.  However, splitting in this way only gives me a (window-width) of 61, so emacs will always split into vertically stacked windows.  By setting this to 80, the first split should always be vertical.

#+begin_src elisp
(setopt split-width-threshold 80)
#+end_src

Define a handy function that allows me to do a full text search of every file in my home directory.  For the most part, this works well; ripgrep avoids binary files.  However, in some files with embedded images, it can add a lot of junk to the output.

#+begin_src elisp
  (defun noa/consult-rg-home ()
    (interactive)
    (consult-ripgrep "~/"))
  (global-set-key (kbd "M-<menu>") #'noa/consult-rg-home)
#+end_src

#+begin_src elisp
(setopt shell-file-name "/bin/sh")
#+end_src

#+begin_src elisp
(defun snarf-song (url)
  (interactive "sYoutube url:")
  (async-shell-command
   (concat "yt-dlp -x --audio-format=mp3 -o "
           (shell-quote-argument "~/media/music/%(title)s [%(id)s].%(ext)s")
           " "
           (shell-quote-argument url))))
#+end_src

Put a quote in the scratch buffer

#+begin_src elisp
(setopt cookie-file "~/data/quotes")
(setopt initial-scratch-message
        (concat (with-temp-buffer
                  (emacs-lisp-mode)
                  (insert (cookie cookie-file))
                  (mark-whole-buffer)
                  (comment-region (mark) (point))
                  (buffer-substring (mark) (point)))
                "\n\n"))
#+end_src

* Eshell
Eshell is a command shell written in elisp.  It integrates with emacs in a more consistent manner than the other shells available, although it still has its own quirks.

In general, i try and avoid using a shell if possible, because i think that bespoke emacs interfaces to different commands tend to have more pleasant interaction methods.  But there are still lots of things which are simply easier to do with a shell.

** Set the eshell banner
This is equivalent to the message of the day present in some shells.  I wanted it to print a new quote every time eshell opened, but when i tried that eshell refused to load.  Probably some mistake on my end.

#+begin_src elisp
(defun noa/eshell-banner-message ()
  (concat (cookie cookie-file)
          "\n\n"))
(setopt eshell-banner-message (noa/eshell-banner-message))
#+end_src

** Environment variables

#+begin_src elisp
(setenv "PAGER" "cat")
(setenv "TERM" "dumb")
(setenv "NO_COLOR")
(setenv "GPG_AGENT_INFO" nil)
#+end_src

* Pass(1)
The auth-source library allows packages to request password information.  It has a password store backend, which is nicer to work with than netrc files.

#+begin_src elisp
(require 'auth-source-pass)
(add-to-list 'auth-sources 'password-store)
(auth-source-forget-all-cached)
#+end_src

#+begin_src elisp
  (package-ensure 'pass)
#+end_src

#+begin_src elisp
(setopt confirm-kill-emacs 'y-or-n-p)
#+end_src

#+begin_src elisp
  (global-set-key (kbd "C-=") #'calc)
#+end_src

* Version control
I don't use magit.  I tried it once, but my use of version control is very limited to just making some changes and then committing them.  I don't work in programming and my projects are simple.

* Email
I like to have my email offline.  Of course my preference is also to have it inside of emacs for consistency with everything else.  Currently i use mu4e as my mail user agent, which relies on an external tool.  I also use two small command line programs to download and send mail.

#+begin_src sh :tangle no
  sudo apt install maildir-utils isync msmtp
  #+end_src

** Fetching mail with mbsync
Mbsync is downloaded with the isync package.  The configuration is not complicated.

I use fastmail to host my email.

#+begin_src text :tangle no
  IMAPAccount fastmail
  Host imap.fastmail.com
  User noa@gaiwan.org
 #+end_src

The ~passcmd~ is run to fetch my password.  I should put this into pass, but this isn't an ordinary password.  It's an app password, and needs to be updated on every new system.

#+begin_src text :tangle no
  PassCmd "cat ~/.config/mbsync-pass"
#+end_src

#+begin_src text :tangle no
  SSLType IMAPS

  IMAPStore fastmail-remote
  Account fastmail

  MaildirStore fastmail-local
  SubFolders Verbatim
  Path "~/mail/"
  Inbox "~/mail/Inbox"

  Channel fastmail
  Far :fastmail-remote:
  Near :fastmail-local:
  Patterns *
  Create Near
  Expunge Near
  SyncState *
  CopyArrivalDate yes
#+end_src

** Reading mail with mu4e

  #+begin_src elisp
    (setopt mu4e-headers-skip-duplicates t)
    (setopt mu4e-view-show-images t)
    (setopt mu4e-view-show-addresses t)
#+end_src

In theory i like format flowed, but what i like even more is just not hard wrapping messages and dealing with hacks to get around that to begin with.  If software supported soft wrapping at an arbitrary column, the world would be a better place.

#+begin_src elisp
(setopt mu4e-compose-format-flowed nil)
(setopt mu4e-change-filenames-when-moving t)
(setopt mu4e-use-fancy-chars nil)
(setopt mu4e-confirm-quit nil)
(setopt mu4e-headers-leave-behavior 'apply)
(setopt mu4e-headers-precise-alignment t)
(setopt mu4e-headers-fields '((:flags . 6)
                              (:from . 32)
                              (:subject)))
(setopt mu4e-search-threads nil)
(setopt mu4e-hide-index-messages t)
(setopt mu4e-get-mail-command "mbsync -c ~/.config/mbsyncrc fastmail")
(setopt mu4e-maildir "~/mail")
(setopt mu4e-drafts-folder "/Drafts")
(setopt mu4e-sent-folder   "/Sent")
(setopt mu4e-refile-folder "/Archive")
(setopt mu4e-trash-folder  "/Trash")
(setopt mu4e-bookmarks '((:name "Inbox" :query "maildir:/Inbox" :key ?i)
                         (:name "Feeds" :query "maildir:/Feeds" :key ?f)
                         (:name "Paper trail" :query "\"maildir:/Paper trail\"" :key ?p)))
#+end_src

function to move mails to trash

#+begin_src elisp
(with-eval-after-load 'mu4e
  (bind-key (kbd "d") #'noa/move-to-trash 'mu4e-headers-mode-map)
  (bind-key (kbd "d") #'noa/move-to-trash 'mu4e-view-mode-map)
  (fset 'noa/move-to-trash "mTrash"))
#+end_src

The first string is for when fancy characters are disabled, and the second is for when they are enabled.  But i set them all manually because some of the other characters are ugly in fancy mode, but i like my unicode thread icons.

#+begin_src elisp
(setq mu4e-headers-thread-connection-prefix '("│ " . "│ ")
      mu4e-headers-thread-last-child-prefix '("└ " . "└ ")
      mu4e-headers-thread-blank-prefix '("  " . "  ")
      mu4e-headers-thread-root-prefix '("□ " . "□ ")
      mu4e-headers-thread-child-prefix '("│ " . "│ ")
      mu4e-headers-thread-orphan-prefix '("♢ " . "♢ ")
      mu4e-headers-thread-duplicate-prefix '("≡ " . "≡ ")
      mu4e-headers-thread-first-child-prefix '("⚬ " . "⚬ ")
      mu4e-headers-thread-single-orphan-prefix '("♢ " . "♢ "))
#+end_src

Setting this to nil stops auto-fill from being automatically enabled in message buffers.

#+begin_src elisp
(setopt message-fill-column nil)
#+end_src

It's nice to have a message signature.  I want the signature to be loaded from a file, which is store in my configuration directory.

#+begin_src elisp
  (setopt message-signature t)
  (setopt message-signature-file "~/.config/signature")
#+end_src

The contents of the signature file is listed below.   Currently i only have two bullet points, when i think three would look nicer, but i don't have three things to say.

#+begin_src text :tangle ~/.config/signature
  ~noa (https://noa.pub)
   • I try to reply to formal emails in three sentences or fewer; excuse my brevity.
   • I queue replies and batch send them at intervals; excuse my untimeliness.
	  #+end_src

#+begin_src elisp
(add-hook 'mu4e-view-mode-hook #'visual-line-fill-column-mode)
#+end_src

** Reading mail with rmail
I have experimented with lots of different methods of reading mail, both in and out of emacs.  But i keep coming back to rmail, despite its many, many warts.

#+begin_src elisp
  (setopt rmail-primary-inbox-list
	  '("mbox://~/data/mail/spool.mbox"))
  (setopt rmail-file-name "~/data/mail/rmail.mbox")
  (setopt rmail-user-mail-address-regexp
	  (rx "noa@noa.pub"))
  (setopt rmail-mime-prefer-html nil)
  (setopt rmail-mime-attachment-dirs-alist '(("" "~/media")))
  (setopt rmail-displayed-headers
	  (rx bol (or "To" "Cc" "From" "Date" "Subject") ":"))
  (setopt rmail-secondary-file-directory "~/data/mail/archive.mbox")
  (setopt rmail-secondary-file-regexp "\\.mbox\\'")
  (setopt rmail-delete-after-output t)
  (setopt rmail-default-file "~/")
  (setopt mail-dont-reply-to-names rmail-user-mail-address-regexp)

  (add-hook 'rmail-show-message-hook #'visual-line-fill-column-mode)

  (setopt rmail-display-summary t)
  (setopt rmail-redisplay-redisplay-summary t)
  (setopt rmail-summary-line-count-flag nil)
  (setopt rmail-summary-window-size 12)
#+end_src

** Sending mail with msmtp
#+begin_src elisp
  (setopt message-send-mail-function 'message-send-mail-with-sendmail)
  (setopt sendmail-program (executable-find "msmtpq"))

  (setopt message-sendmail-extra-arguments '("--read-envelope-from"))
  (setopt message-sendmail-envelope-from 'header)
  (setopt message-kill-buffer-on-exit t)
  (setopt message-sendmail-f-is-evil t)
  (setopt message-forward-as-mime t)
  (setopt message-interactive t)
#+end_src

* Unsorted
Just a few settings i haven't put into another category yet.

#+begin_src elisp
  (setopt save-interprogram-paste-before-kill t
	  mouse-yank-at-point t
	  require-final-newline t
	  load-prefer-newer t
	  ediff-window-setup-function 'ediff-setup-windows-plain)
#+end_src