code / dots / emacs/init.el

1;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*-
2;; Author: Case Duckworth <acdw@acdw.net>
3;; Bankruptcy: 12
4
5;;; Initialization -- see also ~/.emacs.d/early-init.el
6;; TODO -- think about making C-q a personal leader
7
8(setopt custom-file (locate-user-emacs-file "custom.el"))
9(load custom-file :no-error)
10(add-hook 'Custom-mode-hook
11          (lambda ()
12            (run-with-idle-timer 0.25 nil #'custom-show-all-widgets)))
13
14(defvar user-private-file (locate-user-emacs-file "private.el")
15  "Private customizations")
16;; make sure it's really private!
17(and (file-exists-p user-private-file)
18     (= (file-attribute-user-id (file-attributes user-private-file))
19        (user-uid)) ; is it owned by this me?
20     (set-file-modes user-private-file #o600))
21(load user-private-file :no-error)
22
23(setopt case-foreground "black"
24        case-background "linen"
25        case-highlight-1 "cyan"
26        case-highlight-2 "salmon"
27        case-string "misty rose"
28        case-mode-line-active "pink"
29        case-mode-line-inactive "bisque"
30        case-region "lavender"
31        case-shadow "peru")
32(load-theme 'case :no-confirm) ; see ~/.emacs.d/case-theme.el
33
34(after init setup-faces)
35
36;;; Basic settings
37
38;; Auth
39(with-package ((keepassxc-shim
40                :url "https://codeberg.org/acdw/keepassxc-shim.el"))
41  (keepassxc-shim-activate)
42  (setopt auth-sources '("secrets:default"))
43  (add-hook 'auth-info-hook #'truncate-lines-local-mode))
44
45;; ENVIRONMENT
46(setenv "PAGER" "cat")  ; emacs is a pager
47(setenv "TERM" "dumb")  ; no fancy graphics!
48(setenv "NO_COLOR" "1") ; no color!
49
50;; Startup
51(setopt inhibit-startup-screen t)
52(setopt initial-buffer-choice #'eshell)
53(setopt initial-scratch-message nil)
54
55;; Dialogs
56(setopt use-dialog-box nil)
57(setopt use-file-dialog nil)
58(setopt read-answer-short t)
59(setopt use-short-answers t)
60(setopt echo-keystrokes 0.01)
61
62;; Cursor
63(blink-cursor-mode -1)
64
65;; Whitespace
66(add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line)
67
68(setopt whitespace-style '(face trailing tabs tab-mark))
69(setopt whitespace-global-modes '(not rcirc-mode jabber-chat-mode))
70(global-whitespace-mode)
71(hide-minor-mode 'global-whitespace-mode)
72(with-eval-after-load 'whitespace
73  (setf/alist whitespace-display-mappings 'tab-mark
74    '(9 [?· 9] [?» 9] [?\\ 9])))
75
76;; Automatically indent buffer when saving
77(define-minor-mode indent-on-save-mode
78  "Automatically re-indent the buffer on save."
79  :lighter " >"
80  (if indent-on-save-mode
81      (add-hook 'before-save-hook #'fixup-whitespace nil t)
82    (remove-hook 'before-save-hook #'fixup-whitespace t)))
83
84(add-hook 'prog-mode-hook #'indent-on-save-mode)
85(remove-hook 'makefile-mode-hook #'indent-on-save-mode) ; makefiles is dumb
86(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
87
88;; Comments
89(setopt comment-column 0)
90(setopt comment-indent-offset 1)
91
92;;; Efficiency ... ?
93
94(setopt load-prefer-newer t)
95(setopt read-process-output-max (* 512 1024))
96
97(when (and (featurep 'native-compile)
98           (fboundp 'native-comp-available-p)
99           (native-comp-available-p))
100  (setopt native-comp-jit-compilation t)
101  (setopt package-native-compile t)
102  (setopt native-comp-async-report-warnings-errors 'silent)
103  (setopt native-comp-warning-on-missing-source nil))
104
105(setopt byte-compile-warnings nil)
106(setopt byte-compile-verbose nil)
107
108(setf/alist display-buffer-alist '("\\`\\*\\(Warnings\\|Compile-Log\\)\\*\\'")
109  '((display-buffer-no-window)
110    (allow-no-window . t)))
111
112;;; UI stuff
113
114(setopt cursor-type 'bar)
115(setopt cursor-in-non-selected-windows nil)
116(setopt highlight-nonselected-windows nil)
117(hide-minor-mode 'buffer-face-mode)
118(setopt tab-bar-show 1)
119
120(with-package (mixed-pitch)
121  (add-hook 'text-mode-hook #'mixed-pitch-mode))
122
123(with-package (valign)                   ; needed for variable-pitch org-mode
124  (add-hook 'org-mode-hook #'valign-mode)
125  (setopt valign-fancy-bar t))
126
127(with-package (form-feed)
128  (global-form-feed-mode))
129
130;;; Mode line
131
132(setopt mode-line-position-line-format '("%l"))
133(setopt mode-line-position-column-line-format '("%l:%c"))
134
135(package-ensure 'minions)
136
137(defvar mode-line-major-mode-keymap*
138  (let ((map (make-sparse-keymap)))
139    (bindings--define-key map [mode-line down-mouse-1]
140      `(menu-item "Menu Bar" ignore
141                  :filter ,(lambda (_) (mouse-menu-major-mode-map))))
142    (define-key map [mode-line mouse-2] #'describe-mode)
143    (define-key map [mode-line mouse-3] #'minions-minor-modes-menu)
144    map)
145  "Keymap to display on major mode.")
146
147(defun make-mode-line-mode-disabler (lighter help mode)
148  (propertize lighter
149              'help-echo (concat help "\n1:cancel")
150              'face 'italic
151              'mouse-face 'mode-line-highlight
152              'local-map
153              (make-mode-line-mouse-map
154               'mouse-1 (lambda (ev) (interactive "e")
155                          (with-selected-window
156                              (posn-window (event-start ev))
157                            (funcall mode -1)
158                            (force-mode-line-update))))))
159
160(setopt mode-line-format
161        `("%e"
162          mode-line-front-space
163          (:propertize ("" mode-line-modified mode-line-remote)
164                       display (min-width (5.0)))
165          " %[" mode-line-buffer-identification " %]"
166          (vc-mode (" [" vc-mode "]"))
167          " ( "
168          (:propertize ("" mode-name)
169                       help-echo "Major mode\n1:menu\n2:help\n3:minor"
170                       mouse-face mode-line-highlight
171                       local-map ,mode-line-major-mode-keymap*)
172          (auto-fill-function
173           ,(make-mode-line-mode-disabler "-f" "Auto-filling"
174                                          #'auto-fill-mode))
175          (visual-line-mode
176           ,(make-mode-line-mode-disabler "-v" "Visual lines"
177                                          #'visual-line-mode))
178          (truncate-lines-local-mode
179           ,(make-mode-line-mode-disabler "-t" "Truncating lines"
180                                          #'truncate-lines-local-mode))
181          " "
182          (defining-kbd-macro (:propertize "🔴" help-echo "Defining kbd macro"))
183          (isearch-mode (:propertize "🔍" help-echo "Searching"))
184          (overwrite-mode
185           ,(make-mode-line-mode-disabler "✒️" "Overwriting"
186                                          #'overwrite-mode))
187          (debug-on-error
188           ,(make-mode-line-mode-disabler "‼️" "Debug on error"
189                                          (lambda (_)
190                                            (setq debug-on-error nil))))
191          (debug-on-quit
192           ,(make-mode-line-mode-disabler "🚫" "Debug on quit"
193                                          (lambda (_)
194                                            (setq debug-on-quit nil))))
195          ") "
196          (text-scale-mode text-scale-mode-lighter)
197          (mode-line-process (" " mode-line-process " "))
198          "  "
199          ,(let ((lc/width 5.0))
200             `(line-number-mode
201               (column-number-mode
202                (:propertize "%l/%c"
203                             help-echo "Line/column"
204                             display (min-width (,lc/width)))
205                (:propertize "%l"
206                             help-echo "Line"
207                             display (min-width (,lc/width))))
208               (column-number-mode
209                (:propertize "/%c"
210                             help-echo "Column"
211                             display (min-width (,lc/width)))
212                "")))
213          (:propertize ("" (-3 "%o")c)
214                       help-echo "Position in buffer"
215                       display (min-width (6.0)))
216          ,(propertize "%n"
217                       'help-echo "Narrowed\n1:widen"
218                       'mouse-face 'mode-line-highlight
219                       'local-map (make-mode-line-mouse-map
220                                   'mouse-1 #'mode-line-widen))
221          ("" global-mode-string)
222          mode-line-end-spaces))
223
224;;; Completions
225
226(setopt tab-always-indent 'complete)
227(setopt completion-styles '(basic partial-completion substring flex))
228
229(setopt completion-ignore-case t)
230(setopt read-buffer-completion-ignore-case t)
231(setopt read-file-name-completion-ignore-case t)
232(setopt completion-flex-nospace t)
233
234(setopt completion-show-help nil)
235(setopt completions-detailed t)
236(setopt completions-group t)
237(setopt completion-auto-help 'visible)
238(setopt completion-auto-select 'second-tab)
239(setopt completions-header-format nil)
240(setopt completions-format 'one-column)
241(setopt completions-max-height 10)
242
243(setf/alist display-buffer-alist "\\`\\*Completions\\*\\'"
244  '(nil (window-parameters (mode-line-format . " --- %b"))))
245
246(keymap-set minibuffer-local-map "C-p" #'minibuffer-previous-completion)
247(keymap-set minibuffer-local-map "C-n" #'minibuffer-next-completion)
248(keymap-set minibuffer-local-map "M-DEL" #'minibuffer-delete-directory)
249(keymap-set completion-list-mode-map "C-g" #'quit-window) ; is this a good idea?
250
251;;; Minibuffer
252
253(setopt enable-recursive-minibuffers t)
254(setopt minibuffer-default-prompt-format " [%s]")
255(minibuffer-depth-indicate-mode)
256(minibuffer-electric-default-mode)
257
258(setopt minibuffer-prompt-properties '( read-only t
259                                        cursor-intangible t
260                                        face minibuffer-prompt))
261(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
262
263(setopt file-name-shadow-properties '(invisible t intangible t))
264(file-name-shadow-mode)
265
266(setopt history-length t)
267(setopt history-delete-duplicates t)
268(setopt savehist-save-minibuffer-history t)
269(setopt savehist-autosave-interval 5)
270(setopt savehist-additional-variables
271        '(kill-ring
272          command-history
273          set-variable-value-history
274          custom-variable-history
275          query-replace-history
276          read-expression-history
277          minibuffer-history
278          read-char-history
279          face-name-history
280          bookmark-history
281          file-name-history))
282(savehist-mode)
283
284(define-minor-mode truncate-lines-local-mode
285  "Toggle `truncate-lines' in the current buffer."
286  :lighter ""
287  (setq-local truncate-lines truncate-lines-local-mode))
288
289(add-hook 'completion-list-mode-hook #'truncate-lines-local-mode)
290(add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode)
291
292(with-package (visual-fill-column)
293  (setopt visual-fill-column-enable-sensible-window-split t)
294  (add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
295  (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
296
297(with-package (adaptive-wrap)
298  (add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)
299  (add-hook 'gemtext-mode-hook #'adaptive-wrap-prefix-mode))
300
301;;; Completing-read and friends
302
303;; Consult
304(with-package (consult t)
305  (keymap-global-set "C-x b" #'consult-buffer)
306  (keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
307  (keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
308  (keymap-global-set "C-x r b" #'consult-bookmark)
309  (keymap-global-set "M-y" #'consult-yank-pop)
310  (keymap-global-set "M-g g" #'consult-goto-line)
311  (keymap-global-set "M-g M-g" #'consult-goto-line)
312  (keymap-global-set "M-g o" #'consult-outline)
313  (keymap-global-set "M-g m" #'consult-mark)
314  (keymap-global-set "M-g i" #'consult-imenu)
315  (keymap-global-set "M-s d" #'consult-find)
316  (keymap-global-set "M-s D" #'consult-locate)
317  (keymap-global-set "M-s l" #'consult-line)
318  (keymap-global-set "M-s k" #'consult-keep-lines)
319  (keymap-global-set "M-s u" #'consult-focus-lines)
320  (keymap-global-set "M-s e" #'consult-isearch-history)
321  (keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
322  (keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
323  (keymap-set isearch-mode-map "M-s l" #'consult-line)
324  (setopt xref-show-xrefs-function #'consult-xref)
325  (setopt xref-show-definitions-function #'xref-show-definitions-completing-read)
326  (setopt consult-preview-key "M-."))
327
328(define-advice completing-read-multiple (:filter-args (args) indicator)
329  (cons (format "[CRM%s] %s"
330                (replace-regexp-in-string
331                 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
332                 crm-separator)
333                (car args))
334        (cdr args)))
335
336;; Marginalia
337(with-package (marginalia)
338  (marginalia-mode))
339
340;; Embark
341(with-package (embark)
342  (package-ensure 'embark-consult)
343  (keymap-global-set "M-." #'embark-dwim)
344  (keymap-global-set "C-." #'embark-act)
345  (keymap-global-set "C-h B" #'embark-bindings)
346  (add-to-list 'display-buffer-alist
347               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil
348                 (window-parameters (mode-line-format . none))))
349  (add-hook 'embark-collect-mode #'consult-preview-at-point-mode)
350  (setopt embark-indicators '(embark-mixed-indicator
351                              embark-highlight-indicator
352                              embark-isearch-highlight-indicator)))
353
354;;; Frames / Windows
355
356(winner-mode)
357
358;;; Files
359
360(setopt auto-revert-verbose nil)
361(setopt global-auto-revert-non-file-buffers t)
362(global-auto-revert-mode)
363
364(setopt create-lockfiles nil)
365(setopt require-final-newline t)
366(setopt view-read-only t)
367(setopt save-silently t)
368(setopt delete-by-moving-to-trash t)
369(setopt auto-save-default t)
370(setopt auto-save-no-message t)
371(setopt auto-save-interval 30)
372(setopt auto-save-timeout 5)
373(setopt auto-save-visited-interval 5)
374(setopt remote-file-name-inhibit-auto-save t)
375(setopt remote-file-name-inhibit-auto-save-visited t)
376(let ((auto-save-dir (locate-user-emacs-file "auto-save/")))
377  (unless (file-exists-p auto-save-dir)
378    (make-directory auto-save-dir t))
379  (add-to-list 'auto-save-file-name-transforms
380               `(".*" ,auto-save-dir t)))
381(auto-save-visited-mode)
382
383(add-hook 'window-selection-change-functions
384          (defun save-old-selected-window-buffer (frame)
385            (with-current-buffer
386                (window-buffer (frame-old-selected-window))
387              (when (and (buffer-file-name) (buffer-modified-p))
388                (save-buffer)))))
389
390(add-hook 'buffer-list-update-hook
391          (defun save-other-buffer ()
392            (with-current-buffer (other-buffer)
393              (when (and (buffer-file-name) (buffer-modified-p))
394                (save-buffer)))))
395
396(add-function :after after-focus-change-function
397              (defun focus-out-save ()
398                (save-some-buffers t)))
399
400(setopt backup-by-copying t)
401(setopt version-control t)
402(setopt kept-new-versions 3)
403(setopt kept-old-versions 3)
404(setopt delete-old-versions t)
405(add-to-list 'backup-directory-alist '("^/dev/shm/" . nil))
406(add-to-list 'backup-directory-alist '("^/tmp/" . nil))
407(when-let ((xrd (getenv "XDG_RUNTIME_DIR")))
408  (add-to-list 'backup-directory-alist (cons xrd nil)))
409(add-to-list 'backup-directory-alist
410             (cons "." (locate-user-emacs-file "backup/"))
411             :append)
412
413(setopt recentf-max-menu-items 100)
414(setopt recentf-max-saved-items nil)
415(setopt recentf-case-fold-search t)
416(with-eval-after-load 'recentf
417  (add-to-list 'recentf-exclude "-autoloads.el\\'"))
418(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
419(add-hook 'after-save-hook #'recentf-save-list)
420(recentf-mode)
421
422(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux))
423(save-place-mode)
424
425(add-hook 'find-file-not-found-functions #'create-missing-directories)
426
427;;; Buffers
428
429;; Unique names
430(setopt uniquify-buffer-name-style 'forward)
431(setopt uniquify-after-kill-buffer-p t)
432(setopt uniquify-ignore-buffers-re "^\\*")
433
434;; Persistent undo
435(with-package (undo-fu-session)
436  (setopt undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
437                                               "/git-rebase-todo\\'"))
438  (undo-fu-session-global-mode))
439
440;; Encodings
441(set-language-environment "UTF-8")
442(setopt buffer-file-coding-system 'utf-8-unix)
443(setopt coding-system-for-read 'utf-8-unix)
444(setopt coding-system-for-write 'utf-8-unix)
445(setopt default-process-coding-system '(utf-8-unix . utf-8-unix))
446(setopt locale-coding-system 'utf-8-unix)
447(set-charset-priority 'unicode)
448(prefer-coding-system 'utf-8-unix)
449(set-default-coding-systems 'utf-8-unix)
450(set-terminal-coding-system 'utf-8-unix)
451(set-keyboard-coding-system 'utf-8-unix)
452(pcase system-type
453  ((or 'ms-dos 'windows-nt)
454   (set-clipboard-coding-system 'utf-16-le)
455   (set-selection-coding-system 'utf-16-le))
456  (_
457   (set-selection-coding-system 'utf-8)
458   (set-clipboard-coding-system 'utf-8)))
459
460;;; Search
461
462(setopt isearch-lazy-count nil)
463(setopt isearch-regexp-lax-whitespace t)
464(setopt isearch-wrap-pause 'no)
465(setopt search-whitespace-regexp "[       ]+")
466(setopt search-ring-max 256)
467(setopt regexp-search-ring-max 256)
468
469(define-advice isearch-cancel (:before () add-to-history)
470  "Add search string to history when canceling isearch."
471  (unless (string-equal "" isearch-string)
472    (isearch-update-ring isearch-string isearch-regexp)))
473
474(with-package (isearch-mb t)
475  (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
476  (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history)
477  (add-to-list 'isearch-mb--after-exit #'consult-line)
478  (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line)
479  (isearch-mb-mode))
480
481;; Default to regexen
482(setopt search-default-mode t) ; Isearch
483(keymap-global-set "M-%" #'query-replace-regexp)
484(keymap-global-set "C-M-%" #'query-replace)
485
486;;; Keybindings
487
488(repeat-mode)
489
490;; Separate C-<key> and control keys from halcyon ascii days
491;; (define-key input-decode-map [?\C-i] [C-i])
492;; (define-key input-decode-map [?\C-m] [C-m])
493
494;; Modified default keybindings
495(keymap-global-set "C-x C-k" #'kill-buffer-dwim)
496(keymap-global-set "M-o" #'other-window-dwim)
497(keymap-global-set "C-x o" #'other-window-dwim)
498(keymap-global-set "C-x 0" #'delete-window-dwim)
499(keymap-global-set "M-SPC" #'cycle-spacing*)
500(keymap-global-set "C-x C-c" #'save-buffers-kill*)
501(keymap-global-set "C-g" #'keyboard-quit*)
502(keymap-global-set "C-M-\\" #'fixup-whitespace)
503(keymap-global-set "C-g" #'keyboard-quit-dwim)
504
505;; New bindings for existing stuff
506(keymap-global-set "C-x C-b" #'ibuffer)
507(keymap-global-set "M-/" #'hippie-expand)
508(keymap-global-set "M-u" #'universal-argument)
509(keymap-set universal-argument-map "M-u" #'universal-argument-more)
510
511;; Prefix maps
512(keymap-global-set "C-c d"
513                   (defun insert-current-iso8601 ()
514                     (interactive)
515                     (insert (format-time-string "%FT%TZ" (current-time) t))))
516
517(keymap-global-set "C-c i"
518                   (define-keymap
519                     :prefix 'find-init-map
520                     "i" (find-user-file init)
521                     "e" (find-user-file early-init
522                           (locate-user-emacs-file "early-init.el"))
523                     "c" (find-user-file custom custom-file)
524                     "p" (find-user-file private)
525                     "t" (find-user-file theme
526                           (locate-user-emacs-file "case-theme.el"))
527                     "x" (find-user-file exwm
528                           (expand-file-name "~/.exwm"))
529                     "s" #'scratch-buffer
530                     "a" (find-user-file authinfo
531                           (expand-file-name "~/.authinfo"))))
532
533(keymap-global-set "C-c t"
534                   (define-keymap
535                     :prefix 'toggle-map
536                     "e" #'toggle-debug-on-error
537                     "q" #'toggle-debug-on-quit
538                     "c" #'column-number-mode
539                     "l" #'line-number-mode
540                     "L" #'display-line-numbers-mode
541                     "t" #'truncate-lines-local-mode
542                     "o" #'overwrite-mode
543                     "f" #'auto-fill-mode))
544
545(keymap-global-set "M-c"
546                   (define-keymap
547                     :prefix 'case-map
548                     "M-u" #'upcase-dwim "u"    #'upcase-dwim
549                     "M-c" #'capitalize-dwim "c" #'capitalize-dwim
550                     "M-l" #'downcase-dwim "l" #'downcase-dwim))
551(put 'upcase-dwim 'repeat-map 'case-map)
552(put 'capitalize-dwim 'repeat-map 'case-map)
553(put 'downcase-dwim 'repeat-map 'case-map)
554
555;;; Un-keybinds
556;; Why do I want to zoom with the mouse?
557(keymap-global-unset "C-<wheel-down>" t)
558(keymap-global-unset "C-<wheel-up>" t)
559;; These are ripe for re-binding
560(keymap-global-unset "C-\\" t)
561(keymap-global-unset "M-l" t)
562(keymap-global-unset "<f2>" t)
563
564;; Key settings
565(setopt set-mark-command-repeat-pop t)
566
567;;; Text-editing packages
568
569;; Hungry delete
570(with-package (hungry-delete)
571  (setopt hungry-delete-chars-to-skip " \t")
572  (with-eval-after-load 'hungry-delete
573    (dolist (m '( eshell-mode
574                  eww-mode
575                  special-mode
576                  jabber-chat-mode))
577      (add-to-list 'hungry-delete-except-modes m)))
578  (global-hungry-delete-mode))
579
580;; ;;; Writing
581
582(add-hook 'text-mode-hook #'visual-line-mode)
583
584(setopt dictionary-server "dict.org")
585(setopt dictionary-use-single-buffer t)
586
587;;; Programming
588
589(add-hook 'prog-mode-hook #'electric-pair-local-mode)
590(setopt tab-width 8)
591(setopt sh-basic-offset tab-width)
592(setopt perl-indent-level tab-width)
593(setopt c-basic-offset tab-width)
594
595(defvar space-indent-modes '(emacs-lisp-mode
596                             lisp-interaction-mode
597                             lisp-mode
598                             scheme-mode
599                             python-mode
600                             haskell-mode
601                             text-mode
602                             web-mode
603                             css-mode)
604  "Modes to indent with spaces, not tabs.")
605
606(add-hook 'prog-mode-hook
607          (defun indent-tabs-mode-maybe ()
608            (setq indent-tabs-mode
609                  (if (apply #'derived-mode-p space-indent-modes) nil t))))
610
611(setf/alist auto-mode-alist "\\`ok\\'" #'sh-mode)
612
613;; Eldoc
614(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
615
616;; Elisp
617(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun)
618(keymap-set emacs-lisp-mode-map "C-c C-m"
619            (defun pp-macroexpand-defun ()
620              (interactive)
621              (beginning-of-defun)
622              (forward-sexp)
623              (pp-macroexpand-last-sexp nil)))
624(keymap-set emacs-lisp-mode-map "C-c C-b"
625            (defun eval-buffer@pulse () (interactive)
626                   (eval-buffer)
627                   (pulse-momentary-highlight-region (point-min) (point-max))))
628(advice-add 'eval-region :after #'pulse@eval)
629(add-hook 'emacs-lisp-mode-hook #'elisp-enable-lexical-binding)
630
631;; Makefile
632(setopt makefile-backslash-align nil)
633(setopt makefile-cleanup-continuations t)
634
635(add-hook 'makefile-mode-hook
636          (^local-unhook 'write-file-functions 'makefile-warn-suspicious-lines))
637(add-hook 'makefile-mode-hook
638          (^local-unhook 'write-file-functions 'makefile-warn-continuations))
639
640;; Scheme -- CHICKEN
641(setopt scheme-program-name (or (executable-find "csi")))
642(add-to-list 'auto-mode-alist '("\\.egg\\'" . scheme-mode))
643
644;; Scheme Indentation
645(defun scheme-module-indent (state indent-point normal-indent) 0)
646(put 'module 'scheme-indent-function 'scheme-module-indent)
647(put 'and-let* 'scheme-indent-function 1)
648(put 'parameterize 'scheme-indent-function 1)
649(put 'handle-exceptions 'scheme-indent-function 1)
650(put 'when 'scheme-indent-function 1)
651(put 'unless 'scheme-indent-function 1)
652(put 'match 'scheme-indent-function 1)
653
654;; Geiser
655(with-package (geiser)
656  (package-ensure 'geiser-chicken)
657  (setopt geiser-mode-auto-p nil)
658  (setopt geiser-repl-history-filename "~/.emacs.d/geiser-history")
659  (setopt geiser-chicken-init-file "~/.csirc")
660  (add-hook 'scheme-mode-hook #'geiser-mode)
661  (add-hook 'geiser-repl-mode-hook #'electric-pair-local-mode)
662  (advice-add 'geiser-eval-region :after #'pulse@eval))
663
664;; Lisp
665(with-package (sly)
666  (setopt inferior-lisp-program (executable-find "sbcl"))
667  (setopt sly-command-switch-to-existing-lisp 'ask) ; oof
668  (setopt sly-lisp-implementations
669          `((sbcl ("sbcl" "--dynamic-space-size" "2000")
670                  :coding-system utf-8-unix)))
671  (add-hook 'lisp-mode-hook #'sly-mode)
672  (add-hook 'sly-mrepl-mode-hook #'electric-pair-mode)
673
674  ;; Completion
675  (after sly-completion ; Follow my other completion setup
676    (sly-symbol-completion-mode -1)
677    (setf sly-complete-symbol-function #'sly-flex-completions)
678    (keymap-set sly--completion-transient-mode-map
679                "TAB" #'sly-next-completion)
680    (keymap-set sly--completion-transient-mode-map
681                "<backtab>" #'sly-prev-completion)
682
683    (define-advice sly-completion-annotation
684        (:filter-return (annotation) fix-spacing)
685      (concat (propertize " " 'display '(space :align-to center))
686              annotation)))
687
688  ;; Misc. integration
689  (load "/home/acdw/.quicklisp/clhs-use-local.el" t)
690  (put 'define-package 'sly-common-lisp-indent-function 1)
691  (put 'define-glossary-term 'sly-common-lisp-indent-function 2)
692
693  ;; Sly contribs
694  (add-to-list 'sly-contribs 'sly-mrepl)
695  (package-ensure 'sly-repl-ansi-color)
696  (add-to-list 'sly-contribs 'sly-repl-ansi-color t)
697  (package-ensure 'sly-asdf)
698  (add-to-list 'sly-contribs 'sly-asdf t)
699  (package-ensure 'sly-macrostep)
700  (add-to-list 'sly-contribs 'sly-macrostep t)
701  (package-ensure 'sly-quicklisp)
702  (add-to-list 'sly-contribs 'sly-quicklisp t)
703
704  (require 'sly-autoloads)
705
706  (defvar sly-mrepl-previous-buffer nil
707    "The buffer visited before sly-mrepl.")
708
709  (defun sly-mrepl* ()
710    (interactive)
711    (if (bound-and-true-p sly-buffer-connection)
712        (if sly-mrepl-previous-buffer
713            (switch-to-buffer-other-window sly-mrepl-previous-buffer)
714          (other-window))
715      (progn
716        (setq sly-mrepl-previous-buffer (current-buffer))
717        (if (bound-and-true-p sly-net-processes)
718            (sly-mrepl #'switch-to-buffer-other-window)
719          (sly)))))
720
721  (keymap-set lisp-mode-map "C-c C-z" #'sly-mrepl*)
722  (after sly-mode-hook
723    (keymap-set sly-mode-map "C-c C-z" #'sly-mrepl*)
724    (keymap-unset sly-mode-map "C-c i" t)
725    (keymap-set sly-mode-map "C-c C-i" #'sly-import-symbol-at-point)))
726
727(require 'autoinsert)
728(setf/alist auto-insert-alist '("\\.asd\\'" . "ASDF System Definition")
729  '("System name? "
730    "(defsystem \"" str "\"" \n
731    '(setq v1 (or user-full-name
732                  (skeleton-read "System author? ")))
733    ":author \"" v1 "\"" \n
734    ":maintainer \"" v1 "\"" \n
735    '(setq v2 (skeleton-read "License? " "BSD 3-Clause"))
736    ":license \"" v2 "\"" \n
737    ":homepage \"\"" \n
738    ":version \"0.1\"" \n
739    ":depends-on (\"mgl-pax\")" \n
740    ":components "
741    "((:module \"src\"" \n ":serial t" \n
742    ":components ((:file \"" str "\"))))" \n
743    ":description \"\"" \n
744    '(setq v3 (format "\"%s/tests\"" str))
745    ":in-order-to ((test-op (test-op " v3 "))))"
746    \n \n
747    "(defsystem " v3 \n
748    ":author \"" v1 "\"" \n
749    ":license \"BSD-3\"" \n
750    ":depends-on (\"" str "\"" \n "\"try\")" \n
751    ":components "
752    "((:module \"t\"" \n ":serial t" \n
753    ":components ((:file \"" str "\")))))"))
754(setf/alist auto-insert-alist '("\\.lisp\\'" . "Lisp package file")
755  '(nil
756    '(setq v1 (skeleton-read "Package name? "))
757    '(setq v2 (format ":net.acdw.%s" v1))
758    "(mgl-pax:define-package " v2 \n
759    "(:nicknames :" v1 ")" \n
760    "(:use #:common-lisp)" \n
761    "(:import-from #:mgl-pax #:defsection))" \n
762    "(in-package " v2 ")"))
763
764;;; Forth
765(with-package (forth-mode)
766  (defun forth-switch* ()
767    (interactive)
768    (if forth-interaction-buffer
769        (forth-switch-to-output-buffer)
770      (run-forth)))
771  (after forth-mode
772    (keymap-set forth-mode-map "C-c C-c" #'forth-eval-defun)
773    (advice-add #'forth-eval-region :after #'pulse@eval)))
774
775;;; VC
776
777;; Fossil
778(package-ensure 'vc-fossil)
779
780;;; Compilation
781
782(setopt compilation-always-kill t)
783(setopt compilation-ask-about-save nil)
784(setopt compilation-scroll-output t)
785(setopt compilation-auto-jump-to-first-error t)
786(setopt compilation-max-output-line-length nil)
787
788;;; Languages
789
790(package-ensure 'gemtext-mode)
791(package-ensure 'markdown-mode)
792
793;;; Miscellaneous
794
795;; Settings
796(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
797(add-hook 'messages-buffer-mode-hook
798          (^turn-off 'display-fill-column-indicator-mode))
799(after prog-mode-hook
800  auto-fill-mode
801  electric-pair-local-mode)
802(context-menu-mode)
803(delete-selection-mode)
804(global-goto-address-mode)
805(global-so-long-mode)
806(pixel-scroll-precision-mode)
807(setopt bookmark-save-flag 1)
808(setopt disabled-command-function nil)
809(setopt display-fill-column-indicator-character ?·)
810(setopt electric-pair-skip-whitespace 'chomp)
811(setopt eval-expression-print-length nil)
812(setopt eval-expression-print-level nil)
813(setopt fill-column 80)
814(setopt finger-X.500-host-regexps '(".*tilde.*"))
815(setopt help-window-keep-selected t)
816(setopt help-window-select t)
817(setopt read-extended-command-predicate #'command-completion-default-include-p)
818(setopt recenter-positions '(top middle bottom))
819(setopt save-interprogram-paste-before-kill t)
820(setopt scroll-conservatively 20)
821(setopt show-paren-delay 0.01)
822(setopt show-paren-style 'parenthesis)
823(setopt show-paren-when-point-in-periphery t)
824(setopt show-paren-when-point-inside-paren t)
825(setopt switch-to-buffer-obey-display-actions t)
826(setopt tmm-completion-prompt nil)
827(setopt tmm-mid-prompt " -- ")
828(setopt x-underline-at-descent-line t)
829(show-paren-mode)
830(tooltip-mode -1)
831
832;; Advice & Hooks
833
834(define-advice canonically-space-region
835    (:around (orig &rest args) double-space-sentences)
836  "Always double-space sentences canonically."
837  (let ((sentence-end-double-space t))
838    (apply orig args)))
839
840(define-advice switch-to-buffer (:after (&rest _) normal-mode)
841  "Automatically determine the mode for non-file buffers."
842  (when-let ((_ (and (eq major-mode 'fundamental-mode)
843                     (not buffer-file-name)))
844             (buffer-file-name (buffer-name)))
845    (normal-mode)))
846
847(add-hook 'special-mode-hook
848          (defun hl-line@special-mode ()
849            (unless (derived-mode-p 'help-mode ; add other modes here
850                                    'Info-mode
851                                    'Man-mode
852                                    'eww-mode
853                                    'elpher-mode)
854              (hl-line-mode))))
855(add-hook 'dired-mode-hook #'hl-line-mode)
856
857;;; Eshell
858
859(setopt cookie-file
860        (expand-file-name "oblique.txt" (car (process-lines "xdg-user-dir"
861                                                            "DOCUMENTS"))))
862(setopt eshell-banner-message (format "%s\n" (cookie cookie-file)))
863(setopt eshell-prompt-function
864        (defun @eshell-prompt ()
865          (let ((rootp (zerop (user-uid))))
866            (concat "( "
867                    (unless (= 0 eshell-last-command-status)
868                      (format "*%d " eshell-last-command-status))
869                    (abbreviate-file-name (eshell/pwd))
870                    (if rootp ":root" "")
871                    " ) "))))
872(setopt eshell-prompt-regexp "^(.*) ")
873(setopt eshell-destroy-buffer-when-process-dies t)
874(setopt eshell-error-if-no-glob t)
875(setopt eshell-hist-ignoredups 'erase)
876(setopt eshell-kill-on-exit t)
877(setopt eshell-prefer-lisp-functions t)
878(setopt eshell-prefer-lisp-variables t)
879(setopt eshell-scroll-to-bottom-on-input 'this)
880(setopt eshell-history-size 1024)
881
882(keymap-global-set "C-z" #'popup-eshell)
883(keymap-global-set "C-c C-z" #'popup-eshell)
884(add-hook 'eshell-first-time-mode-hook
885          (defun @eshell-once ()
886            (keymap-set eshell-mode-map "C-z" #'quit-window)))
887(add-hook 'eshell-mode-hook
888          (defun @eshell-fix-completion () ; No idea why this is necessary
889            (kill-local-variable 'completion-ignore-case)))
890
891(keymap-global-set "M-!" #'eshell-command)
892
893;; Eat
894(with-package (eat)
895  (add-hook 'eshell-mode-hook #'eat-eshell-mode))
896
897;;; Browsing
898
899;; Dired (files)
900(add-hook 'dired-mode-hook #'dired-hide-details-mode)
901(add-hook 'dired-mode-hook #'truncate-lines-local-mode)
902(setopt dired-auto-revert-buffer t)
903(setopt dired-clean-confirm-killing-deleted-buffers nil)
904(setopt dired-create-destination-dirs 'always)
905(setopt dired-do-revert-buffer t)
906(setopt dired-dwim-target t)
907(setopt dired-hide-details-hide-symlink-targets nil)
908(setopt dired-listing-switches "-AlFhv --group-directories-first")
909(setopt dired-ls-F-marks-symlinks t)
910(setopt dired-no-confirm '(byte-compile load chgrp chmod chown copy move
911                                        hardlink symlink shell touch))
912(setopt dired-recursive-copies 'always)
913(setopt dired-recursive-deletes 'always)
914(after dired
915  (require 'dired-x)
916  (setopt dired-omit-files (regexp-concat dired-omit-files
917                                          ;; "^\\..+$" ; hidden files
918                                          ;; CHICKEN ... this may be overkill
919                                          "\\.s?o$"
920                                          "\\.import\\.scm$"
921                                          "\\.\\(build\\|install\\)\\.sh$"
922                                          "\\.link$"))
923  (keymap-set dired-mode-map ")" #'dired-omit-mode)
924
925  (define-advice dired-jump (:around (fn &rest r))
926    (let ((dired-kill-when-opening-new-dired-buffer t))
927      (apply fn r)))
928  (keymap-set dired-mode-map "C-j" #'dired-jump))
929
930(with-package (dired-subtree)
931  (after dired
932    (keymap-set dired-mode-map "i" #'dired-subtree-toggle)))
933
934(with-package (dired-hide-dotfiles)
935  (after dired
936    (keymap-set dired-mode-map "." #'dired-hide-dotfiles-mode)))
937
938(with-package (trashed)
939  (setopt trashed-action-confirmer 'y-or-n-p)
940  (setopt trashed-use-header-line t)
941  (setopt trash-sort-key '("Date deleted" . t))
942  (setopt trashed-date-format "%Y-%m-%d %H:%M:%s")
943  (after trashed-mode-hook
944    fixed-pitch-mode))
945
946;; Elpher (gemini/gopher)
947(with-package (elpher)
948  (after elpher
949    ;; Try to emulate eww bindings if possible
950    (keymap-set elpher-mode-map "l" #'elpher-back)
951    (keymap-set elpher-mode-map "g" #'elpher-reload)
952    (keymap-set elpher-mode-map "G" #'elpher-go)
953    (keymap-set elpher-mode-map "v" #'elpher-view-raw)
954    (keymap-set elpher-mode-map "E" #'elpher-set-gopher-coding-system)))
955
956;;; HTTP browsing
957
958;; Eww / Shr
959(setopt shr-max-width nil)                ; covered in hook below
960(setopt shr-max-image-proportion 0.9)
961(setopt shr-discard-aria-hidden t)
962(setopt eww-auto-rename-buffer
963        (defun title+url ()
964          (when (eq major-mode 'eww-mode)
965            (let ((title (plist-get eww-data :title))
966                  (url (plist-get eww-data :url)))
967              (cond
968               ((and title url) (format "*eww: %s :: %s" title url))
969               ((or title url) (format "*eww: %s") (or title url)))))))
970(after eww-after-render-hook
971  (fixed-pitch-mode)
972  (visual-fill-column-mode)
973  (eww-reload t))
974(with-eval-after-load 'eww
975  (setopt eww-use-browse-url ".")
976  (keymap-set eww-mode-map "B" #'bookmark-jump)
977  (keymap-set eww-mode-map "b" #'bookmark-set)
978  (keymap-unset eww-mode-map "M-n" t)
979  (keymap-unset eww-mode-map "M-p" t))
980
981;; Browse-url
982(setopt browse-url-browser-function #'eww-browse-url)
983(setopt browse-url-firefox-program (executable-find "librewolf"))
984(setopt browse-url-firefox-arguments '("--new-tab"))
985(setopt browse-url-firefox-new-window-is-tab t)
986
987(setopt browse-url-generic-program (executable-find "dillo"))
988(setopt browse-url-generic-args nil)
989
990(setopt browse-url-secondary-browser-function #'browse-url-firefox)
991
992(with-package (link-hint)
993  (keymap-global-set "M-l"
994                     (define-keymap
995                       :prefix 'link-map
996                       "M-l" #'link-hint-open-link "l" #'link-hint-open-link
997                       "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link))
998  ;; With link-hint we get avy "for free"
999  (keymap-global-set "M-j" #'avy-goto-char-timer)
1000  (setopt avy-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
1001
1002;; PDFs
1003(with-package (pdf-tools)
1004  (pdf-loader-install))
1005
1006;; EPUBs
1007(when (libxml-available-p)
1008  (with-package (nov)
1009    (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
1010    (setopt nov-text-width t)
1011    (add-hooks 'nov-mode-hook
1012      (^turn-off #'whitespace-mode)
1013      (^turn-off #'hl-line-mode)
1014      #'visual-line-mode
1015      #'visual-fill-column-mode
1016      #'variable-pitch-mode)))
1017
1018;;; EXWM
1019
1020(setf/alist display-buffer-alist shell-command-buffer-name-async
1021  '(display-buffer-no-window))
1022
1023(when (and (getenv "DISPLAY") (getenv "IN_EXWM"))
1024  (package-ensure 'exwm t)
1025  (load (expand-file-name "~/.exwm")))
1026
1027;;; Emacs everywhere
1028
1029(if (cl-every #'executable-find '("xclip" "xdotool" "xprop" "xwininfo"))
1030    (with-package (emacs-everywhere)
1031      (setopt emacs-everywhere-frame-parameter
1032              '((name . "emacs-everywhere")
1033                (fullscreen . nil)
1034                (width . 80)
1035                (height . 24)))
1036      (setopt emacs-everywhere-top-padding nil))
1037  (defalias #'emacs-everywhere #'ignore))
1038
1039;;; Lua
1040
1041(with-package (lua-mode)
1042  (setopt lua-indent-level 4)
1043  (add-hook 'lua-mode-hook (^turn-off #'indent-tabs-mode)))
1044
1045;;; Fish
1046
1047(when (executable-find "fish")
1048  (package-ensure 'fish-mode)
1049  (with-package (fish-completion t)
1050    (global-fish-completion-mode)))
1051
1052(when (executable-find "bash")
1053  (with-package (bash-completion)
1054    (bash-completion-setup)
1055    (add-hooks 'eshell-mode-hook
1056      (^local-hook 'completion-at-point-functions
1057                   #'bash-completion-capf-nonexclusive))))
1058
1059;;; Go
1060
1061(package-ensure 'go-mode)
1062
1063;;; Web
1064
1065(with-package (web-mode)
1066  (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
1067  (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
1068  (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
1069  (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
1070  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
1071  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
1072  (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
1073  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)))
1074
1075(with-package ((elastic-indent
1076                :url "https://github.com/jyp/elastic-modes")
1077               t)
1078  (require 'elastic-table)
1079  (after variable-pitch-mode
1080    elastic-indent-mode
1081    elastic-table-mode)
1082  (after (completion-list-mode-hook
1083          dired-mode-hook
1084          ibuffer-mode-hook)
1085    fixed-pitch-mode))
1086
1087;;; Gnus
1088
1089(setopt gnus-select-method
1090        '(nnimap "Fastmail"
1091                 (nnimap-address "imap.fastmail.com")
1092                 (nnimap-user "acdw@fastmail.com")
1093                 (nnimap-authenticator login)
1094                 (nnimap-server-port 993)
1095                 (nnimap-stream ssl)))