about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore31
-rw-r--r--LICENSE14
l---------README.org1
-rw-r--r--config.org3638
-rw-r--r--early-init.el297
-rw-r--r--etc/eshell/aliases1
-rw-r--r--init.el608
-rw-r--r--var/elpher-bookmarks.el18
8 files changed, 787 insertions, 3821 deletions
diff --git a/.gitignore b/.gitignore index 7014eed..6836320 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,25 +1,6 @@
1# ignore everything 1.org-id-locations
2* 2eln-cache/
3 3var/
4# except ... 4etc/
5!config.org 5straight/
6!init.el 6transient/
7!early-init.el
8!.gitignore
9!.gitattributes
10!README.*
11
12# and ...
13!var/
14var/*
15!var/elpher-bookmarks.el
16!var/elfeed/
17var/elfeed/*
18!var/elfeed/db
19
20!etc/
21etc/*
22!etc/eshell
23!etc/eshell/*
24
25!elfeed.org
diff --git a/LICENSE b/LICENSE deleted file mode 100644 index de537e6..0000000 --- a/LICENSE +++ /dev/null
@@ -1,14 +0,0 @@
1DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
3Version 2, December 2004
4
5Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
6
7Everyone is permitted to copy and distribute verbatim or modified copies of
8this license document, and changing it is allowed as long as the name is changed.
9
10DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
12TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
13
140. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.org b/README.org deleted file mode 120000 index f13833e..0000000 --- a/README.org +++ /dev/null
@@ -1 +0,0 @@
1config.org \ No newline at end of file
diff --git a/config.org b/config.org deleted file mode 100644 index c4af8b0..0000000 --- a/config.org +++ /dev/null
@@ -1,3638 +0,0 @@
1#+TITLE: Emacs configuration, literate-style
2#+AUTHOR: Case Duckworth
3#+STARTUP: overview
4#+PROPERTY: header-args :results output silent
5#+AUTO_TANGLE: yes
6
7* About me
8
9#+begin_src emacs-lisp :noweb-ref settings
10 (setq user-full-name "Case Duckworth"
11 user-mail-address "acdw@acdw.net")
12#+end_src
13
14** Where I am
15
16#+begin_src emacs-lisp :noweb-ref settings
17 (setq calendar-location-name "Baton Rouge, LA"
18 calendar-latitude 30.4
19 calendar-longitude -91.1)
20#+end_src
21
22** Auth-sources
23
24Here feels like as good a place as any to setup =auth-sources=. Yes, I
25/need/ to use GPG. I'll get around to it. Until then, /please/ don't
26break into my workplace and steal my secrets.
27
28#+begin_src emacs-lisp :noweb-ref settings
29 (setq-default auth-sources '("~/.authinfo"))
30#+end_src
31
32* Look and feel
33
34** Frames
35
36*** Initial frame setup
37:PROPERTIES:
38:header-args: :noweb-ref early-init-frame
39:END:
40
41I tangle this section to =early-init.el=, since that's evaluated
42before GUI set-up. Which, in turn, means Emacs will skip the "flash
43of unstyled content" thing.
44
45**** Tool bar
46
47#+begin_src emacs-lisp
48 (add-to-list 'default-frame-alist
49 '(tool-bar-lines . 0))
50
51 (tool-bar-mode -1)
52#+end_src
53
54**** Menu bar
55
56#+begin_src emacs-lisp
57 (add-to-list 'default-frame-alist
58 '(menu-bar-lines . 0))
59
60 (menu-bar-mode -1)
61#+end_src
62
63**** Scroll bars
64
65#+begin_src emacs-lisp
66 (add-to-list 'default-frame-alist
67 '(vertical-scroll-bars . nil)
68 '(horizontal-scroll-bars . nil))
69
70 (scroll-bar-mode -1)
71 (horizontal-scroll-bar-mode -1)
72#+end_src
73
74**** Resizing
75
76I don't want the frame to resize when I change fonts and stuff, and I
77want it to resize by pixels -- we /are/ using a GUI, after all.
78
79#+begin_src emacs-lisp
80 (setq-default frame-inhibit-implied-resize t
81 frame-resize-pixelwise t)
82#+end_src
83
84*** Frame titles
85
86#+begin_src emacs-lisp :noweb-ref settings
87 (setq-default frame-title-format
88 '("Emacs "
89 mode-line-client
90 mode-line-modified
91 " "
92 (:eval (if (buffer-file-name)
93 (abbreviate-file-name (buffer-file-name))
94 "%b"))
95 ))
96#+end_src
97
98*** Fringes
99:PROPERTIES:
100:header-args: :noweb-ref early-init-frame
101:END:
102
103I have grown to love Emacs's little fringes on the side of the
104windows. In fact, I love them so much that I really went overboard
105and have made a custom fringe bitmap.
106
107**** Indicate empty lines after the end of the buffer
108
109#+begin_src emacs-lisp
110 (setq-default indicate-empty-lines t)
111#+end_src
112
113**** Indicate the boundaries of the buffer
114
115#+begin_src emacs-lisp
116 (setq-default indicate-buffer-boundaries 'right)
117#+end_src
118
119**** Indicate continuation lines, but only on the left fringe
120
121#+begin_src emacs-lisp
122 (setq-default visual-line-fringe-indicators '(left-curly-arrow nil))
123#+end_src
124
125**** Customize fringe bitmaps
126
127***** Curly arrows (continuation lines)
128
129#+begin_src emacs-lisp
130 (defun hook--setup-fringes-curly-arrows ()
131 "Set up curly-arrow fringes."
132 (define-fringe-bitmap 'left-curly-arrow
133 [#b11000000
134 #b01100000
135 #b00110000
136 #b00011000])
137
138 (define-fringe-bitmap 'right-curly-arrow
139 [#b00011000
140 #b00110000
141 #b01100000
142 #b11000000]))
143
144 (add-hook 'after-init-hook #'hook--setup-fringes-curly-arrows)
145#+end_src
146
147***** Arrows (truncation lines)
148
149#+begin_src emacs-lisp
150 (defun hook--setup-fringes-arrows ()
151 "Setup arrow fringe bitmaps."
152 (define-fringe-bitmap 'left-arrow
153 [#b00000000
154 #b01010100
155 #b01010100
156 #b00000000])
157
158 (define-fringe-bitmap 'right-arrow
159 [#b00000000
160 #b00101010
161 #b00101010
162 #b00000000]))
163
164 (add-hook 'after-init-hook #'hook--setup-fringes-arrows)
165#+end_src
166
167** Windows
168
169*** Window Dividers
170
171#+begin_src emacs-lisp :noweb-ref settings
172 (setq-default window-divider-default-places 'right-only ; only right
173 window-divider-default-bottom-width 2
174 window-divider-default-right-width 2)
175#+end_src
176
177#+begin_src emacs-lisp :noweb-ref modes
178 (window-divider-mode +1)
179#+end_src
180
181*** Splitting windows sensibly
182
183This is extremely fiddly and I'd love another option.
184- [[https://www.emacswiki.org/emacs/ToggleWindowSplit][ToggleWindowSplit, EmacsWiki]]
185
186#+begin_src emacs-lisp :noweb-ref settings
187 (setq-default split-width-threshold 100
188 split-height-threshold 50)
189#+end_src
190
191*** Window layouts
192
193Let's try settings from [[https://github.com/nex3/perspective-el#some-musings-on-emacs-window-layouts][nex3]] on Github.
194
195#+begin_src emacs-lisp :noweb-ref settings
196 (setq-default
197 ;; `display-buffer-alist' is the big alist of things
198 display-buffer-alist
199 '((".*" (display-buffer-reuse-window display-buffer-same-window)))
200 ;; reuse windows in other frames
201 display-buffer-reuse-frames t
202 ;; avoid resizing
203 even-window-sizes nil)
204#+end_src
205
206*** Switch to other window or buffer :crux:
207
208#+begin_src emacs-lisp :noweb-ref bindings
209 (acdw/bind "M-o" #'crux-other-window-or-switch-buffer)
210#+end_src
211
212*** The *Help* window
213
214I want to select the *Help* window by default, so I can easily quit it.
215
216#+begin_src emacs-lisp :noweb-ref settings
217 (setq-default help-window-select t)
218#+end_src
219
220*** Split and switch
221
222from [[https://github.com/danielmai/.emacs.d/blob/master/config.org#window][Daniel Mai]], though I've seen it before.
223
224#+begin_src emacs-lisp :noweb-ref functions
225 (defun vsplit-other-window ()
226 "Split the window vertically and switch to the new window."
227 (interactive)
228 (split-window-vertically)
229 (other-window 1 nil))
230
231 (defun hsplit-other-window ()
232 "Split the window horizontally and switch to the new window."
233 (interactive)
234 (split-window-horizontally)
235 (other-window 1 nil))
236#+end_src
237
238#+begin_src emacs-lisp :noweb-ref bindings
239 (acdw/bind "C-x 2" #'vsplit-other-window)
240 (acdw/bind "C-x 3" #'hsplit-other-window)
241#+end_src
242
243** Buffers
244
245*** Uniquify buffers
246
247The default way Emacs makes buffer names unique is really ugly and,
248dare I say it, stupid. Instead, I want them to be uniquified by their
249filesystem paths.
250
251#+begin_src emacs-lisp :noweb-ref requires
252 (require 'uniquify)
253#+end_src
254
255#+begin_src emacs-lisp :noweb-ref settings
256 (setq-default uniquify-buffer-name-style 'forward
257 uniquify-separator "/"
258 uniquify-after-kill-buffer-p t
259 uniquify-ignore-buffers-re "^\\*")
260#+end_src
261
262*** Startup buffers
263
264When Emacs starts up, I want a blank slate: the *scratch* buffer. I
265also want it to show a cute little message to myself.
266
267#+begin_src emacs-lisp :noweb-ref settings
268 (setq-default inhibit-startup-screen t ; Don't show that splash screen thing.
269 initial-buffer-choice t ; Start on *scratch*
270 initial-scratch-message
271 (concat ";; Howdy, "
272 (nth 0 (split-string user-full-name)) "!"
273 " Welcome to Emacs."
274 "\n\n"))
275#+end_src
276
277*** Immortal =*scratch*= buffer
278
279I don't want to accidentally kill the *scratch* buffer. So, I add a
280function to the =kill-buffer-query-functions= hook that will return
281=nil= if the buffer is *scratch*.
282
283#+begin_src emacs-lisp :noweb-ref functions
284 (defun immortal-scratch ()
285 (if (not (eq (current-buffer) (get-buffer "*scratch*")))
286 t
287 (bury-buffer)
288 nil))
289#+end_src
290
291#+begin_src emacs-lisp :noweb-ref hooks
292 (add-hook 'kill-buffer-query-functions #'immortal-scratch)
293#+end_src
294
295*** An /even better/ scratch buffer :package:
296
297The aptly-named =scratch= pops open a new scratch buffer /with the same
298mode as the file you're currently editing/. I'm pretty chuffed about
299it.
300
301I found it from [[https://old.reddit.com/r/emacs/comments/l4v1ux/one_of_the_most_useful_small_lisp_functions_in_my/][this discussion]], which might also come in handy
302someday.
303
304#+begin_src emacs-lisp :noweb-ref packages
305 (straight-use-package 'scratch)
306#+end_src
307
308#+begin_src emacs-lisp :noweb-ref bindings
309 (acdw/bind "C-x" #'scratch :map acdw/leader)
310#+end_src
311
312*** Kill buffers better
313
314#+begin_src emacs-lisp :noweb-ref functions
315 (defun kill-a-buffer (&optional prefix)
316 "Kill a buffer and its window, prompting only on unsaved changes.
317
318 `kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill:
319 0 => Kill current buffer & window
320 4 (C-u) => Kill OTHER buffer & window
321 16 (C-u C-u) => Run `kill-buffer' without a prefix arg."
322 (interactive "P")
323 (pcase (or (car prefix) 0)
324 (0 (kill-current-buffer)
325 (unless (one-window-p) (delete-window)))
326 (4 (other-window 1)
327 (kill-current-buffer)
328 (unless (one-window-p) (delete-window)))
329 (16 (let ((current-prefix-arg nil))
330 (kill-buffer)))))
331#+end_src
332
333#+begin_src emacs-lisp :noweb-ref bindings
334 (acdw/bind "C-x k" #'kill-a-buffer)
335#+end_src
336
337*** Kill old buffers after a while
338
339Adapted from =midnight-mode=, using suggestions from [[https://old.reddit.com/r/emacs/comments/l6jpxf/how_do_emacs_users_usually_have_multiple_files/gl2249u/][u/ndamee]].
340
341#+begin_src emacs-lisp :noweb-ref packages
342 (unless (fboundp 'clean-buffer-list)
343 (autoload #'clean-buffer-list "midnight"))
344#+end_src
345
346What time I run the clean up is a little tricky for me, since I use
347Emacs at work /and/ at home, and all at different times. However, I
348realized that since I close out of Emacs at work pretty much every
349day, I don't need to worry about too many buffers there -- so I just
350have =clean-buffer-list= run at 8:00 PM.
351
352#+begin_src emacs-lisp :noweb-ref settings
353 (setq-default acdw/clean-buffer-list-timer
354 (run-at-time "20:00" 86400 #'clean-buffer-list)
355 clean-buffer-list-delay-general 5
356 clean-buffer-list-delay-special (* 7 24 60 60))
357
358 (with-eval-after-load 'midnight
359 (add-to-list 'clean-buffer-list-kill-buffer-names "*Completions*")
360 (add-to-list 'clean-buffer-list-kill-buffer-names "*Calendar*"))
361#+end_src
362
363** Cursor
364
365*** Cursor shape
366
367I like a vertical bar, but only in the selected window.
368
369#+begin_src emacs-lisp :noweb-ref settings
370 (setq-default cursor-type 'bar
371 cursor-in-non-selected-windows nil)
372#+end_src
373
374*** Don't blink the cursor
375
376#+begin_src emacs-lisp :noweb-ref modes
377 (blink-cursor-mode -1)
378#+end_src
379
380** Tabs
381
382*** Tab bar mode settings
383
384#+begin_src emacs-lisp :noweb-ref settings
385 (setq-default tab-bar-show 1 ; show the tab bar when more than 1 tab
386 tab-bar-new-tab-choice "*scratch*"
387 tab-bar-tab-name-function
388 #'tab-bar-tab-name-current-with-count)
389#+end_src
390
391*** Tab bar history
392
393#+begin_src emacs-lisp :noweb-ref modes
394 (tab-bar-history-mode +1)
395#+end_src
396
397#+begin_src emacs-lisp :noweb-ref settings
398 (setq-default tab-bar-history-limit 25)
399#+end_src
400
401** Fonts
402
403On Linux, I have a custom build of Iosevka that I like.
404
405#+begin_src emacs-lisp :noweb-ref linux-specific
406 (set-face-attribute 'default nil
407 :family "Iosevka Acdw"
408 :height 105)
409
410 (set-face-attribute 'fixed-pitch nil
411 :family "Iosevka Acdw"
412 :height 105)
413
414 (set-face-attribute 'variable-pitch nil
415 :family "DejaVu Serif"
416 :height 110)
417#+end_src
418
419But on Windows, I use Consolas.
420
421#+begin_src emacs-lisp :noweb-ref windows-specific
422 (set-face-attribute 'default nil
423 :family "Consolas"
424 :height 100)
425
426 (set-face-attribute 'fixed-pitch nil
427 :family "Consolas"
428 :height 100)
429
430 (set-face-attribute 'variable-pitch nil
431 :family "Cambria"
432 :height 105)
433#+end_src
434
435*** Underlines
436
437I like the /fancy/ underlines in newer browsers that skip all the
438descenders. Emacs doesn't /quite/ have that, but it can put the
439underline below all the text.
440
441#+begin_src emacs-lisp :noweb-ref settings
442 (setq-default x-underline-at-descent-line t)
443#+end_src
444
445*** Unicode fonts :package:
446
447=unicode-fonts= pulls in some other packages that still require the
448deprecated =cl= library. So, I've forked those libraries to require
449=cl-lib= instead.
450
451**** First: un-fuck =font-utils= and =list-utils= ... and =persistent-soft=
452
453***** List-utils
454
455Since =font-utils= depends on =list-utils=, if I load the former first, it
456pulls in the unpatched latter. /So/ I need to do =list-utils= first.
457(=*straight-process*= is your friend, y'all!)
458
459Since =list-utils= requires =cl= in line 259 (see [[https://github.com/rolandwalker/list-utils/issues/6][this issue]], apparently
460just changing it breaks many tests, but I'll run with it until Emacs
461complains), I need to fork and change that to a =cl-lib=.
462
463#+begin_src emacs-lisp :noweb-ref packages
464 (straight-use-package '(list-utils
465 :host github
466 :repo "rolandwalker/list-utils"
467 :fork (:repo "duckwork/list-utils")))
468#+end_src
469
470***** Persistent-soft
471
472#+begin_src emacs-lisp :noweb-ref packages
473 (straight-use-package '(persistent-soft
474 :host github
475 :repo "rolandwalker/persistent-soft"
476 :fork (:repo "duckwork/persistent-soft")))
477#+end_src
478
479***** Font-utils
480
481I was able to actually create a [[https://github.com/rolandwalker/font-utils/pull/2][PR]] for this one, so fingers crossed.
482Since the last update on =font-utils= was in 2015, I'm not super hopeful
483that my fix will get merged upstream, but I'm using a =:fork= argument
484to stay hopeful.
485
486#+begin_src emacs-lisp :noweb-ref packages
487 (straight-use-package '(font-utils
488 :host github
489 :repo "rolandwalker/font-utils"
490 :fork (:repo "duckwork/font-utils")))
491#+end_src
492
493***** A function in case it comes up again
494
495I keep googling [[https://github.com/hlissner/doom-emacs/issues/3372][this Doom Emacs issue]], because I keep forgetting what
496I need to do to see where =Package cl is deprecated= is coming from.
497So... function!
498
499#+begin_src emacs-lisp :noweb-ref functions
500 ;; Make the compiler happy
501 (autoload 'file-dependents "loadhist")
502 (autoload 'feature-file "loadhist")
503
504 (defun acdw/fucking-cl ()
505 "Find out where the fuck `cl' is being required from."
506 (interactive)
507 (require 'loadhist)
508 (message "%S" (file-dependents (feature-file 'cl))))
509#+end_src
510
511**** Unicode-fonts
512
513/Okay/ ... pull requests in, time to load =unicode-fonts=.
514
515#+begin_src emacs-lisp :noweb-ref packages
516 (straight-use-package '(unicode-fonts
517 :host github
518 :repo "rolandwalker/unicode-fonts"))
519 (unless (fboundp 'unicode-fonts-setup)
520 (autoload #'unicode-fonts-setup "unicode-fonts"))
521#+end_src
522
523According to [[https://github.com/rolandwalker/unicode-fonts/issues/3][Issue #3]], there can be problems with =unicode-fonts-setup=
524when using a daemon. Instead of forking this repo and merging [[https://github.com/rolandwalker/unicode-fonts/pull/4][PR #4]]
525into my personal fork, I'll use the workaround described in the
526issue.
527
528#+begin_src emacs-lisp :noweb-ref hooks
529 (defun hook--unicode-fonts-setup ()
530 "Run `unicode-fonts-setup', then remove the hook."
531 (when (window-system)
532 (unicode-fonts-setup))
533 (remove-hook 'after-init-hook #'hook--unicode-fonts-setup))
534
535 (add-hook 'after-init-hook #'hook--unicode-fonts-setup)
536#+end_src
537
538*** Draw form-feeds (=^L=) properly :package:
539
540#+begin_src emacs-lisp :noweb-ref packages
541 (straight-use-package 'form-feed)
542#+end_src
543
544#+begin_src emacs-lisp :noweb-ref modes
545 (global-form-feed-mode +1)
546 (blackout 'form-feed-mode)
547#+end_src
548
549*** =variable-pitch= fonts in =text-mode=
550
551#+begin_src emacs-lisp :noweb-ref modes
552 (add-hook 'text-mode-hook #'variable-pitch-mode)
553#+end_src
554
555** Theme
556
557*** Modus themes :package:
558
559I want the git version.
560
561#+begin_src emacs-lisp :noweb-ref packages
562 (straight-use-package '(modus-themes
563 :host gitlab
564 :repo "protesilaos/modus-themes"))
565#+end_src
566
567#+begin_src emacs-lisp :noweb-ref settings
568 (setq-default modus-themes-slanted-constructs t
569 modus-themes-bold-constructs t
570 modus-themes-region 'bg-only
571 modus-themes-org-blocks 'grayscale
572 modus-themes-headings '((1 . section)
573 (t . no-color))
574 modus-themes-scale-headings nil
575 modus-themes-mode-line nil)
576#+end_src
577
578**** Force headings to be =fixed-pitch=
579
580To enable the proper alignment of Org tags, I want headings to inherit from the
581=fixed-pitch= family as well as themselves. This paves the way to enable
582=variable-pitch-mode= in Emacs.
583
584#+begin_src emacs-lisp :noweb-ref settings
585 (dolist (face '(modus-theme-heading-1
586 modus-theme-heading-2
587 modus-theme-heading-3
588 modus-theme-heading-4
589 modus-theme-heading-5
590 modus-theme-heading-6
591 modus-theme-heading-7
592 modus-theme-heading-8))
593 (doremi-face-set face
594 '((t (:inherit (face fixed-pitch bold))))))
595#+end_src
596
597*** Change themes based on time of day
598
599#+begin_src emacs-lisp :noweb-ref functions
600 (defun acdw/run-with-sun (sunrise-command sunset-command)
601 "Run commands at sunrise and sunset."
602 (let* ((times-regex (rx (* nonl)
603 (: (any ?s ?S) "unrise") " "
604 (group (repeat 1 2 digit) ":"
605 (repeat 1 2 digit)
606 (: (any ?a ?A ?p ?P) (any ?m ?M)))
607 (* nonl)
608 (: (any ?s ?S) "unset") " "
609 (group (repeat 1 2 digit) ":"
610 (repeat 1 2 digit)
611 (: (any ?a ?A ?p ?P) (any ?m ?M)))
612 (* nonl)))
613 (ss (sunrise-sunset))
614 (_m (string-match times-regex ss))
615 (sunrise-time (match-string 1 ss))
616 (sunset-time (match-string 2 ss)))
617 (run-at-time sunrise-time (* 60 60 24) sunrise-command)
618 (run-at-time sunset-time (* 60 60 24) sunset-command)))
619#+end_src
620
621#+begin_src emacs-lisp :noweb-ref hooks
622 (acdw/run-with-sun #'modus-themes-load-operandi
623 #'modus-themes-load-vivendi)
624#+end_src
625
626*** Mode line
627
628**** Minions mode :package:
629
630#+begin_src emacs-lisp :noweb-ref packages
631 (straight-use-package 'minions)
632 (require 'minions)
633#+end_src
634
635#+begin_src emacs-lisp :noweb-ref modes
636 (minions-mode +1)
637#+end_src
638
639**** Blackout some modes :package:
640
641Like =diminish= or =delight=, =blackout= allows me to remove some
642minor-modes from the modeline.
643
644#+begin_src emacs-lisp :noweb-ref packages
645 (straight-use-package '(blackout
646 :host github
647 :repo "raxod502/blackout"))
648#+end_src
649
650**** Which-function mode
651
652Shows where we are in the modeline.
653
654#+begin_src emacs-lisp :noweb-ref modes
655 (which-function-mode +1)
656#+end_src
657
658**** Mode-line faces
659
660#+begin_src emacs-lisp :noweb-ref linux-specific
661 (doremi-face-set 'mode-line
662 '((t (:family "Terminus"
663 :height 1.0))))
664 (doremi-face-set 'mode-line-inactive
665 '((t (:family "Terminus"
666 :height 1.0))))
667#+end_src
668
669*** Setting faces
670
671It took me a while to find a function that'll let me customize faces
672/without/ using *customize*. Thanks to [[https://www.emacswiki.org/emacs/CustomizingFaces#toc5][Drew Adams]], I've got it!
673
674#+begin_src emacs-lisp :noweb-ref functions
675 (defun doremi-face-set (face spec)
676 "Tell Customize that FACE has been set to value SPEC.
677 SPEC is as for `defface'."
678 (put face 'customized-face spec)
679 (face-spec-set face spec)
680 (message "Customized face %s." (symbol-name face)))
681#+end_src
682
683* Interactivity
684
685** Dialogs and alerts
686
687*** Don't use a dialog box
688
689Ask in the modeline instead.
690
691#+begin_src emacs-lisp :noweb-ref settings
692 (setq-default use-dialog-box nil)
693#+end_src
694
695*** Yes or no questions
696
697I just want to type =y= or =n=, okay?
698
699#+begin_src emacs-lisp :noweb-ref functions
700 (fset 'yes-or-no-p #'y-or-n-p)
701#+end_src
702
703*** The Bell
704
705The only system I /sort of/ like the bell on is my Thinkpad, which
706does a little on-board speaker beep. Until I can figure out how to
707let it do its thing, though, I'll just change the bell on all my
708systems.
709
710#+begin_src emacs-lisp :noweb-ref settings
711 (setq-default visible-bell nil
712 ring-bell-function #'flash-mode-line)
713#+end_src
714
715**** Flash the mode-line
716
717#+begin_src emacs-lisp :noweb-ref functions
718 (defun flash-mode-line ()
719 (invert-face 'mode-line)
720 (run-with-timer 0.2 nil #'invert-face 'mode-line))
721#+end_src
722
723** Minibuffer
724
725*** Keep the cursor away from the minibuffer prompt
726
727#+begin_src emacs-lisp :noweb-ref settings
728 (setq-default minibuffer-prompt-properties
729 '(read-only t
730 cursor-intangible t
731 face minibuffer-prompt))
732#+end_src
733
734*** Enable a recursive minibuffer
735
736#+begin_src emacs-lisp :noweb-ref
737 (setq-default enable-recursive-minibuffers t)
738#+end_src
739
740*** Show the recursivity of the minibuffer in the mode-line
741
742#+begin_src emacs-lisp :noweb-ref modes
743 (minibuffer-depth-indicate-mode +1)
744#+end_src
745
746** Completing-read
747
748*** Shadow file names
749
750When typing =~= or =/= in the file-selection dialog, Emacs "pretends"
751that you've typed them at the beginning of the line. By default,
752however, it only /fades out/ the previous contents of the line. I want
753to /hide/ those contents.
754
755#+begin_src emacs-lisp :noweb-ref settings
756 (setq-default file-name-shadow-properties '(invisible t))
757#+end_src
758
759#+begin_src emacs-lisp :noweb-ref modes
760 (file-name-shadow-mode +1)
761#+end_src
762
763*** Ignore case
764
765#+begin_src emacs-lisp :noweb-ref
766 (setq-default completion-ignore-case t
767 read-buffer-completion-ignore-case t
768 read-file-name-completion-ignore-case t)
769#+end_src
770
771*** Icomplete
772
773#+begin_src emacs-lisp :noweb-ref packages
774 (straight-use-package 'icomplete-vertical)
775#+end_src
776
777#+begin_src emacs-lisp :noweb-ref settings
778 (setq-default
779 completion-styles '(partial-completion substring flex)
780 completion-category-overrides '((file
781 (styles basic substring flex))))
782 (setq-default
783 icomplete-delay-completions-threshold 0
784 icomplete-max-delay-chars 0
785 icomplete-compute-delay 0
786 icomplete-show-matches-on-no-input t
787 icomplete-hide-common-prefix nil
788 icomplete-with-completion-tables t
789 icomplete-in-buffer t)
790#+end_src
791
792#+begin_src emacs-lisp :noweb-ref bindings
793 (acdw/bind-after-map icomplete icomplete-minibuffer-map
794 (("<down>" #'icomplete-forward-completions)
795 ("C-n" #'icomplete-forward-completions)
796 ("<up>" #'icomplete-backward-completions)
797 ("C-p" #'icomplete-backward-completions)))
798
799 (acdw/bind "C-v" #'icomplete-vertical-toggle
800 :after 'icomplete-vertical
801 :map icomplete-minibuffer-map)
802#+end_src
803
804#+begin_src emacs-lisp :noweb-ref modes
805 (fido-mode -1) ; I take care of this myself
806 (icomplete-mode +1)
807 (icomplete-vertical-mode +1)
808#+end_src
809
810*** Orderless :package:
811
812#+begin_src emacs-lisp :noweb-ref packages
813 (straight-use-package 'orderless)
814 (unless (fboundp 'orderless)
815 (autoload #'orderless "orderless"))
816#+end_src
817
818#+begin_src emacs-lisp :noweb-ref settings
819 (setq-default completion-styles '(orderless))
820#+end_src
821
822*** Consult :package:
823
824Consult has a lot of great bindings that work well with Emacs's
825default completion system. These all come from the [[https://github.com/minad/consult#configuration][example configuration]].
826
827#+begin_src emacs-lisp :noweb-ref bindings
828 (acdw/bind-after-map consult nil
829 ;; C-c bindings (`mode-specific-map')
830 (("C-c h" #'consult-history)
831 ("C-c m" #'consult-mode-command)
832 ;; C-x bindings (`ctl-x-map')
833 ("C-x M-:" #'consult-complex-command)
834 ("C-x b" #'consult-buffer)
835 ("C-x 4 b" #'consult-buffer-other-window)
836 ("C-x 5 b" #'consult-buffer-other-frame)
837 ("C-x r x" #'consult-register)
838 ("C-x r b" #'consult-bookmark)
839 ;; M-g bindings (`goto-map')
840 ("M-g g" #'consult-line)
841 ("M-g M-g" #'consult-line)
842 ("M-g o" #'consult-outline)
843 ("M-g m" #'consult-mark)
844 ("M-g k" #'consult-global-mark)
845 ("M-g i" #'consult-imenu)
846 ("M-g e" #'consult-error)
847 ;; M-s bindings (`search-map')
848 ("M-s g" #'consult-grep) ; alts:
849 ; consult-git-grep,
850 ; consult-ripgrep
851 ("M-s f" #'consult-find) ; alts:
852 ; consult-locate
853 ("M-s l" #'consult-line)
854 ("M-s m" #'consult-multi-occur)
855 ("M-s k" #'consult-keep-lines)
856 ("M-s u" #'consult-focus-lines)
857 ;; Other bindings
858 ("M-y" #'consult-yank-pop)
859 ("<f1> a" #'consult-apropos)
860 ("C-h a" #'consult-apropos)))
861#+end_src
862
863#+begin_src emacs-lisp :noweb-ref settings
864 (autoload 'consult-register-preview "consult") ; make the compiler happy
865 (setq-default register-preview-delay 0
866 register-preview-function #'consult-register-preview)
867#+end_src
868
869*** Marginalia :package:
870
871Finally, =marginalia= provides extra information about completion
872candidates.
873
874#+begin_src emacs-lisp :noweb-ref packages
875 (straight-use-package 'marginalia)
876 (require 'marginalia)
877#+end_src
878
879#+begin_src emacs-lisp :noweb-ref modes
880 (marginalia-mode +1)
881#+end_src
882
883I like the rich annotations provided by =marginalia=.
884
885#+begin_src emacs-lisp :noweb-ref settings
886 (setq-default marginalia-annotators
887 '(marginalia-annotators-heavy
888 marginalia-annotators-light
889 nil))
890#+end_src
891
892** Imenu
893
894#+begin_src emacs-lisp :noweb-ref settings
895 (setq-default imenu-auto-rescan t)
896#+end_src
897
898** Completion
899
900*** Hippie Expand
901
902Before I install any completion framework, I want a good default for
903completing. =hippie-expand= fills that niche.
904
905#+begin_src emacs-lisp :noweb-ref bindings
906 (acdw/bind "M-/" #'hippie-expand)
907#+end_src
908
909** Bindings
910
911*** Acdw Mode
912
913I've decided to set up a custom minor mode for my keybindings, as
914suggested in [[https://github.com/larstvei/dot-emacs#key-bindings][Lars Tvei]]'s config, so that I can override all other
915modes with my own keybindings. Plus I can easily turn it off and back
916on as I please.
917
918#+begin_src emacs-lisp :noweb-ref acdw-mode
919 (defvar acdw/map (make-sparse-keymap)
920 "A keymap for my custom bindings.")
921
922 (define-minor-mode acdw/mode
923 "A mode for `acdw/map'."
924 :init-value t
925 :lighter " acdw"
926 :keymap acdw/map)
927
928 (define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode)
929#+end_src
930
931#+begin_src emacs-lisp :noweb-ref modes
932 (blackout 'acdw/mode)
933#+end_src
934
935*** =acdw/bind= macro
936
937Since defining keys can be a chore, I've written this macro to make it just a
938/little/ bit easier. It's /not/ as comprehensive as =bind-key=, but it's just a
939little sugar on top of =define-key= et al.
940
941#+begin_src emacs-lisp :noweb-ref functions
942 (defmacro acdw/bind (key def &rest args)
943 "A simple key-binding macro that takes care of the annoying stuff.
944
945 If KEY is a vector, it's passed directly to `define-key',
946 otherwise it wraps it in `kbd'. It does NOT quote any
947 definitions, because I like to keep those explicit in the
948 definitions.
949
950 The following keywords are recognized:
951
952 :after PACKAGE-OR-FEATURE .. wrap key definition in `with-eval-after-load'
953 :map KEYMAP .. define key in KEYMAP instead of `acdw/map'"
954 (let* ((after (plist-get args :after))
955 (map (or (plist-get args :map) 'acdw/map))
956 (key (if (vectorp key) key `(kbd ,key)))
957 (def-key `(define-key ,map ,key ,def)))
958 (if after
959 `(with-eval-after-load ,after
960 ,def-key)
961 def-key)))
962
963 (defmacro acdw/bind-after-map (feature keymap bindings)
964 "Wrap multiple calls to `acdw/bind' in a `with-eval-after-load' form.
965
966 FEATURE is the argument to `with-eval-after-load'. KEYMAP is
967 passed to the `:map' argument of `acdw/bind', if it's non-nil."
968 (declare (indent 2))
969 (let ((bind-list)
970 (name-string (if (stringp feature) feature
971 (symbol-name feature))))
972 (dolist (bind bindings bind-list)
973 (push `(progn (unless (fboundp ,(cadr bind))
974 (autoload ,(cadr bind)
975 ,name-string nil t))
976 (acdw/bind ,@bind
977 :map
978 ,(if keymap keymap
979 'acdw/map)))
980 bind-list))
981 `(with-eval-after-load ',feature
982 ,@bind-list)))
983#+end_src
984
985**** Turn off acdw/mode in the minibuffer
986
987#+begin_src emacs-lisp :noweb-ref acdw-mode
988 (defun acdw/mode--disable ()
989 "Turn off acdw/mode."
990 (acdw/mode -1))
991
992 (add-hook 'minibuffer-setup-hook #'acdw/mode--disable)
993#+end_src
994
995**** Custom leader
996
997Since =C-z= is generally pretty useless in Emacs (minimize the window?
998really?), I rebind it to be a sort of personal leader key. I
999generally use it as a leader for entering applications.
1000
1001#+begin_src emacs-lisp :noweb-ref acdw-mode
1002 (defvar acdw/leader
1003 (let ((map (make-sparse-keymap))
1004 (c-z (global-key-binding "\C-z")))
1005 ;(global-unset-key "\C-z")
1006 (define-key acdw/map "\C-z" map)
1007 (define-key map "\C-z" c-z)
1008 map))
1009
1010 ;; Just in case I want to run hooks after defining the leader map
1011 (run-hooks 'acdw/leader-defined-hook)
1012#+end_src
1013
1014*** Show keybindings with =which-key= :package:
1015
1016#+begin_src emacs-lisp :noweb-ref packages
1017 (straight-use-package 'which-key)
1018#+end_src
1019
1020#+begin_src emacs-lisp :noweb-ref modes
1021 (which-key-mode +1)
1022 (blackout 'which-key-mode)
1023#+end_src
1024
1025** Scrolling
1026
1027*** Fast scrolling
1028
1029According to [[https://github.com/mpereira/.emacs.d#make-cursor-movement-an-order-of-magnitude-faster][Murilo Pereira]], these settings will make Emacs scrolling
1030"an order of magnitude faster."
1031
1032#+begin_src emacs-lisp :noweb-ref settings
1033 (setq-default auto-window-vscroll nil
1034 fast-but-imprecise-scrolling t)
1035#+end_src
1036
1037*** Scroll margins
1038
1039#+begin_src emacs-lisp :noweb-ref settings
1040 (setq-default scroll-margin 0
1041 scroll-conservatively 101 ; if greater than 100 ...
1042 scroll-preserve-screen-position 1)
1043#+end_src
1044
1045** Enable commands
1046
1047I think the /disabled command/ feature of Emacs is stupid, especially
1048for a program that values freedom so much.
1049
1050#+begin_src emacs-lisp :noweb-ref settings
1051 (setq-default disabled-command-function nil)
1052#+end_src
1053
1054** CRUX :package:crux:
1055
1056A collection of generally-useful functions that I don't want to bother
1057including here myself. This is kind of an experiment, to be honest.
1058
1059#+begin_src emacs-lisp :noweb-ref packages
1060 (straight-use-package '(crux
1061 :host github
1062 :repo "bbatsov/crux"))
1063 (require 'crux)
1064#+end_src
1065
1066A note: I /don't/ do the same with [[https://github.com/alphapapa/unpackaged.el][unpackaged]] (see below, specifically
1067the *Org* sections) because it pulls in =hydra= and =use-package=, et al.
1068
1069* Persistence
1070
1071** Minibuffer history
1072
1073The =savehist= package saves minibuffer history between sessions, as
1074well as the option for some other variables. Since storage is cheap,
1075I keep all of it.
1076
1077#+begin_src emacs-lisp :noweb-ref requires
1078 (run-with-idle-timer 3 nil #'require 'savehist nil t)
1079#+end_src
1080
1081#+begin_src emacs-lisp :noweb-ref modes
1082 (setq-default savehist-additional-variables
1083 '(kill-ring
1084 search-ring
1085 regexp-search-ring)
1086 history-length t ; Don't truncate
1087 history-delete-duplicates t
1088 savehist-autosave-interval 60)
1089#+end_src
1090
1091#+begin_src emacs-lisp :noweb-ref modes
1092 (savehist-mode +1)
1093#+end_src
1094
1095** File places
1096
1097The =saveplace= package saves where I've been in my visited files.
1098
1099#+begin_src emacs-lisp :noweb-ref requires
1100 (require 'saveplace)
1101#+end_src
1102
1103Since storage is cheap, but I'm impatient -- especially on Windows --
1104I'm not going to check whether the files =save-place= saves the places
1105of are readable or not when I'm not at home.
1106
1107#+begin_src emacs-lisp :noweb-ref settings
1108 (setq-default save-place-forget-unreadable-files
1109 (memq system-type '(gnu gnu/linux gnu/kfreebsd)))
1110#+end_src
1111
1112#+begin_src emacs-lisp :noweb-ref modes
1113 (save-place-mode +1)
1114#+end_src
1115
1116** Recent files
1117
1118I also like to keep track of recently-opened files. =recentf= helps
1119with that.
1120
1121#+begin_src emacs-lisp :noweb-ref requires
1122 (run-with-idle-timer 5 nil #'require 'recentf nil t)
1123#+end_src
1124
1125#+begin_src emacs-lisp :noweb-ref settings
1126 (setq-default recentf-max-menu-items 100
1127 recentf-max-saved-items nil
1128 recentf-auto-cleanup 'never)
1129#+end_src
1130
1131#+begin_src emacs-lisp :noweb-ref modes
1132 (with-eval-after-load 'recentf
1133 (recentf-mode +1))
1134#+end_src
1135
1136I also want to ignore the =no-littering-var-directory= and
1137=no-littering-etc-directory=, since those aren't useful.
1138
1139#+begin_src emacs-lisp :noweb-ref no-littering
1140 (with-eval-after-load 'recentf
1141 (add-to-list 'recentf-exclude no-littering-var-directory)
1142 (add-to-list 'recentf-exclude no-littering-etc-directory))
1143#+end_src
1144
1145*** Save the recentf list periodically
1146
1147#+begin_src emacs-lisp :noweb-ref functions
1148 (defun maybe-save-recentf ()
1149 "Save `recentf-file' every five minutes, but only when out of focus."
1150 (defvar recentf--last-save (time-convert nil 'integer)
1151 "When we last saved the `recentf-save-list'.")
1152
1153 (when (> (time-convert (time-since recentf--last-save) 'integer)
1154 (* 60 5))
1155 (setq-default recentf--last-save (time-convert nil 'integer))
1156 (when-unfocused #'recentf-save-list)))
1157#+end_src
1158
1159#+begin_src emacs-lisp :noweb-ref hooks
1160 (with-eval-after-load 'recentf
1161 (add-function :after after-focus-change-function
1162 #'maybe-save-recentf))
1163#+end_src
1164
1165* Responsiveness
1166
1167Emacs has a slew of well-documented problems with snappiness.
1168Luckily, there are a number of solutions.
1169
1170** Only do things when unfocused
1171
1172Sometimes, we can fake responsiveness by only performing commands when
1173the user is looking at something else.
1174
1175#+begin_src emacs-lisp :noweb-ref functions
1176 (defun when-unfocused (func &rest args)
1177 "Run FUNC, with ARGS, iff all frames are out of focus."
1178 (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
1179 (apply func args)))
1180#+end_src
1181
1182** Garbage collection
1183
1184*** Simple GC munging about
1185
1186From [[https://bling.github.io/blog/2016/01/18/why-are-you-changing-gc-cons-threshold/][bling]], from ... 2016? Maybe this isn't great, but it's one less package so
1187I'm going to try it for now.
1188
1189#+begin_src emacs-lisp :noweb-ref hooks
1190 (defconst gc-cons-basis (* 800 1000)
1191 "The basis value to which to return after a max jump.
1192
1193 800,000 (800 KB) is Emacs' default.")
1194
1195 (defun hook--gc-cons-maximize ()
1196 "Set `gc-cons-threshold' to the highest possible.
1197 For memory-intensive features."
1198 (setq gc-cons-threshold most-positive-fixnum))
1199
1200 (defun hook--gc-cons-baseline ()
1201 "Return `gc-cons-threshold' to `gc-cons-basis'.
1202 For after memory intensive operations."
1203 (setq gc-cons-threshold gc-cons-basis))
1204
1205 (add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize)
1206 (add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline)
1207#+end_src
1208
1209*** Garbage Collect when out of focus
1210
1211#+begin_src emacs-lisp :noweb-ref hooks
1212 (defun hook--gc-when-unfocused ()
1213 (when-unfocused #'garbage-collect))
1214
1215 (add-function :after after-focus-change-function
1216 #'hook--gc-when-unfocused)
1217#+end_src
1218
1219** Startup time
1220
1221Just for me to know, and in case I ever want to make it snappier. This function
1222is from [[https://blog.d46.us/advanced-emacs-startup/][Joe Schafer]].
1223
1224As a benchmark, on Windows, =emacs -Q= starts up in *0.188585* seconds, and
1225=emacs -q= starts in *0.373297*.
1226
1227#+begin_src emacs-lisp :noweb-ref hooks
1228 (defun hook--message-startup-time ()
1229 "Message Emacs' startup time."
1230 (message "Emacs ready in %s with %d garbage collections."
1231 (format "%.2f seconds"
1232 (float-time (time-subtract after-init-time
1233 before-init-time)))
1234 gcs-done))
1235
1236 (add-hook 'emacs-startup-hook #'hook--message-startup-time)
1237#+end_src
1238
1239* Files
1240
1241** Encoding
1242
1243*** UTF-8
1244
1245It's 2020. Let's encode files like it is.
1246
1247#+begin_src emacs-lisp :noweb-ref settings
1248 (set-charset-priority 'unicode)
1249 (set-language-environment "UTF-8")
1250
1251 (prefer-coding-system 'utf-8-unix)
1252 (set-default-coding-systems 'utf-8-unix)
1253 (set-terminal-coding-system 'utf-8-unix)
1254 (set-keyboard-coding-system 'utf-8-unix)
1255 (set-selection-coding-system 'utf-8-unix)
1256
1257 (setq-default locale-coding-system 'utf-8-unix
1258 coding-system-for-read 'utf-8-unix
1259 coding-system-for-write 'utf-8-unix
1260 buffer-file-coding-system 'utf-8-unix
1261
1262 org-export-coding-system 'utf-8-unix
1263 org-html-coding-system 'utf-8-unix ; doesn't take from above
1264
1265 default-process-coding-system '(utf-8-unix . utf-8-unix)
1266 x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
1267#+end_src
1268
1269*** UNIX-style line endings
1270
1271This function is from the [[https://www.emacswiki.org/emacs/EndOfLineTips][Emacs Wiki]].
1272
1273#+begin_src emacs-lisp :noweb-ref functions
1274 (defun ewiki/no-junk-please-were-unixish ()
1275 "Convert line endings to UNIX, dammit."
1276 (let ((coding-str (symbol-name buffer-file-coding-system)))
1277 (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
1278 (set-buffer-file-coding-system 'unix))))
1279#+end_src
1280
1281I add it to both =file-find-hook= /and/ =before-save-hook= because I'm
1282/that/ over it. I don't want to ever work with anything other than
1283UNIX line endings ever again. I just don't care. Even Microsoft
1284Notepad can handle UNIX line endings, so I don't want to hear it.
1285
1286#+begin_src emacs-lisp :noweb-ref hooks
1287 (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish)
1288 (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish)
1289#+end_src
1290
1291** Keep =~/.emacs.d= clean :package:
1292
1293#+begin_src emacs-lisp :noweb-ref packages :noweb yes
1294 (straight-use-package 'no-littering)
1295 (require 'no-littering)
1296 (with-eval-after-load 'no-littering
1297 <<no-littering>>
1298 ) ; end of no-littering
1299#+end_src
1300
1301** Backups
1302
1303#+begin_src emacs-lisp :noweb-ref settings
1304 (setq-default backup-by-copying t
1305 ;; Don't delete old versions
1306 delete-old-versions -1
1307 ;; Make numeric backups unconditionally
1308 version-control t
1309 ;; Also backup files covered by version control
1310 vc-make-backup-files t)
1311#+end_src
1312
1313#+begin_src emacs-lisp :noweb-ref no-littering
1314 (let ((dir (no-littering-expand-var-file-name "backup")))
1315 (make-directory dir :parents)
1316 (setq-default backup-directory-alist
1317 `((".*" . ,dir))))
1318#+end_src
1319
1320** Autosaves :package:
1321
1322I don't use the =auto-save= system, preferring instead to use
1323Bozhidar Batsov's [[https://github.com/bbatsov/super-save][super-save]] package.
1324
1325#+begin_src emacs-lisp :noweb-ref settings
1326 (setq-default auto-save-default nil)
1327
1328 (setq-default super-save-remote-files nil
1329 super-save-exclude '(".gpg")
1330 super-save-auto-save-when-idle t)
1331#+end_src
1332
1333#+begin_src emacs-lisp :noweb-ref packages
1334 (straight-use-package 'super-save)
1335#+end_src
1336
1337#+begin_src emacs-lisp :noweb-ref modes
1338 (super-save-mode +1)
1339 (blackout 'super-save-mode)
1340#+end_src
1341
1342** Lockfiles
1343
1344I don't think these are really necessary as of now.
1345
1346#+begin_src emacs-lisp :noweb-ref settings
1347 (setq-default create-lockfiles nil)
1348#+end_src
1349
1350** Auto-revert files
1351
1352I like to keep the buffers Emacs has in-memory in sync with the actual
1353contents of the files the represent on-disk. Thus, we have
1354=auto-revert-mode=.
1355
1356#+begin_src emacs-lisp :noweb-ref settings
1357 (setq-default auto-revert-verbose nil)
1358#+end_src
1359
1360#+begin_src emacs-lisp :noweb-ref modes
1361 (global-auto-revert-mode +1)
1362#+end_src
1363
1364* Editing
1365
1366** Lines
1367
1368*** Fill-column
1369
1370#+begin_src emacs-lisp :noweb-ref settings
1371 (setq-default fill-column 80)
1372#+end_src
1373
1374I also want to display the fill-column:
1375
1376#+begin_src emacs-lisp :noweb-ref modes
1377 (global-display-fill-column-indicator-mode +1)
1378#+end_src
1379
1380By default, Emacs uses =C-x f= to set the =fill-column=. I think it's
1381pretty dumb that such an easy-to-reach binding (for Emacs) is set to a function
1382that I /literally/ never use. So I'm going to bind it to =find-file= ... since
1383that's the only time I accidentally call it, anyway.
1384
1385#+begin_src emacs-lisp :noweb-ref bindings
1386 (acdw/bind "C-x f" #'find-file)
1387#+end_src
1388
1389*** Auto-fill vs. Visual-line
1390
13911. Enable =auto-fill-mode= with text modes.
1392
1393#+begin_src emacs-lisp :noweb-ref hooks
1394 (add-hook 'text-mode-hook #'auto-fill-mode)
1395#+end_src
1396
13972a. Enable =visual-line-mode= everywhere. I do this because when I'm typing a
1398long line, I don't want it to accidentally push my viewport out to the right. I
1399want the text to invisibly wrap, and /then/ wrap "in the real world," as it were.
1400
1401#+begin_src emacs-lisp :noweb-ref modes
1402 (global-visual-line-mode +1)
1403#+end_src
1404
14052b. Let's "fix" =visual-line-mode= if we're in =org-mode=.
1406
1407#+begin_src emacs-lisp :noweb-ref hooks
1408 (defun hook--visual-line-fix-org-keys ()
1409 (when (derived-mode-p 'org-mode)
1410 (local-set-key (kbd "C-a") #'org-beginning-of-line)
1411 (local-set-key (kbd "C-e") #'org-end-of-line)
1412 (local-set-key (kbd "C-k") #'org-kill-line)))
1413
1414 (add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys)
1415#+end_src
1416
1417I think that'll work -- I only care about line aesthetics with text.
1418Programming modes should be /allowed/ to have long lines, regardless
1419of how /terrible/ it is to have them.
1420
1421#+begin_src emacs-lisp :noweb-ref modes
1422 (blackout 'auto-fill-mode)
1423#+end_src
1424
1425*** Visual fill column mode
1426
1427**** First: fix scrolling in margins
1428
1429This has to be done /before/ loading the package. It's included in
1430=visual-fill-column=, too, but for some reason isn't loaded there.
1431
1432#+BEGIN_SRC emacs-lisp
1433 (dolist (margin '(right-margin left-margin))
1434 (dolist (button '(mouse-1 mouse-2 mouse-3))
1435 (global-set-key (vector margin button)
1436 (global-key-binding (vector button)))))
1437
1438 (mouse-wheel-mode +1)
1439
1440 (when (bound-and-true-p mouse-wheel-mode)
1441 (dolist (margin '(right-margin left-margin))
1442 (dolist (event '(mouse-wheel-down-event
1443 mouse-wheel-up-event
1444 wheel-down
1445 wheel-up
1446 mouse-4
1447 mouse-5))
1448 (global-set-key (vector margin event) #'mwheel-scroll))))
1449#+END_SRC
1450
1451**** Then: =visual-fill-column= :package:
1452
1453In reading-intensive views, this mode keeps the text from getting too
1454wide.
1455
1456#+begin_src emacs-lisp :noweb-ref packages
1457 (straight-use-package 'visual-fill-column)
1458#+end_src
1459
1460#+begin_src emacs-lisp :noweb-ref settings
1461 (setq-default visual-fill-column-center-text t)
1462#+end_src
1463
1464#+begin_src emacs-lisp :noweb-ref hooks
1465 (add-hook 'visual-fill-column-mode-hook #'visual-line-mode)
1466
1467 (with-eval-after-load 'visual-fill-column
1468 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
1469#+end_src
1470
1471*** Stay snappy with long-lined files
1472
1473#+begin_src emacs-lisp :noweb-ref modes
1474 (when (fboundp 'global-so-long-mode)
1475 (global-so-long-mode +1))
1476#+end_src
1477
1478** Whitespace
1479
1480*** Whitespace style
1481
1482The =whitespace-style= defines what kinds of whitespace to clean up on
1483=whitespace-cleanup=, as well as what to highlight (if that option is
1484enabled).
1485
1486#+begin_src emacs-lisp :noweb-ref settings
1487 (setq-default whitespace-style '(empty ; remove blank lines at buffer edges
1488 indentation ; clean up indentation
1489 ;; fix mixed spaces and tabs
1490 space-before-tab
1491 space-after-tab))
1492#+end_src
1493
1494*** Clean up whitespace on save
1495
1496#+begin_src emacs-lisp :noweb-ref hooks
1497 (add-hook 'before-save-hook #'whitespace-cleanup)
1498#+end_src
1499
1500*** USE TABs
1501
1502I love TABs. They're great. I want to use them, dag nab it. Emacs sometimes
1503makes that harder than it should be, but let's start at the basics.
1504
1505#+begin_src emacs-lisp :noweb-ref settings
1506 (setq-default indent-tabs-mode t
1507 tab-width 8
1508 ;; smie is a common indentation thing for a lot of other modes.
1509 smie-indent-basic 8)
1510#+end_src
1511
1512**** Smart tabs :package:
1513
1514#+begin_src emacs-lisp :noweb-ref packages
1515 (straight-use-package 'smart-tabs-mode)
1516#+end_src
1517
1518#+begin_src emacs-lisp :noweb-ref modes
1519 (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml)
1520#+end_src
1521
1522**** TODO smart-tabs-mode
1523
1524[[https://github.com/natecox/dotfiles/blob/master/emacs/emacs.d/nathancox.org#indentation][see here]], and [[https://github.com/jcsalomon/smarttabs][here]].
1525
1526** Killing & Yanking
1527
1528*** Replace the selection when typing
1529
1530#+begin_src emacs-lisp :noweb-ref modes
1531 (delete-selection-mode +1)
1532#+end_src
1533
1534*** Work better with the system clipboard
1535
1536#+begin_src emacs-lisp :noweb-ref settings
1537 (setq-default
1538 ;; Save existing clipboard text to the kill ring before replacing it.
1539 save-interprogram-paste-before-kill t
1540 ;; Update the X selection when rotating the kill ring.
1541 yank-pop-change-selection t
1542 ;; Enable clipboards
1543 x-select-enable-clipboard t
1544 x-select-enable-primary t
1545 ;; Copy a region when it's selected with the mouse
1546 mouse-drag-copy-region t)
1547#+end_src
1548
1549*** Don't append the same thing twice to the kill ring
1550
1551#+begin_src emacs-lisp :noweb-ref settings
1552 (setq-default kill-do-not-save-duplicates t)
1553#+end_src
1554
1555*** Kill the line if there is no region :crux:
1556
1557#+begin_src emacs-lisp :noweb-ref hooks
1558 (crux-with-region-or-line kill-ring-save)
1559 (crux-with-region-or-line kill-region)
1560#+end_src
1561
1562** Overwrite mode
1563
1564*** Change the cursor
1565
1566#+begin_src emacs-lisp :noweb-ref hooks
1567 (defun hook--overwrite-mode-change-cursor ()
1568 (setq cursor-type (if overwrite-mode t 'bar)))
1569
1570 (add-hook 'overwrite-mode-hook #'hook--overwrite-mode-change-cursor)
1571#+end_src
1572
1573** The Mark
1574
1575see also
1576- [[https://spwhitton.name/blog/entry/transient-mark-mode/][Gnu Emacs' Transient Mark mode]], Sean Whitton
1577
1578*** Repeat popping the mark without repeating the prefix argument
1579
1580#+begin_src emacs-lisp :noweb-ref settings
1581 (setq-default set-mark-repeat-command-pop t)
1582#+end_src
1583
1584** The Region
1585
1586*** Expand region :package:
1587
1588#+begin_src emacs-lisp :noweb-ref packages
1589 (straight-use-package 'expand-region)
1590#+end_src
1591
1592#+begin_src emacs-lisp :noweb-ref bindings
1593 (acdw/bind "C-=" #'er/expand-region)
1594#+end_src
1595
1596*** Pulse the modified region with goggles
1597
1598#+begin_src emacs-lisp :noweb-ref packages
1599 (straight-use-package 'goggles)
1600#+end_src
1601
1602#+begin_src emacs-lisp :noweb-ref hooks
1603 (defun fix--goggles-mode ()
1604 "Fix goggles-mode to blackout the lighter."
1605 (goggles-mode)
1606 (blackout 'goggles-mode))
1607
1608 (add-hook 'text-mode-hook #'fix--goggles-mode)
1609 (add-hook 'prog-mode-hook #'fix--goggles-mode)
1610#+end_src
1611
1612** Undo :package:
1613
1614*** Undo Fu
1615
1616#+begin_src emacs-lisp :noweb-ref packages
1617 (straight-use-package 'undo-fu)
1618#+end_src
1619
1620#+begin_src emacs-lisp :noweb-ref bindings
1621 (acdw/bind "C-/" #'undo-fu-only-undo)
1622 (acdw/bind "C-?" #'undo-fu-only-redo)
1623#+end_src
1624
1625*** Undo Fu session
1626
1627I'm not putting this in [[*Persistence]] because it'd be confusing away
1628from =undo-fu=.
1629
1630#+begin_src emacs-lisp :noweb-ref packages
1631 (straight-use-package 'undo-fu-session)
1632#+end_src
1633
1634#+begin_src emacs-lisp :noweb-ref settings
1635 (setq-default undo-fu-session-incompatible-files
1636 '("/COMMIT_EDITMSG\\'"
1637 "/git-rebase-todo\\'"))
1638#+end_src
1639
1640#+begin_src emacs-lisp :noweb-ref no-littering
1641 (let ((dir (no-littering-expand-var-file-name "undos")))
1642 (make-directory dir :parents)
1643 (setq-default undo-fu-session-directory dir))
1644#+end_src
1645
1646#+begin_src emacs-lisp :noweb-ref modes
1647 (global-undo-fu-session-mode +1)
1648#+end_src
1649
1650** Search/Replace :package:
1651
1652The /biggest/ thing I miss about my Neovim days is its ease of
1653search/replace. It didn't matter where the point was in the buffer;
1654it could wrap around. It had a little highlight to show you all the
1655matching strings, /and/ it could show you what the replacement would
1656look like. =anzu= does /most/ of this, except the wrapping around part --
1657=ctrlf= does the wrapping around okay, but I haven't really tried to get
1658the two packages to play nice together. Until then, I'll just use
1659=anzu= and =isearch=, which is honestly a pretty good search package.
1660
1661*** Isearch
1662
1663I want to search by regexp by default.
1664
1665#+begin_src emacs-lisp :noweb-ref bindings
1666 (define-key acdw/map (kbd "C-s") #'isearch-forward-regexp)
1667 (define-key acdw/map (kbd "C-r") #'isearch-backward-regexp)
1668 (define-key acdw/map (kbd "C-M-s") #'isearch-forward)
1669 (define-key acdw/map (kbd "C-M-r") #'isearch-backward)
1670#+end_src
1671
1672*** Anzu setup :package:
1673
1674#+begin_src emacs-lisp :noweb-ref packages
1675 (straight-use-package 'anzu)
1676#+end_src
1677
1678#+begin_src emacs-lisp :noweb-ref settings
1679 (setq-default anzu-mode-lighter "" ; hide anzu-mode in the modeline
1680 anzu-replace-to-string-separator " → ")
1681
1682 ;; Set up anzu in the modeline
1683 (setq-default anzu-cons-mode-line-p nil)
1684 (setcar (cdr (assq 'isearch-mode minor-mode-alist))
1685 '(:eval (concat " " (anzu--update-mode-line))))
1686#+end_src
1687
1688*** Regex
1689
1690I search with regex by default.
1691
1692#+begin_src emacs-lisp :noweb-ref settings
1693 (setq-default
1694 ;; Search Regex by default
1695 search-default-mode t)
1696#+end_src
1697
1698I've switched =query-replace= and =query-replace-regexp= with their anzu
1699versions, because of the regex thing.
1700
1701#+begin_src emacs-lisp :noweb-ref bindings
1702 (acdw/bind-after-map anzu nil
1703 (([remap query-replace] #'anzu-query-replace-regexp)
1704 ([remap query-replace-regexp] #'anzu-query-replace)
1705 ([remap isearch-query-replace] #'anzu-isearch-query-replace
1706 :map isearch-mode-map)
1707 ([remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp
1708 :map isearch-mode-map)))
1709#+end_src
1710
1711** Smart scan :package:
1712
1713Like Vim's =*= / =#=.
1714
1715#+begin_src emacs-lisp :noweb-ref packages
1716 (straight-use-package 'smartscan)
1717 (autoload 'smartscan-mode "smartscan")
1718#+end_src
1719
1720#+begin_src emacs-lisp :noweb-ref hooks
1721 (add-hook 'prog-mode-hook #'smartscan-mode)
1722#+end_src
1723
1724** Commenting :crux:
1725
1726I don't think the default =M-;= (=M-x comment-dwim=) binding makes sense.
1727I want it to comment out the region or line, or uncomment it if it's
1728already commented. That's it.
1729
1730#+begin_src emacs-lisp :noweb-ref hooks
1731 (crux-with-region-or-line comment-or-uncomment-region)
1732#+end_src
1733
1734#+begin_src emacs-lisp :noweb-ref bindings
1735 (acdw/bind "M-;" #'comment-or-uncomment-region)
1736#+end_src
1737
1738** Goto address mode
1739
1740"Buttonize URLs and Email addresses."
1741
1742#+begin_src emacs-lisp :noweb-ref modes
1743 (when (fboundp 'global-goto-address-mode)
1744 (global-goto-address-mode +1))
1745#+end_src
1746
1747* Writing
1748
1749** Word count
1750
1751*** Key binding
1752
1753I just found out that =M-== counts the words in a region. That's great, but I
1754often want to count the words in the whole buffer.
1755
1756#+begin_src emacs-lisp :noweb-ref bindings
1757 (acdw/bind "M-=" #'count-words)
1758#+end_src
1759
1760** Spell checking
1761
1762*** Settings
1763
1764Let's use =hunspell=.
1765
1766#+begin_src emacs-lisp :noweb-ref packages
1767 (with-eval-after-load "ispell"
1768 (setenv "LANG" "en_US")
1769 (setq-default ispell-program-name "hunspell"
1770 ispell-dictionary "en_US")
1771 (ispell-set-spellchecker-params))
1772
1773 (setq ispell-personal-dictionary "~/.hunspell_personal")
1774 (unless (file-exists-p ispell-personal-dictionary)
1775 (write-region "" nil ispell-personal-dictionary nil 0))
1776#+end_src
1777
1778*** Flyspell
1779
1780#+begin_src emacs-lisp :noweb-ref hooks
1781 (add-hook 'text-mode-hook #'flyspell-mode)
1782 (add-hook 'prog-mode-hook #'flyspell-prog-mode)
1783#+end_src
1784
1785#+begin_src emacs-lisp :noweb-ref modes
1786 (blackout 'flyspell-mode)
1787#+end_src
1788
1789*** Flyspell-correct :package:
1790
1791Display corrections with =completing-read=.
1792
1793#+begin_src emacs-lisp :noweb-ref packages
1794 (straight-use-package 'flyspell-correct)
1795#+end_src
1796
1797#+begin_src emacs-lisp :noweb-ref bindings
1798 (acdw/bind "C-;" #'flyspell-correct-wrapper
1799 :after 'flyspell
1800 :map flyspell-mode-map)
1801#+end_src
1802
1803* Reading
1804
1805** Smooth-scrolling of images :package:
1806
1807#+begin_src emacs-lisp :noweb-ref packages
1808 (straight-use-package 'iscroll)
1809#+end_src
1810
1811#+begin_src emacs-lisp :noweb-ref hooks
1812 (add-hook 'text-mode-hook #'iscroll-mode)
1813#+end_src
1814
1815#+begin_src emacs-lisp :noweb-ref modes
1816 (add-hook 'iscroll-mode-hook
1817 #'(lambda () (blackout 'iscroll-mode)))
1818#+end_src
1819
1820** Reading mode
1821
1822A custom mode to make reading comfy
1823
1824#+begin_src emacs-lisp :noweb-ref modes
1825 (define-minor-mode acdw/reading-mode
1826 "Make reading comfier."
1827 :lighter " Read" ; later: book emoji
1828 (if acdw/reading-mode
1829 (progn ;; turn on
1830 (text-scale-increase +1)
1831 (visual-fill-column-mode +1)
1832 (iscroll-mode +1)
1833 (display-fill-column-indicator-mode -1))
1834 (progn ;; turn off
1835 (text-scale-increase 0)
1836 (visual-fill-column-mode -1)
1837 (iscroll-mode -1)
1838 (display-fill-column-indicator-mode +1))))
1839#+end_src
1840
1841* Programming
1842
1843** Comments
1844
1845*** Auto fill comments in programming modes
1846
1847Okay, so I lied in the [[*Auto-fill vs. Visual-line][Auto-fill vs. Visual-line]] section. I /do/ want
1848to auto-fill in programming modes, but /only/ the comments.
1849
1850#+begin_src emacs-lisp :noweb-ref hooks
1851 (defun hook--comment-auto-fill ()
1852 (setq-local comment-auto-fill-only-comments t)
1853 (auto-fill-mode +1))
1854
1855 (add-hook 'prog-mode-hook #'hook--comment-auto-fill)
1856#+end_src
1857
1858** Parentheses
1859
1860*** Show parentheses
1861
1862#+begin_src emacs-lisp :noweb-ref modes
1863 (show-paren-mode +1)
1864#+end_src
1865
1866#+begin_src emacs-lisp :noweb-ref settings
1867 (setq-default show-paren-delay 0
1868 ;; Show the matching paren if visible, else the whole expression
1869 show-paren-style 'mixed
1870 show-paren-when-point-inside-paren t
1871 show-paren-when-point-in-periphery t)
1872#+end_src
1873
1874*** COMMENT Smart parentheses :package:
1875
1876#+begin_src emacs-lisp :noweb-ref packages
1877 (straight-use-package '(smartparens
1878 :host github
1879 :repo "Fuco1/smartparens"))
1880 (require 'smartparens-config)
1881#+end_src
1882
1883**** Show parens
1884
1885#+begin_src emacs-lisp :noweb-ref settings
1886 (setq-default sp-show-pair-delay 0
1887 sp-show-pair-from-inside t)
1888#+end_src
1889
1890#+begin_src emacs-lisp :noweb-ref hooks
1891 (add-hook 'prog-mode-hook #'show-smartparens-mode)
1892#+end_src
1893
1894**** Hide the =smartparens= lighter
1895
1896#+begin_src emacs-lisp :noweb-ref modes
1897 (blackout 'smartparens-mode)
1898#+end_src
1899
1900**** Enable in programming modes
1901
1902#+begin_src emacs-lisp :noweb-ref hooks
1903 (add-hook 'prog-mode-hook #'smartparens-mode)
1904
1905 (dolist (hook '(lisp-mode-hook
1906 emacs-lisp-mode-hook))
1907 (add-hook hook #'smartparens-strict-mode))
1908#+end_src
1909
1910**** Use =paredit= bindings
1911
1912#+begin_src emacs-lisp :noweb-ref settings
1913 (setq-default sp-base-keybindings 'paredit)
1914#+end_src
1915
1916#+begin_src emacs-lisp :noweb-ref modes
1917 (with-eval-after-load 'smartparens
1918 (sp-use-paredit-bindings))
1919#+end_src
1920
1921*** Electric pairs
1922
1923=smart-parens= is acting up on me, and Emacs has an included mode to do pretty
1924much everything I do with =smart-parens= -- =electric-pair-mode=. Let's try it.
1925
1926#+begin_src emacs-lisp :noweb-ref hooks
1927 (add-hook 'prog-mode-hook #'electric-pair-local-mode)
1928#+end_src
1929
1930** Formatting
1931
1932*** Aggressive indent :package:
1933
1934#+begin_src emacs-lisp :noweb-ref packages
1935 (straight-use-package 'aggressive-indent)
1936#+end_src
1937
1938#+begin_src emacs-lisp :noweb-ref modes
1939 (global-aggressive-indent-mode +1)
1940 (blackout 'aggressive-indent-mode)
1941#+end_src
1942
1943Since aggressive indenting takes care of tabs, I can use =<TAB>= to complete
1944things!
1945
1946#+begin_src emacs-lisp :noweb-ref settings
1947 (setq-default tab-always-indent nil)
1948#+end_src
1949
1950*** Reformatter :package:
1951
1952Steve Purcell's automatic reformatting tool.
1953
1954#+begin_src emacs-lisp :noweb-ref packages
1955 (straight-use-package 'reformatter)
1956 (require 'reformatter)
1957#+end_src
1958
1959** Typesetting
1960
1961*** Prettify-mode
1962
1963I like my pretty =lambda='s -- and maybe one day, I'll add more symbols,
1964but only in prog-mode. I want to see what I'm actually typing in
1965text.
1966
1967#+begin_src emacs-lisp :noweb-ref hooks
1968 (add-hook 'prog-mode-hook #'prettify-symbols-mode)
1969#+end_src
1970
1971Of course, I want to be able to /see/ the actual text in the buffer if
1972I'm /in/ the symbols.
1973
1974#+begin_src emacs-lisp :noweb-ref settings
1975 (setq-default prettify-symbols-unprettify-at-point 'right-edge)
1976#+end_src
1977
1978** Executable scripts
1979
1980This poorly-named function will make a file executable if it looks
1981like a script (looking at the function definition, it looks like it
1982checks for a shebang).
1983
1984#+begin_src emacs-lisp :noweb-ref hooks
1985 (add-hook 'after-save-hook
1986 #'executable-make-buffer-file-executable-if-script-p)
1987#+end_src
1988
1989** Compilation
1990
1991#+begin_src emacs-lisp :noweb-ref settings
1992 (setq-default compilation-ask-about-save nil ; just save
1993 compilation-always-kill t ; kill the old processes
1994 compilation-scroll-output 'first-error)
1995#+end_src
1996
1997#+begin_src emacs-lisp :noweb-ref bindings
1998 (acdw/bind "<f5>" #'recompile)
1999#+end_src
2000
2001** Language-specific
2002
2003*** Generic-x
2004
2005from [[https://www.reddit.com/r/emacs/comments/lfww57/weekly_tipstricketc_thread/gmtk79e/][u/Bodertz]], apparently =generic-x= just ... has syntax highlighting for a ton
2006of (I suppose) generic files.
2007
2008#+begin_src emacs-lisp :noweb-ref packages
2009 (require 'generic-x)
2010#+end_src
2011
2012*** Emacs Lisp
2013
2014**** Don't limit the length of evaluated expressions
2015
2016#+begin_src emacs-lisp :noweb-ref settings
2017 (setq-default eval-expression-print-length nil
2018 eval-expression-print-level nil)
2019#+end_src
2020
2021**** Indent Elisp like Common Lisp
2022
2023#+begin_src emacs-lisp :noweb-ref requires
2024 (require 'cl-lib)
2025#+end_src
2026
2027#+begin_src emacs-lisp :noweb-ref settings
2028 (setq-default lisp-indent-function #'common-lisp-indent-function)
2029 (put 'cl-flet 'common-lisp-indent-function
2030 (get 'flet 'common-lisp-indent-function))
2031 (put 'cl-labels 'common-lisp-indent-function
2032 (get 'labels 'common-lisp-indent-function))
2033 (put 'if 'common-lisp-indent-function 2)
2034 (put 'dotimes-protect 'common-lisp-indent-function
2035 (get 'when 'common-lisp-indent-function))
2036#+end_src
2037
2038**** Macrostep :package:
2039
2040#+begin_src emacs-lisp :noweb-ref packages
2041 (straight-use-package 'macrostep)
2042#+end_src
2043
2044#+begin_src emacs-lisp :noweb-ref bindings
2045 (acdw/bind "`" #'macrostep-expand
2046 :map acdw/leader)
2047#+end_src
2048
2049*** Web
2050
2051#+begin_src emacs-lisp :noweb-ref packages
2052 (straight-use-package 'web-mode)
2053 (unless (fboundp 'web-mode)
2054 (autoload #'web-mode "web-mode" nil t))
2055#+end_src
2056
2057#+begin_src emacs-lisp :noweb-ref hooks
2058 (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
2059 (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
2060 (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
2061 (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
2062 (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
2063 (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
2064 (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
2065 (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
2066#+end_src
2067
2068#+begin_src emacs-lisp :noweb-ref settings
2069 (setq-default web-mode-enable-current-element-highlight t)
2070#+end_src
2071
2072*** i3 config
2073
2074I use i3 ... for now. But I only want to load the relevant mode /if/ I have i3
2075installed.
2076
2077#+begin_src emacs-lisp :noweb-ref packages
2078 (when (executable-find "i3")
2079 (straight-use-package 'i3wm-config-mode))
2080#+end_src
2081
2082*** Shell scripts
2083
2084**** Shellcheck :package:
2085
2086#+begin_src emacs-lisp :noweb-ref packages
2087 (straight-use-package 'flymake-shellcheck)
2088 (autoload 'flymake-shellcheck-load "flymake-shellcheck")
2089#+end_src
2090
2091#+begin_src emacs-lisp :noweb-ref hooks
2092 (add-hook 'sh-mode-hook #'flymake-shellcheck-load)
2093#+end_src
2094
2095**** Formatting
2096
2097***** =shfmt= :package:
2098
2099#+begin_src emacs-lisp :noweb-ref modes
2100 (when (executable-find "shfmt")
2101 (reformatter-define sh-format
2102 :program "shfmt"
2103
2104 :lighter "Shfmt")
2105
2106 (add-hook 'sh-mode-hook #'sh-format-on-save-mode))
2107#+end_src
2108
2109***** =sh-mode= indenting
2110
2111I'm trying to make this match what =shfmt= does as much as I can, to avoid the
2112reformatting when saving.
2113
2114#+begin_src emacs-lisp :noweb-ref settings
2115 (setq-default sh-basic-offset 8
2116 sh-indent-after-case 0)
2117#+end_src
2118
2119* Applications
2120
2121Emacs is well-known for its ability to subsume one's entire computing
2122life. There are a few /killer apps/ that make Emacs really shine.
2123Here, I configure them and a few others.
2124
2125My rubric for what makes a package an application, versus just a
2126package, is mostly based on the way I feel about it. Don't expect to
2127agree with all of my choices.
2128
2129** Web browsing
2130
2131*** Browse-url
2132
2133I like using Firefox.
2134
2135#+begin_src emacs-lisp :noweb-ref settings
2136 (setq-default browse-url-browser-function 'browse-url-firefox
2137 browse-url-new-window-flag t
2138 browse-url-firefox-new-window-is-tab t)
2139#+end_src
2140
2141At work, I need to tell Emacs where Firefox is.
2142
2143#+begin_src emacs-lisp :noweb-ref windows-specific
2144 (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")
2145#+end_src
2146
2147*** SHR
2148
2149#+begin_src emacs-lisp :noweb-ref settings
2150 (setq-default shr-max-width fill-column
2151 shr-width fill-column)
2152#+end_src
2153
2154** Dired
2155
2156#+begin_src emacs-lisp :noweb-ref hooks
2157 (defun hook--dired-mode ()
2158 (hl-line-mode +1)
2159 (dired-hide-details-mode +1))
2160
2161 (add-hook 'dired-mode-hook #'hook--dired-mode)
2162#+end_src
2163
2164A note on =dired-listing-switches=: when I'm able to figure out how to
2165move up a directory with a keybinding, I'll change =-a= to =-A=.
2166
2167#+begin_src emacs-lisp :noweb-ref settings
2168 (setq-default dired-recursive-copies 'always
2169 dired-recursive-deletes 'always
2170 delete-by-moving-to-trash t
2171 dired-listing-switches "-AFgho --group-directories-first"
2172 dired-dwim-target t)
2173#+end_src
2174
2175#+begin_src emacs-lisp :noweb-ref bindings
2176 (acdw/bind "C-x C-j" #'dired-jump)
2177#+end_src
2178
2179*** Expand subtrees :package:
2180
2181Instead of opening each folder in its own buffer, =dired-subtree=
2182enables me to open them in the same buffer, fancily indented.
2183
2184#+begin_src emacs-lisp :noweb-ref packages
2185 (straight-use-package 'dired-subtree)
2186#+end_src
2187
2188#+begin_src emacs-lisp :noweb-ref bindings
2189 (acdw/bind "i" #'dired-subtree-toggle :after 'dired :map dired-mode-map)
2190#+end_src
2191
2192*** Collapse singleton directories :package:
2193
2194If a directory only has one item in it, =dired-collapse= shows what
2195that one item is.
2196
2197#+begin_src emacs-lisp :noweb-ref packages
2198 (straight-use-package 'dired-collapse)
2199#+end_src
2200
2201#+begin_src emacs-lisp :noweb-ref hooks
2202 (add-hook 'dired-mode-hook #'dired-collapse-mode)
2203#+end_src
2204
2205** Git :package:
2206
2207*** Magit
2208
2209#+begin_src emacs-lisp :noweb-ref packages
2210 (straight-use-package 'magit)
2211#+end_src
2212
2213#+begin_src emacs-lisp :noweb-ref bindings
2214 (acdw/bind "g" #'magit-status :map acdw/leader)
2215#+end_src
2216
2217**** Windows setup
2218
2219Following the [[https://github.com/magit/magit/wiki/Pushing-with-Magit-from-Windows][wiki page located here]]. Also make sure to run the
2220following in =cmd.exe= to set =$HOME= correctly:
2221
2222#+begin_src cmd
2223 setx HOME C:\Users\aduckworth\Downloads\acdw
2224#+end_src
2225
2226/and/ run /this/ command to setup a git credential helper:
2227
2228#+begin_src sh
2229 git config --global credential.helper store
2230#+end_src
2231
2232Okay, okay, using the =store= credential.helper is /super/ insecure. But
2233here's the thing -- the Gits I Git at work (a) aren't my /real/ git, and
2234(b) they're just tokens! So any time I think somebody got access, I
2235just revoke the tokens and bingo bongo, good to go. If that's not
2236true, please feel free to hack this repo and change this paragraph.
2237
2238#+begin_src emacs-lisp :noweb-ref windows-specific
2239 (setenv "GIT_ASKPASS" "git-gui--askpass")
2240#+end_src
2241
2242**** Forge :package:
2243
2244#+begin_src emacs-lisp :noweb-ref packages
2245 (straight-use-package 'forge)
2246 (with-eval-after-load 'magit
2247 (require 'forge))
2248#+end_src
2249
2250*** Git file modes :package:
2251
2252#+begin_src emacs-lisp :noweb-ref packages
2253 (dolist (feat '(gitattributes-mode
2254 gitconfig-mode
2255 gitignore-mode))
2256 (straight-use-package feat)
2257 (require feat))
2258#+end_src
2259
2260** Crosswords! :package:
2261
2262I love crosswords. I love Emacs. There ya go.
2263
2264#+begin_src emacs-lisp :noweb-ref packages
2265 (straight-use-package '(crossword
2266 :host github
2267 :repo "Boruch-Baum/emacs-crossword"))
2268#+end_src
2269
2270#+begin_src emacs-lisp :noweb-ref settings
2271 (setq-default crossword-empty-position-char "#")
2272#+end_src
2273
2274#+begin_src emacs-lisp :noweb-ref no-littering
2275 (setq-default crossword-save-path
2276 (no-littering-expand-var-file-name "crosswords/"))
2277 (unless (file-exists-p crossword-save-path)
2278 (make-directory crossword-save-path :parents))
2279#+end_src
2280
2281#+begin_src emacs-lisp :noweb-ref hooks
2282 (defun hook--setup-crossword ()
2283 (setq cursor-type 'hbar))
2284
2285 (add-hook 'crossword-mode-hook #'hook--setup-crossword)
2286#+end_src
2287
2288The problem with this package is that the default faces are pretty
2289bad, to be honest. Let's change em.
2290
2291#+begin_src emacs-lisp :noweb-ref settings
2292 (doremi-face-set 'crossword-current-face
2293 '((((class color)
2294 (background light))
2295 (:inherit 'normal :foreground "black"
2296 :background "lightgreen"))
2297 (((class color)
2298 (background dark))
2299 (:inherit 'normal :foreground "white"
2300 :background "darkgreen"))
2301 (t
2302 (:inherit 'normal :foreground "black"
2303 :background "darkgreen"))))
2304
2305 (doremi-face-set 'crossword-other-dir-face
2306 '((((class color)
2307 (background light))
2308 (:inherit 'normal :foreground "black"
2309 :background "darkgrey"))
2310 (((class color)
2311 (background dark))
2312 (:inherit 'normal :foreground "black"
2313 :background "darkgrey"))
2314 (t
2315 (:inherit 'normal :foreground "black"
2316 :background "darkgrey"))))
2317#+end_src
2318
2319** TODO Gnus
2320
2321See [[https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/gnus-guide-en.org][this guide]] and try it out.
2322
2323** RSS Feeds :package:
2324
2325*** Elfeed
2326
2327#+begin_src emacs-lisp :noweb-ref packages
2328 ;; first, "fix" org-version
2329 (with-eval-after-load 'org
2330 (defun org-version ()
2331 "Fix of `org-version' for `elfeed'.
2332
2333 I don't know what the actual version is, but 9.5 should have us
2334 covered. It's somewhere past 9."
2335 "9.5")
2336 (straight-use-package 'elfeed))
2337#+end_src
2338
2339#+begin_src emacs-lisp :noweb-ref settings
2340 (setq-default elfeed-db-directory
2341 (expand-file-name "elfeed/db"
2342 (or (getenv "XDG_CACHE_HOME")
2343 "~/.cache")))
2344#+end_src
2345
2346#+begin_src emacs-lisp :noweb-ref hooks
2347 (add-hook 'elfeed-show-mode-hook #'acdw/reading-mode)
2348#+end_src
2349
2350#+begin_src emacs-lisp :noweb-ref bindings
2351 (acdw/bind "f" #'elfeed :map acdw/leader)
2352#+end_src
2353
2354**** Elfeed-protocol
2355
2356I have Miniflux set up on my server, so I can use its Fever integration.
2357
2358#+begin_src emacs-lisp :noweb-ref packages
2359 (straight-use-package 'elfeed-protocol)
2360#+end_src
2361
2362#+begin_src emacs-lisp :noweb-ref settings
2363 (setq-default elfeed-use-curl t
2364 elfeed-curl-extra-arguments '("--insecure")
2365 elfeed-protocol-fever-maxsize 1000)
2366#+end_src
2367
2368#+begin_src emacs-lisp :noweb-ref settings
2369 (setq elfeed-feeds
2370 '(("fever+https://acdw@mf.acdw.net"
2371 :api-url "https://mf.acdw.net/fever/"
2372 :use-authinfo)))
2373#+end_src
2374
2375#+begin_src emacs-lisp :noweb-ref modes
2376 (elfeed-protocol-enable)
2377#+end_src
2378
2379** 0x0 (null pointer) :package:
2380
2381An ease-of-life package that lets me upload my shitty code to share it with
2382others.
2383
2384#+begin_src emacs-lisp :noweb-ref packages
2385 (straight-use-package '(0x0
2386 :repo "https://git.sr.ht/~zge/nullpointer-emacs"))
2387#+end_src
2388
2389#+begin_src emacs-lisp :noweb-ref settings
2390 (setq-default 0x0-default-service 'ttm)
2391#+end_src
2392
2393** Gemini/gopher
2394
2395*** Elpher :package:
2396
2397#+begin_src emacs-lisp :noweb-ref packages
2398 (straight-use-package '(elpher
2399 :repo "git://thelambdalab.xyz/elpher.git"))
2400#+end_src
2401
2402#+begin_src emacs-lisp :noweb-ref settings
2403 (setq-default elpher-ipv4-always t)
2404
2405 (doremi-face-set 'elpher-gemini-heading1
2406 '((t (:inherit (modus-theme-heading-1)
2407 :height 1.0))))
2408 (doremi-face-set 'elpher-gemini-heading2
2409 '((t (:inherit (modus-theme-heading-2)
2410 :height 1.0))))
2411 (doremi-face-set 'elpher-gemini-heading3
2412 '((t (:inherit (modus-theme-heading-3)
2413 :height 1.0))))
2414#+end_src
2415
2416#+begin_src emacs-lisp :noweb-ref no-littering
2417 (setq-default elpher-certificate-directory
2418 (no-littering-expand-var-file-name
2419 "elpher-certificates/"))
2420#+end_src
2421
2422#+begin_src emacs-lisp :noweb-ref bindings
2423 (acdw/bind-after-map elpher elpher-mode-map
2424 (("n" #'elpher-next-link)
2425 ("p" #'elpher-prev-link)
2426 ("o" #'elpher-follow-current-link)
2427 ("G" #'elpher-go-current)))
2428#+end_src
2429
2430#+begin_src emacs-lisp :noweb-ref hooks
2431 (add-hook 'elpher-mode-hook #'acdw/reading-mode)
2432#+end_src
2433
2434*** Gemini-mode :package:
2435
2436#+begin_src emacs-lisp :noweb-ref packages
2437 (straight-use-package '(gemini-mode
2438 :repo "https://git.carcosa.net/jmcbray/gemini.el.git"))
2439#+end_src
2440
2441#+begin_src emacs-lisp :noweb-ref settings
2442 (add-to-list 'auto-mode-alist
2443 '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode))
2444
2445 (doremi-face-set 'gemini-heading-face-1
2446 '((t (:inherit (elpher-gemini-heading1)))))
2447 (doremi-face-set 'gemini-heading-face2
2448 '((t (:inherit (elpher-gemini-heading2)))))
2449 (doremi-face-set 'gemini-heading-face3
2450 '((t (:inherit (elpher-gemini-heading3)))))
2451#+end_src
2452
2453*** Gemini-write :package:
2454
2455#+begin_src emacs-lisp :noweb-ref packages
2456 (straight-use-package '(gemini-write
2457 :repo "https://alexschroeder.ch/cgit/gemini-write"
2458 :fork (:repo "https://tildegit.org/acdw/gemini-write"
2459 :branch "main")))
2460
2461 (with-eval-after-load 'elpher
2462 (require 'gemini-write))
2463#+end_src
2464
2465** Eshell
2466
2467I use =eshell= with Emacs, because it works both on Windows and Linux.
2468
2469*** Open an eshell or bury its buffer
2470
2471adapted from [[https://git.sr.ht/~zge/setup][zge's setup]], which might also be an interesting macro to look into
2472one day.
2473
2474#+begin_src emacs-lisp :noweb-ref functions
2475 (defun acdw/eshell-or-bury ()
2476 "Start an `eshell', or bury its buffer if focused."
2477 (interactive)
2478 (if (string= (buffer-name) "*eshell*") ;; XXX: brittle
2479 (bury-buffer)
2480 (eshell)))
2481#+end_src
2482
2483#+begin_src emacs-lisp :noweb-ref bindings
2484 (define-key acdw/leader "s" #'acdw/eshell-or-bury)
2485#+end_src
2486
2487** E-books with nov.el :package:
2488
2489I /love/ the name of this package.
2490
2491#+begin_src emacs-lisp :noweb-ref packages
2492 (straight-use-package 'nov)
2493#+end_src
2494
2495#+begin_src emacs-lisp :noweb-ref settings
2496 (setq-default nov-text-width t)
2497#+end_src
2498
2499#+begin_src emacs-lisp :noweb-ref hooks
2500 (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
2501 (add-hook 'nov-mode-hook #'acdw/reading-mode)
2502#+end_src
2503
2504* Org mode :package:
2505
2506** Install it with =straight.el=
2507
2508I want to use the newest version of Org that I can.
2509
2510#+begin_src emacs-lisp :noweb-ref packages
2511 (straight-use-package 'org)
2512
2513 (with-eval-after-load 'org
2514 (require 'org-tempo)
2515 (require 'ox-md))
2516#+end_src
2517
2518** Basic settings
2519
2520#+begin_src emacs-lisp :noweb-ref settings
2521 (setq-default
2522 ;; Where to look for Org files
2523 org-directory "~/org" ; this is the default
2524 ;; Fontify stuff
2525 org-hide-emphasis-markers t
2526 org-fontify-whole-heading-line t
2527 org-fontify-done-headline t
2528 org-fontify-quote-and-verse-blocks t
2529 org-src-fontify-natively t
2530 org-ellipsis "..."
2531 org-pretty-entities t
2532 org-tags-column (- 0 fill-column -3)
2533 ;; Source blocks
2534 org-src-tab-acts-natively t
2535 org-src-window-setup 'current-window ; the least stupid option
2536 org-confirm-babel-evaluate nil
2537 ;; Behavior
2538 org-adapt-indentation nil ; don't indent things
2539 org-catch-invisible-edits 'smart ; let's try this
2540 org-special-ctrl-a/e t
2541 org-special-ctrl-k t
2542 org-imenu-depth 8
2543 ;; Exporting
2544 org-export-headline-levels 8
2545 org-export-with-smart-quotes t
2546 org-export-with-sub-superscripts t)
2547#+end_src
2548
2549** Aesthetics
2550
2551*** Prettify some other symbols
2552
2553#+begin_src emacs-lisp :noweb-ref functions
2554 (defun acdw/org-mode-prettify ()
2555 "Prettify `org-mode'."
2556 (dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐)
2557 ("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎)
2558 ("#+BEGIN_QUOTE" . ?❝) ("#+begin_quote" . ?❝)
2559 ("#+END_QUOTE" . ?❞) ("#+end_quote" . ?❞)
2560 ("#+END_SRC" . ?■) ("#+end_src" . ?■)))
2561 (add-to-list 'prettify-symbols-alist cell :append))
2562 (prettify-symbols-mode +1))
2563#+end_src
2564
2565#+begin_src emacs-lisp :noweb-ref hooks
2566 (add-hook 'org-mode-hook #'acdw/org-mode-prettify)
2567#+end_src
2568
2569*** Prettify lists and checkboxes using font-lock
2570
2571from [[https://github.com/KaratasFurkan/.emacs.d#org-1][Furkan Karataş]].
2572
2573#+begin_src emacs-lisp :noweb-ref modes
2574 (with-eval-after-load 'org
2575 (font-lock-add-keywords 'org-mode
2576 '(("^ *\\([-]\\) "
2577 (0 (prog1 ()
2578 (compose-region (match-beginning 1) (match-end 1) "•"))))))
2579 (font-lock-add-keywords 'org-mode
2580 '(("^ *\\([+]\\) "
2581 (0 (prog1 ()
2582 (compose-region (match-beginning 1) (match-end 1) "◦"))))))
2583
2584 (defface org-checkbox-done-text
2585 '((t (:inherit 'font-lock-comment-face :slant normal)))
2586 "Face for the text part of a checked org-mode checkbox."
2587 :group 'org)
2588
2589 (font-lock-add-keywords
2590 'org-mode
2591 `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
2592 1 'org-checkbox-done-text prepend))
2593 'append))
2594#+end_src
2595
2596*** COMMENT Org-appear :package:
2597
2598#+begin_src emacs-lisp :noweb-ref packages
2599 (straight-use-package '(org-appear
2600 :host github
2601 :repo "awth13/org-appear"))
2602#+end_src
2603
2604#+begin_src emacs-lisp :noweb-ref settings
2605 (setq-default org-appear-autoemphasis t
2606 org-appear-autolinks nil
2607 org-appear-autosubmarkers t)
2608#+end_src
2609
2610#+begin_src emacs-lisp :noweb-ref hooks
2611 (add-hook 'org-mode-hook #'org-appear-mode)
2612#+end_src
2613
2614*** Visible-mode
2615
2616I'm going to try =visible-mode= instead of =org-appear= since it's built-in.
2617
2618#+begin_src emacs-lisp :noweb-ref bindings
2619 (acdw/bind "v" #'visible-mode
2620 :map acdw/leader)
2621#+end_src
2622
2623** Org templates
2624
2625#+begin_src emacs-lisp :noweb-ref settings
2626 (with-eval-after-load 'org-tempo
2627 (dolist (cell '(("el" . "src emacs-lisp")
2628 ("cr" . "src emacs-lisp :noweb-ref requires")
2629 ("cf" . "src emacs-lisp :noweb-ref functions")
2630 ("cs" . "src emacs-lisp :noweb-ref settings")
2631 ("cm" . "src emacs-lisp :noweb-ref modes")
2632 ("cl" . "src emacs-lisp :noweb-ref linux-specific")
2633 ("cw" . "src emacs-lisp :noweb-ref windows-specific")
2634 ("cp" . "src emacs-lisp :noweb-ref packages")
2635 ("ch" . "src emacs-lisp :noweb-ref hooks")
2636 ("cb" . "src emacs-lisp :noweb-ref bindings")
2637 ("cnl" . "src emacs-lisp :noweb-ref no-littering")))
2638 (add-to-list 'org-structure-template-alist cell)))
2639#+end_src
2640
2641** Org Return: DWIM
2642
2643#+begin_src emacs-lisp :noweb-ref functions
2644 (defun unpackaged/org-element-descendant-of (type element)
2645 "Return non-nil if ELEMENT is a descendant of TYPE.
2646 TYPE should be an element type, like `item' or `paragraph'.
2647 ELEMENT should be a list like that returned by `org-element-context'."
2648 ;; MAYBE: Use `org-element-lineage'.
2649 (when-let* ((parent (org-element-property :parent element)))
2650 (or (eq type (car parent))
2651 (unpackaged/org-element-descendant-of type parent))))
2652
2653 (defun unpackaged/org-return-dwim (&optional default)
2654 "A helpful replacement for `org-return'. With prefix, call `org-return'.
2655
2656 On headings, move point to position after entry content. In
2657 lists, insert a new item or end the list, with checkbox if
2658 appropriate. In tables, insert a new row or end the table."
2659 ;; Inspired by John Kitchin:
2660 ;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
2661 (interactive "P")
2662 (if default
2663 (org-return)
2664 (cond
2665 ;; Act depending on context around point.
2666
2667 ;; NOTE: I prefer RET to not follow links, but by uncommenting
2668 ;; this block, links will be followed.
2669 ;; FURTHER NOTE: Ideally, I would follow links unless point
2670 ;; /appeared/ to be at the end of the line (even if it's still
2671 ;; inside the link) -- when it would do `org-return'. That
2672 ;; would take some /doing/, however.
2673
2674 ;; ((eq 'link (car (org-element-context)))
2675 ;; ;; Link: Open it.
2676 ;; (org-open-at-point-global))
2677
2678 ((org-at-heading-p)
2679 ;; Heading: Move to position after entry content.
2680 ;; NOTE: This is probably the most interesting feature of this function.
2681 (let ((heading-start (org-entry-beginning-position)))
2682 (goto-char (org-entry-end-position))
2683 (cond ((and (org-at-heading-p)
2684 (= heading-start (org-entry-beginning-position)))
2685 ;; Entry ends on its heading; add newline after
2686 (end-of-line)
2687 (insert "\n\n"))
2688 (t
2689 ;; Entry ends after its heading; back up
2690 (forward-line -1)
2691 (end-of-line)
2692 (when (org-at-heading-p)
2693 ;; At the same heading
2694 (forward-line)
2695 (insert "\n")
2696 (forward-line -1))
2697 ;; FIXME: looking-back is supposed to be called with
2698 ;; more arguments.
2699 (while (not (looking-back (rx
2700 (repeat 3
2701 (seq (optional blank)
2702 "\n")))
2703 nil))
2704 (insert "\n"))
2705 (forward-line -1)))))
2706
2707 ((org-at-item-checkbox-p)
2708 ;; Checkbox: Insert new item with checkbox.
2709 (org-insert-todo-heading nil))
2710
2711 ((org-in-item-p)
2712 ;; Plain list. Yes, this gets a little complicated...
2713 (let ((context (org-element-context)))
2714 (if (or (eq 'plain-list (car context)) ; First item in list
2715 (and (eq 'item (car context))
2716 (not (eq (org-element-property :contents-begin context)
2717 (org-element-property :contents-end context))))
2718 ;; Element in list item, e.g. a link
2719 (unpackaged/org-element-descendant-of 'item context))
2720 ;; Non-empty item: Add new item.
2721 (org-insert-item)
2722 ;; Empty item: Close the list.
2723 ;; TODO: Do this with org functions rather than operating
2724 ;; on the text. Can't seem to find the right function.
2725 (delete-region (line-beginning-position) (line-end-position))
2726 (insert "\n"))))
2727
2728 ((when (fboundp 'org-inlinetask-in-task-p)
2729 (org-inlinetask-in-task-p))
2730 ;; Inline task: Don't insert a new heading.
2731 (org-return))
2732
2733 ((org-at-table-p)
2734 (cond ((save-excursion
2735 (beginning-of-line)
2736 ;; See `org-table-next-field'.
2737 (cl-loop with end = (line-end-position)
2738 for cell = (org-element-table-cell-parser)
2739 always (equal (org-element-property :contents-begin cell)
2740 (org-element-property :contents-end cell))
2741 while (re-search-forward "|" end t)))
2742 ;; Empty row: end the table.
2743 (delete-region (line-beginning-position) (line-end-position))
2744 (org-return))
2745 (t
2746 ;; Non-empty row: call `org-return'.
2747 (org-return))))
2748 (t
2749 ;; All other cases: call `org-return'.
2750 (org-return)))))
2751#+end_src
2752
2753#+begin_src emacs-lisp :noweb-ref bindings
2754 (with-eval-after-load 'org
2755 (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim))
2756#+end_src
2757
2758** Insert blank lines around headers
2759
2760#+begin_src emacs-lisp :noweb-ref functions
2761 (defun unpackaged/org-fix-blank-lines (&optional prefix)
2762 "Ensure that blank lines exist between headings and between headings and their contents.
2763 With prefix, operate on whole buffer. Ensures that blank lines
2764 exist after each headings's drawers."
2765 (interactive "P")
2766 (org-map-entries (lambda ()
2767 (org-with-wide-buffer
2768 ;; `org-map-entries' narrows the buffer, which prevents us
2769 ;; from seeing newlines before the current heading, so we
2770 ;; do this part widened.
2771 (while (not (looking-back "\n\n" nil))
2772 ;; Insert blank lines before heading.
2773 (insert "\n")))
2774 (let ((end (org-entry-end-position)))
2775 ;; Insert blank lines before entry content
2776 (forward-line)
2777 (while (and (org-at-planning-p)
2778 (< (point) (point-max)))
2779 ;; Skip planning lines
2780 (forward-line))
2781 (while (re-search-forward org-drawer-regexp end t)
2782 ;; Skip drawers. You might think that `org-at-drawer-p'
2783 ;; would suffice, but for some reason it doesn't work
2784 ;; correctly when operating on hidden text. This
2785 ;; works, taken from `org-agenda-get-some-entry-text'.
2786 (re-search-forward "^[ \t]*:END:.*\n?" end t)
2787 (goto-char (match-end 0)))
2788 (unless (or (= (point) (point-max))
2789 (org-at-heading-p)
2790 (looking-at-p "\n"))
2791 (insert "\n"))))
2792 t (if prefix
2793 nil
2794 'tree)))
2795#+end_src
2796
2797I fix the headline spacing every time I save.
2798
2799#+begin_src emacs-lisp :noweb-ref hooks
2800 (defun hook--org-mode-fix-blank-lines ()
2801 (when (eq major-mode 'org-mode)
2802 (let ((current-prefix-arg 4)) ; Emulate C-u
2803 (call-interactively 'unpackaged/org-fix-blank-lines))))
2804
2805 (add-hook 'before-save-hook #'hook--org-mode-fix-blank-lines)
2806#+end_src
2807
2808** Org Agenda
2809
2810I'm trying to organize my life.
2811Inspo:
2812
2813- [[https://github.com/cadadr/configuration/blob/3e11ef25344188cc55b16f314c3c5358ace8a266/emacs.d/init.el#L4625][Göktuğ Kayaalp]]
2814- [[https://pages.sachachua.com/.emacs.d/Sacha.html#org1db6fe9][Sacha Chua]]
2815
2816*** Basic Agenda Settings
2817
2818#+begin_src emacs-lisp :noweb-ref settings
2819 (setq-default org-agenda-files ; look for files in ~/org
2820 (list org-directory)
2821 ;; agenda
2822 org-agenda-span 5
2823 org-agenda-skip-scheduled-if-done t
2824 org-agenda-skip-deadline-if-done t
2825 org-deadline-warning-days 2
2826 ;; logging
2827 org-log-into-drawer "LOGBOOK"
2828 org-log-done t
2829 ;; archive
2830 org-archive-location (concat (expand-file-name ".archive.org"
2831 org-directory)
2832 "::"))
2833#+end_src
2834
2835#+begin_src emacs-lisp :noweb-ref bindings
2836 (define-key acdw/leader (kbd "C-a") #'org-agenda)
2837#+end_src
2838
2839*** Agenda hooks
2840
2841#+begin_src emacs-lisp :noweb-ref hooks
2842 (defun hook--org-agenda-mode ()
2843 (hl-line-mode +1))
2844
2845 (add-hook 'org-agenda-mode-hook #'hook--org-agenda-mode)
2846#+end_src
2847
2848*** Refile
2849
2850#+begin_src emacs-lisp :noweb-ref settings
2851 (setq org-refile-targets '((org-agenda-files . (:maxlevel . 3))))
2852#+end_src
2853
2854*** Calendar settings
2855
2856I'm not sure where else to put these, to be honest.
2857
2858#+begin_src emacs-lisp :noweb-ref settings
2859 (setq-default calendar-date-style 'iso) ; YYYY-mm-dd
2860#+end_src
2861
2862*** Habits
2863
2864Org can track habits! Great stuff. I need to add it to =org-modules=,
2865though.
2866
2867#+begin_src emacs-lisp :noweb-ref settings
2868 (add-to-list 'org-modules 'org-habit)
2869#+end_src
2870
2871Now I just add a =habit= property to a subtree, and BAM!
2872
2873*** Org Todo Keywords
2874
2875These need some more thinking -- e.g., the MEETING sequence should
2876maybe be a type, or maybe I need to include those in something else
2877altogether. Hm.
2878
2879#+begin_src emacs-lisp :noweb-ref settings
2880 (setq-default org-todo-keywords
2881 '((sequence
2882 "TODO(t)" "STARTED(s)"
2883 "WAITING(w@/!)" "SOMEDAY(.)"
2884 "|" "DONE(x!)" "CANCELLED(c@/!)")
2885 (sequence "RECUR(r)" "|" "DONE(x!)")
2886 (sequence "MEETING(m)")))
2887#+end_src
2888
2889** Org Capture
2890
2891#+begin_src emacs-lisp :noweb-ref packages
2892 (require 'org-capture)
2893#+end_src
2894
2895#+begin_src emacs-lisp :noweb-ref bindings
2896 (with-eval-after-load 'org-capture
2897 (define-key acdw/leader (kbd "C-c") #'org-capture))
2898#+end_src
2899
2900I've still got a lot of thinking to do about what kinds of things I
2901want to capture, but I guess for now I can start with the basics:
2902TODO, and Diary.
2903
2904#+begin_src emacs-lisp :noweb-ref settings
2905 (defvar acdw/org-inbox-file (expand-file-name "inbox.org" org-directory))
2906 (defvar acdw/org-diary-file (expand-file-name "diary.org" org-directory))
2907 (setq-default
2908 org-capture-templates
2909 `(;; Todo -- these go to the Inbox for further processing
2910 ("t" "Quick Task" entry
2911 (file ,acdw/org-inbox-file)
2912 "* TODO %^{Task}\n"
2913 :immediate-finish t)
2914 ("T" "Task" entry
2915 (file ,acdw/org-inbox-file)
2916 "* TODO %^{Task}\n")
2917 ("." "Today" entry
2918 (file ,acdw/org-inbox-file)
2919 "* TODO %^{Task}\nSCHEDULED: %t\n"
2920 :immediate-finish t)
2921 ;; Meetings
2922 ("m" "Meeting" entry
2923 (file ,acdw/org-inbox-file)
2924 "* MEETING %^{Meeting}\n%^T\n\n%?")
2925 ;; Diary -- for the special Diary file
2926 ("j" "Diary entry" entry
2927 (file+olp+datetree ,acdw/org-diary-file)
2928 "* %U\n\n%?"
2929 :empty-lines 1)
2930 ;; Books to read
2931 ("b" "Book to read" entry
2932 (file+headline "books.org" "Later")
2933 "* TOREAD %^{Title}
2934 :PROPERTIES:
2935 :Author: %^{Author}
2936 :END:\n" :immediate-finish t)))
2937
2938#+end_src
2939
2940** Org auto tangle :package:
2941
2942#+begin_src emacs-lisp :noweb-ref packages
2943 (straight-use-package 'org-auto-tangle)
2944 (with-eval-after-load 'org
2945 (require 'org-auto-tangle))
2946#+end_src
2947
2948#+begin_src emacs-lisp :noweb-ref hooks
2949 (add-hook 'org-mode-hook #'org-auto-tangle-mode)
2950#+end_src
2951
2952#+begin_src emacs-lisp :noweb-ref modes
2953 (blackout 'org-auto-tangle-mode)
2954#+end_src
2955
2956* Package management :package:
2957:PROPERTIES:
2958:header-args: :noweb-ref early-init-package
2959:END:
2960
2961Emacs is the /extensible/ editor, and that means I want to use
2962third-party packages. Of course, first I have to /manage/ those
2963packages. I use the excellent =straight.el=.
2964
2965** Update the PATH
2966
2967PATH handling on Emacs is a little complicated. There's the regular
2968environment variable =$PATH=, which we all know and love, and then
2969Emacs has its own special =exec-path= on /top/ of that. From my
2970research, it looks like Emacs uses =exec-path= for itself, and =$PATH=
2971for any shells or other processes it spawns. They don't /have/ to be
2972the same, but luckily for us, Emacs sets =exec-path= from =$PATH= on
2973initialization, so when I add stuff to =exec-path= to, say, run git, I
2974can just change =$PATH= right back to the expanded =exec-path= without
2975any data loss. Here's what all that looks like.
2976
2977#+begin_src emacs-lisp
2978 (let ((win-app-dir "~/Applications"))
2979 (dolist (path (list
2980 ;; Windows
2981 (expand-file-name "exe" win-app-dir)
2982 (expand-file-name "exe/bin" win-app-dir)
2983 (expand-file-name "Git/bin" win-app-dir)
2984 (expand-file-name "Git/usr/bin" win-app-dir)
2985 (expand-file-name "Git/mingw64/bin" win-app-dir)
2986 (expand-file-name "Everything" win-app-dir)
2987 (expand-file-name "Win-builds/bin" win-app-dir)
2988 (expand-file-name "Z/bin" win-app-dir)
2989 ;; Linux
2990 (expand-file-name "bin" user-emacs-directory)
2991 (expand-file-name "~/bin")
2992 (expand-file-name "~/.local/bin")
2993 (expand-file-name "~/Scripts")
2994 ))
2995 (when (file-exists-p path)
2996 (add-to-list 'exec-path path :append))))
2997
2998 ;; Set $PATH
2999 (setenv "PATH" (mapconcat #'identity exec-path path-separator))
3000#+end_src
3001
3002*** References
3003
3004- [[https://emacs.stackexchange.com/questions/550/exec-path-and-path][exec-path and $PATH (StackExchange)]]
3005- [[https://utoi.tistory.com/entry/Difference-Between-Emacss-%E2%80%9Cgetenv-PATH%E2%80%9D-and-%E2%80%9Cexec-path%E2%80%9D][Difference between Emacs's "(getenv PATH)" and "exec-path" (U&I)]]
3006- GUI Emacs sets the exec-path only from Windows environment variable
3007 but not from .emacs file [[https://emacs.stackexchange.com/questions/27326/gui-emacs-sets-the-exec-path-only-from-windows-environment-variable-but-not-from][(StackExchange)]]
3008
3009** Disable =package.el=
3010
3011#+begin_src emacs-lisp
3012 (setq package-enable-at-startup nil)
3013#+end_src
3014
3015** Bootstrap =straight.el=
3016
3017The following is straight (heh) from the straight repo, wrapped in a
3018function so I can call it in another wrapper.
3019
3020#+begin_src emacs-lisp
3021 (defun acdw/bootstrap-straight ()
3022 "Bootstrap straight.el."
3023 (defvar bootstrap-version)
3024 (let ((bootstrap-file
3025 (expand-file-name
3026 "straight/repos/straight.el/bootstrap.el"
3027 user-emacs-directory))
3028 (bootstrap-version 5))
3029 (unless (file-exists-p bootstrap-file)
3030 (with-current-buffer
3031 (url-retrieve-synchronously
3032 (concat
3033 "https://raw.githubusercontent.com/"
3034 "raxod502/straight.el/develop/install.el")
3035 'silent 'inhibit-cookies)
3036 (goto-char (point-max))
3037 (eval-print-last-sexp)))
3038 (load bootstrap-file nil 'nomessage)))
3039#+end_src
3040
3041To actually bootstrap straight, I'll first try running the above
3042directly. If it errors (it tends to on Windows), I'll directly clone
3043the repo using git, /then/ run the bootstrap code.
3044
3045#+begin_src emacs-lisp
3046 (when (executable-find "git")
3047 (unless (ignore-errors (acdw/bootstrap-straight))
3048 (let ((msg "Straight.el didn't bootstrap correctly. Cloning directly"))
3049 (message "%s..." msg)
3050 (call-process "git" nil
3051 (get-buffer-create "*bootstrap-straight-messages*") nil
3052 "clone"
3053 "https://github.com/raxod502/straight.el"
3054 (expand-file-name "straight/repos/straight.el"
3055 user-emacs-directory))
3056 (message "%s...Done." msg)
3057 (acdw/bootstrap-straight))))
3058#+end_src
3059
3060** Ignore =straight/build/=
3061
3062#+begin_src emacs-lisp :noweb-ref settings
3063 (with-eval-after-load 'recentf
3064 (add-to-list 'recentf-exclude
3065 (expand-file-name "straight/build/"
3066 user-emacs-directory)))
3067#+end_src
3068
3069* System integration
3070
3071I use both Linux (at home) and Windows (at work). To make Emacs
3072easier to use in both systems, I've included various system-specific
3073settings and written some ancillary scripts.
3074
3075** All systems
3076
3077I'll put generic system-integrating code here.
3078
3079*** Edit with Emacs :package:
3080
3081Install the [[https://addons.mozilla.org/en-US/firefox/addon/edit-with-emacs1/][Firefox Addon]] alongside this package to edit web forms in
3082Emacs (or the [[https://chrome.google.com/webstore/detail/edit-with-emacs/ljobjlafonikaiipfkggjbhkghgicgoh][Chrome one]] if you... /hate/ freedom :P).
3083
3084#+begin_src emacs-lisp :noweb-ref packages
3085 (straight-use-package 'edit-server)
3086#+end_src
3087
3088#+begin_src emacs-lisp :noweb-ref hooks
3089 (add-hook 'after-init-hook #'edit-server-start)
3090#+end_src
3091
3092*** =git-sync= stuff
3093
3094This function require [[https://github.com/simonthum/git-sync][git-sync]].
3095
3096#+begin_src emacs-lisp :noweb-ref functions
3097 (defun acdw/git-sync (directory)
3098 "Run git-sync in DIRECTORY."
3099 (interactive)
3100 (message "Git-Syncing %s..." directory)
3101 (let ((proc (start-process "git-sync"
3102 (get-buffer-create (format "*git-sync:%s*" directory))
3103 "git" "-C" (expand-file-name directory) "sync")))
3104 (add-function :after (process-sentinel proc)
3105 (lambda (proc ev)
3106 (cond
3107 ((string-match "finished\n\\'" ev)
3108 (message "Git-Syncing %s...Done." directory)))))))
3109#+end_src
3110
3111**** ~/org
3112
3113#+begin_src emacs-lisp :noweb-ref bindings
3114 (defun acdw/git-sync-org ()
3115 "Run `acdw/git-sync' on `org-directory'."
3116 (interactive)
3117 (acdw/git-sync org-directory))
3118
3119 (define-key acdw/leader (kbd "C-M-o") #'acdw/git-sync-org)
3120#+end_src
3121
3122**** ~/.cache/elfeed/db
3123
3124#+begin_src emacs-lisp :noweb-ref bindings
3125 (defun acdw/git-sync-elfeed-db ()
3126 "Run `acdw/git-sync' on `elfeed-db-directory'."
3127 (interactive)
3128 (save-some-buffers :no-query nil)
3129 (acdw/git-sync elfeed-db-directory))
3130
3131 (define-key acdw/leader (kbd "C-M-f") #'acdw/git-sync-elfeed-db)
3132#+end_src
3133
3134*** Passwords
3135
3136**** Password cache
3137
3138#+begin_src emacs-lisp :noweb-ref settings
3139 (setq-default password-cache-expiry nil)
3140#+end_src
3141
3142**** Use =~/.authinfo= for passwords
3143
3144The =auth-info= line should look like this:
3145
3146#+begin_example
3147machine git.example.com user acdw password hahayeahrightyamoroniwouldn'tgiveyouthat
3148#+end_example
3149
3150#+begin_src emacs-lisp :noweb-ref hooks
3151 (autoload 'magit-process-password-auth-source "magit")
3152 (add-hook 'magit-process-find-password-functions
3153 #'magit-process-password-auth-source)
3154#+end_src
3155
3156*** TRAMP
3157
3158It stands for ... something kind of stupid, I don't remember. I'm pulling this
3159from [[https://github.com/grandfoobah/spartan-emacs/blob/master/spartan-layers/spartan-settings.el][Spartan Emacs]]. It recommends the following in =~/.ssh/config=:
3160
3161#+begin_example
3162 Host *
3163 ForwardAgent yes
3164 AddKeysToAgent yes
3165 ControlMaster auto
3166 ControlPath ~/.ssh/master-%r@%h:%p
3167 ControlPersist yes
3168 ServerAliveInterval 10
3169 ServerAliveCountMax 10
3170#+end_example
3171
3172#+begin_src emacs-lisp :noweb-ref settings
3173 (setq-default tramp-default-method "ssh"
3174 tramp-copy-size-limit nil
3175 tramp-use-ssh-controlmaster-options nil
3176 tramp-default-remote-shell "/bin/bash")
3177#+end_src
3178
3179** Linux (home)
3180:PROPERTIES:
3181:header-args: :noweb-ref linux-specific
3182:END:
3183
3184*** Scripts
3185
3186**** em
3187:PROPERTIES:
3188:header-args: :tangle-mode (identity #o755) :mkdirp yes
3189:END:
3190
3191Here's a wrapper script that'll start =emacs --daemon= if there isn't
3192one, and then launch =emacsclient= with the arguments. Install it to
3193your =$PATH= somewhere.
3194
3195#+begin_src sh :shebang "#!/bin/sh" :tangle (if (eq system-type 'gnu/linux) "~/bin/em" "")
3196 if ! emacsclient -nc "$@"; then
3197 emacs --daemon
3198 emacsclient -nc "$@"
3199 fi
3200#+end_src
3201
3202**** emacsclient.desktop
3203:PROPERTIES:
3204:header-args: :mkdirp yes
3205:END:
3206
3207I haven't really tested this yet, but it should allow me to open other
3208files and things in Emacs. From [[https://www.taingram.org/blog/emacs-client.html][taingram]].
3209
3210#+begin_src conf-desktop :tangle (if (eq system-type 'gnu/linux) "~/.local/share/applications/emacsclient.desktop" "")
3211 [Desktop Entry]
3212 Name=Emacs Client
3213 GenericName=Text Editor
3214 Comment=Edit text
3215 MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
3216 Exec=emacsclient -c %f
3217 Icon=emacs
3218 Type=Application
3219 Terminal=false
3220 Categories=Utility;TextEditor;
3221#+end_src
3222
3223** Windows (work)
3224:PROPERTIES:
3225:header-args: :noweb-ref windows-specific
3226:END:
3227
3228I use Windows at work, where I /also/ don't have Admin rights. So I
3229kind of fly-by-night there. Many of the ideas and scripts in this
3230section come from [[https://github.com/termitereform/JunkPile/blob/master/emacs-on-windows.md][termitereform]] on Github.
3231
3232*** Environment variables
3233
3234**** DICPATH, for Hunspell
3235
3236#+begin_src emacs-lisp :noweb-ref windows-specific
3237 (setenv "DICPATH" (expand-file-name "exe/share/hunspell"
3238 "~/Applications/"))
3239#+end_src
3240
3241*** Settings
3242
3243See also [[https://www.gnu.org/software/emacs/manual/html_mono/efaq-w32.html][the GNU FAQ for Windows]]. At some point I should really dig
3244into the multiple settings available for w32 systems.
3245
3246See also [[https://github.com/bbatsov/prelude/blob/master/core/prelude-windows.el][the Prelude Windows configuration]] (modifier keys).
3247
3248#+begin_src emacs-lisp
3249 (setq-default w32-allow-system-shell t ; enable cmd.exe as shell
3250 ;; modifier keys
3251 w32-pass-lwindow-to-system nil
3252 w32-lwindow-modifier 'super
3253 w32-pass-rwindow-to-system nil
3254 w32-rwindow-modifier 'super
3255 w32-pass-apps-to-system nil
3256 w32-apps-modifier 'hyper)
3257#+end_src
3258
3259*** Scripts
3260:PROPERTIES:
3261:header-args: :noweb yes :mkdirp yes
3262:END:
3263
3264**** Common variables
3265
3266#+begin_src bat :noweb-ref w32-bat-common
3267 if not exist %HOME% (set HOME=%~dp0..\..)
3268 set EMACS=%HOME%\Applications\Emacs\bin\runemacs.exe
3269 set EMACSC=%HOME%\Applications\Emacs\bin\emacsclientw.exe
3270 set GIT=%HOME%\Applications\Git\bin\git.exe
3271#+end_src
3272
3273**** Emacs Autostart
3274
3275Either run this once at startup, or put a shortcut of it in the
3276Startup folder:
3277=%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup=.
3278
3279#+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Autostart.cmd" "")
3280 <<w32-bat-common>>
3281 REM git pull the config
3282 start "git" /wait %GIT% -C %HOME%\.emacs.d pull
3283
3284 REM start emacs
3285 chdir %HOME%
3286 start "emacs-daemon" /wait %EMACS% --daemon
3287 start "emacs-client" %EMACSC% -n -c -a "" %*
3288#+end_src
3289
3290**** Emacs Client
3291
3292This will try to connect to the daemon above. If that fails, it'll
3293run =runemacs.exe=.
3294
3295*This is the main shortcut for running Emacs.*
3296
3297#+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs.cmd" "")
3298 <<w32-bat-common>>
3299 start "emacs" "%EMACSC%" -n -c -a "%EMACS%" %*
3300#+end_src
3301
3302**** Emacs Daemon
3303
3304#+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Daemon.cmd" "")
3305 <<w32-bat-common>>
3306 start "emacs-daemon" %EMACS% --daemon
3307#+end_src
3308
3309**** Emacs Safe Start
3310
3311This runs Emacs with the factory settings.
3312
3313#+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Safe Start.cmd" "")
3314 <<w32-bat-common>>
3315 start "emacs-safe" "%EMACS%" -Q %*
3316#+end_src
3317
3318**** Emacs Debug
3319
3320This runs Emacs with the =--debug-init= option enabled.
3321
3322#+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Debug.cmd" "")
3323 <<w32-bat-common>>
3324 start "emacs-debug" "%EMACS%" --debug-init %*
3325#+end_src
3326
3327*** Other configuration files
3328
3329Since I kind of have this weird .. portable setup on Windows, I'm going to start
3330putting other config files in here that I need. It's a real pain in the ass to
3331have them all out of sync.
3332
3333**** ~/.gitconfig
3334:PROPERTIES:
3335:header-args: :noweb no :mkdirp yes
3336:END:
3337
3338#+begin_src gitconfig :tangle (if (eq system-type 'windows-nt) "~/.gitconfig" "")
3339 # tangled from ~/.emacs.d/config.org
3340 [github]
3341 user = duckwork
3342 [credential]
3343 helper = store
3344 [user]
3345 name = Case Duckworth
3346 email = acdw@acdw.net
3347 [pull]
3348 rebase = false
3349 [core]
3350 excludesfile = ~/.gitignore
3351 attributesfile = ~/.gitattributes
3352 autocrlf = false
3353 eol = lf
3354 [diff "lisp"]
3355 # basic
3356 #xfuncname = "^(\\(.*)$"
3357 # advanced
3358 xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$"
3359 [diff "org"]
3360 xfuncname = "^(\\*+ +.*)$"
3361
3362 [url "https://github.com/"]
3363 insteadOf = gh:
3364 pushInsteadOf = gh:
3365
3366 [url "https://gitlab.com/"]
3367 insteadOf = gl:
3368 pushInsteadOf = gl:
3369
3370 [url "https://git.sr.ht/"]
3371 insteadOf = sr:
3372 pushInsteadOf = sr:
3373#+end_src
3374
3375* Appendices
3376
3377** config.el
3378:PROPERTIES:
3379:header-args: :tangle config.el :noweb yes
3380:END:
3381
3382While =config.el= is written above, I use Noweb references to tangle
3383them all together in the following block, which enables me to organize
3384my config here /logically/, while keeping the generated file organized
3385/programmatically/.
3386
3387*** Enable lexical binding
3388
3389#+begin_src emacs-lisp
3390 ;;; config.el --- personal configuration -*- lexical-binding: t -*-
3391#+end_src
3392
3393*** Header & disclaimer
3394:PROPERTIES:
3395:header-args: :noweb-ref disclaimer
3396:END:
3397
3398#+begin_src emacs-lisp
3399 ;; Copyright (C) 2020 Case Duckworth
3400
3401 ;; Author: Case Duckworth <acdw@acdw.net>
3402 ;; Created: Sometime during the Covid-19 lockdown, 2019
3403 ;; Keywords: configuration
3404 ;; URL: https://tildegit.org/acdw/emacs
3405
3406 ;; This file is not part of GNU Emacs.
3407
3408 ;;; Commentary:
3409 ;; This file is automatically tangled from config.org.
3410 ;; Hand edits will be overwritten!
3411
3412#+end_src
3413
3414*** The rest
3415
3416#+begin_src emacs-lisp
3417 <<disclaimer>>
3418 ;;; Code:
3419
3420 ;;; REQUIRES
3421 <<requires>>
3422
3423 ;;; VARIABLES
3424 <<variables>>
3425
3426 ;;; ACDW MODE
3427 <<acdw-mode>>
3428
3429 ;;; PACKAGES
3430 <<packages>>
3431
3432 ;;; FUNCTIONS
3433 <<functions>>
3434
3435 ;;; SETTINGS
3436 <<settings>>
3437
3438 ;;; SYSTEM-DEPENDENT SETTINGS
3439 ;; at home
3440 (eval-and-compile
3441 (when (memq system-type '(gnu gnu/linux gnu/kfreebsd))
3442 <<linux-specific>>
3443 ))
3444
3445 ;; at work
3446 (eval-and-compile
3447 (when (memq system-type '(ms-dos windows-nt))
3448 <<windows-specific>>
3449 ))
3450
3451 ;;; MODES
3452 <<modes>>
3453
3454 ;;; HOOKS
3455 <<hooks>>
3456
3457 ;;; BINDINGS
3458 <<bindings>>
3459 ;;; config.el ends here
3460#+end_src
3461
3462*** Ease of editing
3463:PROPERTIES:
3464:header-args: :tangle no
3465:END:
3466
3467#+begin_src emacs-lisp :noweb-ref functions
3468 (defun acdw/find-config ()
3469 "Find `config.org'."
3470 (interactive)
3471 (find-file (locate-user-emacs-file "config.org")))
3472#+end_src
3473
3474Bind it to =C-z i= because =C-z C-c= is taken for capture.
3475
3476#+begin_src emacs-lisp :noweb-ref bindings
3477 (define-key acdw/leader (kbd "i") #'acdw/find-config)
3478#+end_src
3479
3480*** Ease of reloading
3481:PROPERTIES:
3482:header-args: :tangle no
3483:END:
3484
3485#+begin_src emacs-lisp :noweb-ref functions
3486 (defun acdw/reload ()
3487 "Tangle and reload Emacs configuration."
3488 (interactive)
3489 (let ((config (locate-user-emacs-file "config.org")))
3490 ;; tangle
3491 (with-current-buffer (find-file-noselect config)
3492 (message "Tangling config.org...")
3493 (let ((prog-mode-hook nil)
3494 (inhibit-redisplay t)
3495 (inhibit-message t))
3496 (add-to-list 'load-path (locate-user-emacs-file
3497 "straight/build/org/"))
3498 (require 'org)
3499 (org-babel-tangle)))
3500 (message "Tangling config.org... Done.")
3501 ;; load init files
3502 (load (locate-user-emacs-file "early-init.el"))
3503 (load (locate-user-emacs-file "init.el"))
3504 (load (locate-user-emacs-file "config.el"))))
3505#+end_src
3506
3507#+begin_src emacs-lisp :noweb-ref bindings
3508 (define-key acdw/leader (kbd "C-M-r") #'acdw/reload)
3509#+end_src
3510
3511** init.el
3512:PROPERTIES:
3513:header-args: :tangle init.el :noweb yes
3514:END:
3515
3516The classic Emacs initiation file.
3517
3518*** Header
3519
3520#+begin_src emacs-lisp
3521 ;;; init.el -*- lexical-binding: t; coding: utf-8 -*-
3522 <<disclaimer>>
3523 ;;; Code:
3524#+end_src
3525
3526*** Prefer newer files to older files
3527
3528#+begin_src emacs-lisp
3529 (setq-default load-prefer-newer t)
3530#+end_src
3531
3532*** Load the config
3533
3534I keep most of my config in =config.el=, which is tangled directly from
3535this file. This init just loads that file, either from lisp
3536or directly from Org if it's newer. /Note/ the longish comment before
3537the =unless= form -- it was pretty tough for me to wrap my head around
3538the needed boolean expression to tangle config.org. Booleans, yall!
3539
3540#+begin_src emacs-lisp
3541 (message "%s..." "Loading init.el")
3542 (let* (;; Speed up init
3543 (gc-cons-threshold most-positive-fixnum)
3544 ;; (gc-cons-percentage 0.6)
3545 (file-name-handler-alist nil)
3546 ;; Config file names
3547 (config (expand-file-name "config"
3548 user-emacs-directory))
3549 (config.el (concat config ".el"))
3550 (config.org (concat config ".org"))
3551 (straight-org-dir (locate-user-emacs-file "straight/build/org")))
3552 ;; Okay, let's figure this out.
3553 ;; `and' evaluates each form, and returns nil on the first that
3554 ;; returns nil. `unless' only executes its body if the test
3555 ;; returns nil. So.
3556 ;; 1. Test if config.org is newer than config.el. If it is (t), we
3557 ;; *want* to evaluate the body, so we need to negate that test.
3558 ;; 2. Try to load the config. If it errors (nil), it'll bubble that
3559 ;; to the `and' and the body will be evaluated.
3560 (unless (and (not (file-newer-than-file-p config.org config.el))
3561 (load config :noerror))
3562 ;; A plain require here just loads the older `org'
3563 ;; in Emacs' install dir. We need to add the newer
3564 ;; one to the `load-path', hopefully that's all.
3565 (when (file-exists-p straight-org-dir)
3566 (add-to-list 'load-path straight-org-dir))
3567 ;; Load config.org
3568 (message "%s..." "Loading config.org")
3569 (require 'org)
3570 (org-babel-load-file config.org)
3571 (message "%s... Done" "Loading config.org")))
3572 (message "%s... Done." "Loading init.el")
3573 ;;; init.el ends here
3574#+end_src
3575
3576** early-init.el
3577:PROPERTIES:
3578:header-args: :tangle early-init.el :noweb yes
3579:END:
3580
3581Beginning with 27.1, Emacs also loads an =early-init.el= file, before
3582the package manager or the UI code. The Info says we should put as
3583little as possible in this file, so I only have what I need.
3584
3585#+begin_src emacs-lisp
3586 ;;; early-init.el -*- no-byte-compile: t; coding: utf-8 -*-
3587 <<disclaimer>>
3588 ;;; Code:
3589
3590 (message "%s..." "Loading early-init.el")
3591 ;; BOOTSTRAP PACKAGE MANAGEMENT
3592 <<early-init-package>>
3593 ;; SETUP FRAME
3594 <<early-init-frame>>
3595 (message "%s... Done." "Loading early-init.el")
3596 ;;; early-init.el ends here
3597#+end_src
3598
3599** License
3600:PROPERTIES:
3601:header-args: :tangle LICENSE
3602:END:
3603
3604Copyright © 2020 Case Duckworth <acdw@acdw.net>
3605
3606This work is free. You can redistribute it and/or modify it under the
3607terms of the Do What the Fuck You Want To Public License, Version 2,
3608as published by Sam Hocevar. See the =LICENSE= file, tangled from the
3609following source block, for details.
3610
3611#+begin_src text
3612 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3613
3614 Version 2, December 2004
3615
3616 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
3617
3618 Everyone is permitted to copy and distribute verbatim or modified copies of
3619 this license document, and changing it is allowed as long as the name is changed.
3620
3621 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3622
3623 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
3624
3625 0. You just DO WHAT THE FUCK YOU WANT TO.
3626#+end_src
3627
3628*** Note on the license
3629
3630It's highly likely that the WTFPL is completely incompatible with the
3631GPL, for what should be fairly obvious reasons. To that, I say:
3632
3633*SUE ME, RMS!*
3634
3635** Inspiration and further reading
3636
3637- [[https://github.com/bbatsov/prelude][Emacs Prelude]]
3638- [[https://github.com/alphapapa/unpackaged.el][Unpackaged]]
diff --git a/early-init.el b/early-init.el index ef72beb..36bacbe 100644 --- a/early-init.el +++ b/early-init.el
@@ -1,21 +1,180 @@
1;;; early-init.el -*- no-byte-compile: t; coding: utf-8 -*- 1;;; early-init.el -*- lexical-binding: t; coding: utf-8 -*-
2;; Copyright (C) 2020 Case Duckworth 2;; Copyright (C) 2020-2021 Case Duckworth
3 3;;
4;; Author: Case Duckworth <acdw@acdw.net> 4;; Author: Case Duckworth <acdw@acdw.net>
5;; Created: Sometime during the Covid-19 lockdown, 2019 5;; Created: Sometime during Covid-19, 2020
6;; Keywords: configuration 6;; Keywords: configuration
7;; URL: https://tildegit.org/acdw/emacs 7;; URL https://tildegit.org/acdw/emacs
8;;
9;; This file is NOT part of GNU Emacs.
10;;
11;;; License:
12;;
13;; Everyone is permitted to do whatever with this software, without
14;; limitation. This software comes without any warranty whatsoever,
15;; but with two pieces of advice:
16;; - Don't hurt yourself.
17;; - Make good choices.
18;;
19;;; Comentary:
20;;
21;; Starting with Emacs 27.1, `early-init' is sourced before `package'
22;; or any frames. So those are the settings I run in this file.
23;;
24;;; Code:
8 25
9;; This file is not part of GNU Emacs. 26;; Speed up init
27(setq gc-cons-threshold most-positive-fixnum
28 gc-cons-percentage 0.6
29 comp-deferred-compilation nil)
10 30
11;;; Commentary: 31(defconst gc-cons-basis (* 800 1024)
12;; This file is automatically tangled from config.org. 32 "The basis value to which to return after a max jump.
13;; Hand edits will be overwritten! 33800,000 (800 KB) is Emacs' default.")
14 34
15;;; Code: 35(add-hook 'after-init-hook #'(lambda ()
36 (setq gc-cons-threshold gc-cons-basis
37 gc-cons-percentage 0.1)))
38
39(defun hook--gc-cons-maximize ()
40 "Set `gc-cons-threshold' to the highest possible.
41 For memory-intensive features."
42 (setq gc-cons-threshold most-positive-fixnum))
43
44(defun hook--gc-cons-baseline ()
45 "Return `gc-cons-threshold' to `gc-cons-basis'.
46 For after memory intensive operations."
47 (setq gc-cons-threshold gc-cons-basis))
48
49(add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize)
50(add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline)
51
52;; From doom-emacs
53(unless (daemonp)
54 (defvar doom--initial-file-name-handler-alist file-name-handler-alist)
55 (setq file-name-handler-alist nil)
56 (defun doom-reset-file-handler-alist-h ()
57 (dolist (handler file-name-handler-alist)
58 (add-to-list 'doom--initial-file-name-handler-alist handler))
59 (setq file-name-handler-alist doom--initial-file-name-handler-alist))
60 (add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h))
61
62;; Where are we?
63(defconst acdw/system (pcase system-type
64 ('gnu/linux :home)
65 ((or 'msdos 'windows-nt) :work)
66 (_ :other)))
67
68;; Frame initiation
69
70;; Initialize frames with as little UI as possible.
71(setq-default
72 default-frame-alist ; The default look of frames
73 `((tool-bar-lines . 0) ; Remove tool bar
74 (menu-bar-lines . 0) ; Remove menu bar
75 (vertical-scroll-bars) ; Remove vertical scroll bars
76 (horizontal-scroll-bars) ; Remove horizontal scroll bars
77 (width . 84) ; A /little/ wider than `fill-column'
78 (height . 30) ; Text characters
79 (left-fringe . 8) ; Width of fringes
80 (right-fringe . 8) ; (8 is the default)
81 (font . ,(pcase acdw/system ; Default font
82 (:home "Terminus 12")
83 (:work "Consolas 11")))
84 )
85
86 x-underline-at-descent-line t ; underline at the descent line
87
88 scroll-margin 0 ; how many lines to show at window edge
89 scroll-conservatively 101 ; just enough to bring text into view
90 scroll-preserve-screen-position 1 ; always keep screen position
91
92 frame-title-format ; Titles for frames
93 '((:eval (if (buffer-file-name) ; (cf. `mode-line-format')
94 (abbreviate-file-name (buffer-file-name))
95 "%b"))
96 " "
97 mode-line-client
98 mode-line-modified
99 " - GNU Emacs")
100 )
101
102;; Set the rest of the fonts after initiation
103(defun hook--setup-fonts ()
104 (pcase acdw/system
105 (:home (set-face-attribute 'default nil
106 :family "Terminus"
107 :height 120)
108 (set-face-attribute 'fixed-pitch nil
109 :family "Terminus"
110 :height 1.0)
111 (set-face-attribute 'variable-pitch nil
112 :family "DejaVu Sans"
113 :height 1.0))
114 (:work (set-face-attribute 'default nil
115 :familiy "Consolas"
116 :height 110)
117 (set-face-attribute 'fixed-pitch nil
118 :family "Consolas"
119 :height 1.0)
120 (set-face-attribute 'variable-pitch nil
121 :family "Cambria"
122 :height 1.0))))
123
124(add-hook 'after-init-hook #'hook--setup-fonts)
125
126;; In case I do want the UI elements later, I also disable the modes
127;; -- otherwise I'd have to run the mode twice to actually show the
128;; thing.
129
130(defun hook--disable-ui-modes ()
131 (dolist (mode '(tool-bar-mode
132 menu-bar-mode
133 scroll-bar-mode
134 horizontal-scroll-bar-mode))
135 (funcall mode -1)))
136
137;; I run it on the `after-init-hook' so it doesn't slow down init so much.
138(add-hook 'after-init-hook #'hook--disable-ui-modes)
139
140;; Customize the fringe
141(setq-default
142 indicate-empty-lines t ; show an indicator at the end of the buffer
143 indicate-buffer-boundaries 'right ; show buffer boundaries on the right
144 visual-line-fringe-indicators ; show continuation indicators on the left
145 '(left-curly-arrow nil))
146
147(defun hook--setup-fringe-bitmaps ()
148 (define-fringe-bitmap 'left-curly-arrow
149 [#b11000000
150 #b01100000
151 #b00110000
152 #b00011000])
153 (define-fringe-bitmap 'right-curly-arrow
154 [#b00011000
155 #b00110000
156 #b01100000
157 #b11000000])
158 (define-fringe-bitmap 'left-arrow
159 [#b00000000
160 #b01010100
161 #b01010100
162 #b00000000])
163 (define-fringe-bitmap 'right-arrow
164 [#b00000000
165 #b00101010
166 #b00101010
167 #b00000000]))
168(add-hook 'after-init-hook #'hook--setup-fringe-bitmaps)
169
170;; Resize like it's 2021
171(setq-default frame-inhibit-implied-resize t
172 frame-resize-pixelwise t)
173
174;; Bootstrap package manager (`straight')
175
176;; First, I need to make sure it's in the `exec-path'.
16 177
17(message "%s..." "Loading early-init.el")
18;; BOOTSTRAP PACKAGE MANAGEMENT
19(let ((win-app-dir "~/Applications")) 178(let ((win-app-dir "~/Applications"))
20 (dolist (path (list 179 (dolist (path (list
21 ;; Windows 180 ;; Windows
@@ -31,21 +190,44 @@
31 (expand-file-name "bin" user-emacs-directory) 190 (expand-file-name "bin" user-emacs-directory)
32 (expand-file-name "~/bin") 191 (expand-file-name "~/bin")
33 (expand-file-name "~/.local/bin") 192 (expand-file-name "~/.local/bin")
34 (expand-file-name "~/Scripts") 193 (expand-file-name "~/usr/bin")
35 )) 194 ))
36 (when (file-exists-p path) 195 (when (file-exists-p path)
37 (add-to-list 'exec-path path :append)))) 196 (add-to-list 'exec-path path :append))))
38 197
39;; Set $PATH 198;; Set $PATH back to `exec-path', for symmetry's sake.
40(setenv "PATH" (mapconcat #'identity exec-path path-separator)) 199(setenv "PATH" (mapconcat #'identity exec-path path-separator))
41(setq package-enable-at-startup nil) 200
42(defun acdw/bootstrap-straight () 201;; Set some variables
43 "Bootstrap straight.el." 202(defvar acdw/etc-dir
203 (expand-file-name "etc/" user-emacs-directory)
204 "Where to put other configurations.")
205(defvar acdw/var-dir
206 (expand-file-name "var/" user-emacs-directory)
207 "Where to put variable stuff.")
208
209(setq-default package-enable-at-startup nil
210 package-quickstart nil
211 straight-use-package-by-default t
212 straight-host-usernames '((github . "duckwork")
213 (gitlab . "acdw"))
214 straight-base-dir acdw/var-dir)
215
216;; Run `straight''s bootstrap code, after gitting the code directly.
217(if (not (executable-find "git"))
218 (error "No 'git' in $PATH!")
219 (call-process "git" nil
220 (get-buffer-create "*bootstrap-straight-messages*")
221 nil
222 "clone"
223 "https://github.com/raxod502/straight.el"
224 (expand-file-name "repos/straight.el"
225 straight-base-dir))
44 (defvar bootstrap-version) 226 (defvar bootstrap-version)
45 (let ((bootstrap-file 227 (let ((bootstrap-file
46 (expand-file-name 228 (expand-file-name
47 "straight/repos/straight.el/bootstrap.el" 229 "repos/straight.el/bootstrap.el"
48 user-emacs-directory)) 230 straight-base-dir))
49 (bootstrap-version 5)) 231 (bootstrap-version 5))
50 (unless (file-exists-p bootstrap-file) 232 (unless (file-exists-p bootstrap-file)
51 (with-current-buffer 233 (with-current-buffer
@@ -57,67 +239,20 @@
57 (goto-char (point-max)) 239 (goto-char (point-max))
58 (eval-print-last-sexp))) 240 (eval-print-last-sexp)))
59 (load bootstrap-file nil 'nomessage))) 241 (load bootstrap-file nil 'nomessage)))
60(when (executable-find "git")
61 (unless (ignore-errors (acdw/bootstrap-straight))
62 (let ((msg "Straight.el didn't bootstrap correctly. Cloning directly"))
63 (message "%s..." msg)
64 (call-process "git" nil
65 (get-buffer-create "*bootstrap-straight-messages*") nil
66 "clone"
67 "https://github.com/raxod502/straight.el"
68 (expand-file-name "straight/repos/straight.el"
69 user-emacs-directory))
70 (message "%s...Done." msg)
71 (acdw/bootstrap-straight))))
72;; SETUP FRAME
73(add-to-list 'default-frame-alist
74 '(tool-bar-lines . 0))
75
76(tool-bar-mode -1)
77(add-to-list 'default-frame-alist
78 '(menu-bar-lines . 0))
79
80(menu-bar-mode -1)
81(add-to-list 'default-frame-alist
82 '(vertical-scroll-bars . nil)
83 '(horizontal-scroll-bars . nil))
84
85(scroll-bar-mode -1)
86(horizontal-scroll-bar-mode -1)
87(setq-default frame-inhibit-implied-resize t
88 frame-resize-pixelwise t)
89(setq-default indicate-empty-lines t)
90(setq-default indicate-buffer-boundaries 'right)
91(setq-default visual-line-fringe-indicators '(left-curly-arrow nil))
92(defun hook--setup-fringes-curly-arrows ()
93 "Set up curly-arrow fringes."
94 (define-fringe-bitmap 'left-curly-arrow
95 [#b11000000
96 #b01100000
97 #b00110000
98 #b00011000])
99 242
100 (define-fringe-bitmap 'right-curly-arrow
101 [#b00011000
102 #b00110000
103 #b01100000
104 #b11000000]))
105
106(add-hook 'after-init-hook #'hook--setup-fringes-curly-arrows)
107(defun hook--setup-fringes-arrows ()
108 "Setup arrow fringe bitmaps."
109 (define-fringe-bitmap 'left-arrow
110 [#b00000000
111 #b01010100
112 #b01010100
113 #b00000000])
114 243
115 (define-fringe-bitmap 'right-arrow 244;; `use-package'
116 [#b00000000 245
117 #b00101010 246(straight-use-package 'use-package)
118 #b00101010 247(require 'use-package)
119 #b00000000])) 248
120 249;; Message startup time
121(add-hook 'after-init-hook #'hook--setup-fringes-arrows) 250(defun hook--message-startup-time ()
122(message "%s... Done." "Loading early-init.el") 251 "Message Emacs' startup time."
123;;; early-init.el ends here 252 (message "Emacs ready in %s with %d garbage collections."
253 (format "%.2f seconds"
254 (float-time (time-subtract after-init-time
255 before-init-time)))
256 gcs-done))
257
258(add-hook 'emacs-startup-hook #'hook--message-startup-time)
diff --git a/etc/eshell/aliases b/etc/eshell/aliases deleted file mode 100644 index 24a7efc..0000000 --- a/etc/eshell/aliases +++ /dev/null
@@ -1 +0,0 @@
1alias e find-file $1
diff --git a/init.el b/init.el index 7cf9f7a..9808ae2 100644 --- a/init.el +++ b/init.el
@@ -1,51 +1,573 @@
1;;; init.el -*- lexical-binding: t; coding: utf-8 -*- 1;;; init.el -*- lexical-binding: t; coding: utf-8 -*-
2;; Copyright (C) 2020 Case Duckworth 2;; Copyright (C) 2020-2021 Case Duckworth
3 3;;
4;; Author: Case Duckworth <acdw@acdw.net> 4;; Author: Case Duckworth <acdw@acdw.net>
5;; Created: Sometime during the Covid-19 lockdown, 2019 5;; Created: Sometime during Covid-19, 2020
6;; Keywords: configuration 6;; Keywords: configuration
7;; URL: https://tildegit.org/acdw/emacs 7;; URL https://tildegit.org/acdw/emacs
8;;
9;; This file is NOT part of GNU Emacs.
10;;
11;;; License:
12;;
13;; Everyone is permitted to do whatever with this software, without
14;; limitation. This software comes without any warranty whatsoever,
15;; but with two pieces of advice:
16;; - Don't hurt yourself.
17;; - Make good choices.
18;;
19;;; Comentary:
20;;
21;;; Code:
8 22
9;; This file is not part of GNU Emacs. 23;; User information
24(setq user-full-name "Case Duckworth"
25 user-mail-address "acdw@acdw.net"
26 calendar-location-name "Baton Rouge, LA"
27 calendar-latitude 30.4
28 calendar-longitude -91.1
29 calendar-date-style 'iso)
10 30
11;;; Commentary: 31;; Load newer files first
12;; This file is automatically tangled from config.org. 32(setq-default load-prefer-newer t)
13;; Hand edits will be overwritten!
14 33
15;;; Code: 34;; Make C-z more useful
35(defvar acdw/leader
36 (let ((map (make-sparse-keymap))
37 (c-z (global-key-binding "\C-z")))
38 (global-unset-key "\C-z")
39 (global-set-key "\C-z" map)
40 (define-key map "\C-z" c-z)
41 map)
42 "A leader key for apps and stuff.")
16 43
17(setq-default load-prefer-newer t) 44(defun when-unfocused (func &rest args)
45 "Run FUNC with ARGS iff all frames are out of focus."
46 (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
47 (apply func args)))
48
49;; Dialogs & alerts
50(setq-default use-dialog-box nil) ; Don't use a dialog box
51(fset 'yes-or-no-p #'y-or-n-p)
52
53(defun flash-mode-line ()
54 (ding)
55 (invert-face 'mode-line)
56 (run-with-timer 0.2 nil #'invert-face 'mode-line))
57
58(setq-default visible-bell nil ; Don't use a visible bell
59 ring-bell-function #'flash-mode-line)
60
61(defun hook--gc-when-unfocused ()
62 (when-unfocused #'garbage-collect))
63
64(add-function :after after-focus-change-function
65 #'hook--gc-when-unfocused)
66
67;; Minibuffer
68(setq-default
69 minibuffer-prompt-properties '(read-only t
70 cursor-intangible t
71 face minibuffer-prompt)
72 enable-recursive-minibuffers t
73 file-name-shadow-properties '(invisible t))
74(file-name-shadow-mode +1)
75(minibuffer-depth-indicate-mode +1)
76
77(use-package savehist
78 :straight nil
79 :init
80 (setq-default
81 savehist-file (expand-file-name "history" acdw/var-dir)
82 savehist-additional-variables '(kill-ring search-ring regexp-search-ring)
83 history-length t
84 history-delete-duplicates t
85 savehist-autosave-interval 60)
86 :config (savehist-mode +1))
87
88;; Backups
89(setq-default backup-by-copying t
90 delete-old-versions -1 ; Don't delete old versions
91 version-control t ; Make numeric backups
92 vc-make-backup-files t ; Backup version-controlled files
93 )
94
95(let ((dir (expand-file-name "backup" acdw/var-dir)))
96 (make-directory dir 'parents)
97 (setq-default backup-directory-alist
98 `((".*" . ,dir))))
99
100;; Lockfiles
101(setq-default create-lockfiles nil) ; Are these necessary?
102
103;; Autosaves
104(use-package super-save
105 :defer 5 ; This package can wait
106 :init
107 (setq-default
108 auto-save-default nil ; Don't use `auto-save' system
109 super-save-remote-files nil ; Don't save remote files
110 super-save-exclude '(".gpg") ; Wouldn't work anyway
111 super-save-auto-save-when-idle t)
112 :config
113 (super-save-mode +1))
114
115;; Auto-revert
116(global-auto-revert-mode +1) ; Automatically revert a file
117 ; to its on-disk contents
118
119(use-package saveplace
120 :straight nil
121 :init
122 (setq-default
123 save-place-file (expand-file-name "places" acdw/var-dir)
124 save-place-forget-unreadable-files (eq acdw/system :home))
125 :config (save-place-mode +1))
126
127(use-package recentf
128 :straight nil
129 :init
130 (setq recentf-save-file (expand-file-name "recentf" acdw/var-dir)
131 recentf-max-menu-items 100
132 recentf-max-saved-items nil
133 recentf-auto-cleanup 'never)
134 (defun maybe-save-recentf ()
135 "Save `recentf-file' every five minutes, but only when out of focus."
136 (defvar recentf--last-save (time-convert nil 'integer)
137 "When we last saved the `recentf-save-list'.")
138
139 (when (> (time-convert (time-since recentf--last-save) 'integer)
140 (* 60 5))
141 (setq-default recentf--last-save (time-convert nil 'integer))
142 (when-unfocused #'recentf-save-list)))
143 :config
144 (recentf-mode +1)
145 (add-to-list 'recentf-exclude acdw/var-dir)
146 (add-to-list 'recentf-exclude acdw/etc-dir)
147 (add-function :after after-focus-change-function
148 #'maybe-save-recentf))
149
150
151;; Uniquify
152(use-package uniquify
153 :straight nil
154 :init
155 (setq-default
156 uniquify-buffer-name-style 'forward ; bubble 'up' the directory tree
157 uniquify-separator "/" ; separate path elements
158 uniquify-after-kill-buffer-p t ; hook into buffer kills
159 uniquify-ignore-buffers-re "^\\*" ; don't worry about special buffers
160 ))
161
162;; Scratch
163(setq-default
164 inhibit-startup-screen t ; Don't show the splash screen
165 initial-buffer-choice t ; Start on *scratch*
166 initial-scratch-message
167 (concat ";; Howdy, "
168 (nth 0 (split-string user-full-name)) "!"
169 " Welcome to GNU Emacs.\n\n"))
170
171(defun immortal-scratch ()
172 "Don't kill *scratch* when asked to by `kill-buffer'."
173 (if (not (eq (current-buffer) (get-buffer "*scratch*")))
174 t
175 (bury-buffer)
176 nil))
177(add-hook 'kill-buffer-query-functions #'immortal-scratch)
178
179;; Easier buffer-killing
180(defun kill-a-buffer (&optional prefix)
181 "Kill a buffer and its window, prompting only on unsaved changes.
182
183`kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill:
1840 => Kill THIS buffer & window
1854 (C-u) => Kill OTHER buffer & window
18616 (C-u C-u) => Run the default `kill-buffer'."
187 (interactive "P")
188 (pcase (or (car prefix) 0)
189 (0 (kill-current-buffer)
190 (unless (one-window-p) (delete-window)))
191 (4 (other-window 1)
192 (kill-current-buffer)
193 (unless (one-window-p) (delete-window)))
194 (16 (let ((current-prefix-arg nil))
195 (kill-buffer)))))
196
197(bind-key "C-x k" #'kill-a-buffer)
198
199;; UTF-8 with LF line endings
200(set-charset-priority 'unicode)
201(set-language-environment "UTF-8")
202
203(prefer-coding-system 'utf-8-unix)
204(set-default-coding-systems 'utf-8-unix)
205(set-terminal-coding-system 'utf-8-unix)
206(set-keyboard-coding-system 'utf-8-unix)
207(set-selection-coding-system 'utf-8-unix)
208
209(setq-default
210 locale-coding-system 'utf-8-unix
211 coding-system-for-read 'utf-8-unix
212 coding-system-for-write 'utf-8-unix
213 buffer-file-coding-system 'utf-8-unix
214
215 org-export-coding-system 'utf-8-unix
216 org-html-coding-system 'utf-8-unix ; doesn't take from above
217
218 default-process-coding-system '(utf-8-unix . utf-8-unix)
219 x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
220
221(defun ewiki/no-junk-please-were-unixish ()
222 "Convert line endings to UNIX, dammit."
223 (let ((coding-str (symbol-name buffer-file-coding-system)))
224 (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
225 (set-buffer-file-coding-system 'unix))))
226
227(add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish)
228(add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish)
229
230;; Cursor
231(setq-default cursor-type 'bar
232 cursor-in-non-selected-windows nil)
233(blink-cursor-mode 0)
234
235;; Filling text
236(setq-default fill-column 80)
237(global-display-fill-column-indicator-mode +1)
238
239(bind-key "C-x f" #'find-file) ; I don't set `fill-column', ever
240
241(setq-default comment-auto-fill-only-comments t)
242;; Enable `auto-fill-mode' everywhere
243(add-hook 'text-mode-hook #'auto-fill-mode)
244(add-hook 'prog-mode-hook #'auto-fill-mode)
245;; Also enable `visual-line-mode' everywhere
246(global-visual-line-mode +1)
247;; "Fix" `visual-line-mode' in `org-mode'
248(defun hook--visual-line-fix-org-keys ()
249 (when (derived-mode-p 'org-mode)
250 (local-set-key (kbd "C-a") #'org-beginning-of-line)
251 (local-set-key (kbd "C-e") #'org-end-of-line)
252 (local-set-key (kbd "C-k") #'org-kill-line)))
253(add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys)
254
255(use-package visual-fill-column
256 :init (setq-default visual-fill-column-center-text t)
257 :hook visual-fill-column-mode
258 :config
259 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
260
261(when (fboundp 'global-so-long-mode)
262 (global-so-long-mode +1))
263
264;; Whitespace
265(setq-default whitespace-style '(empty ; remove blank lines at buffer edges
266 indentation ; clean up indentation
267 ;; mixed tabs & spaces
268 space-before-tab
269 space-after-tab))
270(add-hook 'before-save-hook #'whitespace-cleanup)
271
272(setq-default indent-tabs-mode t
273 tab-width 8)
274
275(use-package smart-tabs-mode
276 :config
277 (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml))
278
279;; Window layouts
280(setq-default
281 split-width-threshold 100 ; minimum width for window splits
282 split-height-threshold 50 ; minimum height for window splits
283 display-buffer-alist ; how to display buffers
284 '((".*" . (display-buffer-reuse-window display-buffer-same-window)))
285 display-buffer-reuse-frames t ; allow reuse of frames
286 even-window-sizes nil ; avoid resizing windows to even them
287 help-window-select t ; select *Help* window when opened
288 )
289
290(defun vsplit-other-window ()
291 "Split the window vertically and switch to the new window."
292 (interactive)
293 (split-window-vertically)
294 (other-window 1 nil))
295
296(defun hsplit-other-window ()
297 "Split the window horizontally and switch to the new window."
298 (interactive)
299 (split-window-horizontally)
300 (other-window 1 nil))
301
302(bind-key "C-x 2" #'vsplit-other-window)
303(bind-key "C-x 3" #'hsplit-other-window)
304
305;; Theming
306
307(use-package form-feed
308 :config (global-form-feed-mode +1))
309
310(use-package modus-themes
311 :straight (:host gitlab :repo "protesilaos/modus-themes")
312 :demand
313 :init
314 (setq-default modus-themes-slanted-constructs t
315 modus-themes-bold-constructs t
316 modus-themes-region 'bg-only
317 modus-themes-org-blocks 'grayscale
318 modus-themes-headings '((1 . section)
319 (t . no-color))
320 modus-themes-scale-headings nil
321 modus-themes-mode-line nil)
322 :custom-face
323 (modus-theme-heading-1
324 ((t (:inherit (modus-theme-heading-1 fixed-pitch bold)))))
325 (modus-theme-heading-2
326 ((t (:inherit (modus-theme-heading-2 fixed-pitch bold)))))
327 (modus-theme-heading-3
328 ((t (:inherit (modus-theme-heading-3 fixed-pitch bold)))))
329 (modus-theme-heading-4
330 ((t (:inherit (modus-theme-heading-4 fixed-pitch bold)))))
331 (modus-theme-heading-5
332 ((t (:inherit (modus-theme-heading-5 fixed-pitch bold)))))
333 (modus-theme-heading-6
334 ((t (:inherit (modus-theme-heading-6 fixed-pitch bold)))))
335 (modus-theme-heading-7
336 ((t (:inherit (modus-theme-heading-7 fixed-pitch bold)))))
337 (modus-theme-heading-8
338 ((t (:inherit (modus-theme-heading-8 fixed-pitch bold))))))
339
340;; Change themes based on time of day
341
342(defun acdw/run-with-sun (sunrise-command sunset-command)
343 "Run commands at sunrise and sunset."
344 (let* ((times-regex (rx (* nonl)
345 (: (any ?s ?S) "unrise") " "
346 (group (repeat 1 2 digit) ":"
347 (repeat 1 2 digit)
348 (: (any ?a ?A ?p ?P) (any ?m ?M)))
349 (* nonl)
350 (: (any ?s ?S) "unset") " "
351 (group (repeat 1 2 digit) ":"
352 (repeat 1 2 digit)
353 (: (any ?a ?A ?p ?P) (any ?m ?M)))
354 (* nonl)))
355 (ss (sunrise-sunset))
356 (_m (string-match times-regex ss))
357 (sunrise-time (match-string 1 ss))
358 (sunset-time (match-string 2 ss)))
359 (run-at-time sunrise-time (* 60 60 24) sunrise-command)
360 (run-at-time sunset-time (* 60 60 24) sunset-command)
361 (run-at-time "0:00" (* 60 60 24) sunset-command)))
362
363(acdw/run-with-sun #'modus-themes-load-operandi
364 #'modus-themes-load-vivendi)
365
366(use-package minions
367 :config (minions-mode +1))
368
369(which-function-mode +1)
370
371(use-package which-key
372 :config (which-key-mode +1))
373
374(delete-selection-mode +1)
375
376(setq-default
377 save-interprogram-paste-before-kill t ; save existing text before replacing
378 yank-pop-change-selection t ; update X selection when rotating ring
379 x-select-enable-clipboard t ; Enable X clipboards
380 x-select-enable-primary t
381 mouse-drag-copy-region t ; Copy a region when mouse-selected
382 kill-do-not-save-duplicates t ; Don't append the same thing twice
383 )
384
385(use-package smartscan
386 :config
387 (global-smartscan-mode +1))
388
389(when (fboundp 'global-goto-address-mode)
390 (global-goto-address-mode +1))
391
392(use-package flyspell
393 :init
394 (setenv "LANG" "en_US")
395 (setq-default ispell-program-name "hunspell"
396 ispell-dictionary "en_US"
397 ispell-personal-dictionary "~/.hunspell_personal")
398 :hook
399 (text-mode . flyspell-mode)
400 (prog-mode . flyspell-prog-mode)
401 :config
402 (ispell-set-spellchecker-params)
403 (unless (file-exists-p ispell-personal-dictionary)
404 (write-region "" nil ispell-personal-dictionary nil 0)))
405
406(use-package flyspell-correct
407 :bind ("C-;" . flyspell-correct-wrapper))
408
409(setq-default show-paren-delay 0
410 show-paren-style 'mixed
411 show-paren-when-point-inside-paren t
412 show-paren-when-point-in-periphery t)
413(show-paren-mode +1)
414
415(add-hook 'prog-mode-hook #'electric-pair-local-mode)
416
417(setq-default prettify-symbols-unprettify-at-point 'right-edge)
418(add-hook 'prog-mode-hook #'prettify-symbols-mode)
419
420(add-hook 'after-save-hook
421 #'executable-make-buffer-file-executable-if-script-p)
422
423(setq-default compilation-ask-about-save nil ; just save the buffer
424 compilation-always-kill t ; kill the processes without asking
425 compilation-scroll-output 'first-error)
426
427(use-package reformatter
428 :demand)
429
430;; Shell scripts
431(setq-default sh-basic-offset 8
432 smie-indent-basic 8)
433
434(use-package flymake-shellcheck
435 :when (executable-find "shellcheck")
436 :hook sh-mode)
437
438(when (executable-find "shfmt")
439 (reformatter-define sh-format
440 :program "shfmt"
441 :lighter "Shfmt")
442 (add-hook 'sh-mode-hook #'sh-format-on-save-mode))
443
444(bind-key "M-/" #'hippie-expand)
445
446;; Tabs
447(setq-default
448 tab-bar-show 1 ; show the tab bar when more than one
449 tab-bar-new-tab-choice "*scratch*" ; what to show on a new tab
450 tab-bar-tab-name-function ; how to name a new tab
451 #'tab-bar-tab-name-current-with-count
452 tab-bar-history-limit 25 ; how many tabs to save in history
453 )
454
455(tab-bar-history-mode +1)
456
457;; Smart hungry delete
458(use-package smart-hungry-delete
459 :defer nil
460 :bind (("<backspace>" . smart-hungry-delete-backward-char)
461 ("C-d" . smart-hungry-delete-forward-char))
462 :config (smart-hungry-delete-add-default-hooks))
463
464;; Enable all commands
465(setq-default disabled-command-function nil)
466
467;; Magit
468(use-package magit
469 :bind ("C-z g" . magit-status))
470
471;; crux
472(use-package crux
473 :straight (:host github :repo "bbatsov/crux")
474 :bind
475 ("M-o" . crux-other-window-or-switch-buffer)
476 :config
477 (crux-with-region-or-line kill-ring-save)
478 (crux-with-region-or-line kill-region)
479 (crux-with-region-or-line comment-or-uncomment-region))
480
481;; Completion and... stuff
482(setq-default
483 completion-ignore-case t
484 read-buffer-completion-ignore-case t
485 read-file-name-completion-ignore-case t)
486
487(use-package icomplete-vertical
488 :demand
489 :init
490 (setq-default
491 icomplete-delay-completions-threshold 0
492 icomplete-max-delay-chars 0
493 icomplete-compute-delay 0
494 icomplete-show-matches-on-no-input t
495 icomplete-hide-common-prefix nil
496 icomplete-with-completion-tables t
497 icomplete-in-buffer t)
498 :bind (:map icomplete-minibuffer-map
499 ("<down>" . icomplete-forward-completions)
500 ("C-n" . icomplete-forward-completions)
501 ("<up>" . icomplete-backward-completions)
502 ("C-p" . icomplete-backward-completions)
503 ("C-v" . icomplete-vertical-toggle))
504 :config
505 (fido-mode -1)
506 (icomplete-mode +1)
507 (icomplete-vertical-mode +1))
508
509(use-package orderless
510 :after icomplete
511 :init (setq-default completion-styles '(orderless)))
512
513(use-package marginalia
514 :after icomplete
515 :init (setq-default marginalia-annotators
516 '(marginalia-annotators-heavy
517 marginalia-annotators-light))
518 :config (marginalia-mode +1))
18 519
19(message "%s..." "Loading init.el") 520(use-package consult
20(let* (;; Speed up init 521 :after icomplete
21 (gc-cons-threshold most-positive-fixnum) 522 :bind (;; C-c bindings (mode-specific-map)
22 ;; (gc-cons-percentage 0.6) 523 ("C-c h" . consult-history)
23 (file-name-handler-alist nil) 524 ("C-c m" . consult-mode-command)
24 ;; Config file names 525 ("C-c b" . consult-bookmark)
25 (config (expand-file-name "config" 526 ("C-c k" . consult-kmacro)
26 user-emacs-directory)) 527 ;; C-x bindings (ctl-x-map)
27 (config.el (concat config ".el")) 528 ("C-x M-:" . consult-complex-command) ; orig. repeat-complet-command
28 (config.org (concat config ".org")) 529 ("C-x b" . consult-buffer) ; orig. switch-to-buffer
29 (straight-org-dir (locate-user-emacs-file "straight/build/org"))) 530 ("C-x 4 b" . consult-buffer-other-window) ; orig. switch-to-buffer-other-window
30 ;; Okay, let's figure this out. 531 ("C-x 5 b" . consult-buffer-other-frame) ; orig. switch-to-buffer-other-frame
31 ;; `and' evaluates each form, and returns nil on the first that 532 ;; Custom M-# bindings for fast register access
32 ;; returns nil. `unless' only executes its body if the test 533 ("M-#" . consult-register-load)
33 ;; returns nil. So. 534 ("M-'" . consult-register-store) ; orig. abbrev-prefix-mark (unrelated)
34 ;; 1. Test if config.org is newer than config.el. If it is (t), we 535 ("C-M-#" . consult-register)
35 ;; *want* to evaluate the body, so we need to negate that test. 536 ;; Other custom bindings
36 ;; 2. Try to load the config. If it errors (nil), it'll bubble that 537 ("M-y" . consult-yank-pop) ; orig. yank-pop
37 ;; to the `and' and the body will be evaluated. 538 ("<help> a" . consult-apropos) ; orig. apropos-command
38 (unless (and (not (file-newer-than-file-p config.org config.el)) 539 ;; M-g bindings (goto-map)
39 (load config :noerror)) 540 ("M-g e" . consult-compile-error)
40 ;; A plain require here just loads the older `org' 541 ("M-g g" . consult-goto-line) ; orig. goto-line
41 ;; in Emacs' install dir. We need to add the newer 542 ("M-g M-g" . consult-goto-line) ; orig. goto-line
42 ;; one to the `load-path', hopefully that's all. 543 ("M-g o" . consult-outline)
43 (when (file-exists-p straight-org-dir) 544 ("M-g m" . consult-mark)
44 (add-to-list 'load-path straight-org-dir)) 545 ("M-g k" . consult-global-mark)
45 ;; Load config.org 546 ("M-g i" . consult-imenu)
46 (message "%s..." "Loading config.org") 547 ("M-g I" . consult-project-imenu)
47 (require 'org) 548 ;; M-s bindings (search-map)
48 (org-babel-load-file config.org) 549 ("M-s f" . consult-find)
49 (message "%s... Done" "Loading config.org"))) 550 ("M-s L" . consult-locate)
50(message "%s... Done." "Loading init.el") 551 ("M-s g" . consult-grep)
51;;; init.el ends here 552 ("M-s G" . consult-git-grep)
553 ("M-s r" . consult-ripgrep)
554 ("M-s l" . consult-line)
555 ("M-s m" . consult-multi-occur)
556 ("M-s k" . consult-keep-lines)
557 ("M-s u" . consult-focus-lines)
558 ;; Isearch integration
559 ("M-s e" . consult-isearch)
560 :map isearch-mode-map
561 ("M-e" . consult-isearch) ; orig. isearch-edit-string
562 ("M-s e" . consult-isearch) ; orig. isearch-edit-string
563 ("M-s l" . consult-line)) ; required by consult-line to detect isearch
564 :init
565 (setq register-preview-delay 0
566 register-preview-function #'consult-register-format)
567 (advice-add #'register-preview :override #'consult-register-window)
568 (setq xref-show-xrefs-function #'consult-xref
569 xref-show-definitions-function #'consult-xref)
570 :config
571 ;; (setq consult-preview-key 'any)
572 ;; (setq consult-preview-key (kbd "M-p"))
573 (setq consult-narrow-key "<"))
diff --git a/var/elpher-bookmarks.el b/var/elpher-bookmarks.el deleted file mode 100644 index 311d980..0000000 --- a/var/elpher-bookmarks.el +++ /dev/null
@@ -1,18 +0,0 @@
1; Elpher bookmarks file
2
3; Bookmarks are stored as a list of (label URL) items.
4; Feel free to edit by hand, but take care to ensure
5; the list structure remains intact.
6
7(("MEDUSAE directory" "gemini://medusae.space/")
8 ("Will Lewis" "gemini://wflewis.com/")
9 ("Cornbread Recipe" "gemini://perplexing.space/2021/cornbread-recipe.gmi")
10 ("SMOG" "gemini://gemini.trans-neptunian.space/~smog/")
11 ("Caolan - Vermont Sourdough" "gemini://caolan.uk/baking/2020-11-26_vermont_sourdough.gmi")
12 ("BREADPUNK!" "gemini://breadpunk.club/")
13 ("ACDW" "gemini://gem.acdw.net/")
14 ("Spacewalk" "gemini://rawtext.club/~sloum/spacewalk.gmi")
15 ("CAPCOM" "gemini://gemini.circumlunar.space/capcom/")
16 ("cosmic voyage" "gemini://cosmic.voyage/")
17 ("kayw" "gemini://salejandro.me/")
18 ("low-key: weechat relay" "gemini://low-key.me/guides/weechat_irc_relay.gmi"))