diff options
Diffstat (limited to 'README.md')
-rw-r--r-- | README.md | 2211 |
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 | |||
21 | Straight 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 | |||
51 | Straight can't bootstrap itself on Windows, so I've wrapped the | ||
52 | bootstrap 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 | |||
74 | Now, I'll *try* running it regular-style, ignoring the errors. If it | ||
75 | doesn'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 | |||
120 | This 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 | |||
130 | I 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 | |||
203 | from [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 | |||
271 | from [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 | |||
369 | Since this *comes* with smart mode line, I’m just going to use it, | ||
370 | instead of `diminish` or another package. I do have to write this | ||
371 | helper 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 | |||
660 | from [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 | |||
673 | from [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 | |||
780 | from [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 | |||
796 | from [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 | |||
804 | I add it to the `find-file-hook` *and* `before-save-hook` because I | ||
805 | don't want to ever work with anything other than UNIX line endings | ||
806 | ever again. I just don't care. Even Microsoft Notepad can handle | ||
807 | UNIX 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 | |||
843 | Because 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 | |||
881 | I’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 | |||
1075 | from [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 | |||
1178 | see 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 | |||
1191 | This 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 | |||
1322 | from [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 | |||
1335 | I’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 | |||
1385 | from [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 | |||
1524 | from [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 | |||
1758 | I’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 | |||
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 | |||
2120 | Here's a wrapper script that'll start `emacs --daemon` if there isn't | ||
2121 | one, and then launch `emacsclient` with the arguments. I'd recommend | ||
2122 | installing with either `ln -s bin/emacsdc $HOME/.local/bin/`, or | ||
2123 | adding `$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 | |||
2133 | Here's a wrapper script that'll run Emacs on Windows, with a | ||
2134 | custom `$HOME`. I have mine setup like this: Emacs is downloaded | ||
2135 | from [the GNU mirror](https://mirrors.tripadvisor.com/gnu/emacs/windows/emacs-27/emacs-27.1-x86_64.zip) and unzipped to `~/Downloads/emacs/`. For | ||
2136 | some reason, Emacs by default sets `$HOME` to `%APPDATA%`, which | ||
2137 | doesn’t make a lot of sense to me. I change it to | ||
2138 | `%USERPROFILE%\Downloads\home`, and then run Emacs with the | ||
2139 | supplied arguments. | ||
2140 | |||
2141 | As far as creating a shortcut to the Desktop, you’ll have to do | ||
2142 | that yourself – *apparently* Windows doesn’t have a built-in | ||
2143 | shortcut-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 | |||
2161 | from [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 | |||
2177 | Copyright © 2020 Case Duckworth <acdw@acdw.net> | ||
2178 | |||
2179 | This work is free. You can redistribute it and/or modify it under the | ||
2180 | terms of the Do What the Fuck You Want To Public License, Version 2, | ||
2181 | as published by Sam Hocevar. See the `LICENSE` file, tangled from the | ||
2182 | following 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 | |||
2202 | It's highly likely that the WTFPL is completely incompatible with the | ||
2203 | GPL, for what should be fairly obvious reasons. To that, I say: | ||
2204 | |||
2205 | **SUE ME, RMS!** | ||
2206 | |||
2207 | |||
2208 | ## TODO Local variables | ||
2209 | |||
2210 | One 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 | |||