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