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.el2336
1 files changed, 949 insertions, 1387 deletions
diff --git a/init.el b/init.el index ffe7f81..3fcf374 100644 --- a/init.el +++ b/init.el
@@ -1,1228 +1,977 @@
1;;; emacs init --- an init for emacs -*- lexical-binding: t; -*- 1;;; init.el --- a config of one's own -*- lexical-binding: t; -*-
2;; by C. Duckworth <acdw@acdw.net> 2;; by C. Duckworth <acdw@acdw.net>
3;; URL: https://git.acdw.net/emacs 3;; Bankruptcy: 9.3
4;; Bankruptcy: 9 4
5;; 5;;; Code:
6;; Everyone is permitted to do whatever they like with this software 6
7;; without limitation. This software comes without any warranty 7;;; Remove when done bankrupting
8;; whatsoever, but with two pieces of advice: 8
9;; - Be kind to yourself. 9(defkeys t "C-x C-c" #'restart-emacs)
10;; - Make good choices. 10
11 11;;; My extras
12(yoke +emacs 12
13 (require* '+emacs '+window '+lisp) 13(push (expand-file-name (locate-user-emacs-file "lisp")) load-path)
14 ;; Settings 14(require 'acdw) ; Omnibus fun stuff
15 (setc truncate-string-ellipsis "…" 15
16 ring-bell-function #'ignore 16;;; Basic defaults
17 read-file-name-completion-ignore-case t 17
18 comment-auto-fill-only-comments t 18(use-package no-littering
19 password-cache t 19 :ensure t
20 password-cache-expiry (* 60 60) 20 :demand t
21 switch-to-buffer-in-dedicated-window 'pop 21 :custom
22 switch-to-buffer-obey-display-actions t 22 (no-littering-etc-directory etc/)
23 initial-buffer-choice (defun +initial-buffer-choose () 23 (no-littering-var-directory var/)
24 (cond 24 (custom-file (etc/ "custom.el"))
25 ((equal (get-buffer "*Messages*") 25 (auto-save-file-name-transforms `(("." ,(var/ "auto-save/") t)))
26 (other-buffer)) 26 (auto-save-list-file-prefix (var/ "auto-save/.saves-" t))
27 (get-buffer "*scratch*")) 27 (backup-directory-alist `(("." . ,(var/ "backup/" t)))))
28 (:else (other-buffer))))) 28
29 ;; "Safe" variables 29(use-package custom-allowed
30 (dolist (var+pred 30 :load-path "~/src/emacs/custom-allowed/"
31 '((browse-url-browser-function 31 :custom
32 ;; All types defined by custom are safe. 32 (custom-file (private/ "custom.el"))
33 . (lambda (f) 33 :config
34 ;; Whooooo boy 34 (dolist (var '(safe-local-variable-values
35 (memq f (mapcar (lambda (i) 35 warning-suppress-types
36 (plist-get (cdr i) :value)) 36 ispell-buffer-session-localwords
37 (seq-filter 37 calendar-latitude
38 (lambda (i) 38 calendar-longitude
39 (eq (car i) 'function-item)) 39 user-full-name
40 (cdr (get 'browse-url-browser-function 40 user-mail-address))
41 'custom-type))))))))) 41 (add-to-list 'custom-allowed-variables var))
42 (put (car var+pred) 'safe-local-variable (cdr var+pred))) 42 :hook
43 ;; Keys 43 (after-init-hook . custom-allowed-load-custom-file))
44 (defkeys t 44
45 "C-x C-k" #'kill-current-buffer 45(use-package modus-themes
46 "C-/" #'undo-only 46 :load-path "~/usr/share/emacs/30.0.50/etc/themes/"
47 "C-?" #'undo-redo 47 :custom
48 "C-x C-c" (defun delete-frame-or-quit (arg) 48 (modus-themes-bold-constructs t)
49 (interactive "P") 49 (modus-themes-italic-constructs t)
50 (cond (arg (delete-frame nil :force)) 50 (modus-themes-variable-pitch-ui nil))
51 ((= 1 (length (frame-list))) 51
52 (and (yes-or-no-p "Kill emacs? ") 52(use-package dawn
53 (save-buffers-kill-emacs t))) 53 :load-path "~/src/emacs/dawn/"
54 (:else (delete-frame)))) 54 :after modus-themes
55 "C-x r q" (defun really-quit-emacs (arg) 55 :config
56 (interactive "P") 56 (load-theme 'modus-operandi :noconfirm :noenable)
57 (cond (arg (save-buffers-kill-emacs t)) 57 (load-theme 'modus-vivendi :noconfirm :noenable)
58 (:else (save-buffers-kill-terminal t)))) 58 (defhook custom-allowed-after-load-hook
59 "M-SPC" #'+cycle-spacing 59 (dawn-schedule-themes 'modus-operandi
60 ;; "M-/" #'hippie-expand ; `hippie-completing-read' 60 'modus-vivendi)
61 "M-=" #'count-words 61 (set-face-attribute 'default nil
62 "C-x C-b" #'ibuffer 62 :family "IBM Plex Mono"
63 "C-x 4 n" #'clone-buffer 63 :height 100)
64 "S-<down-mouse-1>" #'mouse-set-mark 64 (set-face-attribute 'variable-pitch nil
65 "C-x 0" #'+delete-window-or-bury-buffer 65 :family "Georgia"
66 ;; "M-j" nil ; `avy' 66 :height 1.2)))
67 "<Scroll_Lock>" nil 67
68 "C-z" nil 68(use-package midnight
69 "M-o" #'other-window|switch-buffer 69 :config
70 "C-M-;" #'+lisp-comment-or-uncomment-sexp 70 (add-hook 'midnight-mode-hook #'recentf-cleanup)
71 "C-x 5 z" #'suspend-frame 71 (midnight-mode))
72 "C-x f" #'find-file 72
73 "C-c t" (defmap toggle-map 73(use-package sophomore
74 "A map for toggling various settings." 74 :load-path "~/src/emacs/sophomore/"
75 "d" (defmap toggle-debug-map 75 :config
76 "Easily toggle debug flavors." 76 (sophomore-enable-all)
77 "e" #'toggle-debug-on-error 77 (sophomore-disable 'view-hello-file
78 "q" #'toggle-debug-on-quit) 78 'describe-gnu-project
79 "w" #'toggle-word-wrap 79 'suspend-frame)
80 "t" #'toggle-truncate-lines 80 (sophomore-mode))
81 "c" #'column-number-mode 81
82 "l" #'line-number-mode 82;;; Completions
83 "v" (defmap toggle-view-map 83
84 "Easily toggle UI elements' views." 84(use-package vertico
85 "c" #'display-fill-column-indicator-mode 85 :ensure t
86 "l" #'display-line-numbers-mode 86 :custom
87 "m" #'menu-bar-mode 87 (resize-mini-windows 'grow-only)
88 "t" #'tool-bar-mode 88 (vertico-cycle t)
89 "s" #'scroll-bar-mode))) 89 :init
90 (defkeys text-mode-map 90 (use-package vertico-directory
91 "C-M-k" #'kill-paragraph 91 :after vertico
92 "C-o" (defun open-paragraph (&optional arg) 92 :config
93 "Open a paragraph after paragraph at point. 93 (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
94A paragraph is defined as continguous non-empty lines of text 94 (vertico-mode))
95surrounded by empty lines, so opening a paragraph means to make 95
96three blank lines, then place the point on the second one. 96(use-package marginalia
97 97 :ensure t
98Called with prefix ARG, open a paragraph before point." 98 :demand t
99 ;; TODO: Take an integer as ARG, allowing for skipping paragraphs up and down. 99 :config
100 (interactive "*P") 100 (add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode)
101 ;; Go to next blank line. This /isn't/ `end-of-paragraph-text' because 101 (marginalia-mode))
102 ;; that's weird with org, and I'm guessing other modes too.
103 (unless (looking-at "^$") (forward-line (if arg -1 +1)))
104 (while (and (not (looking-at "^$"))
105 (= 0 (forward-line (if arg -1 +1)))))
106 (newline)
107 (when arg (newline) (forward-line -2))
108 (delete-blank-lines)
109 (newline 2)
110 (previous-line)))
111 ;; Hooks
112 (add-hook 'after-save-hook
113 #'executable-make-buffer-file-executable-if-script-p)
114 (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
115 (add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs)
116 (add-hook 'text-mode-hook #'abbrev-mode)
117 (add-hook 'find-file-hook #'+vc-off-when-remote)
118 (add-hook 'prog-mode-hook #'auto-fill-mode)
119 ;; Advice
120 (add-function :after after-focus-change-function
121 #'+save-some-buffers-debounce)
122 (define-advice keyboard-escape-quit (:around (fn &rest r) keep-window-open)
123 "Don't close quits on `keyboard-escape-quit'."
124 (let ((buffer-quit-function #'ignore))
125 (apply fn r)))
126 ;; Faces
127 (set-face-attribute 'default nil :family "Comic Code" :height 100)
128 (set-face-attribute 'bold nil :family "Comic Code" :weight 'bold)
129 (set-face-attribute 'variable-pitch nil :family "Comic Code")
130 ;; Modes
131 (winner-mode))
132
133(yoke custom ; This is `cus-edit' but meh
134 (require '+custom)
135 (setf custom-file (private/ "custom.el"))
136 (add-to-list* '+custom-allowed-variables
137 'safe-local-variable-values
138 'warning-suppress-types
139 'ispell-buffer-session-localwords)
140 (eval-after init
141 (+custom-load-some-customizations :noerror)))
142
143;; (yoke modus-themes
144;; (setc modus-themes-bold-constructs t
145;; modus-themes-italic-constructs t
146;; modus-themes-headings '((1 monochrome bold italic)
147;; (2 monochrome bold)
148;; (3 monochrom italic)
149;; (t monochrome)))
150;; (defhook modus-themes-after-load-theme-hook
151;; :name modus-monochrome
152;; (modus-themes-with-colors
153;; (cl-loop for x being the symbols
154;; if (string-match-p "\\`font-lock-.*-face\\'"
155;; (symbol-name x))
156;; do
157;; (custom-set-faces
158;; `(,x ((,class :foreground
159;; ,(cond
160;; ((memq x '(font-lock-string-face
161;; font-lock-doc-face
162;; font-lock-doc-markup-face))
163;; fg-special-warm)
164;; ((memq x '(font-lock-warning-face))
165;; fg-lang-warning)
166;; ((memq x '(font-lock-comment-face))
167;; fg-alt)
168;; (:else 'unspecified))
169;; :background unspecified
170;; :weight
171;; ,(cond
172;; ((memq x '(font-lock-keyword-face))
173;; 'bold)
174;; (:else 'normal))
175;; :slant
176;; ,(cond
177;; ((memq x '(font-lock-doc-face
178;; font-lock-comment-face))
179;; 'italic)
180;; (:else 'normal))
181;; :underline
182;; ,(cond
183;; ((memq x '(font-lock-warning-face))
184;; t)
185;; (:else nil)))))))))
186;; (when (or (custom-theme-enabled-p 'modus-operandi)
187;; (custom-theme-enabled-p 'modus-vivendi))
188;; (modus-monochrome))
189;; (cond ((require 'dawn nil :noerrer)
190;; (defhook +custom-after-load-hook
191;; :name dawn@custom
192;; (load-theme 'modus-operandi :noconfirm :noenable)
193;; (load-theme 'modus-vivendi :noconfirm :noenable)
194;; (dawn-schedule #'modus-themes-load-operandi
195;; #'modus-themes-load-vivendi)))
196;; (:else (modus-themes-load-operandi))))
197
198(yoke time
199 (setc display-time-mail-function
200 (defun +notmuch-new-mail-p ()
201 (plist-get (cl-find "inbox+unread"
202 (ignore-errors
203 (notmuch-hello-query-counts notmuch-saved-searches))
204 :key (lambda (l) (plist-get l :name))
205 :test #'equal)
206 :count))
207 display-time-use-mail-icon nil
208 display-time-mail-string (format "⋅ Mail (%s)" (+notmuch-new-mail-p))
209 read-mail-command #'+notmuch-goto
210 display-time-format " %a %-e, %H:%M"
211 ;; `display-time-format' makes these unnecessary, but I'll keep em
212 display-time-24hr-format t
213 display-time-day-and-date t
214 display-time-default-load-average nil)
215 (define-advice display-time-update (:after (&rest _) update-mail-count)
216 (setq display-time-mail-string (format "⋅ Mail (%s)" (+notmuch-new-mail-p))))
217 (display-time-mode))
218
219(yoke pita
220 (require 'pita)
221 (advice-add 'indent-region :before #'with-region-or-buffer))
222
223(yoke (undo-fu-session "https://codeberg.org/ideasman42/emacs-undo-fu-session")
224 (setc undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
225 "/git-rebase-todo\\'")
226 undo-fu-session-directory (.etc "undo/" t)
227 undo-fu-session-compression (cond
228 ((executable-find "gzip") 'gz)
229 ((executable-find "bzip2") 'bz2)
230 ((executable-find "xz") 'xz)
231 (t nil)))
232 (global-undo-fu-session-mode))
233
234(yoke whitespace
235 (setc whitespace-line-column nil
236 whitespace-style '( face trailing
237 tabs tab-mark
238 indentation
239 space-after-tab space-before-tab))
240 (defhook (text-mode-hook prog-mode-hook read-only-mode-hook)
241 :name +whitespace-mode-for-writable-buffers
242 :doc "Turn on `whitespace-mode' if the buffer is writable, off otherwise."
243 (whitespace-mode (if buffer-read-only -1 t)))
244 (defhook before-save-hook #'whitespace-cleanup)
245 (define-advice whitespace-cleanup (:around (fn &rest r) preserve-point)
246 (let ((col (current-column)))
247 (apply fn r)
248 (move-to-column col t)
249 (set-buffer-modified-p nil))))
250
251(yoke elisp-mode
252 (setc eval-expression-print-length nil ; remove ellipses from `eval-expression'
253 eval-expression-print-level nil)
254 (defkeys (emacs-lisp-mode-map lisp-interaction-mode-map)
255 "C-c C-c" #'eval-defun
256 "C-c C-k" (defun +elisp-eval-region-or-buffer ()
257 (interactive)
258 (cond
259 ((region-active-p)
260 (eval-region (region-beginning) (region-end))
261 (message "Region evaluated."))
262 (t
263 (eval-buffer)
264 (message "Buffer %s evaluated." (buffer-name)))))
265 "C-c C-z" #'ielm)
266 (define-advice eval-region (:around (fn beg end &rest args) pulse)
267 (apply fn beg end args)
268 (pulse-momentary-highlight-region beg end)))
269
270(yoke isearch
271 (defkeys t
272 "C-s" #'isearch-forward-regexp
273 "C-r" #'isearch-backward-regexp
274 "C-M-s" #'isearch-forward
275 "C-M-r" #'isearch-backward))
276 102
277(yoke ispell 103(use-package orderless
278 (require* '+ispell 'ispell) 104 :ensure t
279 (add-hook 'before-save-hook 105 :custom
280 #'+ispell-move-buffer-words-to-dir-locals-hook) 106 (completion-styles '(orderless basic))
281 (setc ispell-program-name (or (executable-find "ispell") 107 (completion-category-overrides
282 (executable-find "aspell"))) 108 '((file (styles basic partial-completion)))))
283 (put 'ispell-buffer-session-localwords 109
284 'safe-local-variable #'+ispell-safe-local-p)) 110(use-package consult
285 111 :ensure t
286 112 :custom
287(yoke mouse 113 (register-preview-delay 0.01)
288 ;; Brand new for Emacs 28: see https://ruzkuku.com/texts/emacs-mouse.html 114 (register-preview-function #'consult-register-format)
289 ;; Actually, look at this as well: https://www.emacswiki.org/emacs/Mouse3 115 (xref-show-xrefs-function #'consult-xref)
290 (when (fboundp 'context-menu-mode) 116 (tab-always-indent 'complete)
291 (setc context-menu-functions '(context-menu-ffap 117 (completion-in-region-function #'consult-completion-in-region)
292 context-menu-region 118 (consult-narrow-key "<")
293 context-menu-undo 119 (consult--regexp-compiler #'consult--default-regexp-compiler)
294 ;; context-menu-dictionary 120 :config
295 ))
296 (context-menu-mode +1))
297 (dolist (click '(;; Fix scrolling in the margin
298 wheel-down double-wheel-down triple-wheel-down
299 wheel-up double-wheel-up triple-wheel-up))
300 (global-set-key (vector 'right-margin click) 'mwheel-scroll)
301 (global-set-key (vector 'left-margin click) 'mwheel-scroll)))
302
303(yoke dired
304 (require 'dired-x)
305 (setc dired-recursive-copies 'always
306 dired-recursive-deletes 'always
307 dired-create-destination-dirs 'always
308 dired-do-revert-buffer t
309 dired-hide-details-hide-symlink-targets nil
310 dired-isearch-filenames 'dwim
311 delete-by-moving-to-trash t
312 dired-auto-revert-buffer t
313 dired-listing-switches "-AlF"
314 ls-lisp-dirs-first t
315 dired-ls-F-marks-symlinks t
316 dired-clean-confirm-killing-deleted-buffers nil
317 dired-no-confirm '(byte-compile
318 load chgrp chmod chown
319 copy move hardlink symlink
320 shell touch)
321 dired-dwim-target t)
322 (setq-local-hook dired-mode-hook
323 truncate-lines t)
324 (defkeys t
325 "C-x C-j" #'dired-jump
326 [remap list-directory] #'dired)
327 (defkeys ((dired-mode-map :after dired))
328 "<backspace>" #'dired-up-directory
329 "C-j" #'dired-up-directory)
330 (defhook dired-mode-hook
331 #'dired-hide-details-mode
332 #'hl-line-mode))
333
334(yoke (dired-hacks "https://github.com/Fuco1/dired-hacks")
335 (defkeys dired-mode-map
336 "TAB" #'dired-subtree-sycle
337 "i" #'dired-subtree-toggle)
338 (defhook 'dired-mode-hook
339 #'dired-collapse-mode))
340
341(yoke auth-source
342 (setc auth-sources `(default "secrets:passwords"))
343 (setq-local-hook authinfo-mode-hook
344 truncate-lines t))
345
346(yoke (consult "https://github.com/minad/consult")
347 (require 'consult)
348 (setf register-preview-delay 0
349 register-preview-function #'consult-register-format
350 xref-show-xrefs-function #'consult-xref
351 tab-always-indent 'complete
352 completion-in-region-function #'consult-completion-in-region
353 consult-narrow-key "<"
354 consult--regexp-compiler #'consult--default-regexp-compiler)
355 (advice-add #'register-preview :override #'consult-register-window) 121 (advice-add #'register-preview :override #'consult-register-window)
356 (define-key* (current-global-map) 122 (define-advice completing-read-multiple (:filter-args (args) indicator)
357 ;; Etc 123 (cons (format "[CRM%s] %s"
358 "M-S-x" #'consult-mode-command 124 (replace-regexp-in-string
359 ;; C-c bindings (mode-specific-map) 125 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
360 "C-c h" #'consult-history 126 crm-separator)
361 "C-c b" #'consult-bookmark 127 (car args))
362 "C-c k" #'consult-kmacro 128 (cdr args)))
363 ;; C-x bindings (ctl-x-map) 129 :bind
364 "C-x M-:" #'consult-complex-command 130 (([remap switch-to-buffer] . consult-buffer)
365 "C-x b" #'consult-buffer 131 ([remap switch-to-buffer-other-window] . consult-buffer-other-window)
366 "C-x 4 b" #'consult-buffer-other-window 132 ([remap switch-to-buffer-other-frame] . consult-buffer-other-frame)
367 "C-x 5 b" #'consult-buffer-other-frame 133 ([remap yank-pop] . consult-yank-pop)
368 ;; Custom M-# bindings for fast register access 134 ("M-g g" . consult-goto-line)
369 "M-#" #'consult-register-load 135 ("M-g M-g" . consult-goto-line)
370 "M-'" #'consult-register-store 136 ("M-g i" . consult-imenu)
371 "C-M-#" #'consult-register 137 ("M-g M-i" . consult-imenu)
372 ;; Other custom bindings 138 ("M-s l" . consult-line)
373 "M-y" #'consult-yank-pop 139 ("M-s f" . consult-find)
374 ;;("<f1> a" . consult-apropos) 140 ("M-s M-f" . consult-find)
375 ;; M-g bindings (goto-map) 141 ("M-s g" . consult-grep)
376 "M-g e" #'consult-compile-error 142 ("M-s M-g" . consult-grep)))
377 "M-g f" #'consult-flymake ; or consult-flycheck 143
378 "M-g g" #'consult-goto-line 144;;; General enhancements
379 "M-g M-g" #'consult-goto-line 145
380 "M-g o" #'consult-outline ; or consult-org-heading 146(use-package embark
381 "M-g m" #'consult-mark 147 :ensure t
382 "M-g k" #'consult-global-mark 148 :custom
383 "M-g i" #'consult-imenu 149 (prefix-help-command #'embark-prefix-help-command)
384 "M-g M-i" #'consult-imenu 150 :bind
385 "M-g I" #'consult-imenu-multi 151 (("C-." . embark-act)
386 ;; M-s bindings (search-map) 152 ("M-." . embark-dwim)
387 "M-s f" #'consult-find 153 ("C-h b" . embark-bindings))
388 "M-s F" #'consult-locate 154 :custom
389 "M-s g" #'consult-grep 155 (add-to-list 'display-buffer-alist
390 "M-s G" #'consult-git-grep 156 '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
391 "M-s r" #'consult-ripgrep 157 nil
392 "M-s l" #'consult-line 158 (window-parameters (mode-line-format . none)))))
393 "M-s L" #'consult-line-multi 159
394 "M-s m" #'consult-multi-occur 160(use-package embark-consult
395 "M-s k" #'consult-keep-lines 161 :ensure t
396 "M-s u" #'consult-focus-lines 162 :after (embark consult)
397 ;; Isearch integration 163 :hook (embark-collect-mode-hook . consult-preview-at-point-mode))
398 "M-s e" #'consult-isearch-history) 164
399 (eval-after isearch-mode 165(use-package undo-fu-session
400 (define-key* isearch-mode-map 166 :ensure t
401 "M-e" #'consult-isearch-history 167 :custom
402 "M-s e" #'consult-isearch-history 168 (undo-fu-session-directory (etc/ "undo/" t))
403 "M-s l" #'consult-line 169 (undo-fu-session-incompatible-files
404 "M-s L" #'consult-line-multi)) 170 '("/COMMIT_EDITMSG\\'"
405 (eval-after org 171 "/git-rebase-todo\\'"))
406 (define-key org-mode-map (kbd "M-g o") #'consult-org-heading)) 172 (undo-fu-session-compression
407 (eval-after consult-imenu 173 (cl-loop for (exe . sym) in '(("gzip" . gz)
408 (setf (alist-get ?y (plist-get (alist-get 'emacs-lisp-mode 174 ("bzip2" . bz2)
409 consult-imenu-config) 175 ("xz" . xz))
410 :types)) 176 if (executable-find exe)
411 '("Yoke")))) 177 return sym))
412 178 :config
413(yoke (orderless "https://github.com/oantolin/orderless") 179 (global-undo-fu-session-mode))
414 (require 'orderless)
415 (setf completion-styles '(substring orderless basic)
416 completion-category-defaults nil
417 completion-category-overrides
418 '((file (styles basic partial-completion)))
419 orderless-component-separator #'orderless-escapable-split-on-space))
420
421(yoke (vertico "https://github.com/minad/vertico")
422 (require 'vertico)
423 (setf resize-mini-windows 'grow-only
424 vertico-count-format nil
425 vertico-cycle t)
426 (vertico-mode)
427 (add-to-list 'load-path (expand-file-name "vertico/extensions" yoke-dir))
428 (require 'vertico-directory)
429 (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
430
431(yoke (embark "https://github.com/oantolin/embark")
432 (require 'embark)
433 (setf prefix-help-command #'embark-prefix-help-command
434 embar-keymap-prompter-key ";")
435 (defkeys (t minibuffer-local-map)
436 "C-." #'embark-act
437 "M-." #'embark-dwim
438 "<f1> B" #'embark-bindings)
439 (define-key* embark-file-map
440 "l" #'vlf)
441 (eval-after (embark consult)
442 (require 'embark-consult)
443 (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)))
444
445(yoke (marginalia "https://github.com/minad/marginalia/")
446 (marginalia-mode))
447 180
448(yoke (wgrep "https://github.com/mhayashi1120/Emacs-wgrep") 181(use-package ws-butler
449 (require 'wgrep) 182 :ensure t
450 (define-key* grep-mode-map 183 :custom
451 "C-x C-q" #'wgrep-change-to-wgrep-mode)) 184 (ws-butler-trim-predicate (lambda (begin end)
185 (not (eq 'font-lock-string-face
186 (get-text-property end 'face)))))
187 :config
188 (ws-butler-global-mode))
189
190(use-package minions
191 :ensure t
192 :config
193 (minions-mode))
452 194
453(yoke (slime "https://github.com/slime/slime") 195(use-package mode-line-bell
454 :when (executable-find "sbcl") 196 :vc ( :url "https://github.com/duckwork/mode-line-bell"
455 (setc inferior-lisp-program (executable-find "sbcl")) 197 :rev "remap-face")
456 (defhook lisp-mode-hook 198 :custom
457 :name slime-mode-setup 199 (mode-line-bell-flash-time 0.25)
458 (load (expand-file-name "~/quicklisp/slime-helper.el") :noerror) 200 :config
459 (slime-mode)) 201 (mode-line-bell-mode))
460 (eval-after slime 202
461 (setc slime-completion-at-point-functions 203(use-package electric-cursor
462 (delq 'slime-c-p-c-completion-at-point 204 :load-path "~/src/emacs/electric-cursor/"
463 slime-completion-at-point-functions)))) 205 :custom
464 206 (electric-cursor-alist '((overwrite-mode . box)
465(yoke (puni "https://github.com/amaikinono/puni") 207 (t . bar)))
466 (electric-pair-mode) 208 (electric-cursor-mode))
467 (defkeys puni-mode-map
468 "C-)" #'puni-slurp-forward
469 "C-(" #'puni-slurp-backward
470 "C-}" #'puni-barf-forward
471 "C-{" #'puni-barf-backward
472 "M-(" (defun +puni-open-then-slurp-forward (&optional n)
473 (interactive "p")
474 (insert "()")
475 (backward-char)
476 (ignore-errors (puni-slurp-forward n))))
477 (defhook (prog-mode-hook
478 lisp-interaction-mode-hook emacs-lisp-mode-hook
479 lisp-mode-hook scheme-mode-hook
480 ielm-mode-hook eval-expression-minibuffer-setup-hook)
481 #'puni-mode))
482
483(yoke (hungry-delete "https://github.com/nflath/hungry-delete")
484 (setc hungry-delete-chars-to-skip " \t"
485 hungry-delete-join-reluctantly nil)
486 (eval-after hungry-delete
487 (add-to-list* 'hungry-delete-except-modes
488 #'eshell-mode
489 #'nim-mode
490 #'python-mode))
491 (defun +hungry-delete-or (hd-fn fn arg)
492 (funcall (if (looking-back (format "[%s]" hungry-delete-chars-to-skip) arg)
493 hd-fn
494 fn)
495 arg))
496 (defkeys puni-mode-map
497 [remap puni-backward-delete-char]
498 (defun +puni|hungry-delete-backward (arg)
499 (interactive "p")
500 (+hungry-delete-or #'hungry-delete-backward
501 #'puni-backward-delete-char
502 arg))
503 [remap puni-forward-delete-char]
504 (defun +puni|hungry-delete-forward (arg)
505 (interactive "p")
506 (+hungry-delete-or #'hungry-delete-forward
507 #'puni-forward-delete-char
508 arg)))
509 (global-hungry-delete-mode))
510 209
511(yoke (cape "https://github.com/minad/cape") 210(use-package visual-fill-column
512 ;; Insinuate in a lot of modes 211 :ensure t
513 (defvar +capes '(cape-file cape-dabbrev)) 212 :custom
514 (defun +cape-insinuate (hook capf &optional capes) 213 (visual-fill-column-center-text t)
515 "Insinuate CAPES into a HOOK along with CAPF function. 214 :hook ((visual-fill-column-mode-hook . visual-line-mode)
516CAPES defaults to `+capes'. CAPF will be made un-exclusive." 215 (eww-mode-hook . visual-fill-column-mode))
517 (setq-local-hook hook 216 :config
518 completion-at-point-functions 217 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
519 (apply #'list (cape-capf-properties capf :exclusive 'no)
520 (or capes +capes))))
521 (+cape-insinuate 'emacs-lisp-mode-hook #'elisp-completion-at-point))
522
523(yoke (minions "https://github.com/tarsius/minions")
524 (minions-mode))
525 218
526(yoke (magit "https://github.com/magit/magit" 219(use-package cape
527 :load "lisp") 220 :ensure t
528 :depends ((transient "https://github.com/magit/transient" 221 :demand t
529 :load "lisp") 222 :init
530 (dash "https://github.com/magnars/dash.el") 223 (dolist (fn '(cape-file cape-dabbrev))
531 (with-editor "https://github.com/magit/with-editor" 224 (add-hook 'completion-at-point-functions fn 90))
532 :load "lisp")) 225 :config
533 (autoload #'transient--with-suspended-override "transient") 226 (require '+cape)
534 (autoload #'magit "magit" nil :interactive) 227 (advice-add 'emacs-completion-at-point :around #'cape-wrap-nonexclusive)
535 (defkeys t 228 (+cape-insinuate text-mode-hook ( cape-dict cape-ispell
536 "C-x g" #'magit)) 229 cape-file cape-dabbrev)))
230
231(use-package wgrep
232 :ensure t
233 :custom
234 (wgrep-enable-key (kbd "C-x C-q"))
235 :bind (:map grep-mode-map
236 ("C-x C-q" . wgrep-change-to-wgrep-mode)))
237
238(progn
239 (use-package-statistics-gather :use-package 'mlscroll nil)
240 (use-package-ensure-elpa 'mlscroll
241 '(t)
242 'nil)
243 (use-package-statistics-gather :preface 'mlscroll nil)
244 (eval-and-compile
245 (defhook modus-themes-after-load-theme-hook :name modus-themes-load@mlscroll
246 (mlscroll-mode -1)
247 (when
248 (or
249 (memq 'modus-vivendi custom-enabled-themes)
250 (memq 'modus-operandi custom-enabled-themes))
251 (modus-themes-with-colors
252 (setf mlscroll-in-color fg-dim mlscroll-out-color bg-inactive)))
253 (eval-after 1
254 (mlscroll-mode 1))))
255 (defvar use-package--warning99
256 (function
257 (lambda
258 (keyword err)
259 (let
260 ((msg
261 (format "%s/%s: %s" 'mlscroll keyword
262 (error-message-string err))))
263 (display-warning 'use-package msg :error)))))
264 (condition-case-unless-debug err
265 (progn
266 (use-package-statistics-gather :init 'mlscroll nil)
267 (condition-case-unless-debug err
268 (when
269 (daemonp)
270 (add-hook 'server-after-make-frame
271 (function modus-themes-load@ml-scroll)))
272 (error
273 (funcall use-package--warning99 :init err)))
274 (run-with-idle-timer 1 nil
275 (function require)
276 'mlscroll nil t)
277 (eval-after-load 'mlscroll
278 '(progn
279 (use-package-statistics-gather :config 'mlscroll nil)
280 (let
281 ((now
282 (current-time)))
283 (message "%s..." "Configuring package mlscroll")
284 (prog1
285 (condition-case-unless-debug err
286 (progn
287 (modus-themes-load@mlscroll)
288 t)
289 (error
290 (funcall use-package--warning99 :config err)))
291 (let
292 ((elapsed
293 (float-time
294 (time-subtract
295 (current-time)
296 now))))
297 (if
298 (> elapsed 0.1)
299 (message "%s...done (%.3fs)" "Configuring package mlscroll" elapsed)
300 (message "%s...done" "Configuring package mlscroll")))))
301 (use-package-statistics-gather :config 'mlscroll t)))
302 (use-package-statistics-gather :init 'mlscroll t))
303 (error
304 (funcall use-package--warning99 :catch err)))
305 (use-package-statistics-gather :preface 'mlscroll t)
306 (use-package-statistics-gather :use-package 'mlscroll t))
307
308(use-package avy
309 :ensure t
310 :demand t
311 :custom
312 (avy-background t)
313 (avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))
314 :bind (("M-j" . avy-goto-char-timer)
315 :map isearch-mode-map
316 ("M-j" . avy-isearch)))
317
318(use-package zzz-to-char
319 :ensure t
320 :bind (("M-z" . zzz-to-char)))
321
322;;; Searching
323
324(use-package isearch-mb
325 :ensure t
326 :custom
327 (isearch-lazy-count t)
328 (isearch-regexp-lax-whitespace t)
329 ;; Space matches whitespace, newlines, punctuation
330 (search-whitespace-regexp "\\W+")
331 (search-default-mode t "Search using regexp by default.")
332 ;; Space matches any sequence of characters in a line
333 ;; search-whitespace-regexp ".*?"
334 (isearch-wrap-pause 'no)
335 :config
336 (require '+isearch)
337 (advice-add 'isearch-cancel :before
338 #'+isearch-cancel@add-search-to-history)
339 (advice-add 'perform-replace :around
340 #'+perform-replace-dont-exit-on-anykey)
341 (eval-after (isearch-mb consult)
342 (add-to-list 'isearch-mb--after-exit #'consult-line)
343 (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
344 (defkeys isearch-mb-minibuffer-map
345 "M-s l" #'consult-line
346 "M-r" #'consult-isearch-history))
347 (eval-after (isearch-mb anzu)
348 (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace)
349 (defkeys isearch-mb-minibuffer-map
350 "M-%" #'anzu-isearch-query-replace-regexp
351 "C-M-%" #'anzu-isearch-query-replace))
352 (isearch-mb-mode))
537 353
538(yoke (git-modes "https://github.com/magit/git-modes") 354(use-package anzu
539 (require 'git-modes)) 355 :ensure t
356 :bind
357 (("M-%" . anzu-query-replace-regexp)
358 ("C-M-%" . anzu-query-replace)))
540 359
541(yoke (visual-fill-column "https://codeberg.org/joostkremers/visual-fill-column") 360(use-package frowny
542 (setc visual-fill-column-center-text t) 361 :load-path "~/src/emacs/frowny/"
543 (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) 362 :config
544 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) 363 (global-frowny-mode))
545 364
546(yoke (org "https://git.savannah.gnu.org/git/emacs/org-mode.git" 365(use-package transpose-frame
547 :load "lisp") 366 :ensure t
548 :depends ((org-contrib "https://git.sr.ht/~bzg/org-contrib" 367 :bind
549 :load "lisp")) 368 (("C-x 5 t" . transpose-frame)))
550 ;; DON'T load system org 369
551 (setc load-path 370;;; Text editing
552 (cl-remove-if (lambda (path) (string-match-p "lisp/org\\'" path)) 371
553 load-path)) 372(use-package org
554 (setc org-adapt-indentation nil 373 :defer t
555 org-auto-align-tags t 374 :after derived
556 org-archive-mark-done t 375 :init
557 org-fold-catch-invisible-edits 'show-and-error 376 (require 'init-org)
558 org-clock-clocked-in-display 'mode-line 377 (require '_work))
559 org-clock-frame-title-format (cons 378
560 '(t org-mode-line-string) 379(use-package ispell
561 (cons " --- " frame-title-format)) 380 :custom
562 org-clock-string-limit 7 ; just the clock bit 381 (ispell-program-name (choose-executable "ispell" "aspell"))
563 ;; org-clock-string-limit 25 ; gives enough information 382 :config
564 org-clock-persist nil 383 (require '+ispell)
565 org-confirm-babel-evaluate nil 384 (add-hook 'before-save-hook
566 org-cycle-separator-lines 0 385 #'+ispell-move-buffer-words-to-dir-locals-hook)
567 org-directory (sync/ "org/" t) 386 (put 'ispell-buffer-session-localwords 'safe-local-variable
568 org-ellipsis (or truncate-string-ellipsis "…") 387 #'+ispell-safe-local-p))
569 org-fontify-done-headline t 388
570 org-fontify-quote-and-verse-blocks t 389(use-package flyspell
571 org-fontify-whole-heading-line t 390 :hook org-mode-hook)
572 org-hide-emphasis-markers t 391
573 org-html-coding-system 'utf-8-unix 392(use-package flyspell-correct
574 org-image-actual-width (list (* (window-font-width) 393 :ensure t
575 (- fill-column 8))) 394 :after flyspell
576 org-imenu-depth 3 395 :custom
577 org-indent-indentation-per-level 0 396 (flyspell-correct--cr-key ";")
578 org-indent-mode-turns-on-hiding-stars nil 397 :bind
579 org-insert-heading-respect-content t 398 (:map flyspell-mode-map
580 org-list-demote-modify-bullet '(("-" . "+") 399 (("C-;" . flyspell-correct-wrapper)
581 ("+" . "-")) 400 ("<f7>" . +flyspell-correct-buffer)))
582 org-log-done 'time 401 :config
583 org-log-into-drawer t 402 (require '+flyspell-correct)
584 org-num-skip-commented t 403 (defkeys flyspell-mode-map
585 org-num-skip-unnumbered t 404 "C-," nil
586 org-num-skip-footnotes t 405 "C-." nil))
587 org-outline-path-complete-in-steps nil 406
588 org-pretty-entities t 407(use-package text-mode
589 org-pretty-entities-include-sub-superscripts nil 408 :config
590 org-refile-targets '((nil . (:maxlevel . 2)) 409 (defhook text-mode-hook
591 (org-agenda-files . (:maxlevel . 1))) 410 #'abbrev-mode))
592 org-refile-use-outline-path 'file 411
593 org-special-ctrl-a/e t 412(use-package filldent
594 org-special-ctrl-k t 413 :load-path "~/src/emacs/filldent/"
595 org-src-fontify-natively t 414 :bind
596 org-src-tab-acts-natively t 415 ("M-q" . filldent-dwim))
597 org-src-window-setup 'current-window 416
598 org-startup-truncated nil 417(use-package scule
599 org-startup-with-inline-images t 418 :load-path "~/src/emacs/scule/"
600 org-tags-column -77 ;; (- (- fill-column 1 (length org-ellipsis))) 419 :config
601 org-todo-keywords
602 '((sequence "TODO(t)" "WAIT(w@/!)" "ONGOING(o@)"
603 "|" "DONE(d!)" "ASSIGNED(a@/!)")
604 (sequence "|" "CANCELED(k@)")
605 (sequence "MEETING(m)"))
606 org-use-speed-commands t
607 org-emphasis-alist '(("*" org-bold)
608 ("/" org-italic)
609 ("_" org-underline)
610 ("=" org-verbatim)
611 ("~" org-code)
612 ("+" org-strikethrough)))
613 (defhook org-mode-hook
614 #'variable-pitch-mode
615 #'visual-fill-column-mode
616 #'turn-off-auto-fill
617 #'org-indent-mode
618 #'prettify-symbols-mode
619 #'abbrev-mode
620 (defhook ((before-save-hook nil :local))
621 :name before-save@org-mode
622 (+org-hide-drawers-except-point)
623 (org-align-tags 'all)))
624 (eval-after org
625 (require '+org)
626 (org-clock-persistence-insinuate)
627 (+org-agenda-inhibit-hooks-mode)
628 (defkeys org-mode-map
629 "C-M-k" #'kill-paragraph
630 "C-M-t" #'transpose-paragraphs
631 "RET" #'+org-return-dwim
632 "S-<return>" #'+org-table-copy-down|+org-return
633 "C-c C-o" #'+org-open-at-point-dwim))
634 (eval-after ol ; org-link
635 (defmacro define-org-link-type (type args &rest body)
636 "Define an org link TYPE with ARGS that does something.
637 If BODY is blank, message the user about the link."
638 (declare (indent 2) (doc-string 3) (debug (sexp sexp def-body)))
639 (let ((fn (intern (format "org-%s-open" type)))
640 (body (or body `((message ,(format "%s: %%S" type) ,(car args)))))
641 (type-string (format "%s" type)))
642 `(prog1
643 (defun ,fn ,args
644 ,@body)
645 (org-link-set-parameters ,type-string :follow #',fn))))
646 (define-org-link-type sms (number _))
647 (define-org-link-type tel (number _))))
648
649(yoke org-word-count ; in lisp/
650 (eval-after org
651 (require 'org-word-count)
652 (add-hook 'org-mode-hook #'org-word-count-mode)))
653
654(yoke org-agenda
655 (setq org-agenda-skip-deadline-if-done t
656 org-agenda-skip-scheduled-if-done t
657 org-agenda-span 10
658 org-agenda-block-separator ?─
659 org-agenda-time-grid
660 '((daily today require-timed)
661 (800 1000 1200 1400 1600 1800 2000)
662 " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
663 org-agenda-current-time-string
664 "← now ─────────────────────────────────────────────────"
665 org-agenda-include-diary nil ; I use the org-diary features
666 org-agenda-todo-ignore-deadlines 'near
667 org-agenda-todo-ignore-scheduled 'future
668 org-agenda-include-deadlines t
669 org-deadline-warning-days 0
670 org-agenda-show-future-repeats 'next
671 org-agenda-window-setup 'current-window
672 org-agenda-file-skip-regexp "sync-conflict")
673 (defcustom org-agenda-file-skip-regexp nil
674 "Files matching this regexp are removed from `org-agenda-files'."
675 :group 'org-agenda)
676 (define-advice org-agenda-files (:filter-return (files) skip-regexp)
677 (when org-agenda-file-skip-regexp
678 (setq files (seq-remove (lambda (file)
679 (string-match-p org-agenda-file-skip-regexp
680 file))
681 files)))
682 files)
683 (setq-local-hook org-agenda-mode-hook
684 truncate-lines t)
685 (add-hook 'org-agenda-mode-hook #'hl-line-mode)
686 (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree)
687 (defkeys t 420 (defkeys t
688 "C-c c" #'org-capture 421 "M-c" (defmap scule-map
689 "C-c a" #'org-agenda) 422 "Keymap for twiddling scules."
690 (eval-after org-capture 423 "M-u" #'scule-upcase
691 (require '+org-capture))) 424 "M-l" #'scule-downcase
692 425 "M-c" #'scule-capitalize)))
693(yoke ox ; org-export 426
694 (eval-after org (require 'ox)) 427(use-package titlecase
695 (eval-after ox 428 :load-path "~/src/emacs/titlecase.el/"
696 (require* '+ox '(ox-md nil t)) 429 :after org
697 (+org-export-pre-hooks-insinuate)) 430 :bind (:map scule-map
698 (setq org-export-coding-system 'utf-8-unix 431 ("M-t" . titlecase-dwim))
699 org-export-headline-levels 8 432 :config
700 org-export-with-drawers nil 433 (require '+titlecase))
701 org-export-with-section-numbers nil 434
702 org-export-with-smart-quotes t 435;;; Programming
703 org-export-with-sub-superscripts t 436
704 org-export-with-toc nil)) 437(use-package prog-mode
705 438 :config
706(yoke (electric-cursor "https://codeberg.org/acdw/electric-cursor.el") 439 (defhook prog-mode-hook
707 (setq electric-cursor-alist '((overwrite-mode . hbar) 440 #'auto-fill-mode
708 (t . bar))) 441 ;; Use `indent-tabs-mode' unless one of the following modes
709 (electric-cursor-mode)) 442 (indent-tabs-mode (if (derived-mode-p 'emacs-lisp-mode
443 'python-mode
444 'haskell-mode)
445 -1 1))))
446
447(use-package paredit
448 :ensure t
449 :preface
450 (defun +paredit-newline ()
451 (interactive)
452 (call-interactively
453 (if (derived-mode-p 'lisp-interaction-mode)
454 #'eval-print-last-sexp
455 #'paredit-newline)))
456 :hook ((emacs-lisp-mode-hook
457 eval-expression-minibuffer-setup-hook
458 ielm-mode-hook lisp-interaction-mode-hook
459 lisp-mode-hook scheme-mode-hook
460 fennel-repl-mode-hook fennel-mode-hook
461 geiser-mode-hook geiser-repl-mode-hook
462 ielm-mode-hook)
463 . enable-paredit-mode)
464 :bind
465 (("C-j" . +paredit-newline))
466 :config
467 (defkeys paredit-mode-map
468 "RET" nil
469 "M-s" nil)
470 (require '+paredit)
471 (add-to-list 'paredit-space-for-delimiter-predicates
472 #'+paredit-space-for-delimiter)
473 (eval-after (paredit eldoc)
474 (eldoc-add-command #'paredit-backward-delete #'paredit-close-round)))
475
476(use-package hungry-delete
477 :ensure t
478 :custom
479 (hungry-delete-chars-to-skip " \t")
480 (hungry-delete-join-reluctantly nil)
481 :config
482 (dolist (m '(eshell-mode
483 nim-mode
484 python-mode))
485 (add-to-list 'hungry-delete-except-modes m))
486 (require '+hungry-delete)
487 (eval-after paredit
488 (defkeys paredit-mode-map
489 [remap paredit-backward-delete]
490 (+hungry-delete-define-alternative
491 paredit-backward-delete hungry-delete-backward)
492 [remap paredit-forward-delete]
493 (+hungry-delete-define-alternative
494 paredit-forward-delete hungry-delete-forward)))
495 (global-hungry-delete-mode))
710 496
711(yoke _work 497(use-package ediff
712 :depends ((bbdb "https://git.savannah.nongnu.org/git/bbdb.git" 498 :custom
713 :load "lisp") 499 (ediff-keep-variants nil)
714 (bbdb-vcard "https://github.com/tohojo/bbdb-vcard/")) 500 (ediff-split-window-function #'split-window-horizontally)
715 (setf bbdb-complete-mail-allow-cycling t 501 (ediff-window-setup-function #'ediff-setup-windows-plain))
716 bbdb-file (private/ "bbdb"))
717 (defhook +custom-after-load-hook
718 :name _work@after-custom
719 (require* 'private '_work)
720 (require* 'bbdb 'bbdb-message)
721 (bbdb-initialize 'gnus 'message)))
722
723(yoke (org-taskwise "https://codeberg.org/acdw/org-taskwise.el"))
724
725(yoke scule
726 (require 'scule)
727 (defvar scule-map (let ((map (make-sparse-keymap)))
728 (define-key map (kbd "M-u") #'scule-upcase)
729 (define-key map (kbd "M-l") #'scule-downcase)
730 (define-key map (kbd "M-c") #'scule-capitalize)
731 map)
732 "Keymap for scule twiddling.")
733 (define-key* (current-global-map)
734 "M-c" scule-map
735 "M-u" #'universal-argument)
736 (define-key universal-argument-map (kbd "M-u") #'universal-argument-more))
737
738(yoke (titlecase "https://codeberg.org/acdw/titlecase.el")
739 (eval-after titlecase
740 (add-to-list* 'titlecase-skip-words-regexps
741 (rx word-boundary
742 (+ (any upper digit))
743 word-boundary)))
744 (eval-after scule
745 (define-key* scule-map
746 "M-t" #'titlecase-dwim)))
747
748(yoke (flyspell-correct "https://github.com/duckwork/flyspell-correct")
749 (eval-after flyspell
750 (require* 'flyspell-correct
751 `(+flyspell-correct ,(locate-user-emacs-file "lisp/+flyspell-correct")))
752 (define-key* flyspell-mode-map
753 "C-;" #'flyspell-correct-wrapper
754 "<f7>" #'+flyspell-correct-buffer
755 "C-," nil
756 "C-." nil))
757 (add-hook 'org-mode-hook #'flyspell-mode)
758 (setq flyspell-correct--cr-key ";"))
759
760(yoke (helpful "https://github.com/Wilfred/helpful")
761 :depends ((dash "https://github.com/magnars/dash.el")
762 (f "https://github.com/rejeep/f.el")
763 (s "https://github.com/magnars/s.el")
764 (elisp-refs "https://github.com/Wilfred/elisp-refs"))
765 (defkeys t
766 "<f1> f" #'helpful-callable
767 "<f1> v" #'helpful-variable
768 "<f1> k" #'helpful-key
769 "<f1> ." #'helpful-at-point
770 "<f1> o" #'helpful-symbol)
771 (unless (featurep 'info-look)
772 (run-with-idle-timer 5 nil (lambda ()
773 (require 'info-look)
774 (let ((inhibit-message t))
775 (info-lookup-setup-mode 'symbol
776 'emacs-lisp-mode)))))
777 (defhook window-configuration-change-hook
778 :name side-window-setup
779 (setf fit-window-to-buffer-horizontally t
780 (alist-get (rx (or "*helpful" "*Help" "*info"))
781 display-buffer-alist nil nil #'string=)
782 `(display-buffer-in-side-window
783 ,@(if (< (frame-text-width) (frame-text-height))
784 '((side . bottom) (window-height . 24))
785 '((side . right) (window-width . fit-window-to-buffer)))))))
786
787(yoke (hippie-completing-read
788 "https://codeberg.org/acdw/hippie-completing-read.el")
789 (define-key* (current-global-map)
790 "M-/" #'hippie-completing-read))
791
792(yoke dictionary ; Comes with Emacs 29!
793 (defkeys (t (org-mode-map :after org))
794 "C-c d" #'dictionary-search)
795 (defkeys ((embark-identifier-map :after embark))
796 "@" #'dictionary-search)
797 (setc dictionary-server (if (or (executable-find "dictd")
798 (file-exists-p "/usr/sbin/dictd")) ; oh debian
799 "localhost"
800 "dict.org"))
801 (setf (alist-get "^\\*Dictionary\\*" display-buffer-alist nil nil #'string=)
802 '((display-buffer-in-side-window)
803 (side . bottom)
804 (window-height . 20))))
805
806(yoke (anzu "https://github.com/emacsorphanage/anzu")
807 (require 'anzu)
808 (global-anzu-mode)
809 (defkeys t
810 [remap query-replace] #'anzu-query-replace-regexp
811 [remap query-replace-regexp] #'anzu-query-replace)
812 (defkeys (isearch-mode-map (isearch-mb-minibuffer-map :after isearch-mb))
813 [remap isearch-query-replace] #'anzu-isearch-query-replace-regexp
814 [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace)
815 (defun anzu-qr@window (fn &rest r)
816 "ADVICE to query-replace from the beginning of the window."
817 (let ((scroll-margin 0))
818 (cond ((region-active-p)
819 (apply fn r))
820 (:else (save-excursion
821 (goto-char (window-start))
822 (apply fn r))))))
823 (advice-add 'anzu-query-replace-regexp :around #'anzu-qr@window)
824 (advice-add 'anzu-query-replace :around #'anzu-qr@window))
825
826(yoke tempo
827 (require '+tempo))
828
829(yoke (0x0 "https://gitlab.com/willvaughn/emacs-0x0")
830 (setf 0x0-default-server 'ttm)
831 (define-advice 0x0-shorten-uri (:around (fn server uri) use-0x0)
832 (interactive (list (cdr (assq 'envs 0x0-servers))
833 (read-string "URI: ")))
834 (funcall fn server uri))
835 (eval-after embark
836 (define-key* embark-region-map
837 "U" #'0x0-dwim)))
838
839(yoke (filldent "https://codeberg.org/acdw/filldent.el")
840 (define-advice canonically-space-region (:around (orig &rest r) double-space)
841 (let ((sentence-end-double-space t))
842 (apply orig r)))
843 (defkeys t
844 "M-q" #'filldent-unfill-toggle))
845
846(yoke (avy "https://github.com/abo-abo/avy")
847 (require 'avy)
848 (setf avy-background t
849 (alist-get ?. avy-dispatch-alist)
850 (defun avy-action-embark (pt)
851 (unwind-protect
852 (save-excursion
853 (goto-char pt)
854 (embark-act))
855 (select-window
856 (cdr (ring-ref avy-ring 0))))
857 t))
858 (define-key* (current-global-map)
859 "M-j" #'avy-goto-char-timer)
860 (define-key* isearch-mode-map
861 "M-j" #'avy-isearch))
862
863(yoke (frowny "https://codeberg.org/acdw/frowny.el")
864 (setf frowny-eyes (rx (any ":=") (opt "'") (? "-")))
865 (global-frowny-mode))
866 502
867(yoke (isearch-mb "https://github.com/astoff/isearch-mb") 503;;; Lisps
868 (eval-after (consult anzu)
869 (require 'isearch-mb)
870 (dolist (spec '((isearch-mb--with-buffer
871 ("M-e" . consult-isearch)
872 ("C-o" . loccur-isearch))
873 (isearch-mb--after-exit
874 ("M-%" . anzu-isearch-query-replace)
875 ("M-s l" . consult-line))))
876 (let ((isearch-mb-list (car spec))
877 (isearch-mb-binds (cdr spec)))
878 (dolist (cell isearch-mb-binds)
879 (let ((key (car cell))
880 (command (cdr cell)))
881 (when (fboundp command)
882 (add-to-list isearch-mb-list command)
883 (define-key isearch-mb-minibuffer-map (kbd key) command)))))))
884 (isearch-mb-mode))
885 504
886(yoke (keepassxc-shim "https://codeberg.org/acdw/keepassxc-shim.el") 505(use-package elisp-mode
506 :custom
507 (eval-expression-print-length nil)
508 (eval-expression-print-level nil)
509 :preface
510 (defun elisp-eval-region|eval-buffer ()
511 (interactive)
512 (if (region-active-p)
513 (let ((start (region-beginning))
514 (end (region-end)))
515 (eval-region start end)
516 (message "Region from %s to %s evaluated."
517 start end))
518 (eval-buffer)
519 (message "Buffer `%s' evaluated." (buffer-name))))
520 (defkeys (emacs-lisp-mode-map lisp-interaction-mode-map)
521 "C-c C-c" #'eval-defun
522 "C-c C-k" #'elisp-eval-region|eval-buffer
523 "C-c C-z" #'ielm)
524 :config
525 (define-advice eval-region (:around (orig start end &rest args) pulse)
526 (apply orig start end args)
527 (pulse-momentary-highlight-region start end))
528 (defhook emacs-lisp-mode-hook
529 (setq-local page-delimiter "^\\( \\|;;;+\\).*")))
530
531(use-package macrostep
532 :ensure t
533 :after elisp-mode
534 :bind ( :map emacs-lisp-mode-map
535 ("C-c e" . macrostep-expand)
536 :map lisp-interaction-mode-map
537 ("C-c e" . macrostep-expand)))
538
539(use-package eros
540 :ensure t
541 :after elisp-mode
542 :custom (eros-eval-result-prefix "; ")
543 :hook emacs-lisp-mode-hook)
544
545(use-package package-lint
546 :ensure t)
547
548(use-package sly
549 :ensure t
550 :when (executable-find "sbcl")
551 :custom
552 (inferior-lisp-program (executable-find "sbcl"))
553 (sly-net-coding-system 'utf-8-unix)
554 :bind (:map sly-mode-map
555 (("C-c C-z" . sly-mrepl)))
556 :config
557 (sly-symbol-completion-mode -1))
558
559(use-package edebug
560 :preface
561 (defun turn-off-eldoc-mode ()
562 (eldoc-mode -1))
563 :config
564 (add-hook 'edebug-mode-hook #'turn-off-eldoc-mode))
565
566;;; Web languages
567
568(use-package web-mode
569 :ensure t
570 :mode ("\\.phtml\\'"
571 "\\.tpl\\.php\\'"
572 "\\.[agj]sp\\'"
573 "\\.as[cp]x\\'"
574 "\\.erb\\'"
575 "\\.mustache\\'"
576 "\\.djhtml\\'"
577 "\\.html?\\'"))
578
579;;; Applications
580
581(use-package dired
582 :init (require 'dired-x)
583 :custom
584 (dired-recursive-copies 'always)
585 (dired-recursive-deletes 'always)
586 (dired-create-destination-dirs 'always)
587 (dired-do-revert-buffer t)
588 (dired-hide-details-hide-symlink-targets nil)
589 (dired-isearch-filenames 'dwim)
590 (delete-by-moving-to-trash t)
591 (dired-auto-revert-buffer t)
592 (dired-listing-switches "-AlF")
593 (ls-lisp-dirs-first t)
594 (dired-ls-F-marks-symlinks t)
595 (dired-clean-confirm-killing-deleted-buffers nil)
596 (dired-no-confirm '(byte-compile
597 load chgrp chmod chown
598 copy move hardlink symlink
599 shell touch))
600 (dired-dwim-target t)
601 :bind
602 (("C-x C-j" . dired-jump)
603 ([remap list-directory] . dired)
604 :map dired-mode-map
605 ("C-j" . dired-up-directory)
606 ("<backspace>" . dired-up-directory))
607 :init
608 (defhook dired-mode-hook
609 #'dired-hide-details-mode
610 #'hl-line-mode
611 #'truncate-lines-local-mode))
612
613(use-package dired-subtree
614 :ensure t
615 :after dired
616 :bind (:map dired-mode-map
617 (("TAB" . dired-subtree-cycle)
618 ("i" . dired-subtree-toggle))))
619
620(use-package dired-collapse
621 :ensure t
622 :hook dired-mode-hook)
623
624(use-package dired-hide-dotfiles
625 :ensure t
626 :bind (:map dired-mode-map
627 ("." . dired-hide-dotfiles-mode)))
628
629(use-package magit
630 :ensure t
631 :bind
632 ("C-x g" . magit))
633
634(use-package auth-source
635 :custom
636 (auth-sources '(default "secrets:passwords"))
637 :config
638 (defhook authinfo-mode-hook
639 #'truncate-lines-local-mode))
640
641(use-package keychain-environment
642 :ensure t
643 :when (executable-find "keychain")
644 :hook (after-init-hook . keychain-refresh-environment))
645
646(use-package keepassxc-shim
647 :load-path "~/src/emacs/keepassxc-shim/"
648 :config
887 (keepassxc-shim-activate)) 649 (keepassxc-shim-activate))
888 650
889(yoke (keychain-environment "https://github.com/tarsius/keychain-environment") 651(use-package eat
890 :when (executable-find "keychain") 652 :ensure t
891 (keychain-refresh-environment)) 653 :commands eat-eshell-mode
892 654 :hook (eshell-load-hook . eat-eshell-mode))
893(yoke (exec-path-from-shell "https://github.com/purcell/exec-path-from-shell") 655
894 :when (eq system-type 'gnu/linux) 656(use-package dictionary
895 (require 'exec-path-from-shell) 657 :custom
896 (dolist (var '("SSH_AUTH_SOCK" 658 (dictionary-server (if (or (executable-find "dictd")
897 "SSH_AGENT_PID" 659 (file-exists-p "/usr/sbin/dictd")) ; debian
898 "GPG_AGENT_INFO" 660 "localhost"
899 "LANG" 661 "dict.org"))
900 "LC_CTYPE" 662 :bind
901 "XDG_CONFIG_HOME" 663 (("C-c d" . dictionary-search)))
902 "XDG_CONFIG_DIRS" 664
903 "XDG_DATA_HOME" 665(use-package pdf-tools
904 "XDG_DATA_DIRS" 666 :ensure t
905 "XDG_CACHE_HOME")) 667 :mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode)
906 (add-to-list 'exec-path-from-shell-variables var)) 668 :magic ("%PDF" . pdf-view-mode)
907 (exec-path-from-shell-initialize)) 669 :config
908 670 (pdf-tools-install))
909(yoke (sophomore "https://codeberg.org/acdw/sophomore.el")
910 (sophomore-enable-all)
911 (sophomore-disable #'view-hello-file
912 #'describe-gnu-project)
913 (sophomore-disable-with 'confirm #'save-buffers-kill-terminal))
914
915(yoke (macrostep "https://github.com/joddie/macrostep")
916 (eval-after elisp-mode (require 'macrostep))
917 (define-key* '(emacs-lisp-mode-map
918 lisp-interaction-mode-map)
919 "C-c e" #'macrostep-expand))
920
921(yoke (expand-region "https://github.com/magnars/expand-region.el")
922 (define-advice er/clear-history (:after (&rest _) refold-org)
923 (when (derived-mode-p 'org-mode)
924 (+org-hide-drawers-except-point)
925 (org-link-descriptive-ensure)
926 (font-lock-update)))
927 (defkeys t
928 "C-=" #'er/expand-region))
929 671
930(yoke (embrace "https://github.com/cute-jumper/embrace.el") 672;;; Mail
931 :depends ((expand-region "https://github.com/magnars/expand-region.el")) 673
932 (defkeys t 674(use-package bbdb
933 "C-\"" #'embrace-commander) 675 :ensure t
934 (dolist (fnhook '((org-mode-hook embrace-org-mode-hook) 676 :custom
935 (ruby-mode-hook embrace-ruby-mode-hook) 677 (bbdb-complete-mail-allow-cycling t)
936 (emacs-lisp-mode-hook embrace-emacs-lisp-mode-hook) 678 (bbdb-file (private/ "bbdb"))
937 (latex-mode-hook embrace-LaTeX-mode-hook))) 679 :init
938 (apply #'add-hook fnhook)) 680 (defhook custom-allowed-after-load-hook
939 (eval-after org 681 :name bbdb@after-custom
940 (defkeys org-mode-map 682 (require 'bbdb)
941 "C-\"" #'embrace-commander) 683 (require 'bbdb-message)
942 (defmacro org-insert-or-embrace (char) 684 (bbdb-initialize 'message)))
943 "Define a function to insert CHAR, or `embrace' the region with it." 685
944 (let* ((fn-name (intern (format "org-insert-or-embrace-%s" char))) 686(use-package bbdb-vcard
945 (char (cond ((characterp char) char) 687 :ensure t)
946 ((stringp char) (string-to-char char)) 688
947 (t (user-error "Bad format for char: %S" char))))) 689(use-package notmuch
948 `(defun ,fn-name (n) 690 :load-path "~/usr/share/emacs/site-lisp/"
949 ,(format "Insert N %ss, or surround the region with them." 691 :preface
950 (char-to-string char)) 692 (defdir notmuch/ (sync/ "emacs/notmuch"))
951 (interactive "p") 693 :init
952 (if (region-active-p) 694 (use-package +notmuch
953 (dotimes (_ n) 695 :after notmuch
954 (embrace--add-internal (region-beginning) (region-end) ,char) 696 :load-path "lisp/"
955 (forward-char 1)) 697 :bind (("C-c n" . +notmuch-goto)
956 (self-insert-command n ,char))))) 698 :map notmuch-search-mode-map
957 (define-key* org-mode-map 699 ("!" . +notmuch-search-mark-spam)
958 "*" (org-insert-or-embrace "*") 700 :map notmuch-tree-mode-map
959 "/" (org-insert-or-embrace "/") 701 ("!" . +notmuch-search-mark-spam-then-next)
960 "_" (org-insert-or-embrace "_") 702 ("M-<" . +notmuch-tree-beginning)
961 "=" (org-insert-or-embrace "=") 703 ("M->" . +notmuch-tree-end))
962 "~" (org-insert-or-embrace "~") 704 :hook (message-send-hook . +send-mail-dispatch)
963 "+" (org-insert-or-embrace "+")))) 705 :config
964 706 ;; Saved searches
965(yoke (notmuch "~/usr/share/emacs/site-lisp") 707 (+notmuch-make-saved-search "inbox+unread" "m" 'tree "tag:inbox" "tag:unread" "NOT tag:Spam")
966 (eval-after bbdb 708 (+notmuch-make-saved-search "inbox" "i" 'tree "tag:inbox" "NOT tag:Spam")
967 (require* 'notmuch '+notmuch '+message)) 709 (+notmuch-make-saved-search "lists+unread" "l" 'tree "tag:/List/" "tag:unread")
968 (+define-dir notmuch/ (sync/ "emacs/notmuch") 710 (+notmuch-make-saved-search "lists" "L" 'tree "tag:/List/")
969 "Notmuch configuration and data.") 711 (+notmuch-make-saved-search "unread" "u" 'tree "tag:unread" "NOT tag:Spam")
970 (setf notmuch-init-file (notmuch/ "notmuch-init.el" t) 712 (+notmuch-make-saved-search "flagged" "f" 'tree "tag:flagged")
971 notmuch-address-save-filename (notmuch/ "addresses" t) 713 (+notmuch-make-saved-search "sent" "t" 'tree "tag:sent")
972 notmuch-address-use-company (featurep 'company) 714 (+notmuch-make-saved-search "drafts" "d" 'tree "tag:draft")
973 notmuch-search-oldest-first nil 715 (+notmuch-make-saved-search "all mail" "a" 'tree "*"))
974 notmuch-archive-tags '("-inbox" "-unread") 716 (use-package message
975 notmuch-draft-tags '("+draft" "-inbox" "-unread") 717 :after notmuch
976 mail-user-agent 'notmuch-user-agent 718 :hook (message-setup-hook . message-signature-setup)
977 bbdb-mail-user-agent 'notmuch-user-agent 719 :config (require '+message))
978 message-mail-user-agent t) 720 (defhook (notmuch-show-mode-hook notmuch-message-mode-hook)
979 (define-key* (current-global-map)
980 "C-c m" #'notmuch-mua-new-mail
981 "C-c n" #'+notmuch-goto)
982 ;; Reading mail
983 (setf notmuch-show-indent-content nil)
984 (add-hook* '(notmuch-show-mode-hook
985 notmuch-message-mode-hook)
986 #'visual-fill-column-mode) 721 #'visual-fill-column-mode)
987 (eval-after notmuch 722 :custom
988 (define-key* notmuch-search-mode-map 723 (notmuch-init-file (notmuch/ "notmuch-init.el" t))
989 "RET" #'notmuch-search-show-thread 724 (notmuch-address-save-filename (notmuch/ "addresses" t))
990 "M-RET" #'notmuch-tree-from-search-thread 725 (notmuch-address-use-company (featurep 'company))
991 "!" #'+notmuch-search-mark-spam) 726 (notmuch-search-oldest-first nil)
992 (define-key* notmuch-tree-mode-map 727 (notmuch-archive-tags '("-inbox" "-unread"))
993 "!" #'+notmuch-search-mark-spam-then-next 728 (notmuch-draft-tags '("+draft" "-inbox" "-unread"))
994 "M-<" (notmuch-tree--define-do-in-message-window 729 (mail-user-agent 'notmuch-user-agent)
995 notmuch-tree-beginning-of-message beginning-of-buffer) 730 (bbdb-mail-user-agent 'notmuch-user-agent)
996 "M->" (notmuch-tree--define-do-in-message-window 731 (message-mail-user-agent t)
997 notmuch-tree-end-of-message end-of-buffer))) 732 (notmuch-show-indent-content nil)
998 ;; Writing mail 733 (message-kill-buffer-on-exit t)
999 (setf message-kill-buffer-on-exit t 734 (message-auto-save-directory nil)
1000 message-auto-save-directory nil) 735 (send-mail-function #'sendmail-send-it)
1001 ;; Sending mail 736 (mail-specify-envelope-from t)
1002 (setf send-mail-function #'sendmail-send-it 737 (message-sendmail-envelope-from 'header)
1003 mail-specify-envelope-from t 738 (message-envelope-from 'header)
1004 message-sendmail-envelope-from 'header 739 (notmuch-saved-searches nil)
1005 message-envelope-from 'header) 740 :bind
1006 ;; Extras 741 (("C-c m" . notmuch-mua-new-mail)
1007 (define-advice mm-save-part-to-file (:before (_handle file) create-directory) 742 :map notmuch-search-mode-map
1008 (let ((directory (file-name-directory file))) 743 ("RET" . notmuch-search-show-thread)
1009 (when (yes-or-no-p (format "Directory %s doesn't exist. Create?" directory)) 744 ("M-RET" . notmuch-tree-from-search-thread))
1010 (make-directory directory :parents)))) 745 :config
1011 (eval-after notmuch 746 (load notmuch-init-file :noerror)
1012 (require '+notmuch) 747 (advice-add 'notmuch-tag :filter-args #'+notmuch-correct-tags)
1013 (load notmuch-init-file :noerror) 748 ;; Mailing lists
1014 (add-hook 'message-setup-hook #'+message-signature-setup) 749 (add-to-list 'notmuch-message-headers "List-Post" :append)
1015 (add-hook 'message-send-hook #'+send-mail-dispatch) 750 (define-advice notmuch-mua-new-reply (:around (orig &rest r) list-aware)
1016 (advice-add 'notmuch-tag :filter-args #'+notmuch-correct-tags) 751 "Make `notmuch-mua-new-reply' list-aware."
1017 (advice-add 'notmuch-bury-or-kill-this-buffer :after 752 (let ((ml (notmuch-show-get-header :List-Post)))
1018 (defun +display-time@notmuch (&rest _) 753 (apply orig r)
1019 ;; (display-time-event-handler) 754 (when ml
1020 (setq display-time-mail-string 755 (with-buffer-modified-unmodified
1021 (replace-regexp-in-string "(.*)" 756 (message-remove-header "To")
1022 (format "(%s)" (+notmuch-new-mail-p)) 757 (message-add-header
1023 display-time-mail-string)) 758 (format "To: %s"
1024 (display-time-update) 759 (replace-regexp-in-string "<mailto:\\(.*\\)>" "\\1" ml)))
1025 (force-mode-line-update))) 760 (message-goto-body))))))
1026 (add-to-list 'notmuch-message-headers "List-Post" :append) 761
1027 (define-advice notmuch-mua-new-reply (:around (orig &rest r) list-aware) 762;;; The INTERNET
1028 "Make `notmuch-mua-new-reply' list-aware." 763
1029 (let ((ml (notmuch-show-get-header :List-Post))) 764(use-package link-hint
1030 (apply orig r) 765 :ensure t
1031 (when ml 766 :demand t
1032 (with-buffer-modified-unmodified 767 :preface
1033 (message-remove-header "To") 768 (global-set-key (kbd "M-l") nil)
1034 (message-add-header 769 :custom
1035 (format "To: %s" 770 (link-hint-avy-style 'at-full)
1036 (replace-regexp-in-string "<mailto:\\(.*\\)>" "\\1" ml))) 771 (link-hint-avy-all-windows t)
1037 (message-goto-body))))) 772 :config
1038 (setf notmuch-saved-searches (list 773 (require '+link-hint)
1039 (list :name "inbox+unread" 774 (+link-hint-open-secondary-setup)
1040 :query (+notmuch-query-concat 775 (+link-hint-open-chrome-setup)
1041 "tag:inbox" 776 :bind
1042 "tag:unread" 777 (("M-l M-l" . +link-hint-open-link)
1043 "NOT tag:Spam") 778 ("M-l l" . +link-hint-open-link)
1044 :key "m" 779 ("M-l M-o" . +link-hint-open-secondary)
1045 :search-type 'tree) 780 ("M-l o" . +link-hint-open-secondary)
1046 (list :name "inbox" 781 ("M-l M-m" . +link-hint-open-multiple-links)
1047 :query (+notmuch-query-concat 782 ("M-l m" . +link-hint-open-multiple-links)
1048 "tag:inbox" 783 ("M-l M-w" . link-hint-copy-link)
1049 "NOT tag:Spam") 784 ("M-l w" . link-hint-copy-link)
1050 :key "i" 785 ("M-l M-c" . +link-hint-open-chrome)
1051 :search-type 'tree) 786 ("M-l c" . +link-hint-open-chrome)))
1052 (list :name "lists+unread" 787
1053 :query (+notmuch-query-concat 788(use-package browse-url
1054 "tag:/List/" 789 :demand t
1055 "tag:unread") 790 :preface
1056 :key "l" 791 (require '+browse-url)
1057 :search-type 'tree) 792 :config
1058 (list :name "lists" 793 (eval-after chd
1059 :query "tag:/List/" 794 (add-to-list 'browse-url-handlers (cons chd/url-regexps
1060 :key "L" 795 #'browse-url-chrome))
1061 :search-type 'tree) 796 (add-to-list '+browse-url-other-safe-browser-functions
1062 (list :name "unread" 797 #'chd/browse-url))
1063 :query (+notmuch-query-concat 798 (put 'browse-url-browser-function 'safe-local-variable
1064 "tag:unread" 799 #'+browse-url-browser-function-safe-p)
1065 "NOT tag:Spam") 800 :custom
1066 :key "u" 801 (browse-url-browser-function #'eww-browse-url)
1067 :search-type 'tree) 802 (browse-url-chromium-program (choose-executable "chromium"))
1068 (list :name "flagged" 803 (browse-url-chrome-program (choose-executable "chrome" "google-chrome-stable"))
1069 :query "tag:flagged" 804 (browse-url-firefox-program (choose-executable "firefox" "firefox-esr"))
1070 :key "f" 805 (browse-url-firefox-new-window-is-tab t)
1071 :search-type 'tree) 806 (browse-url-firefox-arguments '("--new-tab"))
1072 (list :name "sent" 807 (browse-url-generic-program (or browse-url-firefox-program
1073 :query "tag:sent" 808 browse-url
1074 :key "t" 809 browse-url-chrome-program))
1075 :search-type 'tree) 810 (browse-url-generic-args
1076 (list :name "drafts" 811 (cl-loop for prog in '(firefox chromium chrome)
1077 :query "tag:draft" 812 for progfn = (intern (format "browse-url-%s-program"
1078 :key "d" 813 prog))
1079 :search-type 'tree) 814 for progargs = (intern (format "browse-url-%s-arguments"
1080 (list :name "all mail" 815 prog))
1081 :query "*" 816 if (equal browse-url-generic-program
1082 :key "a" 817 (symbol-value progfn))
1083 :search-type 'tree))))) 818 return (symbol-value progargs)
1084 819 finally return nil))
1085(yoke (cider "https://github.com/clojure-emacs/cider") 820 (browse-url-handlers
1086 :depends ((clojure-mode "http://github.com/clojure-emacs/clojure-mode") 821 `(;; Videos
1087 (parseedn "https://github.com/clojure-emacs/parseedn/") 822 (,(+browse-url-matches "youtube\\.com" "youtu\\.be"
1088 (parseclj "https://github.com/clojure-emacs/parseclj/") ; parseedn 823 "invidious" "yewtu\\.be"
1089 (queue "https://elpa.gnu.org/packages/queue-0.2.el" :type 'http) 824 (rx "." (or "mp4" "gif" "mov" "MOV" "webm")
1090 (spinner "https://github.com/Malabarba/spinner.el") 825 eos))
1091 (sesman "https://github.com/vspinu/sesman")) 826 . +browse-url-with-mpv)
1092 :when (executable-find "clojure")) 827 ;; Music
1093 828 (,(+browse-url-matches "soundcloud\\.com" "bandcamp\\.com"
1094(yoke (web-mode "https://github.com/fxbois/web-mode") 829 (rx "." (or "ogg" "mp3" "opus" "m4a") eos))
1095 (setf (alist-get (rx "." (or "htm" "html" "phtml" "tpl.php" 830 . +browse-url-with-mpv)
1096 "asp" "gsp" "jsp" "ascx" "aspx" 831 ;; Images
1097 "erb" "mustache" "djhtml") 832 (,(+browse-url-matches "pbs\\.twimg\\.com"
1098 eos) 833 (rx "." (or "jpeg" "jpg" "png" "bmp" "webp")
1099 auto-mode-alist nil nil #'string=) 834 eos))
1100 'web-mode)) 835 . +browse-url-with-mpv-image)
1101 836 ;; Blobs (binary files)
1102(yoke (chicken-geiser "https://gitlab.com/emacs-geiser/chicken") 837 (,(+browse-url-matches (rx "." (or ".tar.gz" ".pdf") eos))
1103 :depends ((geiser "https://gitlab.com/emacs-geiser/geiser" 838 . +browse-url-download)
1104 :load "elisp")) 839 ;; External URLs --- these are URLs that don't open in EWW very well,
1105 :when (executable-find "csi") 840 ;; so I want to open them in the external browser.
1106 :pre ((autoload 'geiser-activate-implementation "geiser-impl")) 841 (+browse-url-external-url-p . ,(if (featurep 'xwidget-internal)
1107 (autoload 'geiser "geiser" nil :interactive) 842 #'xwidget-webkit-browse-url
1108 (add-hook 'scheme-mode-hook 'geiser-mode)) 843 browse-url-secondary-browser-function))))
1109 844 (+browse-url-external-domains '("github.com" "gitlab.com" "codeberg.org"
1110(yoke (zoom-frm "https://github.com/emacsmirror/zoom-frm") 845 "tildegit.org" "git.tilde.town"
1111 :depends ((frame-cmds "https://github.com/emacsmirror/frame-cmds") 846 "google.com" "imgur.com" "twitch.tv"
1112 (frame-fns "https://github.com/emacsmirror/frame-fns")) 847 "pixelfed" "instagram.com"
1113 (define-key* (current-global-map) 848 "bibliogram.art" "reddit.com"
1114 "M-+" #'zoom-frm-in 849 "teddit.net" "libreddit.de"
1115 "M-_" #'zoom-frm-out)) 850 "streamable.com" "spotify.com"
1116 851 "hetzner.cloud" "melpa.org"
1117(yoke (jabber "https://codeberg.org/acdw/emacs-jabber") 852 "twitter.com" ("^t\\.co$")
1118 :depends ((srv "https://github.com/legoscia/srv.el") 853 "nitter.snopyta.org" "nitter.net")))
1119 (fsm "https://elpa.gnu.org/packages/fsm-0.2.1.el" :type 'http)) 854
1120 (setf jabber-account-list '(("acdw@hmm.st")) 855(use-package browse-url-transform
1121 jabber-auto-reconnect t 856 :after browse-url
1122 jabber-chat-buffer-format "xmpp:%n" 857 :load-path "~/src/emacs/browse-url-transform/"
1123 jabber-browse-buffer-format "xmpp-browse:%n" 858 :custom
1124 jabber-groupchat-buffer-format "xmpp-muc:%n" 859 (browse-url-transform-alist
1125 jabber-muc-private-buffer-format "xmpp-muc-private:%n" 860 `(;; Privacy-respecting alternatives
1126 jabber-groupchat-prompt-format "%>10n │ " 861 ("twitter\\.com" . "nitter.snopyta.org")
1127 jabber-chat-local-prompt-format "%>10n │ " 862 ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de")
1128 jabber-chat-system-prompt-format " * * * * * *" 863 ("medium\\.com" . "scribe.rip")
1129 jabber-chat-foreign-prompt-format "%>10n │ " 864 (".*substack\\.com.*" . ,substack-proxy)
1130 jabber-muc-private-foreign-prompt-format "%g/%n " 865 ;; Text-mode of non-text-mode sites
1131 jabber-last-read-marker "----------------------------------------" 866 ("www\\.npr\\.org" . "text.npr.org")
1132 jabber-muc-header-line-format '("" jabber-muc-topic) 867 ;; Ask for raw versions of paste sites
1133 jabber-muc-decorate-presence-patterns 868 ("^.*dpaste\\.com.*$" . "\\&.txt")
1134 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$") 869 ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1")
1135 ("." . jabber-muc-presence-dim)) 870 ("\\(paste\\.debian\\.net\\)/\\(.*\\)" . "\\1/plain/\\2")
1136 jabber-activity-make-strings 871 ("\\(pastebin\\.com\\)/\\\(.*\\)" . "\\1/raw/\\2")
1137 #'jabber-activity-make-strings-shorten 872 ("\\(paste\\.centos\\.org/view\\)/\\(.*\\)" . "\\1/raw/\\2")))
1138 ;; (defun +jabber-activity-make-strings (jids) 873 :config
1139 ;; (mapcar (lambda (jid) 874 (browse-url-transform-mode))
1140 ;; (cons jid 875
1141 ;; (let ((s (jabber-activity-make-string-default jid))) 876(use-package eww
1142 ;; (cond 877 :custom
1143 ;; ((string-match-p "%" s) 878 (eww-use-browse-url ".")
1144 ;; (replace-regexp-in-string "%.*" "" s)) 879 :config
1145 ;; (:else s))))) 880 (defhook eww-mode-hook
1146 ;; jids)) 881 (defhook ((visual-fill-column-mode nil :local))
1147 jabber-rare-time-format " - - - - - - %H:%M %F") 882 :name visual-fill-column@eww-mode-refresh
883 (eww-reload t)))
884 (defkeys eww-mode-map
885 "&" (+browse-url-switch-external-browser eww-mode
886 (plist-get eww-data :url))))
887
888(use-package xwidget
889 :when (featurep 'xwidget-internal)
890 :preface
891 (setenv "WEBKIT_FORCE_SANDBOX" "0") ; This is probably a bad idea
892 :custom
893 (xwidget-webkit-cookie-file (cache/ "xwidget-cookies"))
894 (xwidget-webkit-download-dir (expand-file-name "~/var/download"))
895 :config
896 (defkeys xwidget-webkit-mode-map
897 "&" (+browse-url-switch-external-browser xwidget-webkit-mode
898 (xwidget-webkit-uri (xwidget-webkit-current-session)))))
899
900(use-package xwwp
901 :load-path "~/src/emacs/xwwp/"
902 :when (featurep 'xwidget-internal)
903 :custom
904 (xwwp-search-prefix "https://duckduckgo.com/?q=")
905 :bind (:map xwidget-webkit-mode-map
906 ("f" . xwwp-follow-link)))
907
908(use-package jabber
909 :load-path "~/src/emacs/emacs-jabber"
910 :custom
911 (jabber-account-list '(("acdw@hmm.st")))
912 (jabber-auto-reconnect t)
913 (jabber-chat-buffer-format "xmpp:%n")
914 (jabber-browse-buffer-format "xmpp-browse:%n")
915 (jabber-groupchat-buffer-format "xmpp-muc:%n")
916 (jabber-muc-private-buffer-format "xmpp-muc-private:%n")
917 (jabber-groupchat-prompt-format "%>10n │ ")
918 (jabber-chat-local-prompt-format "%>10n │ ")
919 (jabber-chat-system-prompt-format " * * * * * *")
920 (jabber-chat-foreign-prompt-format "%>10n │ ")
921 (jabber-muc-private-foreign-prompt-format "%g/%n ")
922 (jabber-last-read-marker "----------------------------------------")
923 (jabber-muc-header-line-format '("" jabber-muc-topic))
924 (jabber-muc-decorate-presence-patterns
925 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$")
926 ("." . jabber-muc-presence-dim)))
927 (jabber-activity-make-strings
928 #'jabber-activity-make-strings-shorten)
929 (jabber-rare-time-format " - - - - - - %H:%M %F")
930 :custom-face
931 (jabber-chat-prompt-local ((t :inherit font-lock-keyword-face
932 :foreground unspecified)))
933 (jabber-activity-face ((t :inherit jabber-chat-prompt-foreign
934 :foreground unspecified
935 :weight normal)))
936 (jabber-activity-personal-face ((t :inherit font-lock-warning-face
937 :foreground unspecified
938 :weight bold)))
939 (jabber-chat-prompt-foreign ((t :inherit font-lock-constant-face
940 :foreground unspecified)))
941 (jabber-chat-prompt-system ((t :inherit font-lock-doc-face
942 :foreground unspecified)))
943 (jabber-rare-time-face ((t :inherit font-lock-comment-face
944 :foreground unspecified
945 :underline nil)))
946 :init
1148 (defhook (jabber-chat-mode-hook 947 (defhook (jabber-chat-mode-hook
1149 jabber-browse-mode-hook 948 jabber-browse-mode-hook
1150 jabber-roster-mode-hook 949 jabber-roster-mode-hook
1151 jabber-console-mode-hook) 950 jabber-console-mode-hook)
1152 :name jabber-ui-setup 951 :name jabber-ui-setup
952 (visual-fill-column-mode)
1153 (electric-pair-local-mode -1) 953 (electric-pair-local-mode -1)
1154 (auto-fill-mode -1) 954 (auto-fill-mode -1)
1155 #'visual-fill-column-mode) 955 (setq-local wrap-prefix (format "%13s" " ")))
1156 (setq-local-hook jabber-chat-mode-hook 956 :bind-keymap ("C-c j" . jabber-global-keymap)
1157 wrap-prefix (format "%13s" " ")) 957 :bind (("C-c C-SPC" . jabber-activity-switch-to))
1158 (defun +jabber-fix-keybinds-dammit () 958 :config
1159 "Jabber autoloads keybinds which is really annoying." 959 (global-set-key (kbd "C-x C-j") #'dired-jump) ; Extremely annoying fix
1160 (define-key* (current-global-map) 960 (require 'jabber-httpupload nil :noerror)
1161 "C-x C-j" #'dired-jump 961 (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
1162 "C-c j" jabber-global-keymap 962 (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo)
1163 "C-c C-SPC" #'jabber-activity-switch-to)) 963 (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo)
1164 (eval-after init (+jabber-fix-keybinds-dammit)) 964 (add-hook 'jabber-alert-muc-hooks
1165 (eval-after jabber 965 (defun jabber@highlight-acdw (&optional _nick _group buf _text _title)
1166 (require 'jabber-httpupload nil :noerror) 966 (when buf
1167 (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) 967 (with-current-buffer buf
1168 (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo) 968 (let ((regexp (rx word-boundary
1169 (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo) 969 "acdw" ; maybe get from the config?
1170 (add-hook 'jabber-alert-muc-hooks 970 word-boundary)))
1171 (defun jabber@highlight-acdw (&optional _nick _group buf _text _title) 971 (hi-lock-unface-buffer regexp)
1172 (when buf 972 (highlight-regexp regexp 'hi-blue))))))
1173 (with-current-buffer buf 973 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)
1174 (let ((regexp (rx word-boundary 974 (eval-after consult
1175 "acdw" ; maybe get from the config?
1176 word-boundary)))
1177 (hi-lock-unface-buffer regexp)
1178 (highlight-regexp regexp 'hi-blue))))))
1179 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)
1180 (+jabber-fix-keybinds-dammit)
1181 (defkeys jabber-chat-mode-map
1182 "C-l" (defun +jabber-recenter-last-read ()
1183 (interactive)
1184 (cond
1185 ((eq last-command '+jabber-recenter-last-read)
1186 (setq this-command #'recenter)
1187 (recenter -1))
1188 (:else
1189 (save-excursion
1190 (condition-case e
1191 (re-search-backward jabber-last-read-marker)
1192 (search-failed nil)
1193 (:success
1194 (recenter 3)))))))))
1195 (defun jabber-chat-kill-buffers ()
1196 "Kill all `jabber-chat-mode' buffers."
1197 (interactive)
1198 (mapc-buffers (lambda () (message "%S" (buffer-name))) '(jabber-chat-mode)))
1199 (defun jabber-chat@after-modus-themes-load ()
1200 (modus-themes-with-colors
1201 (custom-set-faces
1202 `(jabber-chat-prompt-foreign ((t :foreground unspecified
1203 :inherit modus-themes-bold))
1204 :now)
1205 `(jabber-chat-prompt-local ((t :foreground unspecified
1206 :inherit modus-themes-bold))
1207 :now)
1208 `(jabber-chat-prompt-system ((t :foreground unspecified
1209 :inherit modus-themes-bold))
1210 :now)
1211 `(jabber-activity-face ((t :slant italic)))
1212 `(jabber-activity-personal-face ((t :slant italic :weight bold)))
1213 `(jabber-rare-time-face ((t :inherit font-lock-comment-face)))))
1214 (setq jabber-muc-nick-value
1215 (pcase (frame--current-backround-mode (selected-frame))
1216 ('light 0.5)
1217 ('dark 1.0))))
1218 (eval-after modus-themes
1219 (add-hook 'modus-themes-after-load-theme-hook
1220 #'jabber-chat@after-modus-themes-load))
1221 (when (or (custom-theme-enabled-p 'modus-operandi)
1222 (custom-theme-enabled-p 'modus-vivendi))
1223 (jabber-chat@after-modus-themes-load))
1224 (eval-after (consult jabber)
1225 ;; Jabber.el chat buffers source for `consult-buffer'
1226 (defvar jabber-chat-buffer-source 975 (defvar jabber-chat-buffer-source
1227 `( :name "Jabber" 976 `( :name "Jabber"
1228 :hidden nil 977 :hidden nil
@@ -1238,190 +987,3 @@ CAPES defaults to `+capes'. CAPF will be made un-exclusive."
1238 (add-to-list 'consult-buffer-sources 'jabber-chat-buffer-source :append) 987 (add-to-list 'consult-buffer-sources 'jabber-chat-buffer-source :append)
1239 ;; Also hide xmpp buffers from regular buffer list 988 ;; Also hide xmpp buffers from regular buffer list
1240 (add-to-list 'consult-buffer-filter "\\`xmpp" nil #'string-equal))) 989 (add-to-list 'consult-buffer-filter "\\`xmpp" nil #'string-equal)))
1241
1242(yoke (link-hint "https://github.com/noctuid/link-hint.el/")
1243 :depends ((avy "https://github.com/abo-abo/avy"))
1244 (require '+link-hint)
1245 (+link-hint-open-secondary-setup)
1246 (+link-hint-open-chrome-setup)
1247 (setf link-hint-avy-style 'at-full
1248 link-hint-avy-all-windows t)
1249 (global-set-key (kbd "M-l") +link-hint-map)
1250 (define-key* +link-hint-map
1251 "M-l" #'+link-hint-open-link "l" #'+link-hint-open-link
1252 "M-o" #'+link-hint-open-secondary "o" #'+link-hint-open-secondary
1253 "M-m" #'+link-hint-open-multiple-links "m" #'+link-hint-open-multiple-links
1254 "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link
1255 "M-c" #'+link-hint-open-chrome "c" #'+link-hint-open-chrome))
1256
1257(yoke (elpher "git://thelambdalab.xyz/elpher.git")
1258 (eval-after elpher
1259 (define-key* elpher-mode-map
1260 "l" #'elpher-back)))
1261
1262(yoke (epithet "https://github.com/oantolin/epithet")
1263 (defhook (Info-selection-hook
1264 help-mode-hook
1265 occur-mode-hook
1266 shell-mode-hook)
1267 #'epithet-rename-buffer)
1268 (cond ((boundp 'eww-auto-rename-buffer)
1269 (setc eww-auto-rename-buffer 'title))
1270 (:else (defhook eww-after-render-hook #'epithet-rename-buffer))))
1271
1272(yoke browse-url
1273 (require '+browse-url)
1274 (setf browse-url-browser-function #'eww-browse-url
1275 browse-url-chrome-program (seq-some #'executable-find
1276 '("chromium" "chrome" "google-chrome-stable"))
1277 browse-url-firefox-program (seq-some #'executable-find
1278 '("firefox" "firefox-esr"))
1279 browse-url-generic-program (or browse-url-firefox-program
1280 browse-url-chrome-program)
1281 browse-url-firefox-new-window-is-tab t
1282 browse-url-firefox-arguments "-new-tab"
1283 browse-url-handlers `((video-url-p . +browse-url-with-mpv)
1284 (music-url-p . +browse-url-with-mpv)
1285 (image-url-p . +browse-image-with-mpv)
1286 (blobp . +browse-url-download)
1287 (external-url-p . ,browse-url-secondary-browser-function)
1288 ;; HERE FOR REFERENCE --- OPEN MASTO URLS SOME WAY
1289 (,(defun mastodon-url-p (url)
1290 "Try to determine whether URL is a mastodon URL."
1291 (string-match-p "/@[^/]+\\(/\\|/[[:digit:]]+\\)?$" url))
1292 . ,browse-url-secondary-browser-function)))
1293 (+browse-url-make-external-viewer-handler "mpv" '("--cache-pause-wait=30"
1294 "--cache-pause-initial=yes")
1295 "Video URL: "
1296 :fallback browse-url-secondary-browser-function)
1297 (+browse-url-make-external-viewer-handler "mpv" '("--image-display-duration=inf")
1298 "Image URL: "
1299 :name +browse-image-with-mpv)
1300 (defun video-url-p (url) "Is URL a video?"
1301 (string-match-p (rx (or "youtube.com" "youtu.be" "invidious" "yewtu.be"
1302 (seq "." (or "mp4" "gif" "mov" "MOV" "webm") eos)))
1303 url))
1304 (defun music-url-p (url) "Is URL music?"
1305 (string-match-p (rx "soundcloud.com" "bandcamp.com"
1306 (seq "." (or "ogg" "mp3" "opus" "m4a" "flac") eos))
1307 url))
1308 (defun image-url-p (url) "Is URL an image?"
1309 (string-match-p (rx
1310 (or (: "." (or "jpeg" "jpg" "png" "bmp" "webp") eos)
1311 "pbs.twimg.com"))
1312 url))
1313 (defun external-url-p (url) "Should URL open in an external browser?"
1314 (string-match-p (rx (or "github.com" "gitlab.com" "codeberg.org"
1315 "tildegit.org" "git.tilde.town" "google.com"
1316 "imgur.com" "twitch.tv" "pixelfed" "instagram.com"
1317 "bibliogram.art" "reddit.com" "teddit.net"
1318 ;; "twitter.com" "nitter" "t.co"
1319 "streamable.com" "spotify.com"
1320 "hetzner.cloud" "melpa.org"))
1321 url))
1322 (defun blobp (url) "Is URL some other blob that can't open in Emacs?"
1323 (string-match-p (rx (or (: (or ".tar.gz" ".pdf")
1324 eos)))
1325 url))
1326 (eval-after chd
1327 (add-to-list 'browse-url-handlers (cons chd/url-regexps #'chd/browse-url)))
1328 (require 'browse-url-transform)
1329 (setf browse-url-transform-alist `(;; Privacy-respecting alternatives
1330 ("twitter\\.com" . "nitter.snopyta.org")
1331 ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)"
1332 . "libreddit.de")
1333 ("medium\\.com" . "scribe.rip")
1334 ;; Text-mode of non-text-mode sites
1335 ("www\\.npr\\.org" . "text.npr.org")
1336 ;; Ask for raw versions of paste sites
1337 ("^.*dpaste\\.com.*$" . "\\&.txt")
1338 ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1")
1339 ("\\(paste\\.debian\\.net\\)/\\(.*\\)"
1340 . "\\1/plain/\\2")
1341 ("\\(pastebin\\.com\\)/\\\(.*\\)"
1342 . "\\1/raw/\\2")
1343 ("gist\\.github\\.com/\\(.*\\)"
1344 . "gist.githubusercontent.com/\\1/raw/")))
1345 (browse-url-transform-mode))
1346
1347(yoke eww
1348 (setc eww-use-browse-url ".")
1349 (eval-after eww
1350 (defhook eww-mode-hook
1351 #'visual-fill-column-mode
1352 (defhook ((visual-fill-column-mode-hook nil :local))
1353 :name eww-mode-refresh@visual-fill-column
1354 (eww-reload t)))
1355 (defkeys eww-mode-map
1356 "&"
1357 (defun +eww-browse-with-external-browser (&optional url)
1358 "Browse URL with an external browser and close eww."
1359 (interactive nil eww-mode)
1360 (condition-case e
1361 ;; This is wrapped in a `condition-case' so that the eww window
1362 ;; won't close if there's an error calling the browser.
1363 (funcall browse-url-secondary-browser-function
1364 (or url (plist-get eww-data :url)))
1365 (:success
1366 (when (null url) ; interactive
1367 (quit-window)))
1368 (t (signal (car e) (cdr e)))))))
1369 (eval-after (eww link-hint)
1370 (defkeys eww-mode-map
1371 "f" #'+link-hint-open-link)))
1372
1373(yoke tab-bar
1374 (setf tab-bar-show t
1375 global-mode-string
1376 '((jabber-activity-mode
1377 (:eval
1378 (let ((str (or (bound-and-true-p jabber-activity-mode-string)
1379 "")))
1380 (concat (truncate-string-to-width str 20 nil nil t)
1381 (if (< 0 (length str)) " ⋅" "")))))
1382 display-time-string
1383 "|"))
1384 (eval-after jabber
1385 (defhook jabber-activity-mode-hook
1386 (setf global-mode-string
1387 '((jabber-activity-mode
1388 (:eval
1389 (let ((str (or (bound-and-true-p jabber-activity-mode-string)
1390 "")))
1391 (concat (truncate-string-to-width str 20 nil nil t)
1392 (if (< 0 (length str)) " ⋅" "")))))
1393 display-time-string
1394 "|"))))
1395 (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append)
1396 (add-to-list 'tab-bar-format 'tab-bar-format-global :append)
1397 (tab-bar-mode))
1398
1399(yoke (pdf-tools "https://github.com/vedang/pdf-tools"
1400 :load "lisp")
1401 :depends ((tablist "https://github.com/politza/tablist/"))
1402 :when (executable-find "epdfinfo") ; installed from Debian repos
1403 (pdf-tools-install))
1404
1405(yoke which-function
1406 (setf (alist-get 'which-function-mode mode-line-misc-info)
1407 '((which-func-mode ; Only display if buffer supports it
1408 (:eval (when (which-function)
1409 (list "" which-func-format " "))))))
1410 (which-function-mode))
1411
1412(yoke (zzz-to-char "https://github.com/mrkkrp/zzz-to-char")
1413 :depends ((avy "https://github.com/abo-abo/avy"))
1414 (setf zzz-to-char-reach 120)
1415 (defkeys t
1416 [remap zap-to-char]
1417 (defun +zzz-to-char (&optional prefix)
1418 "Run `zzz-up-to-char', or `zzz-to-char' with PREFIX."
1419 (interactive "P")
1420 (call-interactively (cond (prefix #'zzz-to-char)
1421 (:else #'zzz-up-to-char))))))
1422
1423(yoke sh-mode
1424 (defhook sh-mode-hook
1425 :name turn-off-sh-electric-here-document-mode
1426 (sh-electric-here-document-mode -1)))
1427