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