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
|
;;; init.el --- my emacs configuration -*- lexical-binding: t; -*-
;; Todo:
;; - Look at completion-preview-mode
;; - Look at grep-use-headings
;; - Look at elpa-openwith
(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp")))
;; Give me stats on garbage collection and startup time
(setopt garbage-collection-messages t)
(add-hook 'emacs-startup-hook
#'(lambda ()
(message (format "Initialised in %s seconds with %s garbage collections." (emacs-init-time) gcs-done))))
;; Use the gcmh to collect garbage when i'm not using emacs
(use-package gcmh
:ensure t
:custom
(gcmh-mode t))
(setopt user-full-name "noa")
(setopt user-mail-address "noa@noa.pub")
;; Properly distinguish these chords from their ascii legacy
(define-key input-decode-map [?\C-m] [C-m])
;;; Modern keybindings
;; A lot of people say that you get used to emacs keybindings over time, and this is true. But i find it hard to maintain two sets of keybindings in my head: those for emacs, and those for everything else. So i think that it's best to make emacs use the same bindings as everything else. For now, we do that with wakib mode.
(require 'wakib-keys)
(setopt wakib-keys t)
(with-eval-after-load 'wakib-keys
(add-hook 'after-change-major-mode-hook 'wakib-update-major-mode-map)
(add-hook 'menu-bar-update-hook 'wakib-update-minor-mode-maps)
;; Modifying other modules
;; When remap is used it exits isearch abruptly after first instance
;; Use explicit keybindings instead
(define-key isearch-mode-map (kbd "C-f") 'isearch-repeat-forward)
(define-key isearch-mode-map (kbd "C-S-f") 'isearch-repeat-backward)
(define-key isearch-mode-map (kbd "M-;") 'isearch-repeat-forward)
(define-key isearch-mode-map (kbd "M-:") 'isearch-repeat-backward)
(define-key isearch-mode-map (kbd "C-v") 'isearch-yank-kill)
(define-key isearch-mode-map (kbd "M-d") 'isearch-delete-char))
;; 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.
(setopt minibuffer-depth-indicate-mode t)
;; 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.
(require 'tubthumping-theme)
(load-theme 'tubthumping t)
;;; Fonts
;; Using a proportional font 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.
(custom-set-faces
'(default ((t (:family "Noto Sans" :height 120))))
'(variable-pitch ((t (:family "Noto Sans" :height 120))))
'(fixed-pitch ((t (:family "Noto Sans Mono" :height 120)))))
;; Use simplified not korean hanzi
(dolist (charset '(han cjk-misc))
(set-fontset-font t charset (font-spec :family "Noto Sans CJK SC")))
;; Prioritise fsd and noto emoji over coloured variants
(set-fontset-font t 'emoji (font-spec :family "FSD Emoji") nil 'prepend)
(set-fontset-font t 'emoji (font-spec :family "Noto Emoji") nil 'append)
;; While we're here, let's set up emoji input.
(global-set-key (kbd "C-.") #'emoji-search)
;; 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.
(setopt calendar-week-start-day 1
calendar-date-style 'iso)
;; Ensure the calendar always displays at the bottom of the screen, rather than wrapping weirdly and looking bad when it shows up in a side window.
(add-to-list 'display-buffer-alist
'("\\*Calendar\\*"
(display-buffer-in-side-window)
(side . bottom)))
;; 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.
(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
ebib-index-mood-hook))
;; Ensure that transients look nice with a variable pitch font.
(setopt transient-align-variable-pitch t)
;; 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.
(use-package visual-fill-column
:ensure t
:custom
(visual-fill-column-enable-sensible-window-split t)
(visual-fill-column-fringes-outside-margins nil) ; Keep fringes on the inside so relevant icons are in the right place
:hook
((text-mode . visual-line-fill-column-mode)
(eww-after-render . visual-line-fill-column-mode))
)
;; (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)
;; 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.
;; (use-package adaptive-wrap :ensure t)
;; (add-hook 'visual-fill-column-mode-hook #'visual-wrap-prefix-mode)
;; 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.
(setopt menu-bar-mode nil
tool-bar-mode nil)
;; 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.
(setopt tooltip-mode nil)
;; 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.
(setopt echo-keystrokes 0.1)
;; 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.
(setopt show-paren-mode t)
;; 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.
(setopt show-paren-when-point-in-periphery t)
;; 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.
(setopt show-paren-when-point-inside-paren t)
;; Syntax highlighting
(setopt global-font-lock-mode t)
(setopt font-lock-maximum-decoration nil)
;;; Further options
(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)
;; Shift click to select region with the mouse. I had a poor workaround for this before, but found an article (https://christiantietze.de/posts/2022/07/shift-click-in-emacs-to-select/) which shows me how to do it more pleasantly. In the same article i also discovered the existence of mouse-drag-secondary, which i think i will not make use of but is cool nonetheless. It also showed me mouse-drag-region-rectangle, which doesn't work great with a proportional font, but could still be useful.
(global-set-key (kbd "S-<down-mouse-1>") #'mouse-set-mark)
(global-set-key (kbd "C-S-<down-mouse-1>") #'mouse-drag-region-rectangle)
;; Nicer tables
(package-ensure 'valign)
(add-hook 'markdown-mode-hook #'valign-mode)
(setopt valign-fancy-bar t)
(setopt valign-max-table-size 0)
;; Markdown
(use-package markdown-mode
:ensure t
:mode ("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . markdown-mode)
:custom
(markdown-disable-tooltip-prompt t) ; When inserting a link, only prompt for url and link text
(markdown-enable-html nil) ; I don't believe markdown should have html
(markdown-hide-urls t) ; Make inline urls look a bit neater
(markdown-url-compose-char ?🔗))
;; 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.
(add-hook 'text-mode-hook #'abbrev-mode)
;; 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.
(package-ensure 'jinx)
(setopt global-jinx-mode t)
;; Replace the default spellcheck binding with jinx
(keymap-global-set "M-$" #'jinx-correct)
(keymap-global-set "C-M-$" #'jinx-languages)
;; 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!
(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)
;; 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.
(put 'jinx-overlay 'mouse-face '(jinx-misspelled highlight))
;; 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.
;; Make it easier to close help windows
(setopt help-window-select t)
;; When opening source files from a help window, use the same window
(setopt help-window-keep-selected t)
;; 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.
(setopt browse-url-browser-function 'eww-browse-url
browse-url-secondary-browser-function 'browse-url-default-browser)
;; 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.
(setopt url-cookie-trusted-urls '()
url-cookie-untrusted-urls '(".*")
shr-cookie-policy nil)
;; 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.
(setopt shr-use-colors nil)
;; 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.
(setopt shr-max-width nil)
;; 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.
(setopt shr-max-image-proportion 0.9
shr-discard-aria-hidden t)
(setopt shr-bullet " • ")
;; 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.
(setopt eww-auto-rename-buffer 'title)
;; 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.
(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)
(setopt global-eldoc-mode t)
;; 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
(setopt cursor-type 'bar
blink-cursor-mode t
blink-cursor-interval 0.7)
;; 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.
(setopt delete-by-moving-to-trash t)
;; 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.
(setopt dired-recursive-deletes 'always)
;; Recursive copying isn't even destructive, so i definitely don't want to be asked about that.
(setopt dired-recursive-copies 'always)
;; After we delete some files or directories, it makes sense to get rid of any buffers which are looking at those files or directories.
(setopt dired-clean-up-buffers-too nil)
;; 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.
(setopt dired-dwim-target t)
;; 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.
(setopt dired-listing-switches "-alvFh --group-directories-first")
;; By default, don't show dired details
(add-hook 'dired-mode-hook #'dired-hide-details-mode)
;; 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.
;; (package-ensure 'dired-du)
(setopt dired-du-size-format t)
(add-hook 'dired-mode-hook #'dired-du-mode)
;; Some tramp settings.
(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)
;; 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.
(setopt vc-handled-backends '(Git))
;; (setopt vc-handled-backends '(Git RCS CVS SVN SCCS SRC Bzr Hg))
;; It seems that tramp can also be made faster with these .ssh/config settings.
;; Host *
;; ControlMaster auto
;; ControlPath ~/.ssh/master-%h:%p
;; ControlPersist 10m
;; ForwardAgent yes
;; ServerAliveInterval 60
;; 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.
(setopt switch-to-buffer-obey-display-actions t)
;; History
(setopt history-length 250
kill-ring-max 25)
(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)
(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)
;; 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.
(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)
;; Do not allow the cursor in the minibuffer prompt
(setopt minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
;; Support opening new minibuffers from inside existing minibuffers.
(setopt enable-recursive-minibuffers t)
;; Whether to drop into the debugger on any error. This seems cool, but in practice is a bit annoying.
(setopt debug-on-error nil)
;; Hide commands in M-x which do not work in the current mode.
(setopt read-extended-command-predicate 'command-completion-default-include-p)
(setopt recentf-max-menu-items 25
recentf-save-file "~/.config/emacs/recentf"
recentf-mode t
bookmark-default-file "~/.config/emacs/bookmarks")
;; 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.
(setopt undo-no-redo t)
;; Saving
;; Backups are pointless in long emacs sessions imo, but autosaves are useful.
(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)))
;; Unfill commands
(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)
;; 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.
(setopt recenter-positions '(top middle bottom))
(setopt scroll-margin 1)
;; 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.
;; This doesn't work well sometimes for some reason, so i've disabled it for now.
;; (setopt pixel-scroll-precision-mode t
;; pixel-scroll-precision-use-momentum t)
;; Remember my position in files
(setopt save-place-mode t)
;; 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.
(setopt read-answer-short t)
(setopt use-short-answers t)
;; Disable disabled commands
(setq disabled-command-function nil)
;; Don't save changes in the customize interface
(setopt custom-file (make-temp-file "custom"))
;; Scroll along with text in compilation mode, and stop scrolling at the first error.
(setopt compilation-scroll-output 'first-error)
;; Don't advertise gnu on startup
(setq inhibit-startup-echo-area-message "noa") ;; #userfreedom
;; Better buffer naming
(setopt uniquify-after-kill-buffer-p t
uniquify-buffer-name-style 'forward
uniquify-ignore-buffers-re "^\\*"
uniquify-separator "/")
;; Emacs server
;; (unless (server-running-p) (server-start)))
;; This puts some help in the minibuffer when we leave the point on some interactive text.
(setopt help-at-pt-display-when-idle 'never)
;; Better support for long lines.
(setopt global-so-long-mode t)
;; This will stop us being prompted before killing a buffer with a running process:
(setopt kill-buffer-query-functions
(remq 'process-kill-buffer-query-function
kill-buffer-query-functions))
;; Automatically revert buffers when they change on disk. This doesn't apply to tramp.
(setopt global-auto-revert-mode t)
;; This behaviour changes how we visit symlinks.
(setopt find-file-visit-truename t)
(setopt vc-follow-symlinks 'ask)
;; Use ibuffer instead of list-buffers
(global-set-key [remap list-buffers] 'ibuffer)
;; Clicking around
;; By default, clicking on a character will always put the point in front of that character. But it generally feels nicer for a click to put the point on the nearest side of the character to where the mouse clicked, to allow for slightly sloppier clicking.
(setopt mouse-prefer-closest-glyph t)
;; 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.
;; (package-ensure 'consult)
;; (package-activate 'consult)
;; Consult buffer can be used instead of the default buffer menu. It lists recently used files and bookmarks as well as open buffers.
(autoload #'consult-buffer "consult" nil t)
(global-set-key [remap switch-to-buffer] #'consult-buffer)
;; These are some other almost default functions but with extra interactivity.
(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)
(global-set-key [remap info] #'consult-info)
;; 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.
;; (package-ensure 'marginalia)
(setopt marginalia-mode t)
;; 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.
(setopt marginalia-max-relative-age most-positive-fixnum)
;; 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.
(define-key input-decode-map [?\C-i] [C-i])
;; Now that tab and C-i are properly distinguished, i can bind C-i to completion at point.
(global-set-key (kbd "<C-i>") 'completion-at-point)
;; 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.
(setopt completion-in-region-function 'consult-completion-in-region)
;; 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.
;; (package-ensure 'vertico)
(setopt vertico-mode t)
;; 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.
(setopt vertico-count 12)
;; 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.
(setopt vertico-group-format #("%s " 0 3 (face vertico-group-title)))
;; 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.
(setopt vertico-cycle t)
;; And of course, i want to be able to interact with vertico with the mouse.
(with-eval-after-load 'vertico
(setopt vertico-mouse-mode t))
;; 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.
(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))
;; If i type ~/ etc in a find-file prompt, get rid of the preceding directory names for a cleaner look.
(add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
;; Some settings for nicer completion with the default emacs completion buffer. I don't use this, because i use vertico.
;; (setopt completion-auto-help 'lazy
;; completion-auto-select 'second-tab
;; completion-show-help nil
;; completions-sort nil
;; completions-header-format nil)
;; 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.
(setopt read-buffer-completion-ignore-case t)
(setopt read-file-name-completion-ignore-case t)
(setopt completion-ignore-case t)
;; 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.
;; (package-ensure 'orderless)
(setopt completion-styles '(orderless basic))
(setopt completion-category-overrides '((file (styles basic partial-completion))))
;; By default, emacs overrides the completion styles for email address, but i'm happy with my configuration above.
(setopt completion-category-defaults nil)
;; 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.
(require 'elastic-indent)
(add-hook 'prog-mode-hook #'elastic-indent-mode)
;; Elastic-table-mode is similar; for tab characters within lines, ensure that they change width to make subsequent lines form a table-like layout.
(require 'elastic-table)
(add-hook 'prog-mode-hook #'elastic-table-mode)
;; 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.
(setopt tab-always-indent nil)
;; 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.
(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))))
;; 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.
(setopt sentence-end-double-space nil)
;; If i write a script, i will always run chmod +x after saving it. This command means i don't have to do that.
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
;; 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.
(setopt require-final-newline t)
(setopt window-min-height 1
window-combination-resize t
window-resize-pixelwise t
frame-resize-pixelwise t)
;;; Keybindings
;; Zap up to char
(global-set-key (kbd "M-z") 'zap-up-to-char)
;; Insert date and time at point
;; Sometimes it's useful to be able to add a timestamp. I use this for notes. Currently it's bound to <f5> as that's what notepad uses.
(defun noa/insert-date-time ()
(interactive)
(insert (format-time-string "<%Y-%m-%d>")))
(global-set-key (kbd "<f5>") 'noa/insert-date-time)
;; 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.
(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)))
;; 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
;; (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)
(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "C-x k") 'kill-current-buffer)
;; Window management
;; 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.
(setopt split-width-threshold 80)
;;; Global text search
;; 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.
(defun noa/consult-rg-home ()
(interactive)
(consult-ripgrep "~/"))
(global-set-key (kbd "M-<menu>") #'noa/consult-rg-home)
(setopt shell-file-name "/bin/sh")
(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))))
;; Put a quote in the scratch buffer
(setopt cookie-file "~/Documents/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"))
;;; Other
(setopt confirm-kill-emacs 'y-or-n-p)
(global-set-key (kbd "C-=") #'calc)
;;; 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. I use some external tools to fetch and send the mail.
;; Reading mail
;; 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.
(use-package rmail
:custom
(rmail-primary-inbox-list
(directory-files "~/Documents/mail/inbox" t "^[^\.]"))
(rmail-file-name "~/Documents/mail/rmail.mbox")
(rmail-user-mail-address-regexp
(rx "noa@noa.pub"))
(rmail-mime-prefer-html nil)
(rmail-mime-attachment-dirs-alist '(("" "~/media")))
(rmail-displayed-headers
(rx bol (or "To" "Cc" "From" "Date" "Subject") ":"))
(rmail-secondary-file-directory "~/Documents/mail/archive/")
(rmail-secondary-file-regexp "\\.mbox\\'")
(rmail-delete-after-output t)
(rmail-default-file "~/Documents/mail/archive/")
(mail-dont-reply-to-names rmail-user-mail-address-regexp)
(rmail-display-summary nil)
(rmail-redisplay-redisplay-summary t)
(rmail-summary-line-count-flag nil)
(rmail-summary-window-size 12)
:hook
((rmail-show-message . visual-line-fill-column-mode)))
(defun noa/message-default-headers ()
(format "Fcc: ~/Documents/mail/outbox/%s.mbox"
(format-time-string "%Y-%m")))
(setopt message-default-headers #'noa/message-default-headers)
;; Composing mail
;; Setting this to nil stops auto-fill from being automatically enabled in message buffers.
(setopt message-fill-column nil)
;; It's nice to have a message signature. I want the signature to be loaded from a file, which is stored in my configuration directory.
(setopt message-signature t)
(setopt message-signature-file "~/.config/signature")
;; Sending mail
(setopt message-send-mail-function 'message-send-mail-with-sendmail)
(add-to-list 'exec-path "/home/noa/.config/Scripts")
(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)
(setopt message-auto-save-directory "~/Documents/mail/drafts")
;; Environment variables
(setenv "PAGER" "cat")
(setenv "TERM" "dumb")
(setenv "NO_COLOR")
(setenv "GPG_AGENT_INFO" nil)
;; Dictionary
;; Emacs has built in support for interfacing with dictd. With dictd and some dictionaries installed on debian, this works fine out of the box.
;; Unfortunately, dictionaries in this format tend to be hard wrapped and there isn't a lot of coverage outside of english
(setopt dictionary-search-interface nil)
;; Dictionary tooltip mode lets me hover over a word to view the definition.
(setopt dictionary-tooltip-mode t)
;; Unsorted
;; Just a few settings i haven't put into another category yet.
(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)
;;; Voice notes transcription
(require 'audio-notes-mode)
(setq anm/notes-directory "~/Documents/Voice notes/")
(setq anm/goto-file "~/Documents/notes/notes.org")
(setq anm/player-command '("mpv" "--quiet" file))
;;; Howm
(use-package howm
:ensure t
:init
(setopt howm-view-title-header "#")
:custom
(howm-directory "~/Documents/notes/")
(howm-keyword-file (expand-file-name ".howm-keys" howm-directory))
(howm-history-file (expand-file-name ".howm-history" howm-directory))
(howm-view-summary-omit-same-name nil)
(howm-keyword-case-fold-search t)
(howm-view-use-grep t)
(howm-view-grep-command "ugrep")
;; Don't show the file name
(howm-view-summary-format "")
;; Rename buffers to their title
:hook ((howm-mode . howm-mode-set-buffer-name)
(after-save . howm-mode-set-buffer-name))
;; Don't clobber the help binding
:bind (:map howm-menu-mode-map ("C-h" . nil)
:map riffle-summary-mode-map ("C-h" . nil)
:map howm-view-contents-mode-map ("C-h" . nil))
:bind
("<f12>" . howm-list-all)
("<C-f12>" . howm-create))
(setopt howm-file-name-format "%Y-%m-%dT%H%M%S.md")
;;; deft
(require 'deft)
(use-package deft
:ensure t
:custom
(deft-extensions '("md"))
(deft-directory "~/Documents/notes")
(deft-time-format nil)
(deft-separator " — ")
(deft-strip-summary-regexp
(concat "\\("
"[\n\t]" ;; blank
"\\|^#\\+[[:upper:]_]+:.*$" ;; org-mode metadata
"\\|^#+.*$" ;; markdown titles
"\\)")))
|