summary refs log tree commit diff stats
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md2211
1 files changed, 0 insertions, 2211 deletions
diff --git a/README.md b/README.md deleted file mode 100644 index 1f56e6c..0000000 --- a/README.md +++ /dev/null
@@ -1,2211 +0,0 @@
1
2
3# Basics
4
5
6## Disclaimer
7
8 ;; config.el -*- lexical-binding: t -*-
9 ;; This file is automatically tangled from config.org.
10 ;; Hand edits will be overwritten!
11
12
13## About me
14
15 (setq user-full-name "Case Duckworth"
16 user-mail-address "acdw@acdw.net")
17
18
19## Correct `exec-path`
20
21Straight depends on Git, so I need to tell Emacs where different paths are.
22
23 (let ((win-downloads "c:/Users/aduckworth/Downloads"))
24 (dolist (path (list
25 ;; Linux
26 (expand-file-name "bin"
27 user-emacs-directory)
28 (expand-file-name "~/bin")
29 (expand-file-name "~/.local/bin")
30 (expand-file-name "~/Scripts")
31 ;; Windows
32 (expand-file-name "emacs/bin"
33 win-downloads)
34 (expand-file-name "m/usr/bin"
35 win-downloads)
36 (expand-file-name "m/mingw64/bin"
37 win-downloads)
38 (expand-file-name "PortableGit/bin"
39 win-downloads)
40 (expand-file-name "PortableGit/usr/bin"
41 win-downloads)))
42 (when (file-exists-p path)
43 (add-to-list 'exec-path path))))
44
45
46## Package management
47
48
49### Straight.el
50
51Straight can't bootstrap itself on Windows, so I've wrapped the
52bootstrap code from straight's repo in a function.
53
54 (defun acdw/bootstrap-straight ()
55 "Bootstrap straight.el."
56 (defvar bootstrap-version)
57 (let ((bootstrap-file
58 (expand-file-name
59 "straight/repos/straight.el/bootstrap.el"
60 user-emacs-directory))
61 (bootstrap-version 5))
62 (unless (file-exists-p bootstrap-file)
63 (with-current-buffer
64 (url-retrieve-synchronously
65 (concat
66 "https://raw.githubusercontent.com/"
67 "raxod502/straight.el/"
68 "develop/install.el")
69 'silent 'inhibit-cookies)
70 (goto-char (point-max))
71 (eval-print-last-sexp)))
72 (load bootstrap-file nil 'nomessage)))
73
74Now, I'll *try* running it regular-style, ignoring the errors. If it
75doesn't work, I'll call git directly and clone the repo myself.
76
77 (unless (ignore-errors (acdw/bootstrap-straight))
78 (message "%s" "Straight.el didn't bootstrap correctly. Cloning directly...")
79 (call-process "git" nil
80 (get-buffer-create "*bootstrap-straight-messages*") nil
81 "clone"
82 "https://github.com/raxod502/straight.el"
83 (expand-file-name "straight/repos/straight.el"
84 user-emacs-directory))
85 (acdw/bootstrap-straight))
86
87
88## Ease-of-configuring functions
89
90
91### Emulate use-package’s `:custom`
92
93 (defmacro cuss (var val &optional _docstring)
94 "Basically, `:custom' from `use-package', but without `use-package'."
95 (declare (doc-string 3)
96 (indent 2))
97 `(funcall (or (get ',var 'custom-set) #'set-default)
98 ',var ,val))
99
100
101### Emulate use-package’s `:custom-face`, but better
102
103 (defvar acdw--custom-faces ()
104 "List of custom faces to run through acdw/set-custom-faces.")
105
106 (defun acdw/set-custom-faces ()
107 "Run `customize-set-faces' on `acdw--custom-faces'."
108 (message "%s" "Customizing faces...")
109 (apply #'custom-set-faces acdw--custom-faces))
110
111 (defun cussface (spec)
112 "Add SPEC to `acdw--custom-faces', and add a hook to run
113 `acdw/set-custom-faces' after init."
114 (add-to-list 'acdw--custom-faces spec)
115 (add-hook 'after-init-hook #'acdw/set-custom-faces))
116
117
118### Determine whether any Emacs frame is focused or not
119
120This comes in handy when I want to garbage collect, say, or save recent files.
121
122 (defun acdw/when-unfocused (func &rest args)
123 "Run FUNC with ARGS only if all frames are out of focus."
124 (if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
125 (apply func args)))
126
127
128### Determine where I am
129
130I use Emacs at home, with Linux, and at work, with Windows.
131
132 (defmacro when-at (conditions &rest commands)
133 "Only do COMMANDS when CONDITIONS are met.
134 CONDITIONS are one of `:work', `:home', or a list beginning with
135 the above and other conditions to check."
136 (declare (indent 1))
137 (let ((at-work (memq system-type '(ms-dos windows-nt)))
138 (at-home (memq system-type '(gnu gnu/linux gnu/kfreebsd))))
139 (pcase conditions
140 (:work `(when ',at-work ,@commands))
141 (:home `(when ',at-home ,@commands))
142 (`(:work ,others) `(when (and ',at-work ,others)
143 ,@commands))
144 (`(:home ,others) `(when (and ',at-home ,others)
145 ,@commands)))))
146
147
148## Clean `.emacs.d`
149
150 (straight-use-package 'no-littering)
151 (require 'no-littering)
152
153
154### Don’t clutter `init.el` with customizations
155
156 (with-eval-after-load 'no-littering
157 (cuss custom-file (no-littering-expand-etc-file-name "custom.el")))
158
159
160## Look and feel
161
162
163### Cursor
164
165 (cuss cursor-type 'bar
166 "Show a vertical bar for the cursor.")
167
168 (cuss cursor-in-non-selected-windows 'hbar
169 "Show an empty box in inactive windows.")
170
171 ;; Don't blink the cursor
172 (blink-cursor-mode -1)
173
174
175### Tool Bars
176
177
178#### Tool bars and menu bars
179
180 (menu-bar-mode -1)
181 (tool-bar-mode -1)
182
183
184#### Scroll bars
185
186 (scroll-bar-mode -1)
187 (horizontal-scroll-bar-mode -1)
188
189
190### Dialogs
191
192 (cuss use-dialog-box nil
193 "Don't use dialog boxes to ask questions.")
194
195
196#### Yes or no questions
197
198 (fset 'yes-or-no-p #'y-or-n-p)
199
200
201#### The Bell
202
203from [EmacsWiki](https://www.emacswiki.org/emacs/AlarmBell#h5o-3).
204
205 (setq visible-bell nil
206 ring-bell-function 'flash-mode-line)
207
208 (defun flash-mode-line ()
209 (invert-face 'mode-line)
210 (run-with-timer 0.1 nil #'invert-face 'mode-line))
211
212
213### Frames
214
215
216#### Frame titles
217
218 (cuss frame-title-format (concat invocation-name "@" system-name
219 ": %b %+%+ %f"))
220
221
222#### Fringes
223
224 (cuss indicate-empty-lines t
225 "Show an indicator on the left fringe of empty lines past the
226 end of the buffer.")
227 (cuss indicate-buffer-boundaries 'right
228 "Indicate the beginning and end of the buffer and whether it
229 scrolls off-window in the right fringe.")
230
231 (cuss visual-line-fringe-indicators '(left-curly-arrow nil)
232 "Indicate continuing lines with a curly arrow in the left fringe.")
233
234 ;; redefine the `left-curly-arrow' indicator to be less distracting.
235 (define-fringe-bitmap 'left-curly-arrow
236 [#b11000000
237 #b01100000
238 #b00110000
239 #b00011000])
240
241
242#### Minibuffer
243
244 (cuss minibuffer-prompt-properties
245 '(read-only t cursor-intangible t face minibuffer-prompt)
246 "Keep the cursor away from the minibuffer prompt.")
247
248
249#### Tabs
250
251 (cuss tab-bar-tab-name-function
252 #'tab-bar-tab-name-current-with-count
253 "Show the tab name as the name of the current buffer, plus a
254 count of the windows in the tab.")
255
256 (cuss tab-bar-show 1
257 "Show the tab bar, when there's more than one tab.")
258
259
260### Windows
261
262
263#### Winner mode
264
265 (when (fboundp 'winner-mode)
266 (winner-mode +1))
267
268
269#### Switch windows or buffers if one window
270
271from [u/astoff1](https://www.reddit.com/r/emacs/comments/kz347f/what_parts_of_your_config_do_you_like_best/gjlnp2c/).
272
273 (defun acdw/other-window-or-buffer ()
274 "Switch to other window, or previous buffer."
275 (interactive)
276 (if (eq (count-windows) 1)
277 (switch-to-buffer nil)
278 (other-window 1)))
279
280 (global-set-key (kbd "M-o") #'acdw/other-window-or-buffer)
281
282
283#### Pop-up windows
284
285 (straight-use-package 'popwin)
286 (popwin-mode +1)
287
288
289### Buffers
290
291
292#### Uniquify buffers
293
294 (require 'uniquify)
295 (cuss uniquify-buffer-name-style 'forward
296 "Uniquify buffers' names by going up the path trees until they
297 become unique.")
298
299
300#### Startup buffers
301
302 (cuss inhibit-startup-screen t
303 "Don't show Emacs' startup buffer.")
304
305 (cuss initial-buffer-choice t
306 "Start with *scratch*.")
307
308 (cuss initial-scratch-message ""
309 "Empty *scratch* buffer.")
310
311
312#### Kill the current buffer
313
314 (defun acdw/kill-a-buffer (&optional prefix)
315 "Kill a buffer based on the following rules:
316
317 C-x k ⇒ Kill current buffer & window
318 C-u C-x k ⇒ Kill OTHER window and its buffer
319 C-u C-u C-x C-k ⇒ Kill all other buffers and windows
320
321 Prompt only if there are unsaved changes."
322 (interactive "P")
323 (pcase (or (car prefix) 0)
324 ;; C-x k ⇒ Kill current buffer & window
325 (0 (kill-current-buffer)
326 (unless (one-window-p) (delete-window)))
327 ;; C-u C-x k ⇒ Kill OTHER window and its buffer
328 (4 (other-window 1)
329 (kill-current-buffer)
330 (unless (one-window-p) (delete-window)))
331 ;; C-u C-u C-x C-k ⇒ Kill all other buffers and windows
332 (16 (mapc 'kill-buffer (delq (current-buffer) (buffer-list)))
333 (delete-other-windows))))
334
335 (define-key ctl-x-map "k" #'acdw/kill-a-buffer)
336
337
338##### Remap `C-x M-k` to bring up the buffer-killing menu
339
340 (define-key ctl-x-map (kbd "M-k") #'kill-buffer)
341
342
343#### Immortal `*scratch*` buffer
344
345 (defun immortal-scratch ()
346 (if (eq (current-buffer) (get-buffer "*scratch*"))
347 (progn (bury-buffer)
348 nil)
349 t))
350
351 (add-hook 'kill-buffer-query-functions 'immortal-scratch)
352
353
354### Modeline
355
356
357#### Smart mode line
358
359 (straight-use-package 'smart-mode-line)
360
361 (cuss sml/no-confirm-load-theme t
362 "Pass the NO-CONFIRM flag to `load-theme'.")
363
364 (sml/setup)
365
366
367#### Rich minority
368
369Since this *comes* with smart mode line, I’m just going to use it,
370instead of `diminish` or another package. I do have to write this
371helper function, though, to add things to the whitelist.
372
373 (defun rm/whitelist-add (regexp)
374 "Add a REGEXP to the whitelist for `rich-minority'."
375 (if (listp 'rm--whitelist-regexps)
376 (add-to-list 'rm--whitelist-regexps regexp)
377 (setq rm--whitelist-regexps `(,regexp)))
378 (setq rm-whitelist
379 (mapconcat 'identity rm--whitelist-regexps "\\|")))
380
381 (straight-use-package 'rich-minority)
382
383 (rm/whitelist-add "^$")
384
385
386### Theme
387
388
389#### Modus Themes
390
391 (straight-use-package 'modus-themes)
392
393 (cuss modus-themes-slanted-constructs t
394 "Use more slanted constructs.")
395 (cuss modus-themes-bold-constructs t
396 "Use more bold constructs.")
397
398 (cuss modus-themes-region 'bg-only
399 "Only highlight the background of the selected region.")
400
401 (cuss modus-themes-org-blocks 'grayscale
402 "Show org-blocks with a grayscale background.")
403 (cuss modus-themes-headings
404 '((1 . line)
405 (t . t))
406 "Highlight top headings with `line' style, and others by default.")
407
408 (cuss modus-themes-scale-headings t
409 "Scale headings by the ratios below.")
410 (cuss modus-themes-scale-1 1.1)
411 (cuss modus-themes-scale-2 1.15)
412 (cuss modus-themes-scale-3 1.21)
413 (cuss modus-themes-scale-4 1.27)
414 (cuss modus-themes-scale-5 1.33)
415
416 (load-theme 'modus-operandi t)
417
418
419#### Change themes based on time of day
420
421 (cuss calendar-latitude 30.4515)
422 (cuss calendar-longitude -91.1871)
423
424 (defun acdw/run-with-sun (sunrise-command sunset-command)
425 "Run commands at sunrise and sunset."
426 (let* ((times-regex (rx (* nonl)
427 (: (any ?s ?S) "unrise") " "
428 (group (repeat 1 2 digit) ":"
429 (repeat 1 2 digit)
430 (: (any ?a ?A ?p ?P) (any ?m ?M)))
431 (* nonl)
432 (: (any ?s ?S) "unset") " "
433 (group (repeat 1 2 digit) ":"
434 (repeat 1 2 digit)
435 (: (any ?a ?A ?p ?P) (any ?m ?M)))
436 (* nonl)))
437 (ss (sunrise-sunset))
438 (_m (string-match times-regex ss))
439 (sunrise-time (match-string 1 ss))
440 (sunset-time (match-string 2 ss)))
441 (run-at-time sunrise-time (* 60 60 24) sunrise-command)
442 (run-at-time sunset-time (* 60 60 24) sunset-command)))
443
444 (acdw/run-with-sun #'modus-themes-load-operandi #'modus-themes-load-vivendi)
445
446
447### Fonts
448
449
450#### Define fonts
451
452 (defun set-face-from-alternatives (face frame &rest fontspecs)
453 "Set FACE on FRAME from first available spec from FONTSPECS.
454 FACE and FRAME work the same as with `set-face-attribute.'"
455 (catch :return
456 (dolist (spec fontspecs)
457 (when-let ((found (find-font (apply #'font-spec spec))))
458 (set-face-attribute face frame :font found)
459 (throw :return found)))))
460
461 (defun acdw/setup-fonts ()
462 "Setup fonts. This has to happen after the frame is setup for
463 the first time, so it should be added to `window-setup-hook'. It
464 removes itself from that hook."
465 (interactive)
466 (when (display-graphic-p)
467 (dolist (face '(default fixed-pitch))
468 ;; fixed-pitch /is/ the default
469 (set-face-from-alternatives face nil
470 '(:family "Input Mono"
471 :slant normal
472 :weight normal
473 :height 110)
474 '(:family "Go Mono"
475 :slant normal
476 :weight normal
477 :height 100)
478 '(:family "Consolas"
479 :slant normal
480 :weight normal
481 :height 100)))
482 ;; variable-pitch is different
483 (set-face-from-alternatives 'variable-pitch nil
484 '(:family "Input Sans"
485 :slant normal
486 :weight normal)
487 '(:family "Georgia"
488 :slant normal
489 :weight normal)))
490
491 ;; remove myself from the hook
492 (remove-function after-focus-change-function #'acdw/setup-fonts))
493
494 (add-function :before after-focus-change-function #'acdw/setup-fonts)
495
496
497#### Custom faces
498
499 (cussface '(font-lock-comment-face
500 ((t (:inherit (custom-comment italic variable-pitch))))))
501
502
503#### Line spacing
504
505 (cuss line-spacing 0.1
506 "Add 10% extra space below each line.")
507
508
509#### Underlines
510
511 (cuss x-underline-at-descent-line t
512 "Draw the underline at the same place as the descent line.")
513
514
515#### Unicode Fonts
516
517 (straight-use-package 'unicode-fonts)
518 (require 'unicode-fonts)
519 (unicode-fonts-setup)
520
521
522## Interactivity
523
524
525### Completing read
526
527
528#### Shadow file names in `completing-read`.
529
530 (cuss file-name-shadow-properties '(invisible t))
531
532 (file-name-shadow-mode +1)
533
534
535#### Ignore case in `completing-read`
536
537 (cuss completion-ignore-case t)
538 (cuss read-buffer-completion-ignore-case t)
539 (cuss read-file-name-completion-ignore-case t)
540
541
542#### Minibuffer recursivity
543
544 (cuss enable-recursive-minibuffers t)
545 (minibuffer-depth-indicate-mode +1)
546
547
548#### Selectrum
549
550 (straight-use-package 'selectrum)
551 (require 'selectrum)
552 (selectrum-mode +1)
553
554
555#### Prescient
556
557 (straight-use-package 'prescient)
558 (require 'prescient)
559 (prescient-persist-mode +1)
560
561 (straight-use-package 'selectrum-prescient)
562 (require 'selectrum-prescient)
563 (selectrum-prescient-mode +1)
564
565
566#### Consult
567
568 (straight-use-package '(consult
569 :host github
570 :repo "minad/consult"
571 :files (:defaults "consult-pkg.el")))
572 (require 'consult)
573
574 (with-eval-after-load 'consult
575 (define-key ctl-x-map "b" #'consult-buffer)
576 (define-key ctl-x-map (kbd "C-r") #'consult-buffer)
577 (define-key ctl-x-map "4b" #'consult-buffer-other-window)
578 (define-key ctl-x-map "5b" #'consult-buffer-other-frame)
579
580 (define-key goto-map "o" #'consult-outline)
581 (define-key goto-map "g" #'consult-line)
582 (define-key goto-map (kbd "M-g") #'consult-line)
583 (define-key goto-map "l" #'consult-line)
584 (define-key goto-map "m" #'consult-mark)
585 (define-key goto-map "i" #'consult-imenu)
586 (define-key goto-map "e" #'consult-error)
587
588 (global-set-key (kbd "M-y") #'consult-yank-pop)
589
590 (define-key help-map "a" #'consult-apropos)
591
592 (fset 'multi-occur #'consult-multi-occur))
593
594
595#### Marginalia
596
597 (straight-use-package '(marginalia
598 :host github
599 :repo "minad/marginalia"
600 :branch "main"))
601
602 (cuss marginalia-annotators
603 '(marginalia-annotators-heavy
604 marginalia-annotators-light))
605
606 (marginalia-mode +1)
607
608
609### Completion
610
611 (global-set-key (kbd "M-/") #'hippie-expand)
612
613
614### Garbage collection
615
616 (straight-use-package 'gcmh)
617 (gcmh-mode +1)
618
619 (defun dotfiles--gc-on-last-frame-out-of-focus ()
620 "GC if all frames are inactive."
621 (if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
622 (garbage-collect)))
623
624 (add-function :after after-focus-change-function
625 #'dotfiles--gc-on-last-frame-out-of-focus)
626
627
628## Keyboard
629
630
631### `ESC` cancels all
632
633 (global-set-key (kbd "<escape>") #'keyboard-escape-quit)
634
635
636### Personal prefix key: `C-z`
637
638 (defvar acdw/map
639 (let ((map (make-sparse-keymap))
640 (c-z (global-key-binding "\C-z")))
641 (global-unset-key "\C-z")
642 (define-key global-map "\C-z" map)
643 (define-key map "\C-z" c-z)
644 map))
645
646 (run-hooks 'acdw/map-defined-hook)
647
648
649### Show keybindings
650
651 (straight-use-package 'which-key)
652 (which-key-mode +1)
653
654
655## Mouse
656
657
658### Preserve screen position when scrolling with the mouse wheel
659
660from [u/TheFrenchPoulp](https://www.reddit.com/r/emacs/comments/km9by4/weekly_tipstricketc_thread/ghg2c9d/).
661
662 (advice-add 'mwheel-scroll :around #'me/mwheel-scroll)
663
664 (defun me/mwheel-scroll (original &rest arguments)
665 "Like `mwheel-scroll' but preserve screen position.
666 See `scroll-preserve-screen-position'."
667 (let ((scroll-preserve-screen-position :always))
668 (apply original arguments)))
669
670
671### Scroll much faster
672
673from [mpereira](https://github.com/mpereira/.emacs.d#make-cursor-movement-an-order-of-magnitude-faster), from somewhere else.
674
675 (cuss auto-window-vscroll nil
676 "Don't auto-adjust `window-vscroll' to view long lines.")
677
678 (cuss fast-but-imprecise-scrolling t
679 "Scroll fast, but possibly with inaccurate text rendering.")
680
681 (cuss jit-lock-defer-time 0
682 "Only defer font-locking when input is pending.")
683
684
685## Persistence
686
687
688### Minibuffer history
689
690 (require 'savehist)
691
692 (cuss savehist-additional-variables
693 '(kill-ring
694 search-ring
695 regexp-search-ring)
696 "Other variables to save alongside the minibuffer history.")
697
698 (cuss history-length t
699 "Don't truncate history.")
700
701 (cuss history-delete-duplicates t
702 "Delete history duplicates.")
703
704 (savehist-mode +1)
705
706
707### File places
708
709 (require 'saveplace) ; this isn't required, but ... I like having it here
710
711 (cuss save-place-forget-unreadable-files t
712 "Don't check if files are readable or not.")
713
714 (save-place-mode +1)
715
716
717### Recent files
718
719 (require 'recentf)
720
721 (cuss recentf-max-menu-items 100
722 "The maximum number of items in the recentf menu.")
723 (cuss recentf-max-saved-items nil
724 "Don't limit the number of recent files.")
725
726 (with-eval-after-load 'no-littering
727 (add-to-list 'recentf-exclude no-littering-var-directory)
728 (add-to-list 'recentf-exclude no-littering-etc-directory))
729
730 (recentf-mode +1)
731
732 ;; save recentf list when focusing away
733 (defun acdw/maybe-save-recentf ()
734 "Save `recentf-file' when out of focus, but only if we haven't
735 in five minutes."
736 (defvar recentf-last-save (time-convert nil 'integer)
737 "How long it's been since we last saved the recentf list.")
738
739 (when (> (time-convert (time-since recentf-last-save) 'integer)
740 (* 60 5))
741 (setq recentf-last-save (time-convert nil 'integer))
742 (acdw/when-unfocused #'recentf-save-list)))
743
744 (add-function :after after-focus-change-function
745 #'acdw/maybe-save-recentf)
746
747
748## Undo
749
750 (straight-use-package 'undo-fu)
751 (require 'undo-fu)
752
753 (global-set-key (kbd "C-/") #'undo-fu-only-undo)
754 (global-set-key (kbd "C-?") #'undo-fu-only-redo)
755
756 (straight-use-package 'undo-fu-session)
757 (require 'undo-fu-session)
758
759 (cuss undo-fu-session-incompatible-files
760 '("/COMMIT_EDITMSG\\'"
761 "/git-rebase-todo\\'")
762 "A list of files that are incompatible with the concept of undo sessions.")
763
764 (with-eval-after-load 'no-littering
765 (let ((dir (no-littering-expand-var-file-name "undos")))
766 (make-directory dir 'parents)
767 (cuss undo-fu-session-directory dir)))
768
769 (global-undo-fu-session-mode +1)
770
771
772## Files
773
774
775### Encoding
776
777
778#### UTF-8
779
780from [Mastering Emacs](https://www.masteringemacs.org/article/working-coding-systems-unicode-emacs).
781
782 (prefer-coding-system 'utf-8)
783 (set-default-coding-systems 'utf-8)
784 (set-terminal-coding-system 'utf-8)
785 (set-keyboard-coding-system 'utf-8)
786 ;; backwards compatibility:
787 ;; `default-buffer-file-coding-system' is deprecated in 23.2.
788 (setq default-buffer-file-coding-system 'utf-8)
789
790 ;; Treat clipboard as UTF-8 string first; compound text next, etc.
791 (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
792
793
794#### Convert all files to UNIX-style line endings
795
796from [Emacs Wiki](https://www.emacswiki.org/emacs/EndOfLineTips).
797
798 (defun ewiki/no-junk-please-were-unixish ()
799 "Convert line endings to UNIX, dammit."
800 (let ((coding-str (symbol-name buffer-file-coding-system)))
801 (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
802 (set-buffer-file-coding-system 'unix))))
803
804I add it to the `find-file-hook` *and* `before-save-hook` because I
805don't want to ever work with anything other than UNIX line endings
806ever again. I just don't care. Even Microsoft Notepad can handle
807UNIX line endings, so I don't want to hear it.
808
809 (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish)
810 (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish)
811
812
813### Backups
814
815 (cuss backup-by-copying 1)
816 (cuss delete-old-versions -1)
817 (cuss version-control t)
818 (cuss vc-make-backup-files t)
819
820 (with-eval-after-load 'no-littering
821 (let ((dir (no-littering-expand-var-file-name "backup")))
822 (make-directory dir 'parents)
823 (cuss backup-directory-alist
824 `((".*" . ,dir)))))
825
826
827### Auto-saves
828
829 ;; turn off auto-save-mode until we can set up the right directory for them
830 (auto-save-mode -1)
831
832 (with-eval-after-load 'no-littering
833 (let ((dir (no-littering-expand-var-file-name "autosaves")))
834 (make-directory dir 'parents)
835 (cuss auto-save-file-name-transforms
836 `((".*" ,dir t))))
837
838 (auto-save-mode +1))
839
840
841### Super-save
842
843Because I like *overkill*, or at least … over-*saving*.
844
845 (straight-use-package 'super-save)
846
847 (cuss super-save-remote-files nil
848 "Don't super-save remote files.")
849
850 (cuss super-save-exclude '(".gpg")
851 "Ignore these files when saving.")
852
853 (super-save-mode +1)
854
855
856### Auto-revert buffers to files on disk
857
858 (global-auto-revert-mode +1)
859
860
861### Add a timestamp to files
862
863 (add-hook 'before-save-hook #'time-stamp)
864
865
866### Require a final new line
867
868 (cuss require-final-newline t)
869
870
871### Edit files with `sudo`
872
873 (straight-use-package 'sudo-edit)
874
875 (with-eval-after-load 'sudo-edit
876 (define-key acdw/map (kbd "C-r") #'sudo-edit))
877
878
879#### Don’t add `/sudo:` files to `recentf`, though
880
881I’ve pretty much cribbed this from [recentf-remove-sudo-tramp-prefix](https://github.com/ncaq/recentf-remove-sudo-tramp-prefix/) – it’s a small enough package that I can just include it completely here.
882
883 ;; appease the compiler
884 (declare-function tramp-tramp-file-p "tramp")
885 (declare-function tramp-dissect-file-name "tramp")
886 (declare-function tramp-file-name-method "tramp")
887 (declare-function tramp-file-name-localname "tramp")
888
889 (defun recentf-remove-sudo-tramp-prefix (path)
890 "Remove sudo from PATH."
891 (require 'tramp)
892 (if (tramp-tramp-file-p path)
893 (let ((tx (tramp-dissect-file-name path)))
894 (if (string-equal "sudo" (tramp-file-name-method tx))
895 (tramp-file-name-localname tx)
896 path))
897 path)
898
899 (defun recentf-remove-sudo-tramp-prefix-from-recentf-list ()
900 (require 'recentf)
901 (setq recentf-list
902 (mapcar #'recentf-remove-sudo-tramp-prefix recentf-list)))
903
904 (advice-add 'recentf-cleanup
905 :before #'recentf-remove-sudo-tramp-prefix-from-recentf-list))
906
907
908## Text editing
909
910
911### Operate visually on lines
912
913 (global-visual-line-mode +1)
914
915
916### View long lines like filled lines in the beginning
917
918 (straight-use-package 'adaptive-wrap)
919
920 (when (fboundp 'adaptive-wrap-prefix-mode)
921 (defun acdw/activate-adaptive-wrap-prefix-mode ()
922 "Toggle `visual-line-mode' and `adaptive-wrap-prefix-mode' simultaneously."
923 (adaptive-wrap-prefix-mode (if visual-line-mode
924 +1
925 -1)))
926 (add-hook 'visual-line-mode-hook #'acdw/activate-adaptive-wrap-prefix-mode))
927
928
929### Stay snappy with long-lined files
930
931 (when (fboundp 'global-so-long-mode)
932 (global-so-long-mode +1))
933
934
935### Killing & Yanking
936
937
938#### Replace selection when typing
939
940 (delete-selection-mode +1)
941
942
943#### Work better with the system clipboard
944
945 (cuss save-interprogram-paste-before-kill t
946 "Save existing clipboard text into the kill ring before
947 replacing it.")
948
949 (cuss yank-pop-change-selection t
950 "Update the X selection when rotating the kill ring.")
951
952 (cuss x-select-enable-clipboard t)
953 (cuss x-select-enable-primary t)
954 (cuss mouse-drag-copy-region t)
955
956
957#### Don’t append the same thing twice to the kill-ring
958
959 (cuss kill-do-not-save-duplicates t)
960
961
962### Searching & Replacing
963
964
965#### Replace with Anzu
966
967 (straight-use-package 'anzu)
968 (require 'anzu)
969
970 ;; show search count in the modeline
971 (global-anzu-mode +1)
972
973 (cuss anzu-replace-to-string-separator " → "
974 "What to separate the search from the replacement.")
975
976 (global-set-key [remap query-replace] #'anzu-query-replace)
977 (global-set-key [remap query-replace-regexp] #'anzu-query-replace-regexp)
978
979 (define-key isearch-mode-map [remap isearch-query-replace] #'anzu-isearch-query-replace)
980 (define-key isearch-mode-map [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp)
981
982
983### Overwrite mode
984
985 (defun acdw/overwrite-mode-change-cursor ()
986 (setq cursor-type (if overwrite-mode t 'bar)))
987
988 (add-hook 'overwrite-mode-hook #'acdw/overwrite-mode-change-cursor)
989
990 (rm/whitelist-add "Ovwrt")
991
992
993### The Mark
994
995 (cuss set-mark-repeat-command-pop t
996 "Repeat `set-mark-command' with a prefix argument, without
997 repeatedly entering the prefix argument.")
998
999
1000### Whitespace
1001
1002 (cuss whitespace-style
1003 '(empty ;; remove blank lines at the beginning and end of buffers
1004 indentation ;; clean up indentation
1005 space-before-tab ;; fix mixed spaces and tabs
1006 space-after-tab))
1007
1008 (defun acdw/whitespace-cleanup-maybe ()
1009 "Only cleanup whitespace when out-of-focus."
1010 (acdw/when-unfocused #'whitespace-cleanup))
1011
1012 (add-hook 'before-save-hook #'acdw/whitespace-cleanup-maybe)
1013
1014
1015### Expand region
1016
1017 (straight-use-package 'expand-region)
1018
1019 (global-set-key (kbd "C-=") #'er/expand-region)
1020
1021
1022### Move where I mean
1023
1024 (straight-use-package 'mwim)
1025 (require 'mwim)
1026
1027 (cuss mwim-beginning-of-line-function 'beginning-of-visual-line)
1028 (cuss mwim-end-of-line-function 'end-of-visual-line)
1029
1030 (global-set-key (kbd "C-a") #'mwim-beginning)
1031 (global-set-key (kbd "C-e") #'mwim-end)
1032
1033
1034# Programming
1035
1036
1037## Prettify symbols
1038
1039 (cuss prettify-symbols-unprettify-at-point 'right-edge
1040 "Unprettify a symbol when inside it or next to it.")
1041
1042 (add-hook 'prog-mode-hook #'prettify-symbols-mode)
1043
1044
1045## Parentheses
1046
1047
1048### Smart parentheses
1049
1050 (straight-use-package 'smartparens)
1051 (require 'smartparens-config)
1052
1053 ;; replace show-paren
1054
1055 (cuss sp-show-pair-delay 0
1056 "Don't delay before showing the pairs.")
1057 (cuss sp-show-pair-from-inside t
1058 "Highlight the enclosing pair when immediately inside.")
1059
1060 (add-hook 'prog-mode-hook #'show-smartparens-mode +1)
1061
1062 ;; enable strict smartparens in prog mode
1063 (add-hook 'prog-mode-hook #'smartparens-strict-mode)
1064
1065
1066## Indent aggressively
1067
1068 (straight-use-package 'aggressive-indent)
1069
1070 (global-aggressive-indent-mode +1)
1071
1072
1073## Auto-fill comments only
1074
1075from [EmacsWiki](https://www.emacswiki.org/emacs/AutoFillMode).
1076
1077 (defun comment-auto-fill ()
1078 (setq-local comment-auto-fill-only-comments t)
1079 (auto-fill-mode 1))
1080
1081 (add-hook 'prog-mode-hook #'comment-auto-fill)
1082
1083
1084## Completion
1085
1086 (straight-use-package 'company)
1087
1088 (add-hook 'prog-mode-hook #'company-mode)
1089
1090 (cuss company-idle-delay 0.1
1091 "Show company sooner.")
1092 (cuss company-minimum-prefix-length 3
1093 "Don't try to complete short words.")
1094
1095 (with-eval-after-load 'company
1096 (define-key company-active-map (kbd "C-n")
1097 (lambda () (interactive) (company-complete-common-or-cycle +1)))
1098 (define-key company-active-map (kbd "C-p")
1099 (lambda () (interactive) (company-complete-common-or-cycle -1))))
1100
1101
1102### Give it a frame and better help
1103
1104 (straight-use-package 'company-posframe)
1105
1106 (with-eval-after-load 'company
1107 (company-posframe-mode +1))
1108
1109
1110### Prescient integration
1111
1112 (straight-use-package 'company-prescient)
1113
1114 (add-hook 'company-mode-hook #'company-prescient-mode)
1115
1116
1117## Language-specific packages
1118
1119
1120### Executable scripts
1121
1122 (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
1123
1124
1125### Emacs lisp
1126
1127 (cuss eval-expression-print-length nil
1128 "Don't truncate printed expressions by length.")
1129 (cuss eval-expression-print-level nil
1130 "Don't truncate printed expressions by level.")
1131
1132
1133#### Eros (Evaluation Result OverlayS)
1134
1135 (straight-use-package 'eros)
1136
1137 (cuss eros-eval-result-prefix ";; => "
1138 "Prefix displayed before eros overlays.")
1139
1140 (eros-mode +1)
1141
1142
1143#### Indent Elisp like Common Lisp
1144
1145 (require 'cl-lib)
1146 (setq lisp-indent-function 'common-lisp-indent-function)
1147 (put 'cl-flet 'common-lisp-indent-function
1148 (get 'flet 'common-lisp-indent-function))
1149 (put 'cl-labels 'common-lisp-indent-function
1150 (get 'labels 'common-lisp-indent-function))
1151 (put 'if 'common-lisp-indent-function 2)
1152 (put 'dotimes-protect 'common-lisp-indent-function
1153 (get 'when 'common-lisp-indent-function))
1154
1155
1156### Janet
1157
1158 (straight-use-package 'janet-mode)
1159 (require 'janet-mode)
1160
1161 (straight-use-package '(inf-janet
1162 :host github
1163 :repo "velkyel/inf-janet"))
1164
1165 (add-hook 'janet-mode-hook #'inf-janet-minor-mode)
1166
1167
1168### INI
1169
1170 (straight-use-package 'ini-mode)
1171
1172 (add-to-list 'auto-mode-alist
1173 '("\\.ini\\'" . ini-mode))
1174
1175
1176### PHP
1177
1178see also [this post by Fermin M](https://sasanidas.gitlab.io/f-site/php-development/), it looks really useful.
1179
1180 (straight-use-package 'php-mode)
1181
1182
1183# Writing
1184
1185
1186## Visual fill column
1187
1188
1189### Fix scrolling in margins
1190
1191This has to be done *before* loading the package. It's included in `visual-fill-column`, too, but for some reason isn't loaded there.
1192
1193 (dolist (margin '(right-margin left-margin))
1194 (dolist (button '(mouse-1 mouse-2 mouse-3))
1195 (global-set-key (vector margin button)
1196 (global-key-binding (vector button)))))
1197
1198 (mouse-wheel-mode +1)
1199
1200 (when (bound-and-true-p mouse-wheel-mode)
1201 (dolist (margin '(right-margin left-margin))
1202 (dolist (event '(mouse-wheel-down-event
1203 mouse-wheel-up-event
1204 wheel-down
1205 wheel-up
1206 mouse-4
1207 mouse-5))
1208 (global-set-key (vector margin event) #'mwheel-scroll))))
1209
1210
1211### Load the package
1212
1213 (straight-use-package 'visual-fill-column)
1214
1215 (cuss visual-fill-column-center-text nil
1216 "Whether to center the text in the frame.")
1217
1218 (cuss fill-column 84
1219 "Width of fill-column, and thus, visual-fill-column.")
1220
1221 (advice-add 'text-scale-adjust
1222 :after #'visual-fill-column-adjust)
1223
1224 (global-visual-fill-column-mode +1)
1225
1226
1227## Typographical niceties
1228
1229
1230### Variable pitch in text-modes
1231
1232 (add-hook 'text-mode-hook #'variable-pitch-mode)
1233
1234
1235### Typo mode
1236
1237 (straight-use-package 'typo)
1238
1239 (add-hook 'text-mode-hook #'typo-mode)
1240
1241 ;; Disable `typo-mode' when inside an Org source block
1242 (with-eval-after-load 'typo
1243 (add-to-list 'typo-disable-electricity-functions
1244 #'org-in-src-block-p))
1245
1246
1247### Show `^L` as a horizontal line
1248
1249 (straight-use-package 'form-feed)
1250 (global-form-feed-mode +1)
1251
1252
1253## Word count
1254
1255 (straight-use-package 'wc-mode)
1256
1257 (add-hook 'text-mode-hook #'wc-mode)
1258
1259 (rm/whitelist-add "WC")
1260
1261
1262# Applications
1263
1264
1265## Web browsing
1266
1267 (cuss browse-url-browser-function 'browse-url-firefox)
1268 (cuss browse-url-new-window-flag t
1269 "Always open a new browser window.")
1270
1271 ;;(cuss browse-url-generic-program "firefox")
1272 (cuss browse-url-firefox-new-window-is-tab t
1273 "Or a new tab, in Firefox.")
1274
1275 ;; we need to add Firefox to `exec-path' on Windows
1276 (when-at :work
1277 (add-to-list 'exec-path "c:/Program Files/Mozilla Firefox"))
1278
1279
1280## Dired
1281
1282
1283### Basic customization
1284
1285 (defun acdw/setup-dired-mode ()
1286 (hl-line-mode)
1287 (dired-hide-details-mode))
1288
1289 ;; highlight the current line in dired.
1290 (add-hook 'dired-mode-hook #'acdw/setup-dired-mode)
1291
1292 (cuss dired-recursive-copies 'always
1293 "Always recursively copy.")
1294
1295 (cuss dired-recursive-deletes 'always
1296 "Always recursively delete.")
1297
1298 (cuss delete-by-moving-to-trash t)
1299
1300 (cuss dired-listing-switches "-alh"
1301 "Show (A)lmost all items,
1302 (l)isted out, with (h)uman-readable sizes.")
1303
1304
1305### Expand subtrees
1306
1307 (straight-use-package 'dired-subtree)
1308
1309 (with-eval-after-load 'dired
1310 (define-key dired-mode-map "i" #'dired-subtree-toggle))
1311
1312
1313### Collapse singleton directories
1314
1315 (straight-use-package 'dired-collapse)
1316
1317 (add-hook 'dired-mode-hook #'dired-collapse-mode)
1318
1319
1320### Kill dired buffers
1321
1322from [munen](https://github.com/munen/emacs.d/).
1323
1324 (defun kill-dired-buffers ()
1325 "Kill all open dired buffers."
1326 (interactive)
1327 (mapc (lambda (buffer)
1328 (when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
1329 (kill-buffer buffer)))
1330 (buffer-list)))
1331
1332
1333## Org mode
1334
1335I’ve put org mode under Applications, as opposed to Writing, because it’s more generally-applicable than that.
1336
1337
1338### Basics
1339
1340 (straight-use-package 'org)
1341
1342 (with-eval-after-load 'org
1343 (require 'org-tempo)
1344 (require 'ox-md)
1345 (define-key org-mode-map (kbd "M-n") #'outline-next-visible-heading)
1346 (define-key org-mode-map (kbd "M-p") #'outline-previous-visible-heading))
1347
1348 (cuss org-hide-emphasis-markers t)
1349 (cuss org-fontify-done-headline t)
1350 (cuss org-fontify-whole-heading-line t)
1351 (cuss org-fontify-quote-and-verse-blocks t)
1352 (cuss org-pretty-entities t)
1353 (cuss org-src-tab-acts-natively t)
1354 (cuss org-src-fontify-natively t)
1355 (cuss org-src-window-setup 'current-window)
1356 (cuss org-confirm-babel-evaluate nil)
1357 (cuss org-directory "~/Org")
1358 (cuss org-ellipsis "…")
1359 (cuss org-catch-invisible-edits 'show)
1360 (cuss org-special-ctrl-a/e t)
1361 (cuss org-special-ctrl-k t)
1362
1363 (cuss org-export-headline-levels 8
1364 "Maximum level of headlines to export /as/ a headline.")
1365
1366
1367#### TODO configure `mwim` to work with Org mode
1368
1369
1370#### Tags
1371
1372 (cuss org-tags-column 0
1373 "Show tags directly after the headline.
1374 This is the best-looking option with variable-pitch fonts.")
1375
1376 (cussface
1377 '(org-tag
1378 ((t
1379 (:height 0.8 :weight normal :slant italic :foreground "grey40" :inherit
1380 (variable-pitch))))))
1381
1382
1383##### Align all tags in the buffer on changes
1384
1385from [mpereira](https://github.com/mpereira/.emacs.d#align-all-tags-in-the-buffer-on-tag-changes).
1386
1387 (defun acdw/org-align-all-tags ()
1388 "Align all org tags in the buffer."
1389 (interactive)
1390 (when (eq major-mode 'org-mode)
1391 (org-align-tags t)))
1392
1393 (add-hook 'org-after-tags-change-hook #'acdw/org-align-all-tags)
1394
1395
1396#### Source blocks
1397
1398 (with-eval-after-load 'org-faces
1399 (set-face-attribute 'org-block-begin-line nil
1400 :height 0.85))
1401
1402
1403#### Prettify
1404
1405 (defun acdw/org-mode-prettify ()
1406 "Prettify `org-mode'."
1407 (dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐)
1408 ("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎)
1409 ("#+END_SRC" . ?■) ("#+end_src" . ?■)))
1410 (add-to-list 'prettify-symbols-alist cell :append))
1411 (prettify-symbols-mode +1))
1412
1413 (add-hook 'org-mode-hook #'acdw/org-mode-prettify)
1414
1415
1416### General
1417
1418
1419#### [Org Return: DWIM](https://github.com/alphapapa/unpackaged.el#org-return-dwim)
1420
1421 (defun unpackaged/org-element-descendant-of (type element)
1422 "Return non-nil if ELEMENT is a descendant of TYPE.
1423 TYPE should be an element type, like `item' or `paragraph'.
1424 ELEMENT should be a list like that returned by `org-element-context'."
1425 ;; MAYBE: Use `org-element-lineage'.
1426 (when-let* ((parent (org-element-property :parent element)))
1427 (or (eq type (car parent))
1428 (unpackaged/org-element-descendant-of type parent))))
1429
1430 ;;;###autoload
1431 (defun unpackaged/org-return-dwim (&optional default)
1432 "A helpful replacement for `org-return'. With prefix, call `org-return'.
1433
1434 On headings, move point to position after entry content. In
1435 lists, insert a new item or end the list, with checkbox if
1436 appropriate. In tables, insert a new row or end the table."
1437 ;; Inspired by John Kitchin: http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
1438 (interactive "P")
1439 (if default
1440 (org-return)
1441 (cond
1442 ;; Act depending on context around point.
1443
1444 ;; NOTE: I prefer RET to not follow links, but by uncommenting this block, links will be
1445 ;; followed.
1446
1447 ;; ((eq 'link (car (org-element-context)))
1448 ;; ;; Link: Open it.
1449 ;; (org-open-at-point-global))
1450
1451 ((org-at-heading-p)
1452 ;; Heading: Move to position after entry content.
1453 ;; NOTE: This is probably the most interesting feature of this function.
1454 (let ((heading-start (org-entry-beginning-position)))
1455 (goto-char (org-entry-end-position))
1456 (cond ((and (org-at-heading-p)
1457 (= heading-start (org-entry-beginning-position)))
1458 ;; Entry ends on its heading; add newline after
1459 (end-of-line)
1460 (insert "\n\n"))
1461 (t
1462 ;; Entry ends after its heading; back up
1463 (forward-line -1)
1464 (end-of-line)
1465 (when (org-at-heading-p)
1466 ;; At the same heading
1467 (forward-line)
1468 (insert "\n")
1469 (forward-line -1))
1470 ;; FIXME: looking-back is supposed to be called with more arguments.
1471 (while (not (looking-back (rx (repeat 3 (seq (optional blank) "\n"))) nil))
1472 (insert "\n"))
1473 (forward-line -1)))))
1474
1475 ((org-at-item-checkbox-p)
1476 ;; Checkbox: Insert new item with checkbox.
1477 (org-insert-todo-heading nil))
1478
1479 ((org-in-item-p)
1480 ;; Plain list. Yes, this gets a little complicated...
1481 (let ((context (org-element-context)))
1482 (if (or (eq 'plain-list (car context)) ; First item in list
1483 (and (eq 'item (car context))
1484 (not (eq (org-element-property :contents-begin context)
1485 (org-element-property :contents-end context))))
1486 (unpackaged/org-element-descendant-of 'item context)) ; Element in list item, e.g. a link
1487 ;; Non-empty item: Add new item.
1488 (org-insert-item)
1489 ;; Empty item: Close the list.
1490 ;; TODO: Do this with org functions rather than operating on the text. Can't seem to find the right function.
1491 (delete-region (line-beginning-position) (line-end-position))
1492 (insert "\n"))))
1493
1494 ((when (fboundp 'org-inlinetask-in-task-p)
1495 (org-inlinetask-in-task-p))
1496 ;; Inline task: Don't insert a new heading.
1497 (org-return))
1498
1499 ((org-at-table-p)
1500 (cond ((save-excursion
1501 (beginning-of-line)
1502 ;; See `org-table-next-field'.
1503 (cl-loop with end = (line-end-position)
1504 for cell = (org-element-table-cell-parser)
1505 always (equal (org-element-property :contents-begin cell)
1506 (org-element-property :contents-end cell))
1507 while (re-search-forward "|" end t)))
1508 ;; Empty row: end the table.
1509 (delete-region (line-beginning-position) (line-end-position))
1510 (org-return))
1511 (t
1512 ;; Non-empty row: call `org-return'.
1513 (org-return))))
1514 (t
1515 ;; All other cases: call `org-return'.
1516 (org-return)))))
1517
1518 (with-eval-after-load 'org
1519 (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim))
1520
1521
1522#### Insert blank lines around headers
1523
1524from [unpackaged.el](https://github.com/alphapapa/unpackaged.el#ensure-blank-lines-between-headings-and-before-contents).
1525
1526 ;;;###autoload
1527 (defun unpackaged/org-fix-blank-lines (&optional prefix)
1528 "Ensure that blank lines exist between headings and between headings and their contents.
1529 With prefix, operate on whole buffer. Ensures that blank lines
1530 exist after each headings's drawers."
1531 (interactive "P")
1532 (org-map-entries (lambda ()
1533 (org-with-wide-buffer
1534 ;; `org-map-entries' narrows the buffer, which prevents us
1535 ;; from seeing newlines before the current heading, so we
1536 ;; do this part widened.
1537 (while (not (looking-back "\n\n" nil))
1538 ;; Insert blank lines before heading.
1539 (insert "\n")))
1540 (let ((end (org-entry-end-position)))
1541 ;; Insert blank lines before entry content
1542 (forward-line)
1543 (while (and (org-at-planning-p)
1544 (< (point) (point-max)))
1545 ;; Skip planning lines
1546 (forward-line))
1547 (while (re-search-forward org-drawer-regexp end t)
1548 ;; Skip drawers. You might think that `org-at-drawer-p'
1549 ;; would suffice, but for some reason it doesn't work
1550 ;; correctly when operating on hidden text. This
1551 ;; works, taken from `org-agenda-get-some-entry-text'.
1552 (re-search-forward "^[ \t]*:END:.*\n?" end t)
1553 (goto-char (match-end 0)))
1554 (unless (or (= (point) (point-max))
1555 (org-at-heading-p)
1556 (looking-at-p "\n"))
1557 (insert "\n"))))
1558 t (if prefix
1559 nil
1560 'tree)))
1561
1562
1563##### Add a before-save-hook
1564
1565 (defun cribbed/org-mode-fix-blank-lines ()
1566 (when (eq major-mode 'org-mode)
1567 (let ((current-prefix-arg 4)) ; Emulate C-u
1568 (call-interactively 'unpackaged/org-fix-blank-lines))))
1569
1570 (add-hook 'before-save-hook #'cribbed/org-mode-fix-blank-lines)
1571
1572
1573### Org Templates (`org-tempo`)
1574
1575 (with-eval-after-load 'org
1576 (add-to-list 'org-structure-template-alist
1577 '("el" . "src emacs-lisp")))
1578
1579
1580### Org Agenda
1581
1582 (cuss org-agenda-files
1583 (let ((list))
1584 (dolist (file '(;; add more files to this list
1585 "home.org"
1586 "work.org")
1587 list)
1588 (push (expand-file-name file org-directory) list))))
1589
1590 (define-key acdw/map (kbd "C-a") #'org-agenda)
1591
1592 (cuss org-agenda-span 5
1593 "Show today + N days.")
1594
1595 (cuss org-todo-keywords
1596 '((sequence "RECUR(r)" "TODO(t)" "|" "DONE(d)")
1597 (sequence "APPT(a)")
1598 (sequence "|" "CANCELLED(c)")))
1599
1600 (cuss org-agenda-skip-scheduled-if-done t)
1601 (cuss org-agenda-skip-deadline-if-done t)
1602 (cuss org-deadline-warning-days 0
1603 "Don't warn of an impending deadline.")
1604
1605 (cuss org-log-into-drawer "LOGBOOK"
1606 "Log state changes into the LOGBOOK drawer, instead of after
1607 the headline.")
1608 (cuss org-log-done t
1609 "Save CLOSED timestamp when done.")
1610
1611
1612### Capture
1613
1614
1615#### Templates
1616
1617 (cuss org-default-notes-file (expand-file-name "inbox.org"
1618 org-directory)
1619 "Put unfiled notes in ~/Org/inbox.org.")
1620
1621
1622 (cuss org-capture-templates
1623 '(("w" "Work Todo") ;;; Work stuff
1624 ("ws" "Small Business" entry
1625 (file+headline "work.org" "Small Business")
1626 "* TODO %?
1627 :PROPERTIES:
1628 :Via:
1629 :Note:
1630 :END:
1631 :LOGBOOK:
1632 - State \"TODO\" from \"\" %U
1633 :END:" :empty-lines 1)
1634 ("wc" "Career Center" entry
1635 (file+headline "work.org" "Career Center")
1636 "* TODO %?
1637 :PROPERTIES:
1638 :Via:
1639 :Note:
1640 :END:
1641 :LOGBOOK:
1642 - State \"TODO\" from \"\" %U
1643 :END:" :empty-lines 1)
1644 ("wg" "General" entry
1645 (file+headline "work.org" "General")
1646 "* TODO %?
1647 :PROPERTIES:
1648 :Via:
1649 :Note:
1650 :END:
1651 :LOGBOOK:
1652 - State \"TODO\" from \"\" %U
1653 :END:" :empty-lines 1)
1654
1655 ;;; Regular To-Dos
1656 ("t" "Todo")
1657 ("tt" "Todo" entry
1658 (file+headline "home.org" "TODOs")
1659 "* TODO %?
1660 :PROPERTIES:
1661 :Via:
1662 :Note:
1663 :END:
1664 :LOGBOOK:
1665 - State \"TODO\" from \"\" %U
1666 :END:" :empty-lines 1)
1667
1668 ("td" "Todo with deadline" entry
1669 (file+headline "home.org" "TODOs")
1670 "* TODO %?
1671 DEADLINE: %^t
1672 :PROPERTIES:
1673 :Via:
1674 :Note:
1675 :END:
1676 :LOGBOOK:
1677 - State \"TODO\" from \"\" %U
1678 :END:" :empty-lines 1)
1679
1680
1681 ("g" "Gift idea" entry
1682 (file+headline "home.org" "Gift ideas")
1683 "* %^{Who?}
1684 * %^{What?}" :empty-lines 1)
1685
1686 ("d" "Diary entry" entry
1687 (file+olp+datetree "diary.org")
1688 "* %?
1689 Entered on %U
1690
1691 " :empty-lines 1)))
1692
1693
1694#### Keybindings
1695
1696 (require 'org-capture)
1697
1698 (with-eval-after-load 'org-capture
1699 (define-key acdw/map (kbd "C-c") #'org-capture))
1700
1701
1702### Include Org links in source code
1703
1704 (straight-use-package '(org-link-minor-mode
1705 :host github
1706 :repo "seanohalpin/org-link-minor-mode"))
1707
1708 ;; enable in elisp buffers
1709 (add-hook 'emacs-lisp-mode-hook #'org-link-minor-mode)
1710
1711
1712## Git
1713
1714 (straight-use-package 'magit)
1715
1716 (define-key acdw/map "g" #'magit-status)
1717
1718
1719### Git file modes
1720
1721 (dolist (feat '(gitattributes-mode
1722 gitconfig-mode
1723 gitignore-mode))
1724 (straight-use-package feat)
1725 (require feat))
1726
1727
1728## Beancount mode
1729
1730 (straight-use-package '(beancount-mode
1731 :host github
1732 :repo "beancount/beancount-mode"))
1733 (require 'beancount)
1734
1735 (add-to-list 'auto-mode-alist '("\\.beancount\\'" . beancount-mode))
1736
1737 (defun acdw/disable-aggressive-indent ()
1738 "Turn `aggressive-indent-mode' off for a buffer."
1739 (aggressive-indent-mode -1))
1740
1741 (add-hook 'beancount-mode-hook #'outline-minor-mode)
1742 (add-hook 'beancount-mode-hook #'acdw/disable-aggressive-indent)
1743
1744 (define-key beancount-mode-map (kbd "M-n") #'outline-next-visible-heading)
1745 (define-key beancount-mode-map (kbd "M-p") #'outline-previous-visible-heading)
1746
1747
1748### Company integration with company-ledger
1749
1750 (straight-use-package 'company-ledger)
1751
1752 (with-eval-after-load 'company
1753 (add-to-list 'company-backends 'company-ledger))
1754
1755
1756## PDF Tools
1757
1758I’m only enabling this at home for now, since it requires building stuff.
1759
1760 (defun acdw/disable-visual-fill-column-mode ()
1761 "Disable `visual-fill-column-mode'."
1762 (visual-fill-column-mode -1))
1763
1764 (eval-when-compile
1765 (when-at :home
1766 (straight-use-package 'pdf-tools)
1767 (pdf-loader-install)
1768
1769 (add-hook 'pdf-view-mode-hook #'acdw/disable-visual-fill-column-mode)))
1770
1771
1772## E-book tools
1773
1774 (straight-use-package 'nov)
1775
1776 (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
1777
1778 (cuss nov-text-width t
1779 "Disable text filling -- `visual-fill-column-mode' takes care
1780 of that.")
1781
1782 (defun acdw/setup-nov-mode ()
1783 (visual-fill-column-mode +1)
1784 (setq visual-fill-column-center-text t)
1785 (text-scale-increase +1))
1786
1787 (add-hook 'nov-mode-hook #'acdw/setup-nov-mode)
1788
1789
1790## Email
1791
1792 (when-at (:home (executable-find "mu"))
1793 (add-to-list 'load-path
1794 "/usr/share/emacs/site-lisp/mu4e")
1795 (require 'mu4e)
1796
1797 (cuss mail-user-agent 'mu4e-user-agent)
1798
1799 (cuss mu4e-headers-skip-duplicates t)
1800 (cuss mu4e-view-show-images t)
1801 (cuss mu4e-view-show-addresses t)
1802 (cuss mu4e-compose-format-flowed t)
1803 (cuss mu4e-change-filenames-when-moving t)
1804 (cuss mu4e-attachments-dir "~/Downloads")
1805
1806 (cuss mu4e-maildir "~/.mail/fastmail")
1807 (cuss mu4e-refile-folder "/Archive")
1808 (cuss mu4e-sent-folder "/Sent")
1809 (cuss mu4e-drafts-folder "/Drafts")
1810 (cuss mu4e-trash-folder "/Trash")
1811
1812 (fset 'my-move-to-trash "mTrash")
1813 (define-key mu4e-headers-mode-map (kbd "d") 'my-move-to-trash)
1814 (define-key mu4e-view-mode-map (kbd "d") 'my-move-to-trash)
1815
1816 (cuss message-send-mail-function 'smtpmail-send-it)
1817 (cuss smtpmail-default-smtp-server "smtp.fastmail.com")
1818 (cuss smtpmail-smtp-server "smtp.fastmail.com")
1819 (cuss smtpmail-stream-type 'ssl)
1820 (cuss smtpmail-smtp-service 465)
1821 (cuss smtpmail-local-domain "acdw.net")
1822 (cuss mu4e-compose-signature
1823 "Best,\nCase\n")
1824
1825 (cuss mu4e-completing-read-function 'completing-read)
1826 (cuss message-kill-buffer-on-exit t)
1827 (cuss mu4e-confirm-quit nil)
1828
1829 (cuss mu4e-bookmarks
1830 '((:name "Unread"
1831 :query
1832 "flag:unread AND NOT flag:trashed AND NOT maildir:/Spam"
1833 :key ?u)
1834 (:name "Today"
1835 :query
1836 "date:today..now and not flag:trashed and not maildir:/Spam"
1837 :key ?t)
1838 (:name "This week"
1839 :query
1840 "date:7d..now and not maildir:/Spam and not flag:trashed"
1841 :hide-unread t
1842 :key ?w)))
1843
1844 (cuss mu4e-headers-fields
1845 '((:human-date . 12)
1846 (:flags . 6)
1847 (:mailing-list . 10)
1848 (:from-or-to . 22)
1849 (:thread-subject)))
1850
1851 (cuss mu4e-maildir-shortcuts
1852 `(("/INBOX" . ?i)
1853 (,mu4e-refile-folder . ?a)
1854 (,mu4e-sent-folder . ?s)
1855 (,mu4e-drafts-folder . ?d)
1856 (,mu4e-trash-folder . ?t)))
1857
1858 (cuss mu4e-get-mail-command (cond ((executable-find "mbsync")
1859 "mbsync -a"))
1860 "The command to update mail with.")
1861 (cuss mu4e-update-interval 300
1862 "Update automatically every 5 minutes.")
1863
1864 (defun acdw/setup-mu4e-headers-mode ()
1865 (visual-line-mode -1))
1866 (add-hook 'mu4e-headers-mode #'acdw/setup-mu4e-headers-mode)
1867
1868 (defun acdw/setup-mu4e-view-mode ()
1869 (setq visual-fill-column-center-text t)
1870 (visual-fill-column-mode +1))
1871 (add-hook 'mu4e-view-mode-hook #'acdw/setup-mu4e-view-mode)
1872 (add-hook 'mu4e-compose-mode-hook #'acdw/setup-mu4e-view-mode)
1873
1874 (declare-function mu4e "mu4e")
1875 (mu4e +1))
1876
1877
1878### Add a keybinding
1879
1880 (defun acdw/mu4e-or-warn ()
1881 "If `mu4e' is around, run it, or tell the user it isn't."
1882 (interactive)
1883 (if (featurep 'mu4e)
1884 (mu4e)
1885 (warn "Mu4e isn't available :/.")))
1886
1887 (define-key acdw/map "m" #'acdw/mu4e-or-warn)
1888
1889
1890## Smolweb
1891
1892
1893### A common function to make a cohesive smolweb experience
1894
1895 (defun acdw/setup-smolweb ()
1896 "Configure emacs to view the smolweb."
1897 (setq visual-fill-column-center-text t)
1898 (visual-fill-column-mode +1)
1899 (visual-line-mode +1)
1900 (variable-pitch-mode -1)
1901 (text-scale-increase +1))
1902
1903
1904### Elpher
1905
1906 (straight-use-package '(elpher
1907 :repo "git://thelambdalab.xyz/elpher.git"))
1908
1909 (with-eval-after-load 'no-littering
1910 (cuss elpher-certificate-directory
1911 (no-littering-expand-var-file-name "elpher-certificates/")))
1912
1913 (cuss elpher-ipv4-always t)
1914
1915 (cussface '(elpher-gemini-heading1
1916 ((t (:inherit (modus-theme-heading-1 variable-pitch))))))
1917 (cussface '(elpher-gemini-heading2
1918 ((t (:inherit (modus-theme-heading-2 variable-pitch))))))
1919 (cussface '(elpher-gemini-heading3
1920 ((t (:inherit (modus-theme-heading-3 variable-pitch))))))
1921
1922 (defun elpher:eww-browse-url (original url &optional new-window)
1923 "Handle gemini/gopher links with eww."
1924 (cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url)
1925 (require 'elpher)
1926 (elpher-go url))
1927 (t (funcall original url new-window))))
1928 (advice-add 'eww-browse-url :around 'elpher:eww-browse-url)
1929
1930 (with-eval-after-load 'elpher
1931 (define-key elpher-mode-map "n" #'elpher-next-link)
1932 (define-key elpher-mode-map "p" #'elpher-prev-link)
1933 (define-key elpher-mode-map "o" #'elpher-follow-current-link)
1934 (define-key elpher-mode-map "G" #'elpher-go-current))
1935
1936 (add-hook 'elpher-mode-hook #'acdw/setup-smolweb)
1937
1938 (autoload 'elpher-bookmarks "elpher")
1939 (define-key acdw/map "e" #'elpher-bookmarks)
1940
1941
1942### Gemini-mode
1943
1944 (straight-use-package '(gemini-mode
1945 :repo "https://git.carcosa.net/jmcbray/gemini.el.git"))
1946
1947 (add-to-list 'auto-mode-alist
1948 '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode))
1949
1950 (cussface '(gemini-heading-face-1
1951 ((t (:inherit (elpher-gemini-heading1))))))
1952 (cussface '(gemini-heading-face2
1953 ((t (:inherit (elpher-gemini-heading2))))))
1954 (cussface '(gemini-heading-face3
1955 ((t (:inherit (elpher-gemini-heading3))))))
1956
1957 (add-hook 'gemini-mode-hook #'acdw/setup-smolweb)
1958
1959
1960### Gemini-write
1961
1962 (straight-use-package '(gemini-write
1963 :repo "https://tildegit.org/acdw/gemini-write.git"))
1964 (require 'gemini-write)
1965
1966
1967## RSS
1968
1969
1970### elfeed
1971
1972 (straight-use-package 'elfeed)
1973 (require 'elfeed)
1974 (define-key acdw/map "w" 'elfeed)
1975
1976 (cuss elfeed-use-curl (executable-find "curl"))
1977 (cuss elfeed-curl-extra-arguments '("--insecure")
1978 "Extra arguments for curl.")
1979 (elfeed-set-timeout (* 60 3))
1980
1981 (defun acdw/setup-elfeed-show ()
1982 (setq visual-fill-column-center-text t)
1983 (visual-fill-column-mode +1))
1984
1985 (add-hook 'elfeed-show-mode-hook #'acdw/setup-elfeed-show)
1986
1987
1988### elfeed-protocol
1989
1990 (straight-use-package 'elfeed-protocol)
1991 (require 'elfeed-protocol)
1992
1993 (cuss elfeed-protocol-ttrss-maxsize 200)
1994
1995 (cuss elfeed-feeds (list
1996 (list "ttrss+https://acdw@rss.tildeverse.org"
1997 :use-authinfo t)))
1998
1999 ;; Uncomment this line if elfeed is giving problems.
2000 ;; (setq elfeed-log-level 'debug)
2001
2002 (elfeed-protocol-enable)
2003
2004 (defun acdw/update-elfeed-protocol-feeds ()
2005 "Wrap various (non-working) protocol updaters.
2006 I could probably do this in a much more ... better way."
2007 (interactive)
2008 (elfeed-protocol-ttrss-reinit "https://rss.tildeverse.org"))
2009
2010 (with-eval-after-load 'elfeed
2011 (define-key elfeed-search-mode-map "G" #'acdw/update-elfeed-protocol-feeds))
2012
2013
2014# System integration
2015
2016
2017## Linux
2018
2019
2020### Exec path from shell
2021
2022 (eval-when-compile
2023 (when-at :home
2024 (straight-use-package 'exec-path-from-shell)
2025 (defvar acdw/exec-path-from-shell-initialized nil
2026 "Stores whether we've initialized or not.")
2027 (unless acdw/exec-path-from-shell-initialized
2028 (exec-path-from-shell-initialize)
2029 (setq acdw/exec-path-from-shell-initialized (current-time)))))
2030
2031
2032# Areas for further research
2033
2034- [Use a minor-mode for keybindings](https://github.com/larstvei/dot-emacs#key-bindings)
2035
2036
2037# Appendices
2038
2039
2040## Emacs' files
2041
2042
2043### init.el
2044
2045 ;; init.el -*- lexical-binding: t -*-
2046
2047 (setq load-prefer-newer t)
2048
2049 (let* (;; Speed up init
2050 (gc-cons-threshold most-positive-fixnum)
2051 (file-name-handler-alist nil)
2052 ;; Config file names
2053 (conf (expand-file-name "config"
2054 user-emacs-directory))
2055 (conf-el (concat conf ".el"))
2056 (conf-org (concat conf ".org")))
2057 (unless (and (file-newer-than-file-p conf-el conf-org)
2058 (load conf 'no-error))
2059 ;; A plain require here just loads the older `org'
2060 ;; in Emacs' install dir. We need to add the newer
2061 ;; one to the `load-path', hopefully that's all.
2062 (add-to-list 'load-path (expand-file-name "straight/build/org"
2063 user-emacs-directory))
2064 (require 'org)
2065 (org-babel-load-file conf-org)))
2066
2067
2068### early-init.el
2069
2070 ;; early-init.el -*- no-byte-compile: t; -*-
2071
2072 ;; I use `straight.el' instead of `package.el'.
2073 (setq package-enable-at-startup nil)
2074
2075 ;; Don't resize the frame when loading fonts
2076 (setq frame-inhibit-implied-resize t)
2077 ;; Resize frame by pixels
2078 (setq frame-resize-pixelwise t)
2079
2080
2081## Ease tangling and loading of Emacs' init
2082
2083 (defun refresh-emacs (&optional disable-load)
2084 "Tangle `config.org', then byte-compile the resulting files.
2085 Then, load the byte-compilations unless passed with a prefix argument."
2086 (interactive "P")
2087 (let ((config (expand-file-name "config.org" user-emacs-directory)))
2088 (save-mark-and-excursion
2089 (with-current-buffer (find-file-noselect config)
2090 (let ((prog-mode-hook nil))
2091 ;; generate the readme
2092 (when (file-newer-than-file-p config (expand-file-name
2093 "README.md"
2094 user-emacs-directory))
2095 (message "%s" "Exporting README.md...")
2096 (require 'ox-md)
2097 (with-demoted-errors "Problem exporting README.md: %S"
2098 (org-md-export-to-markdown)))
2099 ;; tangle config.org
2100 (when (file-newer-than-file-p config (expand-file-name
2101 "config.el"
2102 user-emacs-directory))
2103 (message "%s" "Tangling config.org...")
2104 (add-to-list 'load-path (expand-file-name "straight/build/org/"
2105 user-emacs-directory))
2106 (require 'org)
2107 (let ((inits (org-babel-tangle)))
2108 ;; byte-compile resulting files
2109 (message "%s" "Byte-compiling...")
2110 (dolist (f inits)
2111 (when (string-match "\\.el\\'" f)
2112 (byte-compile-file f (not disable-load)))))))))))
2113
2114
2115## Ancillary scripts
2116
2117
2118### emacsdc
2119
2120Here's a wrapper script that'll start `emacs --daemon` if there isn't
2121one, and then launch `emacsclient` with the arguments. I'd recommend
2122installing with either `ln -s bin/emacsdc $HOME/.local/bin/`, or
2123adding `$HOME/.local/bin` to your `$PATH`.
2124
2125 if ! emacsclient -nc "$@" 2>/dev/null; then
2126 emacs --daemon
2127 emacsclient -nc "$@"
2128 fi
2129
2130
2131### Emacs.cmd
2132
2133Here's a wrapper script that'll run Emacs on Windows, with a
2134custom `$HOME`. I have mine setup like this: Emacs is downloaded
2135from [the GNU mirror](https://mirrors.tripadvisor.com/gnu/emacs/windows/emacs-27/emacs-27.1-x86_64.zip) and unzipped to `~/Downloads/emacs/`. For
2136some reason, Emacs by default sets `$HOME` to `%APPDATA%`, which
2137doesn’t make a lot of sense to me. I change it to
2138`%USERPROFILE%\Downloads\home`, and then run Emacs with the
2139supplied arguments.
2140
2141As far as creating a shortcut to the Desktop, you’ll have to do
2142that yourself – *apparently* Windows doesn’t have a built-in
2143shortcut-creating software >:(.
2144
2145 REM Set variables
2146 set HOME=%USERPROFILE%\Downloads\home
2147 set EMACS=%USERPROFILE%\Downloads\emacs\bin\runemacs.exe
2148
2149 REM Change the directory
2150 chdir %HOME%
2151
2152 REM Run "Quick Mode"
2153 REM "%EMACS%" -Q %*
2154
2155 REM Regular
2156 "%EMACS%" %*
2157
2158
2159### emacsclient.desktop
2160
2161from [taingram](https://www.taingram.org/blog/emacs-client.html).
2162
2163 [Desktop Entry]
2164 Name=Emacs Client
2165 GenericName=Text Editor
2166 Comment=Edit text
2167 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++;
2168 Exec=emacsclient -c %f
2169 Icon=emacs
2170 Type=Application
2171 Terminal=false
2172 Categories=Utility;TextEditor;
2173
2174
2175## License
2176
2177Copyright © 2020 Case Duckworth <acdw@acdw.net>
2178
2179This work is free. You can redistribute it and/or modify it under the
2180terms of the Do What the Fuck You Want To Public License, Version 2,
2181as published by Sam Hocevar. See the `LICENSE` file, tangled from the
2182following source block, for details.
2183
2184 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2185
2186 Version 2, December 2004
2187
2188 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
2189
2190 Everyone is permitted to copy and distribute verbatim or modified copies of
2191 this license document, and changing it is allowed as long as the name is changed.
2192
2193 DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2194
2195 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
2196
2197 0. You just DO WHAT THE FUCK YOU WANT TO.
2198
2199
2200### Note on the license
2201
2202It's highly likely that the WTFPL is completely incompatible with the
2203GPL, for what should be fairly obvious reasons. To that, I say:
2204
2205**SUE ME, RMS!**
2206
2207
2208## TODO Local variables
2209
2210One day, I’m going to add a Local Variables block to the end of this file, which will add an `after-save-hook` to auto-tangle the file after saving. But first I need to research how best to do that asynchronously. So.
2211