summary refs log tree commit diff stats
path: root/init.el
diff options
context:
space:
mode:
Diffstat (limited to 'init.el')
-rw-r--r--init.el608
1 files changed, 565 insertions, 43 deletions
diff --git a/init.el b/init.el index 7cf9f7a..9808ae2 100644 --- a/init.el +++ b/init.el
@@ -1,51 +1,573 @@
1;;; init.el -*- lexical-binding: t; coding: utf-8 -*- 1;;; init.el -*- lexical-binding: t; coding: utf-8 -*-
2;; Copyright (C) 2020 Case Duckworth 2;; Copyright (C) 2020-2021 Case Duckworth
3 3;;
4;; Author: Case Duckworth <acdw@acdw.net> 4;; Author: Case Duckworth <acdw@acdw.net>
5;; Created: Sometime during the Covid-19 lockdown, 2019 5;; Created: Sometime during Covid-19, 2020
6;; Keywords: configuration 6;; Keywords: configuration
7;; URL: https://tildegit.org/acdw/emacs 7;; URL https://tildegit.org/acdw/emacs
8;;
9;; This file is NOT part of GNU Emacs.
10;;
11;;; License:
12;;
13;; Everyone is permitted to do whatever with this software, without
14;; limitation. This software comes without any warranty whatsoever,
15;; but with two pieces of advice:
16;; - Don't hurt yourself.
17;; - Make good choices.
18;;
19;;; Comentary:
20;;
21;;; Code:
8 22
9;; This file is not part of GNU Emacs. 23;; User information
24(setq user-full-name "Case Duckworth"
25 user-mail-address "acdw@acdw.net"
26 calendar-location-name "Baton Rouge, LA"
27 calendar-latitude 30.4
28 calendar-longitude -91.1
29 calendar-date-style 'iso)
10 30
11;;; Commentary: 31;; Load newer files first
12;; This file is automatically tangled from config.org. 32(setq-default load-prefer-newer t)
13;; Hand edits will be overwritten!
14 33
15;;; Code: 34;; Make C-z more useful
35(defvar acdw/leader
36 (let ((map (make-sparse-keymap))
37 (c-z (global-key-binding "\C-z")))
38 (global-unset-key "\C-z")
39 (global-set-key "\C-z" map)
40 (define-key map "\C-z" c-z)
41 map)
42 "A leader key for apps and stuff.")
16 43
17(setq-default load-prefer-newer t) 44(defun when-unfocused (func &rest args)
45 "Run FUNC with ARGS iff all frames are out of focus."
46 (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
47 (apply func args)))
48
49;; Dialogs & alerts
50(setq-default use-dialog-box nil) ; Don't use a dialog box
51(fset 'yes-or-no-p #'y-or-n-p)
52
53(defun flash-mode-line ()
54 (ding)
55 (invert-face 'mode-line)
56 (run-with-timer 0.2 nil #'invert-face 'mode-line))
57
58(setq-default visible-bell nil ; Don't use a visible bell
59 ring-bell-function #'flash-mode-line)
60
61(defun hook--gc-when-unfocused ()
62 (when-unfocused #'garbage-collect))
63
64(add-function :after after-focus-change-function
65 #'hook--gc-when-unfocused)
66
67;; Minibuffer
68(setq-default
69 minibuffer-prompt-properties '(read-only t
70 cursor-intangible t
71 face minibuffer-prompt)
72 enable-recursive-minibuffers t
73 file-name-shadow-properties '(invisible t))
74(file-name-shadow-mode +1)
75(minibuffer-depth-indicate-mode +1)
76
77(use-package savehist
78 :straight nil
79 :init
80 (setq-default
81 savehist-file (expand-file-name "history" acdw/var-dir)
82 savehist-additional-variables '(kill-ring search-ring regexp-search-ring)
83 history-length t
84 history-delete-duplicates t
85 savehist-autosave-interval 60)
86 :config (savehist-mode +1))
87
88;; Backups
89(setq-default backup-by-copying t
90 delete-old-versions -1 ; Don't delete old versions
91 version-control t ; Make numeric backups
92 vc-make-backup-files t ; Backup version-controlled files
93 )
94
95(let ((dir (expand-file-name "backup" acdw/var-dir)))
96 (make-directory dir 'parents)
97 (setq-default backup-directory-alist
98 `((".*" . ,dir))))
99
100;; Lockfiles
101(setq-default create-lockfiles nil) ; Are these necessary?
102
103;; Autosaves
104(use-package super-save
105 :defer 5 ; This package can wait
106 :init
107 (setq-default
108 auto-save-default nil ; Don't use `auto-save' system
109 super-save-remote-files nil ; Don't save remote files
110 super-save-exclude '(".gpg") ; Wouldn't work anyway
111 super-save-auto-save-when-idle t)
112 :config
113 (super-save-mode +1))
114
115;; Auto-revert
116(global-auto-revert-mode +1) ; Automatically revert a file
117 ; to its on-disk contents
118
119(use-package saveplace
120 :straight nil
121 :init
122 (setq-default
123 save-place-file (expand-file-name "places" acdw/var-dir)
124 save-place-forget-unreadable-files (eq acdw/system :home))
125 :config (save-place-mode +1))
126
127(use-package recentf
128 :straight nil
129 :init
130 (setq recentf-save-file (expand-file-name "recentf" acdw/var-dir)
131 recentf-max-menu-items 100
132 recentf-max-saved-items nil
133 recentf-auto-cleanup 'never)
134 (defun maybe-save-recentf ()
135 "Save `recentf-file' every five minutes, but only when out of focus."
136 (defvar recentf--last-save (time-convert nil 'integer)
137 "When we last saved the `recentf-save-list'.")
138
139 (when (> (time-convert (time-since recentf--last-save) 'integer)
140 (* 60 5))
141 (setq-default recentf--last-save (time-convert nil 'integer))
142 (when-unfocused #'recentf-save-list)))
143 :config
144 (recentf-mode +1)
145 (add-to-list 'recentf-exclude acdw/var-dir)
146 (add-to-list 'recentf-exclude acdw/etc-dir)
147 (add-function :after after-focus-change-function
148 #'maybe-save-recentf))
149
150
151;; Uniquify
152(use-package uniquify
153 :straight nil
154 :init
155 (setq-default
156 uniquify-buffer-name-style 'forward ; bubble 'up' the directory tree
157 uniquify-separator "/" ; separate path elements
158 uniquify-after-kill-buffer-p t ; hook into buffer kills
159 uniquify-ignore-buffers-re "^\\*" ; don't worry about special buffers
160 ))
161
162;; Scratch
163(setq-default
164 inhibit-startup-screen t ; Don't show the splash screen
165 initial-buffer-choice t ; Start on *scratch*
166 initial-scratch-message
167 (concat ";; Howdy, "
168 (nth 0 (split-string user-full-name)) "!"
169 " Welcome to GNU Emacs.\n\n"))
170
171(defun immortal-scratch ()
172 "Don't kill *scratch* when asked to by `kill-buffer'."
173 (if (not (eq (current-buffer) (get-buffer "*scratch*")))
174 t
175 (bury-buffer)
176 nil))
177(add-hook 'kill-buffer-query-functions #'immortal-scratch)
178
179;; Easier buffer-killing
180(defun kill-a-buffer (&optional prefix)
181 "Kill a buffer and its window, prompting only on unsaved changes.
182
183`kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill:
1840 => Kill THIS buffer & window
1854 (C-u) => Kill OTHER buffer & window
18616 (C-u C-u) => Run the default `kill-buffer'."
187 (interactive "P")
188 (pcase (or (car prefix) 0)
189 (0 (kill-current-buffer)
190 (unless (one-window-p) (delete-window)))
191 (4 (other-window 1)
192 (kill-current-buffer)
193 (unless (one-window-p) (delete-window)))
194 (16 (let ((current-prefix-arg nil))
195 (kill-buffer)))))
196
197(bind-key "C-x k" #'kill-a-buffer)
198
199;; UTF-8 with LF line endings
200(set-charset-priority 'unicode)
201(set-language-environment "UTF-8")
202
203(prefer-coding-system 'utf-8-unix)
204(set-default-coding-systems 'utf-8-unix)
205(set-terminal-coding-system 'utf-8-unix)
206(set-keyboard-coding-system 'utf-8-unix)
207(set-selection-coding-system 'utf-8-unix)
208
209(setq-default
210 locale-coding-system 'utf-8-unix
211 coding-system-for-read 'utf-8-unix
212 coding-system-for-write 'utf-8-unix
213 buffer-file-coding-system 'utf-8-unix
214
215 org-export-coding-system 'utf-8-unix
216 org-html-coding-system 'utf-8-unix ; doesn't take from above
217
218 default-process-coding-system '(utf-8-unix . utf-8-unix)
219 x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
220
221(defun ewiki/no-junk-please-were-unixish ()
222 "Convert line endings to UNIX, dammit."
223 (let ((coding-str (symbol-name buffer-file-coding-system)))
224 (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
225 (set-buffer-file-coding-system 'unix))))
226
227(add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish)
228(add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish)
229
230;; Cursor
231(setq-default cursor-type 'bar
232 cursor-in-non-selected-windows nil)
233(blink-cursor-mode 0)
234
235;; Filling text
236(setq-default fill-column 80)
237(global-display-fill-column-indicator-mode +1)
238
239(bind-key "C-x f" #'find-file) ; I don't set `fill-column', ever
240
241(setq-default comment-auto-fill-only-comments t)
242;; Enable `auto-fill-mode' everywhere
243(add-hook 'text-mode-hook #'auto-fill-mode)
244(add-hook 'prog-mode-hook #'auto-fill-mode)
245;; Also enable `visual-line-mode' everywhere
246(global-visual-line-mode +1)
247;; "Fix" `visual-line-mode' in `org-mode'
248(defun hook--visual-line-fix-org-keys ()
249 (when (derived-mode-p 'org-mode)
250 (local-set-key (kbd "C-a") #'org-beginning-of-line)
251 (local-set-key (kbd "C-e") #'org-end-of-line)
252 (local-set-key (kbd "C-k") #'org-kill-line)))
253(add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys)
254
255(use-package visual-fill-column
256 :init (setq-default visual-fill-column-center-text t)
257 :hook visual-fill-column-mode
258 :config
259 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
260
261(when (fboundp 'global-so-long-mode)
262 (global-so-long-mode +1))
263
264;; Whitespace
265(setq-default whitespace-style '(empty ; remove blank lines at buffer edges
266 indentation ; clean up indentation
267 ;; mixed tabs & spaces
268 space-before-tab
269 space-after-tab))
270(add-hook 'before-save-hook #'whitespace-cleanup)
271
272(setq-default indent-tabs-mode t
273 tab-width 8)
274
275(use-package smart-tabs-mode
276 :config
277 (smart-tabs-insinuate 'c 'c++ 'java 'javascript 'cperl 'python 'ruby 'nxml))
278
279;; Window layouts
280(setq-default
281 split-width-threshold 100 ; minimum width for window splits
282 split-height-threshold 50 ; minimum height for window splits
283 display-buffer-alist ; how to display buffers
284 '((".*" . (display-buffer-reuse-window display-buffer-same-window)))
285 display-buffer-reuse-frames t ; allow reuse of frames
286 even-window-sizes nil ; avoid resizing windows to even them
287 help-window-select t ; select *Help* window when opened
288 )
289
290(defun vsplit-other-window ()
291 "Split the window vertically and switch to the new window."
292 (interactive)
293 (split-window-vertically)
294 (other-window 1 nil))
295
296(defun hsplit-other-window ()
297 "Split the window horizontally and switch to the new window."
298 (interactive)
299 (split-window-horizontally)
300 (other-window 1 nil))
301
302(bind-key "C-x 2" #'vsplit-other-window)
303(bind-key "C-x 3" #'hsplit-other-window)
304
305;; Theming
306
307(use-package form-feed
308 :config (global-form-feed-mode +1))
309
310(use-package modus-themes
311 :straight (:host gitlab :repo "protesilaos/modus-themes")
312 :demand
313 :init
314 (setq-default modus-themes-slanted-constructs t
315 modus-themes-bold-constructs t
316 modus-themes-region 'bg-only
317 modus-themes-org-blocks 'grayscale
318 modus-themes-headings '((1 . section)
319 (t . no-color))
320 modus-themes-scale-headings nil
321 modus-themes-mode-line nil)
322 :custom-face
323 (modus-theme-heading-1
324 ((t (:inherit (modus-theme-heading-1 fixed-pitch bold)))))
325 (modus-theme-heading-2
326 ((t (:inherit (modus-theme-heading-2 fixed-pitch bold)))))
327 (modus-theme-heading-3
328 ((t (:inherit (modus-theme-heading-3 fixed-pitch bold)))))
329 (modus-theme-heading-4
330 ((t (:inherit (modus-theme-heading-4 fixed-pitch bold)))))
331 (modus-theme-heading-5
332 ((t (:inherit (modus-theme-heading-5 fixed-pitch bold)))))
333 (modus-theme-heading-6
334 ((t (:inherit (modus-theme-heading-6 fixed-pitch bold)))))
335 (modus-theme-heading-7
336 ((t (:inherit (modus-theme-heading-7 fixed-pitch bold)))))
337 (modus-theme-heading-8
338 ((t (:inherit (modus-theme-heading-8 fixed-pitch bold))))))
339
340;; Change themes based on time of day
341
342(defun acdw/run-with-sun (sunrise-command sunset-command)
343 "Run commands at sunrise and sunset."
344 (let* ((times-regex (rx (* nonl)
345 (: (any ?s ?S) "unrise") " "
346 (group (repeat 1 2 digit) ":"
347 (repeat 1 2 digit)
348 (: (any ?a ?A ?p ?P) (any ?m ?M)))
349 (* nonl)
350 (: (any ?s ?S) "unset") " "
351 (group (repeat 1 2 digit) ":"
352 (repeat 1 2 digit)
353 (: (any ?a ?A ?p ?P) (any ?m ?M)))
354 (* nonl)))
355 (ss (sunrise-sunset))
356 (_m (string-match times-regex ss))
357 (sunrise-time (match-string 1 ss))
358 (sunset-time (match-string 2 ss)))
359 (run-at-time sunrise-time (* 60 60 24) sunrise-command)
360 (run-at-time sunset-time (* 60 60 24) sunset-command)
361 (run-at-time "0:00" (* 60 60 24) sunset-command)))
362
363(acdw/run-with-sun #'modus-themes-load-operandi
364 #'modus-themes-load-vivendi)
365
366(use-package minions
367 :config (minions-mode +1))
368
369(which-function-mode +1)
370
371(use-package which-key
372 :config (which-key-mode +1))
373
374(delete-selection-mode +1)
375
376(setq-default
377 save-interprogram-paste-before-kill t ; save existing text before replacing
378 yank-pop-change-selection t ; update X selection when rotating ring
379 x-select-enable-clipboard t ; Enable X clipboards
380 x-select-enable-primary t
381 mouse-drag-copy-region t ; Copy a region when mouse-selected
382 kill-do-not-save-duplicates t ; Don't append the same thing twice
383 )
384
385(use-package smartscan
386 :config
387 (global-smartscan-mode +1))
388
389(when (fboundp 'global-goto-address-mode)
390 (global-goto-address-mode +1))
391
392(use-package flyspell
393 :init
394 (setenv "LANG" "en_US")
395 (setq-default ispell-program-name "hunspell"
396 ispell-dictionary "en_US"
397 ispell-personal-dictionary "~/.hunspell_personal")
398 :hook
399 (text-mode . flyspell-mode)
400 (prog-mode . flyspell-prog-mode)
401 :config
402 (ispell-set-spellchecker-params)
403 (unless (file-exists-p ispell-personal-dictionary)
404 (write-region "" nil ispell-personal-dictionary nil 0)))
405
406(use-package flyspell-correct
407 :bind ("C-;" . flyspell-correct-wrapper))
408
409(setq-default show-paren-delay 0
410 show-paren-style 'mixed
411 show-paren-when-point-inside-paren t
412 show-paren-when-point-in-periphery t)
413(show-paren-mode +1)
414
415(add-hook 'prog-mode-hook #'electric-pair-local-mode)
416
417(setq-default prettify-symbols-unprettify-at-point 'right-edge)
418(add-hook 'prog-mode-hook #'prettify-symbols-mode)
419
420(add-hook 'after-save-hook
421 #'executable-make-buffer-file-executable-if-script-p)
422
423(setq-default compilation-ask-about-save nil ; just save the buffer
424 compilation-always-kill t ; kill the processes without asking
425 compilation-scroll-output 'first-error)
426
427(use-package reformatter
428 :demand)
429
430;; Shell scripts
431(setq-default sh-basic-offset 8
432 smie-indent-basic 8)
433
434(use-package flymake-shellcheck
435 :when (executable-find "shellcheck")
436 :hook sh-mode)
437
438(when (executable-find "shfmt")
439 (reformatter-define sh-format
440 :program "shfmt"
441 :lighter "Shfmt")
442 (add-hook 'sh-mode-hook #'sh-format-on-save-mode))
443
444(bind-key "M-/" #'hippie-expand)
445
446;; Tabs
447(setq-default
448 tab-bar-show 1 ; show the tab bar when more than one
449 tab-bar-new-tab-choice "*scratch*" ; what to show on a new tab
450 tab-bar-tab-name-function ; how to name a new tab
451 #'tab-bar-tab-name-current-with-count
452 tab-bar-history-limit 25 ; how many tabs to save in history
453 )
454
455(tab-bar-history-mode +1)
456
457;; Smart hungry delete
458(use-package smart-hungry-delete
459 :defer nil
460 :bind (("<backspace>" . smart-hungry-delete-backward-char)
461 ("C-d" . smart-hungry-delete-forward-char))
462 :config (smart-hungry-delete-add-default-hooks))
463
464;; Enable all commands
465(setq-default disabled-command-function nil)
466
467;; Magit
468(use-package magit
469 :bind ("C-z g" . magit-status))
470
471;; crux
472(use-package crux
473 :straight (:host github :repo "bbatsov/crux")
474 :bind
475 ("M-o" . crux-other-window-or-switch-buffer)
476 :config
477 (crux-with-region-or-line kill-ring-save)
478 (crux-with-region-or-line kill-region)
479 (crux-with-region-or-line comment-or-uncomment-region))
480
481;; Completion and... stuff
482(setq-default
483 completion-ignore-case t
484 read-buffer-completion-ignore-case t
485 read-file-name-completion-ignore-case t)
486
487(use-package icomplete-vertical
488 :demand
489 :init
490 (setq-default
491 icomplete-delay-completions-threshold 0
492 icomplete-max-delay-chars 0
493 icomplete-compute-delay 0
494 icomplete-show-matches-on-no-input t
495 icomplete-hide-common-prefix nil
496 icomplete-with-completion-tables t
497 icomplete-in-buffer t)
498 :bind (:map icomplete-minibuffer-map
499 ("<down>" . icomplete-forward-completions)
500 ("C-n" . icomplete-forward-completions)
501 ("<up>" . icomplete-backward-completions)
502 ("C-p" . icomplete-backward-completions)
503 ("C-v" . icomplete-vertical-toggle))
504 :config
505 (fido-mode -1)
506 (icomplete-mode +1)
507 (icomplete-vertical-mode +1))
508
509(use-package orderless
510 :after icomplete
511 :init (setq-default completion-styles '(orderless)))
512
513(use-package marginalia
514 :after icomplete
515 :init (setq-default marginalia-annotators
516 '(marginalia-annotators-heavy
517 marginalia-annotators-light))
518 :config (marginalia-mode +1))
18 519
19(message "%s..." "Loading init.el") 520(use-package consult
20(let* (;; Speed up init 521 :after icomplete
21 (gc-cons-threshold most-positive-fixnum) 522 :bind (;; C-c bindings (mode-specific-map)
22 ;; (gc-cons-percentage 0.6) 523 ("C-c h" . consult-history)
23 (file-name-handler-alist nil) 524 ("C-c m" . consult-mode-command)
24 ;; Config file names 525 ("C-c b" . consult-bookmark)
25 (config (expand-file-name "config" 526 ("C-c k" . consult-kmacro)
26 user-emacs-directory)) 527 ;; C-x bindings (ctl-x-map)
27 (config.el (concat config ".el")) 528 ("C-x M-:" . consult-complex-command) ; orig. repeat-complet-command
28 (config.org (concat config ".org")) 529 ("C-x b" . consult-buffer) ; orig. switch-to-buffer
29 (straight-org-dir (locate-user-emacs-file "straight/build/org"))) 530 ("C-x 4 b" . consult-buffer-other-window) ; orig. switch-to-buffer-other-window
30 ;; Okay, let's figure this out. 531 ("C-x 5 b" . consult-buffer-other-frame) ; orig. switch-to-buffer-other-frame
31 ;; `and' evaluates each form, and returns nil on the first that 532 ;; Custom M-# bindings for fast register access
32 ;; returns nil. `unless' only executes its body if the test 533 ("M-#" . consult-register-load)
33 ;; returns nil. So. 534 ("M-'" . consult-register-store) ; orig. abbrev-prefix-mark (unrelated)
34 ;; 1. Test if config.org is newer than config.el. If it is (t), we 535 ("C-M-#" . consult-register)
35 ;; *want* to evaluate the body, so we need to negate that test. 536 ;; Other custom bindings
36 ;; 2. Try to load the config. If it errors (nil), it'll bubble that 537 ("M-y" . consult-yank-pop) ; orig. yank-pop
37 ;; to the `and' and the body will be evaluated. 538 ("<help> a" . consult-apropos) ; orig. apropos-command
38 (unless (and (not (file-newer-than-file-p config.org config.el)) 539 ;; M-g bindings (goto-map)
39 (load config :noerror)) 540 ("M-g e" . consult-compile-error)
40 ;; A plain require here just loads the older `org' 541 ("M-g g" . consult-goto-line) ; orig. goto-line
41 ;; in Emacs' install dir. We need to add the newer 542 ("M-g M-g" . consult-goto-line) ; orig. goto-line
42 ;; one to the `load-path', hopefully that's all. 543 ("M-g o" . consult-outline)
43 (when (file-exists-p straight-org-dir) 544 ("M-g m" . consult-mark)
44 (add-to-list 'load-path straight-org-dir)) 545 ("M-g k" . consult-global-mark)
45 ;; Load config.org 546 ("M-g i" . consult-imenu)
46 (message "%s..." "Loading config.org") 547 ("M-g I" . consult-project-imenu)
47 (require 'org) 548 ;; M-s bindings (search-map)
48 (org-babel-load-file config.org) 549 ("M-s f" . consult-find)
49 (message "%s... Done" "Loading config.org"))) 550 ("M-s L" . consult-locate)
50(message "%s... Done." "Loading init.el") 551 ("M-s g" . consult-grep)
51;;; init.el ends here 552 ("M-s G" . consult-git-grep)
553 ("M-s r" . consult-ripgrep)
554 ("M-s l" . consult-line)
555 ("M-s m" . consult-multi-occur)
556 ("M-s k" . consult-keep-lines)
557 ("M-s u" . consult-focus-lines)
558 ;; Isearch integration
559 ("M-s e" . consult-isearch)
560 :map isearch-mode-map
561 ("M-e" . consult-isearch) ; orig. isearch-edit-string
562 ("M-s e" . consult-isearch) ; orig. isearch-edit-string
563 ("M-s l" . consult-line)) ; required by consult-line to detect isearch
564 :init
565 (setq register-preview-delay 0
566 register-preview-function #'consult-register-format)
567 (advice-add #'register-preview :override #'consult-register-window)
568 (setq xref-show-xrefs-function #'consult-xref
569 xref-show-definitions-function #'consult-xref)
570 :config
571 ;; (setq consult-preview-key 'any)
572 ;; (setq consult-preview-key (kbd "M-p"))
573 (setq consult-narrow-key "<"))