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