summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--init.el2351
-rw-r--r--readme.txt4
2 files changed, 1624 insertions, 731 deletions
diff --git a/init.el b/init.el index c6d361b..a0324cf 100644 --- a/init.el +++ b/init.el
@@ -1,23 +1,132 @@
1;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*- 1;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*-
2;; by Case Duckworth <acdw@acdw.net> 2;; by Case Duckworth <acdw@acdw.net>
3;; Bankruptcy 10: "Annoyance" 3;; License: GPLv3
4 4
5;;; Commentary: 5(setopt custom-file (locate-user-emacs-file "custom.el"))
6(load custom-file :noerror)
6 7
7;; This is my Emacs configuration. There are many like it but this 8(add-hook 'after-init-hook
8;; one is mine. 9 (lambda () (load (locate-user-emacs-file "private") :noerror)))
9;;
10;; For the tenth time!
11 10
12;;; Code: 11(require 'package)
12(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
13(package-initialize)
13 14
14(add-hook 'after-init-hook 15(defun package-ensure (pkg &optional local require)
15 (lambda () 16 "Ensure PKG is installed.
16 (load (locate-user-emacs-file "private")))) 17PKG can be a symbol, a string, or a list. A symbol will be
18installed using `package-install' from `package-archives'. A
19string will use `package-vc-install', which see. If given a
20list, it will be interpreted as a full set of arguments to one of
21the above functions, depending on the type of its car.
22
23If LOCAL is t, add ~/src/PKG.el to `load-path' and generate
24autoloads. If LOCAL is a string, Add that directory to
25`load-path'.
26
27If REQUIRE is non-nil, require the package after installing it."
28 (setq pkg (ensure-list pkg))
29
30 (cond
31 (local
32 (unless (stringp local)
33 (setq local (expand-file-name
34 (format "~/src/%s.el" (car pkg)))))
35 (unless (file-directory-p local)
36 (user-error "Package directory does not exist: %s" local))
37 (let ((autoload-file (expand-file-name
38 (format "%s-autoloads.el" (car pkg))
39 local))
40 (backup-inhibited t))
41 (add-to-list 'load-path local)
42 (loaddefs-generate local autoload-file)
43 (load autoload-file nil t))
44 (setq pkg (intern (format "%s" (car pkg)))))
45
46 ((symbolp (car pkg))
47 (unless (ignore-errors (apply #'package-install pkg))
48 (package-refresh-contents)
49 (apply #'package-install pkg))
50 (setq pkg (car pkg)))
51
52 ((stringp (car pkg))
53 (let ((pkg-name (intern (file-name-base (car pkg)))))
54 (unless (package-installed-p pkg-name)
55 (apply #'package-vc-install pkg))
56 (setq pkg pkg-name))))
57
58 (when require
59 (require pkg))
60
61 pkg)
62
63(defmacro setf/assoc (alist key val &optional keep)
64 "Set KEY to VAL in ALIST using `assoc'/`equal' semantics.
65Written as a convenience to writing out this long `alist-get'
66call every time. If VAL is nil, the entry with KEY is removed from ALIST unless
67KEEP is also non-nil."
68 `(setf (alist-get ,key ,alist nil ,(not keep) #'equal)
69 ,val))
70
71(defmacro comment (&rest _) (declare (indent defun)) nil)
72
73(defun add-local-mode-hook (mode-hook hook func)
74 "Add FUNC to HOOK locally in buffers with MODE-HOOK."
75 (add-hook mode-hook
76 (lambda () (add-hook hook func nil t))))
77
78(package-ensure 'crux)
79(crux-reopen-as-root-mode)
80
81(crux-with-region-or-buffer indent-region)
82(crux-with-region-or-buffer tabify)
83(crux-with-region-or-buffer untabify)
84
85(keymap-global-set "C-c i" #'crux-find-user-init-file)
86
87(setopt auth-sources '(default
88 "secrets:passwords"
89 "~/.authinfo"))
90
91(setq disabled-command-function nil)
92
93
94;;; Theme
95
96(setopt modus-themes-bold-constructs t)
97(setopt modus-themes-italic-constructs t)
98(setopt modus-themes-variable-pitch-ui t)
99(setopt modus-themes-disable-other-themes t)
100
101(tool-bar-mode -1)
102(menu-bar-mode -1)
103(scroll-bar-mode -1)
104(tooltip-mode -1)
105
106(setopt scroll-conservatively 101)
107
108(setopt read-answer-short t)
109(setopt use-dialog-box nil)
110(setopt use-file-dialog nil)
111(setopt use-short-answers t)
17 112
18;;; Definitions: 113(setopt inhibit-startup-screen t)
114(setopt initial-buffer-choice t)
115(setopt initial-scratch-message
116 ;; ";; Emacs!\n\n"
117 nil)
19 118
20(defun reset-faces () 119(setopt x-underline-at-descent-line t)
120(setopt blink-cursor-delay 0.25)
121(setopt blink-cursor-interval 0.25)
122(setopt blink-cursor-blinks 4)
123
124(define-advice startup-echo-area-message (:override ())
125 (if (get-buffer "*Warnings*")
126 ";_;"
127 "^_^"))
128
129(defun reset-faces (&rest _)
21 (dolist (face '(font-lock-regexp-face 130 (dolist (face '(font-lock-regexp-face
22 font-lock-builtin-face 131 font-lock-builtin-face
23 font-lock-variable-name-face 132 font-lock-variable-name-face
@@ -31,7 +140,6 @@
31 font-lock-number-face 140 font-lock-number-face
32 font-lock-keyword-face 141 font-lock-keyword-face
33 font-lock-set-face 142 font-lock-set-face
34 font-lock-warning-face
35 font-lock-punctuation-face 143 font-lock-punctuation-face
36 font-lock-constant-face 144 font-lock-constant-face
37 font-lock-type-face 145 font-lock-type-face
@@ -40,76 +148,38 @@
40 font-lock-misc-punctuation-face 148 font-lock-misc-punctuation-face
41 font-lock-bracket-face)) 149 font-lock-bracket-face))
42 (face-spec-set face '((t :foreground unspecified 150 (face-spec-set face '((t :foreground unspecified
43 :background unspecified))))) 151 :background unspecified))))
152 (when-let ((current (cl-loop for modus in modus-themes-collection
153 if (memq modus custom-enabled-themes)
154 return modus
155 finally return nil)))
156 (modus-themes-with-colors
157 (dolist (face '(font-lock-doc-face
158 font-lock-string-face))
159 (face-spec-set face `((t :foreground unspecified
160 :background unspecified
161 :slant italic))))
162 ;; (face-spec-set 'font-lock-comment-face
163 ;; :inherit doesn't work for some reason??
164 ;; `((t :foreground
165 ;; ,fg-alt)))
166 )))
167(advice-add 'load-theme :after #'reset-faces)
168
169(defvar dark-theme 'modus-vivendi-tinted)
170(defvar light-theme 'modus-operandi-tinted)
171
172(load-theme dark-theme :no-confirm :no-enable)
173(load-theme light-theme :no-confirm :no-enable)
174(if (and (executable-find "darkman")
175 (let ((stat (shell-command "darkman get")))
176 (and (= stat 0)
177 (equal (with-current-buffer shell-command-buffer-name
178 (buffer-substring (point-min) (point-max)))
179 "dark\n"))))
180 (load-theme dark-theme :no-confirm)
181 (load-theme light-theme :no-confirm))
44 182
45(defun electric-pair-local-mode-disable ()
46 "Disable `electric-pair-mode', locally."
47 (electric-pair-local-mode -1))
48
49(defun kill-this-buffer (&optional buffer-or-name)
50 "Kill this buffer, or BUFFER-OR-NAME.
51When called interactvely, the user will be prompted when passing
52\\[universal-argument]."
53 (interactive "P")
54 (cond
55 ((bufferp buffer-or-name)
56 (kill-buffer buffer-or-name))
57 ((null buffer-or-name)
58 (kill-current-buffer))
59 (:else
60 (kill-buffer (read-buffer "Kill: " nil :require-match)))))
61
62(defun define-org-capture-template (description &rest args)
63 "Define an template for `org-capture-templates'.
64Will not replace an existing template unless `:force' in ARGS is
65non-nil. ARGS is a plist, which in addition to the additional
66options `org-capture-templates' accepts (which see), also accepts
67the following: `:keys', `:description', `:type', `:target', and
68`:template'."
69 (declare (indent 1))
70 (let* ((keys (plist-get args :keys))
71 (type (plist-get args :type))
72 (target (plist-get args :target))
73 (template (plist-get args :template))
74 (force (plist-get args :force))
75 (template-value
76 (append
77 (list description)
78 (when (or type target template)
79 (list (or type 'entry) target template))
80 (cl-loop for i from 0 below (length args) by 2
81 unless (member (nth i args)
82 '( :keys :description :type
83 :target :template))
84 append (list (nth i args)
85 (plist-get args (nth i args)))))))
86 (if (seq-find (lambda (el) (equal (car el) keys))
87 org-capture-templates)
88 (and force
89 (setf (alist-get keys org-capture-templates nil nil #'equal)
90 template-value))
91 (setf org-capture-templates
92 (append org-capture-templates
93 (list (cons keys template-value)))))
94 org-capture-templates))
95
96(defun other-window-or-switch-buffer (&optional arg)
97 "Switch to the other window.
98If a window is the only buffer on a frame, switch buffer. When
99run with \\[universal-argument], unconditionally switch buffer."
100 (interactive "P")
101 (if (or arg (one-window-p))
102 (switch-to-buffer (other-buffer) nil t)
103 (other-window 1)))
104
105(defun cycle-spacing@ (&optional n)
106 ;; `cycle-spacing' is wildly different in 29.1 over 28.
107 "Negate N argument on `cycle-spacing'.
108That is, with a positive N, deletes newlines as well, leaving -N
109spaces. If N is negative, it will not delete newlines and leave
110N spaces."
111 (interactive "*p")
112 (cycle-spacing (- n)))
113 183
114(defun first-frame@set-fonts () 184(defun first-frame@set-fonts ()
115 (remove-hook 'server-after-make-frame-hook 185 (remove-hook 'server-after-make-frame-hook
@@ -158,486 +228,489 @@ N spaces."
158 (setopt tab-bar-show t) 228 (setopt tab-bar-show t)
159 (tab-bar-mode)) 229 (tab-bar-mode))
160 230
161(defun renz/sort-by-alpha-length (elems) 231(defun run-after-init-or-first-frame (func)
162 "Sort ELEMS first alphabetically, then by length." 232 "Run FUNC after init or after the first frame."
163 (sort elems (lambda (c1 c2) 233 (if (daemonp)
164 (or (string-version-lessp c1 c2) 234 (add-hook 'server-after-make-frame-hook func)
165 (< (length c1) (length c2)))))) 235 (add-hook 'after-init-hook func)))
166
167(defun renz/sort-by-history (elems)
168 "Sort ELEMS by minibuffer history.
169Use `mct-sort-sort-by-alpha-length' if no history is available."
170 (if-let ((hist (and (not (eq minibuffer-history-variable t))
171 (symbol-value minibuffer-history-variable))))
172 (minibuffer--sort-by-position hist elems)
173 (renz/sort-by-alpha-length elems)))
174
175(defun renz/completion-category ()
176 "Return completion category."
177 (when-let ((window (active-minibuffer-window)))
178 (with-current-buffer (window-buffer window)
179 (completion-metadata-get
180 (completion-metadata (buffer-substring-no-properties
181 (minibuffer-prompt-end)
182 (max (minibuffer-prompt-end) (point)))
183 minibuffer-completion-table
184 minibuffer-completion-predicate)
185 'category))))
186
187(defun renz/sort-multi-category (elems)
188 "Sort ELEMS per completion category."
189 (pcase (renz/completion-category)
190 ('nil elems) ; no sorting
191 ('kill-ring elems)
192 ('project-file (renz/sort-by-alpha-length elems))
193 (_ (renz/sort-by-history elems))))
194
195(defvar no-tabs-modes '(emacs-lisp-mode
196 lisp-mode
197 scheme-mode
198 python-mode
199 haskell-mode)
200 "Modes /not/ to indent with tabs.")
201 236
202(defun indent-tabs-mode-maybe () 237(run-after-init-or-first-frame #'first-frame@set-fonts)
203 (if (apply #'derived-mode-p no-tabs-modes)
204 (indent-tabs-mode -1)
205 (indent-tabs-mode 1)))
206 238
207(define-minor-mode truncate-lines-mode 239(setopt frame-resize-pixelwise t)
208 "Buffer-local mode to toggle `truncate-lines'." 240(setopt window-resize-pixelwise t)
209 :lighter ""
210 (setq-local truncate-lines truncate-lines-mode))
211 241
212;;; Region or buffer stuff 242(defun tab-bar-end-space ()
243 `((end menu-item " " ignore)))
213 244
214(defun call-with-region-or-buffer (fn &rest _r) 245(add-to-list 'tab-bar-format 'tab-bar-format-align-right :append)
215 "Call function FN with current region or buffer. 246(add-to-list 'tab-bar-format 'tab-bar-format-global :append)
216Good to use for :around advice." 247(add-to-list 'tab-bar-format 'tab-bar-end-space :append)
217 (if (region-active-p)
218 (funcall fn (region-beginning) (region-end))
219 (funcall fn (point-min) (point-max))))
220 248
221(defun delete-trailing-whitespace-except-current-line () 249(add-hook 'dired-mode-hook #'hl-line-mode)
222 (save-excursion 250(with-eval-after-load 'org-agenda
223 (delete-trailing-whitespace (point-min) 251 (add-hook 'org-agenda-mode-hook #'hl-line-mode))
224 (line-beginning-position)) 252
225 (delete-trailing-whitespace (line-end-position) 253(with-eval-after-load 'tabulated-list
226 (point-max)))) 254 (add-hook 'tabulated-list-mode-hook #'hl-line-mode))
227 255
228(defun create-missing-directories () 256(setopt echo-keystrokes 0.01)
229 "Automatically create missing directories." 257
230 (let ((target-dir (file-name-directory buffer-file-name))) 258(setopt switch-to-buffer-in-dedicated-window 'pop)
231 (unless (file-exists-p target-dir) 259(setopt switch-to-buffer-obey-display-actions t)
232 (make-directory target-dir :parents)))) 260
233 261(when (package-ensure 'adaptive-wrap)
234 262 (add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode))
235(defun vc-remote-off () 263
236 "Turn VC off when remote." 264;;; Ediff
237 (when (file-remote-p (buffer-file-name)) 265
238 (setq-local vc-handled-backends nil))) 266(setopt ediff-split-window-function #'split-window-horizontally)
239 267(setopt ediff-window-setup-function #'ediff-setup-windows-plain)
240(defun +titlecase-sentence-style-dwim (&optional arg) 268
241 "Titlecase a sentence. 269;;; Man pages
242With prefix ARG, toggle the value of 270
243`titlecase-downcase-sentences' before sentence-casing." 271(setopt Man-notify-method 'aggressive)
244 (interactive "P") 272
245 (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences) 273;;; Mode-line
246 titlecase-downcase-sentences))) 274
247 (titlecase-dwim 'sentence))) 275(defun hide-minor-mode (mode &optional hook)
276 "Hide MODE from the mode-line.
277HOOK is used to trigger the action, and defaults to MODE-hook."
278 (setf (alist-get mode minor-mode-alist) (list ""))
279 (add-hook (intern (or hook (format "%s-hook" mode)))
280 (lambda () (hide-minor-mode mode))))
281
282(setq mode-line-modes
283 (let ((recursive-edit-help-echo
284 "Recursive edit, type M-C-c to get out"))
285 (list (propertize "%[" 'help-echo recursive-edit-help-echo)
286 `(:propertize ("" mode-name)
287 help-echo "Major mode\n\
288mouse-1: Display major mode menu\n\
289mouse-2: Show help for major mode\n\
290mouse-3: Toggle minor modes"
291 face bold
292 mouse-face mode-line-highlight
293 local-map ,mode-line-major-mode-keymap)
294 '("" mode-line-process)
295 `(:propertize ("" minor-mode-alist)
296 mouse-face mode-line-highlight
297 help-echo "Minor mode\n\
298mouse-1: Display minor mode menu\n\
299mouse-2: Show help for minor mode\n\
300mouse-3: Toggle minor modes"
301 local-map ,mode-line-minor-mode-keymap)
302 (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
303 'mouse-face 'mode-line-highlight
304 'local-map (make-mode-line-mouse-map
305 'mouse-2 #'mode-line-widen))
306 (propertize "%]" 'help-echo recursive-edit-help-echo)
307 " ")))
248 308
249(defun +titlecase-org-headings () 309(setopt mode-line-format
250 (interactive) 310 '(("%e"
251 (require 'org) 311 mode-line-front-space
252 (save-excursion 312 (:propertize (""
253 (goto-char (point-min)) 313 mode-line-client
254 ;; See also `org-map-tree'. I'm not using that function because I want to 314 mode-line-modified
255 ;; skip the first headline. A better solution would be to patch 315 mode-line-remote)
256 ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc). 316 display (min-width (3.0)))
257 (let ((level (funcall outline-level)) 317 " "
258 (org-special-ctrl-a/e t)) 318 mode-line-buffer-identification
259 (while (and (progn (outline-next-heading) 319 (vc-mode
260 (> (funcall outline-level) level)) 320 (" (" (:eval (string-trim vc-mode)) ")"))
261 (not (eobp))) 321 " "
262 (titlecase-region (progn (org-beginning-of-line) (point)) 322 (mode-line-position
263 (progn (org-end-of-line) (point))))))) 323 (" ∙ " mode-line-position))
324 " ∙ "
325 mode-line-modes ; the one above
326 mode-line-misc-info
327 mode-line-end-spaces)))
264 328
265(defcustom browse-url-safe-browser-functions nil 329
266 "\"Safe\" browser functions." 330;;; Completion and minibuffer
267 :type '(repeat-function))
268 331
269(defun browse-url-browser-function-safe-p (fn) 332(setopt tab-always-indent 'complete)
270 "Return t if FN is a \"safe\" browser function." 333(setopt completion-styles '(basic partial-completion substring flex))
271 (memq f (append browse-url-safe-browser-functions 334
272 (mapcar (lambda (i) 335;; XXX: this is 'too good' when I'm in the process of typing out things.
273 (plist-get (cdr i) :value)) 336;; (when (package-ensure "https://git.sr.ht/~pkal/typo")
274 (seq-filter (lambda (i) 337;; (add-to-list 'completion-styles 'typo :append))
275 (eq (car i) 'function-item)) 338
276 (cdr (get 'browse-url-browser-function 339(setopt completion-ignore-case t)
277 'custom-type))))))) 340(setopt read-buffer-completion-ignore-case t)
341(setopt read-file-name-completion-ignore-case t)
342(setopt completion-flex-nospace t)
343
344;; These aren't /that/ useful if you're not using *Completions*.
345(setopt completions-detailed t)
346(setopt completion-auto-help 'visible)
347(setopt completion-auto-select 'second-tab)
348(setopt completions-header-format nil)
349(setopt completions-format 'one-column)
350(setopt completions-max-height 20)
351
352;; (defun minibuffer-next-completion-or-line (n)
353;; "Move to the next N completion in minibuffer, or Nth next line."
354;; (interactive "p")
355;; (if (and (eq last-command 'minibuffer-next-completion)
356;; (not (minibufferp)))
357;; (forward-line n)
358;; (minibuffer-next-completion n)))
359
360;; (defun minibuffer-previous-completion-or-line (n)
361;; "Move to the previous N completion, or Nth previous line."
362;; (interactive "p")
363;; (setq last-command 'minibuffer-next-completion-or-line)
364;; (minibuffer-next-completion-or-line (- n)))
365
366(progn
367 (keymap-set minibuffer-local-map "C-p"
368 #'minibuffer-previous-completion)
369 (keymap-set minibuffer-local-map "C-n"
370 #'minibuffer-next-completion)
371 ;; (keymap-set completion-in-region-mode-map "C-p"
372 ;; #'minibuffer-previous-completion)
373 ;; (keymap-set completion-in-region-mode-map "C-n"
374 ;; #'minibuffer-next-completion)
375 )
376
377(setf/assoc display-buffer-alist
378 "\\*Completions\\*"
379 '((display-buffer-reuse-mode-window)))
380
381(setopt enable-recursive-minibuffers t)
382(minibuffer-depth-indicate-mode)
383(minibuffer-electric-default-mode)
278 384
279(put 'browse-url-browser-function 'safe-local-variable 385(setopt file-name-shadow-properties '(invisible t intangible t))
280 'browse-url-browser-function-safe-p) 386(file-name-shadow-mode)
387
388(define-minor-mode truncate-lines-local-mode
389 "Toggle `truncate-lines' in the current buffer."
390 :lighter ""
391 (setq-local truncate-lines truncate-lines-local-mode))
392
393(add-hook 'completion-list-mode-hook #'truncate-lines-local-mode)
394(add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode)
395
396(when (package-ensure 'consult nil t)
397 (keymap-global-set "C-x b" #'consult-buffer)
398 (keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
399 (keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
400 (keymap-global-set "C-x r b" #'consult-bookmark)
401 (keymap-global-set "M-y" #'consult-yank-pop)
402 (keymap-global-set "M-g g" #'consult-goto-line)
403 (keymap-global-set "M-g M-g" #'consult-goto-line)
404 (keymap-global-set "M-g o" #'consult-outline)
405 (keymap-global-set "M-g m" #'consult-mark)
406 (keymap-global-set "M-g i" #'consult-imenu)
407 (keymap-global-set "M-s d" #'consult-find)
408 (keymap-global-set "M-s D" #'consult-locate)
409 (keymap-global-set "M-s g" #'consult-grep)
410 (keymap-global-set "M-s G" #'consult-git-grep)
411 (keymap-global-set "M-s r" #'consult-ripgrep)
412 (keymap-global-set "M-s l" #'consult-line)
413 (keymap-global-set "M-s k" #'consult-keep-lines)
414 (keymap-global-set "M-s u" #'consult-focus-lines)
415
416 (keymap-global-set "M-s e" #'consult-isearch-history)
417 (keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
418 (keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
419 (keymap-set isearch-mode-map "M-s l" #'consult-line)
420
421 (setopt xref-show-xrefs-function #'consult-xref)
422 (setopt xref-show-definitions-function #'xref-show-definitions-completing-read)
423
424 (setopt consult-preview-key "M-.")
425
426 (consult-customize
427 consult-ripgrep consult-git-grep consult-grep
428 consult-xref
429 :preview-key '(:debounce 0.4 any)))
430
431(when (package-ensure 'marginalia)
432 (marginalia-mode))
433
434(setopt history-length t)
435(setopt history-delete-duplicates t)
436(setopt savehist-save-minibuffer-history t)
437(setopt savehist-autosave-interval 5)
438(savehist-mode)
281 439
282 440
283;;; Packages: 441;;; Text editing
284 442
285(require 'package) 443(setopt fill-column 80)
286(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) 444(global-so-long-mode)
287(package-initialize)
288 445
289(defun ensure-package (pkg &optional localp) 446(defun cycle-spacing* (&optional n)
290 "Esnure PKG is installed from repositories. 447 "Negate N argument on `cycle-spacing'."
291If LOCALP is t, add ~/src/PKG.el to `load-path'. 448 (interactive "*p")
292If LOCALP is a string, add that directory to the `load-path'." 449 (cycle-spacing (- n)))
293 (cond 450(keymap-global-set "M-SPC" #'cycle-spacing*)
294 ((stringp localp) 451
295 (and (file-exists-p localp) 452(when (package-ensure 'hungry-delete)
296 (add-to-list 'load-path localp))) 453 (setopt hungry-delete-chars-to-skip " \t")
297 (localp 454 (setopt hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip))
298 (ensure-package pkg 455 (setopt hungry-delete-join-reluctantly nil)
299 (expand-file-name 456 (with-eval-after-load 'hungry-delete
300 (format "~/src/%s.el" 457 (add-to-list 'hungry-delete-except-modes 'eshell-mode)
301 (symbol-name pkg))))) 458 (add-to-list 'hungry-delete-except-modes 'nim-mode)
302 (:else 459 (add-to-list 'hungry-delete-except-modes 'python-mode)
303 (unless (package-installed-p pkg) 460 (hide-minor-mode 'hungry-delete-mode))
304 (unless (ignore-errors (package-install pkg)) 461 (global-hungry-delete-mode))
305 (package-refresh-contents) 462
306 (package-install pkg)))))) 463(setopt isearch-lazy-count t)
307 464(setopt isearch-regexp-lax-whitespace t)
308;; Install packages here. Acutal configuration is done in the Configuration 465(setopt isearch-wrap-pause 'no)
309;; section. 466(setopt search-default-mode t)
310(ensure-package 'consult) 467(setopt search-whitespace-regexp ".*?") ; swiper-style
311(ensure-package 'marginalia) 468(setopt search-ring-max 256)
312(ensure-package 'visual-fill-column) 469(setopt regexp-search-ring-max 256)
313(ensure-package 'adaptive-wrap) 470
314(ensure-package 'geiser) 471(define-advice isearch-cancel (:before () add-to-history)
315(when (executable-find "csi") 472 "Add search string to history when canceling isearch."
316 (ensure-package 'geiser-chicken)) 473 (unless (string-equal "" isearch-string)
317(ensure-package 'avy) 474 (isearch-update-ring isearch-string isearch-regexp)))
318(ensure-package 'zzz-to-char) 475
319(ensure-package 'hungry-delete) 476(define-advice perform-replace (:around (fn &rest args) dont-exit-on-anykey)
320(ensure-package 'undohist) 477 "Don't exit replace for anykey that's not in `query-replace-map'."
321(ensure-package 'jinx) 478 (save-window-excursion
322(ensure-package 'markdown-mode) 479 (cl-letf* ((lookup-key-orig
323(ensure-package 'anzu) 480 (symbol-function 'lookup-key))
324 481 ((symbol-function 'lookup-key)
325;; Local packages 482 (lambda (map key &optional accept-default)
326(ensure-package 'scule t) 483 (or (apply lookup-key-orig map key accept-default)
327(ensure-package 'frowny t) 484 (when (eq map query-replace-map) 'help)))))
328(ensure-package 'hippie-completing-read t) 485 (apply fn args))))
329(ensure-package 'mode-line-bell t) 486
330(ensure-package 'titlecase t) 487(when (package-ensure 'isearch-mb)
331(ensure-package 'jabber t) 488 (with-eval-after-load 'isearch-mb
489 (with-eval-after-load 'consult
490 (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
491 (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history)
492 (add-to-list 'isearch-mb--after-exit #'consult-line)
493 (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line))
494 (with-eval-after-load 'anzu
495 (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace)
496 (keymap-set isearch-mb-minibuffer-map "M-%"
497 #'anzu-isearch-query-replace)))
498 (isearch-mb-mode))
499
500(when (package-ensure 'anzu)
501 (setopt anzu-mode-lighter "")
502 (setopt anzu-deactivate-region t)
503 (keymap-global-set "M-%" #'anzu-query-replace-regexp)
504 (keymap-global-set "C-M-%" #'anzu-query-replace)
505 (keymap-set isearch-mode-map "M-%" #'anzu-isearch-query-replace-regexp)
506 (keymap-set isearch-mode-map "C-M-%" #'anzu-isearch-query-replace)
507 (global-anzu-mode))
508
509(keymap-global-set "M-/" #'hippie-expand)
510(keymap-global-set "C-x C-b" #'ibuffer)
332 511
333;;; Jabber 512(add-hook 'ibuffer-mode-hook #'hl-line-mode)
334 513
335(use-package jabber 514(defun call-with-region-or-buffer (fn &rest _r)
336 :load-path "~/src/jabber.el" 515 "Call function FN with current region or buffer.
337 :defer t 516Good to use for :around advice."
338 :bind-keymap (("C-c j" . jabber-global-keymap)) 517 ;; This `interactive' form is needed to override the advised function's form,
339 :preface nil 518 ;; to avoid errors when the region isn't active. This means that FN must take
340 (setq-default jabber-chat-buffer-format "*%n*" 519 ;; 2 arguments, the beginning and the end of the region to act on.
341 jabber-browse-buffer-format "*%n*" 520 (interactive)
342 jabber-groupchat-buffer-format "*%n*" 521 (if (region-active-p)
343 jabber-muc-private-buffer-format "*%n*") 522 (funcall fn (region-beginning) (region-end))
344 :custom-face 523 (funcall fn (point-min) (point-max))))
345 (jabber-activity-face ((t :inherit jabber-chat-prompt-foreign
346 :foreground unspecified
347 :weight normal)))
348 (jabber-activity-personal-face ((t :inherit jabber-chat-prompt-local
349 :foreground unspecified
350 :weight bold)))
351 (jabber-chat-prompt-local ((t :inherit minibuffer-prompt
352 :foreground unspecified
353 :weight normal
354 :slant italic)))
355 (jabber-chat-prompt-foreign ((t :inherit warning
356 :foreground unspecified
357 :weight normal)))
358 (jabber-chat-prompt-system ((t :inherit font-lock-doc-face
359 :foreground unspecified)))
360 (jabber-rare-time-face ((t :inherit font-lock-comment-face
361 :foreground unspecified
362 :underline nil)))
363 :config
364 (require 'jabber-httpupload nil t)
365 (setopt jabber-auto-reconnect t
366 jabber-last-read-marker "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"
367 jabber-muc-decorate-presence-patterns
368 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil)
369 ("Mode #.*" . jabber-muc-presence-dim)
370 ("." . jabber-muc-presence-dim))
371 jabber-activity-make-strings #'jabber-activity-make-strings-shorten
372 jabber-rare-time-format
373 (format " - - - - - %%H:%d %%F"
374 (let ((min (string-to-number (format-time-string "%M"))))
375 (* 5 (floor min 5))))
376 jabber-muc-header-line-format '(" " jabber-muc-topic))
377
378 (setopt jabber-groupchat-prompt-format "%n. "
379 jabber-chat-local-prompt-format "%n. "
380 jabber-chat-foreign-prompt-format "%n. "
381 jabber-muc-private-foreign-prompt-format "%g/%n. ")
382
383 (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to)
384 (map-keymap (lambda (key command)
385 (define-key jabber-global-keymap (vector (+ key #x60)) command))
386 jabber-global-keymap)
387 (keymap-global-set "C-x C-j" #'dired-jump)
388
389 (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
390 (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo)
391 (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo)
392 (add-hook 'jabber-chat-mode-hook 'visual-line-mode)
393 (add-hook 'jabber-chat-mode-hook (defun jabber-no-position ()
394 (setq-local mode-line-position nil)))
395
396 (add-hook 'jabber-alert-muc-hooks
397 (defun jabber@highlight-acdw (&optional _ _ buf _ _)
398 (when buf
399 (with-current-buffer buf
400 (let ((regexp (rx word-boundary
401 "acdw" ; maybe get from the config?
402 word-boundary)))
403 (hi-lock-unface-buffer regexp)
404 (highlight-regexp regexp 'jabber-chat-prompt-local))))))
405
406 (when (fboundp 'jabber-chat-update-focus)
407 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)))
408 524
409 525(delete-selection-mode)
410;;; Configuration:
411 526
412(setopt custom-file (locate-user-emacs-file "custom.el")) 527(when (package-ensure 'avy)
413(load custom-file :noerror) 528 (setopt avy-background t)
529 (setopt avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))
530 (keymap-global-set "M-j" #'avy-goto-char-timer)
531 (keymap-set isearch-mode-map "M-j" #'avy-isearch))
414 532
415;;; General keybinding changes 533(when (package-ensure 'zzz-to-char)
534 (keymap-global-set "M-z"
535 (defun zzz-to-char* (arg)
536 (interactive "P")
537 (call-interactively
538 (if arg #'zzz-to-char #'zzz-to-char-up-to-char)))))
416 539
417(keymap-global-set "M-o" #'other-window-or-switch-buffer) 540;;; Prose
418(keymap-global-set "M-SPC" #'cycle-spacing@)
419(keymap-global-set "M-u" #'universal-argument)
420(keymap-set universal-argument-map "M-u" #'universal-argument-more)
421 541
422;;; Theme 542(add-hook 'text-mode-hook #'visual-line-mode)
423 543
424(if (daemonp) 544(when (package-ensure 'olivetti)
425 (add-hook 'server-after-make-frame-hook #'first-frame@set-fonts) 545 (add-hook 'text-mode-hook #'olivetti-mode))
426 (run-with-idle-timer 1 nil #'first-frame@set-fonts)) 546
547(when (package-ensure 'jinx)
548 (add-hook 'text-mode-hook #'jinx-mode)
549 (with-eval-after-load 'jinx
550 (keymap-set jinx-mode-map "M-$" #'jinx-correct)
551 (keymap-set jinx-mode-map "C-M-$" #'jinx-languages)))
552
553(defun org-fk-region (start end)
554 "Get the Flesch-Kincaid score of an `org-mode' region."
555 (interactive "r")
556 (let ((buf (get-buffer-create " *fk*" t)))
557 (shell-command-on-region start end
558 "pandoc -t plain -f org | ~/src/fk/fk.perl"
559 buf)
560 (with-current-buffer buf
561 (buffer-substring-no-properties (point-min) (- (point-max) 1)))
562 (kill-buffer buf)))
427 563
428(tool-bar-mode -1) 564(crux-with-region-or-buffer org-fk-region)
429 565
430(setopt modus-themes2-bold-constructs nil 566(when (package-ensure 'scule t t)
431 modus-themes-italic-constructs t 567 (keymap-global-set "M-c" scule-map))
432 modus-themes-variable-pitch-ui t)
433 568
434(add-hook 'modus-themes-after-load-theme-hook #'reset-faces) 569(when (package-ensure 'titlecase t)
570 (keymap-set scule-map "M-t" #'titlecase-dwim))
435 571
436(load-theme 'modus-vivendi :no-confirm :no-enable) 572(setopt dictionary-default-popup-strategy "lev") ; Levenshtein distance 1
437(load-theme 'modus-operandi :no-confirm) 573(setopt dictionary-server "dict.org")
574(setopt dictionary-use-single-buffer t)
575(keymap-global-set "M-#"
576 (defun dictionary-lookup-dwim ()
577 (interactive)
578 (unless (ignore-errors (dictionary-lookup-definition))
579 (call-interactively #'dictionary-search))))
438 580
439(add-hook 'text-mode-hook #'visual-line-mode) 581(package-ensure 'markdown-mode)
440(add-hook 'prog-mode-hook #'auto-fill-mode)
441(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
442 582
443;;; Mode line 583;;; Programming
444
445(defvar mode-line-position
446 '(""
447 (:propertize
448 (""
449 (:eval (if line-number-mode "%3l" ""))
450 (:eval (if column-number-mode
451 (if column-number-indicator-zero-based
452 "/%2c"
453 "/%2C")
454 "")))
455 display (min-width (3.0)))
456 (:propertize (" [" (-3 "%p") "] ")
457 display (min-width (6.0)))))
458 584
459(setopt mode-line-format 585(setopt electric-pair-skip-whitespace 'chomp)
460 '(("%e" 586(electric-pair-mode)
461 mode-line-front-space 587
462 (:propertize ("" 588(setopt show-paren-delay 0.01)
463 mode-line-client 589(setopt show-paren-style 'parenthesis)
464 mode-line-modified 590(setopt show-paren-when-point-in-periphery t)
465 mode-line-remote) 591(setopt show-paren-when-point-inside-paren t)
466 display (min-width (3.0))) 592(show-paren-mode)
467 " "
468 mode-line-buffer-identification
469 (vc-mode (" (" (:eval (string-trim vc-mode)) ")"))
470 " "
471 (mode-line-position mode-line-position)
472 mode-line-modes
473 mode-line-misc-info
474 mode-line-end-spaces)))
475 593
476;; Remove modes from mode-line 594(add-hook 'prog-mode-hook #'auto-fill-mode)
477(dolist (minor-mode '(frowny-mode 595(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
478 whitespace-mode 596(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
479 hungry-delete-mode))
480 (setf (alist-get minor-mode minor-mode-alist) (list ""))
481 (add-hook (intern (format "%s-hook" minor-mode))
482 (lambda ()
483 (setf (alist-get minor-mode minor-mode-alist) (list "")))))
484 597
485;;; Completion & minibuffer 598(when (package-ensure 'dumb-jump)
599 (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
486 600
487(fido-vertical-mode) 601(add-hook 'prog-mode-hook #'prettify-symbols-mode)
488(minibuffer-depth-indicate-mode)
489 602
490(setopt completion-auto-help (not icomplete-mode) 603(keymap-global-set "M-:" #'pp-eval-expression)
491 completion-auto-select 'second-tab
492 completions-header-format nil
493 completions-max-height 12
494 completions-format 'one-column
495 completion-styles '(basic partial-completion emacs22 flex)
496 completion-ignore-case t
497 read-buffer-completion-ignore-case t
498 read-file-name-completion-ignore-case t
499 completions-detailed t
500 enable-recursive-minibuffers t
501 file-name-shadow-properties '(invisible t intangible t)
502 minibuffer-eldef-shorten-default t
503 minibuffer-prompt-properties '( read-only t
504 cursor-intangible t
505 face minibuffer-prompt)
506 window-resize-pixelwise t
507 frame-resize-pixelwise t)
508
509(add-hook 'completion-list-mode-hook #'truncate-lines-mode)
510(add-hook 'minibuffer-setup-hook #'truncate-lines-mode)
511
512;; Up/down when completing in the minibuffer
513;; (define-key minibuffer-local-map (kbd "C-p") #'minibuffer-previous-completion)
514;; (define-key minibuffer-local-map (kbd "C-n") #'minibuffer-next-completion)
515
516;; Up/down when competing in a normal buffer
517;; (define-key completion-in-region-mode-map (kbd "C-p") #'minibuffer-previous-completion)
518;; (define-key completion-in-region-mode-map (kbd "C-n") #'minibuffer-next-completion)
519
520(setopt completions-sort #'renz/sort-multi-category)
521 604
522(setopt tab-always-indent 'complete) 605;; Tabs
523 606
524(file-name-shadow-mode) 607(setopt tab-width 8)
525(minibuffer-electric-default-mode)
526 608
527(scroll-bar-mode -1) 609(defvar space-indent-modes '(emacs-lisp-mode
528(menu-bar-mode -1) 610 lisp-interaction-mode
611 lisp-mode
612 scheme-mode
613 python-mode
614 haskell-mode
615 text-mode)
616 "Modes to indent with spaces, not tabs.")
529 617
618(defun indent-tabs-mode-maybe ()
619 (setq indent-tabs-mode
620 (if (apply #'derived-mode-p space-indent-modes) nil t)))
530(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe) 621(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe)
531 622
532(setopt electric-pair-skip-whitespace 'chomp)
533(electric-pair-mode)
534
535(setopt sh-basic-offset tab-width) 623(setopt sh-basic-offset tab-width)
624(setopt perl-indent-level tab-width)
536 625
537(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun) 626;; Scheme
538(keymap-set emacs-lisp-mode-map "C-c C-k" #'eval-buffer)
539(keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun)
540(keymap-set lisp-interaction-mode-map "C-c C-k" #'eval-buffer)
541
542(advice-add 'indent-region :around #'call-with-region-or-buffer)
543(advice-add 'tabify :around #'call-with-region-or-buffer)
544(advice-add 'untabify :around #'call-with-region-or-buffer)
545 627
546(with-eval-after-load 'scheme 628(when (package-ensure 'geiser)
547 (keymap-unset scheme-mode-map "M-o" t) 629 (when (executable-find "csi")
548 ;; Comparse "keywords" --- CHICKEN (http://wiki.call-cc.org/eggref/5/comparse) 630 (when (package-ensure 'geiser-chicken)
549 (put 'sequence* 'scheme-indent-function 1) 631 (setf/assoc auto-mode-alist "\\.egg\\'" 'scheme-mode)))
550 (put 'satisfies 'scheme-indent-function 1) 632 (setopt scheme-program-name (or (executable-find "csi")
633 "scheme"))
551 (add-hook 'scheme-mode-hook #'geiser-mode)) 634 (add-hook 'scheme-mode-hook #'geiser-mode))
552(with-eval-after-load 'geiser-mode
553 (keymap-set geiser-mode-map "C-c C-k" #'geiser-eval-buffer-and-go)
554 (keymap-unset geiser-mode-map "C-." t))
555
556
557(with-eval-after-load 'visual-fill-column
558 (setopt visual-fill-column-center-text t
559 visual-fill-column-width (+ fill-column 2))
560 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
561(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
562(add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)
563
564(setopt major-mode
565 (lambda () ; guess major mode from buffer name
566 (unless buffer-file-name
567 (let ((buffer-file-name (buffer-name)))
568 (set-auto-mode)))))
569
570;; Dialogs
571(unless (boundp 'use-short-answers)
572 (fset 'yes-or-no-p 'y-or-n-p))
573
574(setopt read-answer-short t
575 use-dialog-box nil
576 use-file-dialog nil
577 use-short-answers t)
578
579(require 'savehist)
580(setopt history-length 1024
581 history-delete-duplicates t
582 ;; savehist-file (etc/ "savehist.el")
583 savehist-save-minibuffer-history t
584 savehist-autosave-interval 30)
585(savehist-mode)
586 635
587;; Killing and yanking 636(require 'autoinsert)
588(setopt kill-do-not-save-duplicates t 637(setf/assoc auto-insert-alist
589 kill-read-only-ok t 638 "\\.scm"
590 ;; XXX: This setting causes an error message the first time it's 639 '(nil "#!/bin/sh" \n
591 ;; called: "Selection owner couldn't convert: TIMESTAMP". I have 640 "#| -*- scheme -*-" \n
592 ;; absolutely no idea why I get this error, but it's generated in 641 "exec csi -ss \"$0\" \"$@\"" \n
593 ;; `x_get_foreign_selection'. I also can't inhibit the message or 642 _ \n
594 ;; do anything else with it, so for now, I'll just live with the 643 "|#" \n \n))
595 ;; message.
596 save-interprogram-paste-before-kill t
597 yank-pop-change-selection t)
598(delete-selection-mode)
599 644
600;; Notifying the user 645;; Emacs lisp
601(setopt echo-keystrokes 0.01
602 ring-bell-function #'ignore)
603 646
604;; Point and mark 647(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun)
605(setopt set-mark-command-repeat-pop t) 648(keymap-set emacs-lisp-mode-map "C-c C-b" #'eval-buffer)
649(keymap-set emacs-lisp-mode-map "C-c C-z" #'ielm) ; TODO: better-ize
650(keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun)
651(keymap-set lisp-interaction-mode-map "C-c C-b" #'eval-buffer)
652(keymap-set lisp-interaction-mode-map "C-c C-z" #'ielm) ; TODO: better-ize
606 653
607;; The system 654(add-hook 'after-init-hook
608(setopt read-process-output-max (* 10 1024 1024)) 655 (lambda ()
656 (define-advice eval-buffer (:after (&rest _) message)
657 (message "Buffer %s evaluated." (current-buffer)))))
609 658
610;; Startup 659
611(setopt inhibit-startup-screen t 660;;; Files
612 initial-buffer-choice t
613 initial-scratch-message nil)
614 661
615(define-advice startup-echo-area-message (:override ()) 662(setopt auto-revert-verbose nil)
616 (if (get-buffer "*Warnings*") 663(setopt global-auto-revert-non-file-buffers t)
617 ";_;" 664(global-auto-revert-mode)
618 "^_^"))
619 665
620;; Text editing 666(setopt create-lockfiles nil)
621(setopt fill-column 80 667(setopt mode-require-final-newline t)
622 sentence-end-double-space nil 668(setopt view-read-only t)
623 tab-width 8 669(setopt save-silently t)
624 tab-always-indent 'complete) 670
625(global-so-long-mode) 671(setopt auto-save-default nil)
672(setopt auto-save-no-message t)
673(setopt auto-save-interval 2)
674(setopt auto-save-timeout 2)
675(setopt auto-save-visited-interval 2)
676(setopt remote-file-name-inhibit-auto-save t)
677(setopt remote-file-name-inhibit-auto-save-visited t)
678(add-to-list 'auto-save-file-name-transforms
679 `(".*" ,(locate-user-emacs-file "auto-save/") t))
680(auto-save-visited-mode)
626 681
627(setopt show-paren-delay 0.01 682(setopt backup-by-copying t)
628 show-paren-style 'parenthesis 683(setopt version-control t)
629 show-paren-when-point-in-periphery t 684(setopt kept-new-versions 3)
630 show-paren-when-point-inside-paren t) 685(setopt kept-old-versions 3)
631(show-paren-mode) 686(setopt delete-old-versions t)
687(add-to-list 'backup-directory-alist '("^/dev/shm/" . nil))
688(add-to-list 'backup-directory-alist '("^/tmp/" . nil))
689(when-let ((xrd (getenv "XDG_RUNTIME_DIR")))
690 (add-to-list 'backup-directory-alist (cons xrd nil)))
691(add-to-list 'backup-directory-alist
692 (cons "." (locate-user-emacs-file "backup/"))
693 :append)
694
695(setopt recentf-max-menu-items 100)
696(setopt recentf-max-saved-items nil)
697(setopt recentf-case-fold-search t)
698(with-eval-after-load 'recentf
699 (add-to-list 'recentf-exclude "-autoloads.el\\'"))
700(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
701(add-hook 'after-save-hook #'recentf-save-list)
702(recentf-mode)
632 703
704(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux))
705(save-place-mode)
633 706
634;; Encodings 707;; Encodings
635(set-language-environment "UTF-8") 708(set-language-environment "UTF-8")
636(setopt buffer-file-coding-system 'utf-8-unix 709(setopt buffer-file-coding-system 'utf-8-unix)
637 coding-system-for-read 'utf-8-unix 710(setopt coding-system-for-read 'utf-8-unix)
638 coding-system-for-write 'utf-8-unix 711(setopt coding-system-for-write 'utf-8-unix)
639 default-process-coding-system '(utf-8-unix . utf-8-unix) 712(setopt default-process-coding-system '(utf-8-unix . utf-8-unix))
640 locale-coding-system 'utf-8-unix) 713(setopt locale-coding-system 'utf-8-unix)
641(set-charset-priority 'unicode) 714(set-charset-priority 'unicode)
642(prefer-coding-system 'utf-8-unix) 715(prefer-coding-system 'utf-8-unix)
643(set-default-coding-systems 'utf-8-unix) 716(set-default-coding-systems 'utf-8-unix)
@@ -651,117 +724,87 @@ If LOCALP is a string, add that directory to the `load-path'."
651 (set-selection-coding-system 'utf-8) 724 (set-selection-coding-system 'utf-8)
652 (set-clipboard-coding-system 'utf-8))) 725 (set-clipboard-coding-system 'utf-8)))
653 726
727;; Undo
728(when (package-ensure 'undohist)
729 (undohist-initialize))
654 730
655;; Files 731;;; ...
656(setopt auto-revert-verbose nil
657 global-auto-revert-non-file-buffers t
658 create-lockfiles nil
659 find-file-visit-truename t
660 mode-require-final-newline t
661 view-read-only t
662 save-silently t)
663(global-auto-revert-mode)
664 732
665(setopt auto-save-default nil 733(setopt bookmark-save-flag 1)
666 auto-save-interval 1
667 auto-save-no-message t
668 auto-save-timeout 1
669 auto-save-visited-interval 1
670 remote-file-name-inhibit-auto-save-visited t)
671(add-to-list 'auto-save-file-name-transforms
672 `(".*" ,(locate-user-emacs-file "auto-save/") t))
673(auto-save-visited-mode)
674 734
675(setopt backup-by-copying t 735(defun c-w-dwim (num)
676 version-control t 736 "Delete NUM words backward, or the region if it's active."
677 kept-new-versions 8 737 (interactive "p")
678 kept-old-versions 8 738 (if (region-active-p)
679 delete-old-versions t) 739 (call-interactively #'kill-region)
680(setq-default backup-directory-alist 740 (call-interactively #'backward-kill-word)))
681 `(("^/dev/shm" . nil) 741(keymap-global-set "C-w" #'c-w-dwim)
682 ("^/tmp" . nil)
683 (,(getenv "XDG_RUNTIME_DIR") . nil)
684 ("." . ,(locate-user-emacs-file "backup"))))
685
686(require 'recentf)
687(setopt
688 recentf-max-menu-items 500
689 recentf-max-saved-items nil ; Save the whole list
690 recentf-auto-cleanup 'mode
691 recentf-case-fold-search t)
692;; (add-to-list 'recentf-exclude etc/)
693(add-to-list 'recentf-exclude "-autoloads.el\\'")
694(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
695(add-hook 'after-save-hook #'recentf-save-list)
696(recentf-mode)
697
698(require 'saveplace)
699(setopt
700 save-place-forget-unreadable-files (eq system-type
701 'gnu/linux))
702(save-place-mode)
703
704(require 'uniquify)
705(setq uniquify-after-kill-buffer-p t
706 uniquify-buffer-name-style 'forward
707 uniquify-ignore-buffers-re "^\\*"
708 uniquify-separator path-separator)
709
710(setq-local vc-follow-symlinks t
711 vc-make-backup-files t)
712
713;; Whitespace
714(require 'whitespace)
715(setopt whitespace-style
716 '(face trailing tabs tab-mark))
717(global-whitespace-mode)
718(add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line)
719
720;; Native compilation
721(setopt native-comp-async-report-warnings-errors 'silent
722 native-comp-deferred-compilation t
723 native-compile-target-directory
724 (locate-user-emacs-file "eln"))
725(when (boundp 'native-comp-eln-load-path)
726 (add-to-list 'native-comp-eln-load-path native-compile-target-directory))
727(when (fboundp 'startup-redirect-eln-cache)
728 (startup-redirect-eln-cache native-compile-target-directory))
729 742
730(global-goto-address-mode) 743(setf/assoc display-buffer-alist
744 "\\`\\*Warnings\\*"
745 '((display-buffer-no-window)))
731 746
732;; Winner
733(winner-mode) 747(winner-mode)
734 748
735;;; Hooks 749(setopt set-mark-command-repeat-pop t)
736(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
737(add-hook 'find-file-not-found-functions #'create-missing-directories)
738(add-hook 'find-file-hook #'vc-remote-off)
739(add-hook 'dired-mode-hook #'hl-line-mode)
740(add-hook 'org-agenda-mode-hook #'hl-line-mode)
741
742;;; Tab bar
743
744(defun tab-bar-end-space ()
745 `((end menu-item " " ignore)))
746
747(add-to-list 'tab-bar-format 'tab-bar-format-align-right :append)
748(add-to-list 'tab-bar-format 'tab-bar-format-global :append)
749(add-to-list 'tab-bar-format 'tab-bar-end-space :append)
750;;(setopt tab-bar-show t)
751;;(tab-bar-mode) ; done after setting fonts
752
753;;; Org mode
754 750
755(keymap-global-set "C-c a" #'org-agenda) 751(when (package-ensure 'embark nil t)
756(keymap-global-set "C-c c" #'org-capture) 752 (when (and (package-installed-p 'consult)
757(setopt org-clock-clocked-in-display 'frame-title 753 (package-ensure 'embark-consult nil t))
758 org-clock-frame-title-format 754 (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))
759 '("%b" " - " (t org-mode-line-string)) 755 (keymap-global-set "C-." #'embark-act)
760 org-tags-column (- (- fill-column 3)) 756 (keymap-global-set "M-." #'embark-dwim)
761 org-log-into-drawer t 757 (keymap-global-set "C-h B" #'embark-bindings)
762 org-clock-into-drawer t) 758 (setopt prefix-help-command #'embark-prefix-help-command)
759 (setf/assoc display-buffer-alist
760 "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
761 '(nil (window-parameters (mode-line-format . none)))))
762
763(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
764(setopt eldoc-idle-delay 0.01)
765
766(setopt recenter-positions '(top middle bottom))
767
768(defmacro inhibit-messages (&rest body)
769 "Inhibit all messages in BODY."
770 (declare (indent defun))
771 `(cl-letf (((symbol-function 'message) #'ignore))
772 ,@body))
773
774(add-hook 'find-file-not-found-functions
775 (defun create-missing-directories ()
776 "Automatically create missing directories."
777 (let ((target-dir (file-name-directory buffer-file-name)))
778 (unless (file-exists-p target-dir)
779 (make-directory target-dir :parents)))))
780
781(setopt vc-follow-symlinks t)
782(setopt vc-make-backup-files t)
783(add-hook 'find-file-hook
784 (defun vc-remote-off ()
785 "Turn VC off when remote."
786 (when (file-remote-p (buffer-file-name))
787 (setq-local vc-handled-backends nil))))
788(with-eval-after-load 'vc-dir
789 (add-hook 'vc-dir-mode-hook #'hl-line-mode))
790
791(defun vc-jump ()
792 "Run `vc-dir' on the current directory.
793Saves a keystroke."
794 (interactive)
795 (vc-dir default-directory))
796(keymap-global-set "C-x v j" #'vc-jump)
763 797
764;;; Spelling 798(setopt whitespace-style '(face trailing tabs tab-mark))
799(global-whitespace-mode)
800(hide-minor-mode 'whitespace-mode)
801(add-hook 'before-save-hook
802 (defun delete-trailing-whitespace-except-current-line ()
803 (save-excursion
804 (delete-trailing-whitespace (point-min)
805 (line-beginning-position))
806 (delete-trailing-whitespace (line-end-position)
807 (point-max)))))
765 808
766(defun list-of-strings-p (x) 809(defun list-of-strings-p (x)
767 "Is X a list of strings?" 810 "Is X a list of strings?"
@@ -772,12 +815,140 @@ If LOCALP is a string, add that directory to the `load-path'."
772(put 'ispell-local-words 'safe-local-variable 815(put 'ispell-local-words 'safe-local-variable
773 'list-of-strings-p) 816 'list-of-strings-p)
774 817
775(add-hook 'text-mode-hook #'jinx-mode) 818(package-ensure '0x0) ; TODO: write my own package for rsync
776(with-eval-after-load 'jinx 819
777 (keymap-set jinx-mode-map "M-$" #'jinx-correct) 820(when (package-ensure 'electric-cursor t)
778 (keymap-set jinx-mode-map "C-M-$" #'jinx-languages)) 821 (hide-minor-mode 'electric-cursor-mode)
822 (setopt electric-cursor-alist '((overwrite-mode . (hbar . 8))
823 (t . box)))
824 (electric-cursor-mode))
825
826(defun fill-double-space-sentences-region (start end)
827 "Fill from START to END, double-spacing sentences."
828 (let ((sentence-end-double-space t))
829 (repunctuate-sentences :no-query start end)
830 (fill-region start end)))
831
832(defun unfill-region (start end &optional unfill-func)
833 "Unfill region from START to END."
834 (let ((fill-column most-positive-fixnum)
835 (fill-paragraph-function nil))
836 (funcall (or unfill-func #'fill-region) start end)))
837
838(defun fill-or-unfill-region (start end &optional interactive)
839 "Fill or unfill from START to END."
840 (interactive "*r\np")
841 (if (and interactive
842 (eq last-command 'fill-or-unfill-region))
843 ;; If called interactively more than once, toggle filling mode.
844 (if (with-current-buffer "*Messages*"
845 (goto-char (point-max))
846 (goto-char (beginning-of-line))
847 (looking-at "Unfilling"))
848 (fill-double-space-sentences-region start end)
849 (unfill-region start end #'fill-double-space-sentences-region))
850 ;; Otherwise, detect filled status based on the length of lines in the
851 ;; region. If just one of them is longer than `fill-column', consider the
852 ;; region unfilled.
853 (let ((filled-p (cl-some (lambda (ln) (<= 1 (length ln) fill-column))
854 (string-split (buffer-substring start end)
855 "[\n\r]+"))))
856 (if filled-p
857 (progn
858 (message "Unfilling region")
859 (unfill-region start end #'fill-double-space-sentences-region))
860 (progn
861 (message "Filling region")
862 (fill-double-space-sentences-region start end))))))
863
864(defun fill-or-unfill-dwim ()
865 (interactive)
866 (save-mark-and-excursion
867 (unless (region-active-p)
868 (mark-paragraph))
869 (call-interactively #'fill-or-unfill-region)))
870
871(keymap-global-set "M-q" #'fill-or-unfill-dwim)
779 872
780;;; org-return-dwim 873;; Fix annoying error messages when I type the <FN> key
874(keymap-global-set "<0x100811d0>" #'ignore) ; Keychron
875(keymap-global-set "<WakeUp>" #'ignore) ; Laptop
876
877(keymap-global-set "M-u" #'universal-argument)
878(keymap-set universal-argument-map "M-u" #'universal-argument-more)
879
880(defun kill-buffer-dwim (&optional buffer-or-name)
881 "Kill BUFFER-OR-NAME or the current buffer."
882 (interactive "P")
883 (cond
884 ((bufferp buffer-or-name)
885 (kill-buffer buffer-or-name))
886 ((null buffer-or-name)
887 (kill-current-buffer))
888 (:else
889 (kill-buffer (read-buffer "Kill: " nil :require-match)))))
890(keymap-global-set "C-x C-k" #'kill-buffer-dwim)
891
892(defun other-window-dwim (&optional arg)
893 "Switch to another window/buffer.
894Calls `other-window', which see, unless
895- the current window is alone on its frame
896- `other-window-dwim' is called with \\[universal-argument]
897In these cases, switch to the last-used buffer."
898 (interactive "P")
899 (if (or arg (one-window-p))
900 (switch-to-buffer (other-buffer) nil t)
901 (other-window 1)))
902(keymap-global-set "M-o" #'other-window-dwim)
903(keymap-global-set "C-x o" #'other-window-dwim)
904
905(defun delete-window-dwim ()
906 "Delete the current window or bury its buffer.
907If the current window is alone in its frame, bury the buffer
908instead."
909 (interactive)
910 (unless (ignore-errors (delete-window) t)
911 (bury-buffer)))
912(keymap-global-set "C-x 0" #'delete-window-dwim)
913
914
915;;; Org mode
916
917(keymap-global-set "C-c a" #'org-agenda)
918(keymap-global-set "C-c c" #'org-capture)
919(keymap-global-set "C-c l" #'org-store-link)
920
921(setopt org-clock-clocked-in-display 'mode-line)
922(setopt org-clock-out-remove-zero-time-clocks t)
923(setopt org-clock-frame-title-format '("%b" " - " (t org-mode-line-string)))
924(setopt org-tags-column (- (- fill-column 3)))
925(setopt org-log-into-drawer t)
926(setopt org-clock-into-drawer t)
927(setopt org-special-ctrl-a/e t)
928(setopt org-special-ctrl-k t)
929(setopt org-archive-mark-done t)
930(setopt org-agenda-window-setup 'current-window)
931(setopt org-agenda-restore-windows-after-quit t)
932(setopt org-agenda-skip-deadline-if-done t)
933(setopt org-agenda-skip-scheduled-if-done t)
934(setopt org-agenda-inhibit-startup t)
935(setopt org-deadline-warning-days 0)
936(setopt org-cycle-separator-lines 0)
937(setopt org-agenda-span 10)
938(setopt org-blank-before-new-entry '((heading . t)
939 (plain-list-item . nil)))
940
941(defvar-local org-agenda/setup-done nil)
942
943(add-hook 'org-agenda-after-show-hook
944 (defun org-agenda-after-show/setup ()
945 (org-narrow-to-subtree)
946 (goto-char (point-min))
947 (unless org-agenda/setup-done
948 (run-hooks 'org-mode-hook))
949 (setq org-agenda/setup-done t)))
950
951;; org-return-dwim
781;; https://github.com/alphapapa/unpackaged.el, 952;; https://github.com/alphapapa/unpackaged.el,
782;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ 953;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
783(defun org-return-dwim (&optional arg) 954(defun org-return-dwim (&optional arg)
@@ -872,12 +1043,36 @@ itself. Other values of ARG will call `newline' with that ARG."
872 (org-table-copy-down (or n 1)) 1043 (org-table-copy-down (or n 1))
873 (org-return-dwim n))) 1044 (org-return-dwim n)))
874 1045
1046(defmacro org-insert-or-surround (character)
1047 (let ((c (gensym)))
1048 `(defun ,(intern (format "org-insert-or-surround-%s" character)) (arg)
1049 ,(format "Insert %s or surround the region with it." character)
1050 (interactive "p")
1051 (let ((,c ,(if (stringp character)
1052 (string-to-char character)
1053 character)))
1054 (if (org-region-active-p)
1055 (let ((begin (region-beginning))
1056 (end (region-end)))
1057 (save-mark-and-excursion
1058 (deactivate-mark)
1059 (goto-char begin)
1060 (self-insert-command arg ,c)
1061 (goto-char (+ 1 end))
1062 (self-insert-command arg ,c)))
1063 (self-insert-command arg ,c))))))
1064
875(with-eval-after-load 'org 1065(with-eval-after-load 'org
876 (keymap-set org-mode-map "RET" #'org-return-dwim) 1066 (keymap-set org-mode-map "RET" #'org-return-dwim)
877 (keymap-set org-mode-map "S-RET" #'org-table-copy-down|org-return-dwim)) 1067 (keymap-set org-mode-map "S-<return>" #'org-table-copy-down|org-return-dwim)
878 1068 (keymap-set org-mode-map "*" (org-insert-or-surround "*"))
879;;; Copy rich text to the keyboard 1069 (keymap-set org-mode-map "/" (org-insert-or-surround "/"))
880 1070 (keymap-set org-mode-map "_" (org-insert-or-surround "_"))
1071 (keymap-set org-mode-map "=" (org-insert-or-surround "="))
1072 (keymap-set org-mode-map "~" (org-insert-or-surround "~"))
1073 (keymap-set org-mode-map "+" (org-insert-or-surround "+")))
1074
1075;; Copy rich text to the keyboard
881(defcustom clipboard-html-copy-program 1076(defcustom clipboard-html-copy-program
882 (if (or (equal "wayland" 1077 (if (or (equal "wayland"
883 (getenv "XDG_SESSION_TYPE")) 1078 (getenv "XDG_SESSION_TYPE"))
@@ -889,20 +1084,6 @@ Should be a list of strings---the command line.
889Defaults to 'wl-copy' on wayland and 'xclip' on Xorg." 1084Defaults to 'wl-copy' on wayland and 'xclip' on Xorg."
890 :type '(repeat string)) 1085 :type '(repeat string))
891 1086
892;; Thanks to Oleh Krehel:
893;; https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard
894;; So. Emacs can't do this itself because it doesn't support sending clipboard
895;; or selection contents as text/html. We have to use xclip instead.
896;; (defun org-to-html-to-clipboard (&rest org-export-args)
897;; "Export current org buffer to HTML, then copy it to the clipboard.
898;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
899;; (let ((f (make-temp-file "org-html-export")))
900;; (apply #'org-export-to-file 'html f org-export-args)
901;; (start-process "xclip" " *xclip*"
902;; "xclip" "-verbose" "-i" f
903;; "-t" "text/html" "-selection" "clipboard")
904;; (message "HTML pasted to clipboard.")))
905
906(defun org-export-html-copy (&rest org-export-args) 1087(defun org-export-html-copy (&rest org-export-args)
907 "Export current org buffer to HTML and copy to clipboard as rich text. 1088 "Export current org buffer to HTML and copy to clipboard as rich text.
908ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." 1089ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
@@ -920,110 +1101,826 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
920 (kill-buffer-and-window)) 1101 (kill-buffer-and-window))
921 (message "HTML copied to clipboard."))) 1102 (message "HTML copied to clipboard.")))
922 1103
923;; Wayland version.. TODO: make it work for both
924;; (defun org-to-html-to-clipboard (&rest org-export-args)
925 ;; "Export current org buffer to HTML, then copy it to the clipboard.
926;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
927 ;; (let ((buf (generate-new-buffer "*org-html-clipboard*" t)))
928 ;; (apply #'org-export-to-buffer 'html buf org-export-args)
929 ;; (with-current-buffer buf
930 ;; (call-process-region (point-min) (point-max)
931 ;; "wl-copy" nil nil nil
932 ;; "-t" "text/html")
933 ;; (kill-buffer-and-window))
934 ;; (message "HTML copied to clipboard.")))
935
936(defun org-subtree-to-html-to-clipboard () 1104(defun org-subtree-to-html-to-clipboard ()
937 "Export current subtree to HTML." 1105 "Export current subtree to HTML."
938 (interactive) 1106 (interactive)
939 (org-export-html-copy nil :subtree)) 1107 (org-export-html-copy nil :subtree))
940 1108
941(undohist-initialize) 1109;; (info "(org) Breaking Down Tasks")
942 1110(defun org-summary-todo (n-done n-not-done)
943(require 'hungry-delete) 1111 "Switch entry to DONE when all subentries are done, to TODO otherwise."
944(setopt hungry-delete-chars-to-skip " \t" 1112 (let (org-log-done org-log-states) ; turn off logging
945 hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip) 1113 (org-todo (if (= n-not-done 0) "DONE" "TODO"))))
946 hungry-delete-join-reluctantly nil) 1114(add-hook 'org-after-todo-statistics-hook #'org-summary-todo)
947(add-to-list 'hungry-delete-except-modes 'eshell-mode) 1115
948(add-to-list 'hungry-delete-except-modes 'nim-mode) 1116;; Clean up the buffer view
949(add-to-list 'hungry-delete-except-modes 'python-mode) 1117(defun org-hide-drawers-except-point ()
950(global-hungry-delete-mode) 1118 "Hide all drawers except for the one point is in."
951 1119 ;; Most of this bit is taken from `org-fold--hide-drawers'.
952(setopt avy-background t 1120 (let ((pt (point))
953 avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm")) 1121 (begin (point-min))
954(keymap-global-set "M-j" #'avy-goto-char-timer) 1122 (end (point-max)))
955(keymap-set isearch-mode-map "M-j" #'avy-isearch) 1123 (save-excursion
956(keymap-global-set "M-z" #'zzz-to-char) 1124 (goto-char begin)
957 1125 (while (and (< (point) end)
958(marginalia-mode) 1126 (re-search-forward org-drawer-regexp end t))
959 1127 (if (org-fold-folded-p nil 'drawer)
960(keymap-global-set "C-x b" #'consult-buffer) 1128 (goto-char (org-fold-next-folding-state-change 'drawer nil end))
961(keymap-global-set "C-x 4 b" #'consult-buffer-other-window) 1129 (let* ((drawer (org-element-at-point))
962(keymap-global-set "C-x 5 b" #'consult-buffer-other-frame) 1130 (type (org-element-type drawer))
963(keymap-global-set "C-x r b" #'consult-bookmark) 1131 (el-begin (org-element-property :begin drawer))
964(keymap-global-set "M-y" #'consult-yank-pop) 1132 (el-end (org-element-property :end drawer)))
965(keymap-global-set "M-g g" #'consult-goto-line) 1133 (when (memq type '(drawer property-drawer))
966(keymap-global-set "M-g M-g" #'consult-goto-line) 1134 (org-fold-hide-drawer-toggle
967(keymap-global-set "M-g o" #'consult-outline) 1135 (if (< el-begin pt el-end) 'off 'on)
968(keymap-global-set "M-g m" #'consult-mark) 1136 nil drawer)
969(keymap-global-set "M-g i" #'consult-imenu) 1137 (goto-char el-end))))))))
970(keymap-global-set "M-s d" #'consult-find) 1138(add-local-mode-hook 'org-mode-hook 'before-save-hook
971(keymap-global-set "M-s D" #'consult-locate) 1139 #'org-hide-drawers-except-point)
972(keymap-global-set "M-s g" #'consult-grep) 1140
973(keymap-global-set "M-s G" #'consult-git-grep) 1141;; Fix braindead behavior
974(keymap-global-set "M-s r" #'consult-ripgrep) 1142(with-eval-after-load 'org-mouse
975(keymap-global-set "M-s l" #'consult-line) 1143 (defun org--mouse-open-at-point (orig-fun &rest args)
976(keymap-global-set "M-s k" #'consult-keep-lines) 1144 (let ((context (org-context)))
977(keymap-global-set "M-s u" #'consult-focus-lines) 1145 (cond
978 1146 ;; Don't org-cycle when clicking on headline stars. The biggest problem
979(keymap-global-set "M-s e" #'consult-isearch-history) 1147 ;; is that this function advises `org-open-at-point', so I can't C-c C-o
980(keymap-set isearch-mode-map "M-e" #'consult-isearch-history) 1148 ;; from a headline star.
981(keymap-set isearch-mode-map "M-s e" #'consult-isearch-history) 1149 ;; ((assq :headline-stars context) (org-cycle))
982(keymap-set isearch-mode-map "M-s l" #'consult-line) 1150 ((assq :checkbox context) (org-toggle-checkbox))
983 1151 ((assq :item-bullet context)
984(keymap-set minibuffer-local-map "M-n" #'consult-history) 1152 (let ((org-cycle-include-plain-lists t)) (org-cycle)))
985(keymap-set minibuffer-local-map "M-p" #'consult-history) 1153 ((org-footnote-at-reference-p) nil)
986 1154 (t (apply orig-fun args))))))
987(setopt completion-in-region-function #'consult-completion-in-region
988 xref-show-xrefs-function #'consult-xref
989 xref-show-definitions-function #'consult-xref)
990
991(setopt initial-scratch-message ";;; Emacs!\n\n")
992 1155
993(keymap-global-set "C-x C-b" #'ibuffer) 1156(defun define-org-capture-template (description &rest args)
994(add-hook 'ibuffer-hook #'hl-line-mode) 1157 "Define an template for `org-capture-templates'.
1158Will not replace an existing template unless `:force' in ARGS is
1159non-nil. ARGS is a plist, which in addition to the additional
1160options `org-capture-templates' accepts (which see), also accepts
1161the following: `:keys', `:description', `:type', `:target', and
1162`:template'."
1163 (declare (indent 1))
1164 (let* ((keys (plist-get args :keys))
1165 (type (plist-get args :type))
1166 (target (plist-get args :target))
1167 (template (plist-get args :template))
1168 (force (plist-get args :force))
1169 (template-value
1170 (append
1171 (list description)
1172 (when (or type target template)
1173 (list (or type 'entry) target template))
1174 (cl-loop for i from 0 below (length args) by 2
1175 unless (member (nth i args)
1176 '( :keys :description :type
1177 :target :template))
1178 append (list (nth i args)
1179 (plist-get args (nth i args)))))))
1180 (if (seq-find (lambda (el) (equal (car el) keys))
1181 org-capture-templates)
1182 (and force
1183 (setf (alist-get keys org-capture-templates nil nil #'equal)
1184 template-value))
1185 (setf org-capture-templates
1186 (append org-capture-templates
1187 (list (cons keys template-value)))))
1188 org-capture-templates))
995 1189
996(require 'scule) 1190(add-hook 'org-mode-hook
997(keymap-global-set "M-c" scule-map) 1191 (defun org-mode-line-position ()
998(autoload 'titlecase-dwim "titlecase" nil t) 1192 (setq-local mode-line-position
999(keymap-set scule-map "M-t" #'titlecase-dwim) 1193 '((:propertize
1194 ("" mode-line-percent-position)
1195 local-map mode-line-column-line-number-mode-map
1196 display (min-width (5.0)))
1197 (org-word-count-mode org-word-count-string))))
1198 (setq mode-line-misc-info
1199 (delete '(org-word-count-mode org-word-count-string)
1200 mode-line-misc-info)))
1201
1202;;; Org word count
1203;; also does Flesch-Kincaid reading level.
1204;; TODO: customization ... stuff.
1205
1206(defun fk-region (start end)
1207 (interactive "r")
1208 ;; (let* ((fk-buf (get-buffer-create " *fk*"))
1209 ;; (fk-proc
1210 ;; (start-process "fk" fk-buf "/home/acdw/src/fk/fk.perl")))
1211 ;; (set-process-sentinel fk-proc #'ignore)
1212 ;; (process-send-region fk-proc start end)
1213 ;; (process-send-eof fk-proc)
1214 ;; (with-current-buffer fk-buf
1215 ;; (goto-char (point-max))
1216 ;; (forward-line -1)
1217 ;; (string-chop-newline (buffer-substring-no-properties
1218 ;; (line-beginning-position) (point-max)))))
1219
1220 (let ((shell-command-buffer-name (format " *fk/%s*" (buffer-name))))
1221 (shell-command-on-region start end "~/src/fk/fk.perl")
1222 (with-current-buffer shell-command-buffer-name
1223 (buffer-substring-no-properties (point-min) (- (point-max) 1))))
1224 )
1225
1226(defun org-word-count-region (start end &optional interactive)
1227 (interactive "r\np")
1228 (when (derived-mode-p 'org-mode)
1229 (save-window-excursion
1230 (inhibit-messages
1231 (let ((shell-command-buffer-name (format " *wc/%s*" (buffer-name)))
1232 wc fk)
1233 (shell-command-on-region start end
1234 "pandoc -t plain -f org")
1235 (with-current-buffer shell-command-buffer-name
1236 (setq wc (count-words (point-min) (point-max)))
1237 (setq fk (string-to-number (fk-region (point-min) (point-max)))))
1238 (when interactive (message "%s" wc))
1239 (list wc fk))))))
1240
1241(defvar-local org-word-count-string ""
1242 "Number of words in buffer.")
1243
1244(defun update-org-word-count-string ()
1245 (when (derived-mode-p 'org-mode)
1246 (setq org-word-count-string
1247 (apply #'format " %dw/%.2ffk"
1248 (org-word-count-region (point-min) (point-max))))))
1249
1250(defvar org-word-count-timer nil
1251 "Timer for `org-word-count'.")
1252
1253(define-minor-mode org-word-count-mode
1254 "Count words and update the org-word-count-string."
1255 :lighter " owc"
1256 (cond
1257 ((and (derived-mode-p 'org-mode)
1258 org-word-count-mode)
1259 (unless (timerp org-word-count-timer)
1260 (setq org-word-count-timer
1261 (run-with-idle-timer 1 t #'update-org-word-count-string))))
1262 (:else
1263 (when (timerp org-word-count-timer)
1264 (cancel-timer org-word-count-timer))
1265 (setq org-word-count-timer nil)
1266 (setq org-word-count-mode nil))))
1267(hide-minor-mode 'org-word-count-mode)
1000 1268
1001;; Use M-u for prefix keys 1269(add-hook 'org-mode-hook #'org-word-count-mode)
1002(keymap-global-set "M-u" #'universal-argument) 1270
1003(keymap-set universal-argument-map "M-u" #'universal-argument-more) 1271;;; Org recentering
1272
1273(defun org-recenter (&optional arg)
1274 (interactive "P")
1275 (if (or arg
1276 (eq last-command 'org-recenter))
1277 (recenter-top-bottom arg)
1278 (save-excursion
1279 (unless (org-at-heading-p)
1280 (ignore-errors (org-previous-visible-heading 1)))
1281 (recenter-top-bottom 0))))
1282(with-eval-after-load 'org
1283 (keymap-set org-mode-map "C-l" #'org-recenter))
1284
1285;;; Org links -- extra types
1286
1287(with-eval-after-load 'ol
1288 (org-link-set-parameters "tel" :follow #'ignore)
1289 (org-link-set-parameters "sms" :follow #'ignore))
1290
1291
1292;;; Jabber
1293
1294;; (when (package-ensure 'jabber t t)
1295;; (setopt jabber-chat-buffer-format "*%n*")
1296;; (setopt jabber-browse-buffer-format "*%n*")
1297;; (setopt jabber-groupchat-buffer-format "*%n*")
1298;; (setopt jabber-muc-private-buffer-format "*%n*")
1299
1300;; (face-spec-set 'jabber-activity-face
1301;; '((t :inherit jabber-chat-prompt-foreign
1302;; :foreground unspecified
1303;; :weight normal)))
1304;; (face-spec-set 'jabber-activity-personal-face
1305;; '((t :inherit jabber-chat-prompt-local
1306;; :foreground unspecified
1307;; :weight bold)))
1308;; (face-spec-set 'jabber-chat-prompt-local
1309;; '((t :inherit minibuffer-prompt
1310;; :foreground unspecified
1311;; :weight normal
1312;; :slant italic)))
1313;; (face-spec-set 'jabber-chat-prompt-foreign
1314;; '((t :inherit warning
1315;; :foreground unspecified
1316;; :weight normal)))
1317;; (face-spec-set 'jabber-chat-prompt-system
1318;; '((t :inherit font-lock-doc-face
1319;; :foreground unspecified)))
1320;; (face-spec-set 'jabber-rare-time-face
1321;; '((t :inherit font-lock-comment-face
1322;; :foreground unspecified
1323;; :underline nil)))
1324
1325;; (setopt jabber-auto-reconnect t)
1326;; (setopt jabber-last-read-marker
1327;; "-------------------------------------------------------------------")
1328;; (setopt jabber-muc-decorate-presence-patterns
1329;; '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil)
1330;; ("Mode #.*" . jabber-muc-presence-dim)
1331;; ("." . jabber-muc-presence-dim)))
1332;; (setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten)
1333;; (setopt jabber-rare-time-format
1334;; (format " - - - - - %%H:%d %%F"
1335;; (let ((min (string-to-number (format-time-string "%M"))))
1336;; (* 5 (floor min 5)))))
1337;; (setopt jabber-muc-header-line-format '(" " jabber-muc-topic))
1338
1339;; (setopt jabber-groupchat-prompt-format "%n. ")
1340;; (setopt jabber-chat-local-prompt-format "%n. ")
1341;; (setopt jabber-chat-foreign-prompt-format "%n. ")
1342;; (setopt jabber-muc-private-foreign-prompt-format "%g/%n. ")
1343
1344;; (defun jabber-connect-all* (&optional arg)
1345;; "Connect to all defined jabber accounts.
1346;; If called with ARG non-nil, or with \\[universal-argument],
1347;; disconnect first."
1348;; (interactive "P")
1349;; (when arg (jabber-disconnect))
1350;; (jabber-connect-all))
1351
1352;; (with-eval-after-load 'jabber
1353;; (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to)
1354;; (require 'jabber-httpupload nil t)
1355;; (map-keymap (lambda (key command)
1356;; (define-key jabber-global-keymap (vector (+ key #x60)) command))
1357;; jabber-global-keymap)
1358;; (keymap-global-set "C-x C-j" #'dired-jump)
1359;; (keymap-set jabber-global-keymap "c" #'jabber-connect-all*)
1360;; (keymap-global-set "C-c j" jabber-global-keymap))
1361
1362;; (remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo)
1363;; (remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo)
1364;; (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
1365;; (add-hook 'jabber-chat-mode-hook #'olivetti-mode)
1366;; (add-hook 'jabber-chat-mode-hook
1367;; (defun jabber-chat-mode-no-position ()
1368;; (setq-local mode-line-position nil)))
1369;; (add-hook 'jabber-alert-muc-hooks
1370;; (defun jabber@highlight-acdw (&optional _ _ buf _ _)
1371;; (when buf
1372;; (with-current-buffer buf
1373;; (let ((regexp (rx word-boundary
1374;; "acdw" ; maybe get from the config?
1375;; word-boundary)))
1376;; (hi-lock-unface-buffer regexp)
1377;; (highlight-regexp regexp 'jabber-chat-prompt-local))))))
1378
1379;; (add-hook 'jabber-chat-mode-hook
1380;; (defun electric-pair-local-disable ()
1381;; (electric-pair-local-mode -1)))
1382
1383;; (when (fboundp 'jabber-chat-update-focus)
1384;; (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)))
1385
1386
1387;;; Dired
1004 1388
1005(autoload 'frowny-mode "frowny" nil t) 1389(keymap-global-set "C-x C-j" #'dired-jump)
1006(add-hook 'jabber-chat-mode-hook #'frowny-mode) 1390(with-eval-after-load 'dired
1007(add-hook 'jabber-chat-mode-hook #'electric-pair-local-mode-disable) 1391 (keymap-set dired-mode-map "C-j" #'dired-up-directory))
1008 1392
1009(autoload 'hippie-completing-read "hippie-completing-read" nil t) 1393(setopt dired-auto-revert-buffer t)
1010(keymap-global-set "M-/" #'hippie-completing-read) 1394(setopt dired-dwim-target t) ; dired-dwim-target-next ?
1011 1395
1012(setopt mode-line-bell-flash-time 0.25) 1396
1013(autoload 'mode-line-bell-mode "mode-line-bell" nil t) 1397;;; Browsing the web
1014(mode-line-bell-mode)
1015 1398
1016(keymap-global-set "C-x C-k" #'kill-this-buffer) 1399(setopt browse-url-browser-function #'eww-browse-url)
1017 1400
1018(require 'anzu) 1401(defcustom browse-url-safe-browser-functions nil
1019(global-anzu-mode) 1402 "\"Safe\" browser functions."
1020(setopt search-default-mode t 1403 :type '(repeat-function))
1021 anzu-mode-lighter "" 1404
1022 anzu-deactivate-region t) 1405(defun browse-url-browser-function-safe-p (fn)
1406 "Return t if FN is a \"safe\" browser function."
1407 (memq fn (append browse-url-safe-browser-functions
1408 (mapcar (lambda (i)
1409 (plist-get (cdr i) :value))
1410 (seq-filter (lambda (i)
1411 (eq (car i) 'function-item))
1412 (cdr (get 'browse-url-browser-function
1413 'custom-type)))))))
1023 1414
1024(global-set-key [remap query-replace] #'anzu-query-replace-regexp) 1415(put 'browse-url-browser-function 'safe-local-variable
1025(global-set-key [remap query-replace-regexp] #'anzu-query-replace) 1416 'browse-url-browser-function-safe-p)
1026(define-key isearch-mode-map [remap isearch-query-replace] 1417
1027 #'anzu-isearch-query-replace-regexp) 1418;;; EWW
1028(define-key isearch-mode-map [remap isearch-query-replace-regexp] 1419
1029 #'anzu-isearch-query-replace) 1420(setopt eww-use-browse-url ".")
1421(setopt eww-auto-rename-buffer 'title)
1422(setopt eww-default-download-directory
1423 (or (xdg-user-dir "DOWNLOAD")
1424 "~/Downloads"))
1425(setopt eww-history-limit nil)
1426
1427(defun eww-readable/olivetti ()
1428 (interactive)
1429 (olivetti-mode +1)
1430 (eww-readable)
1431 (eww-reload t))
1432
1433(with-eval-after-load 'eww
1434 (keymap-set eww-mode-map "R" #'eww-readable/olivetti))
1435
1436;; Use Emacs bookmarks for EWW
1437(defun bookmark-eww--make ()
1438 "Make eww bookmark record."
1439 `((filename . ,(plist-get eww-data :url))
1440 (title . ,(plist-get eww-data :title))
1441 (time . ,(current-time-string))
1442 (handler . ,#'bookmark-eww-handler)
1443 (defaults . (,(concat
1444 ;; url without the https and path
1445 (replace-regexp-in-string
1446 "/.*" ""
1447 (replace-regexp-in-string
1448 "\\`https?://" ""
1449 (plist-get eww-data :url)))
1450 " - "
1451 ;; page title
1452 (replace-regexp-in-string
1453 "\\` +\\| +\\'" ""
1454 (replace-regexp-in-string
1455 "[\n\t\r ]+" " "
1456 (plist-get eww-data :title))))))))
1457
1458(defun bookmark-eww-handler (bm)
1459 "Handler for eww bookmarks."
1460 (eww-browse-url (alist-get 'filename bm)))
1461
1462(defun bookmark-eww--setup ()
1463 "Setup eww bookmark integration."
1464 (setq-local bookmark-make-record-function #'bookmark-eww--make))
1465(add-hook 'eww-mode-hook #'bookmark-eww--setup)
1466
1467(with-eval-after-load 'eww
1468 (define-key eww-mode-map "b" #'bookmark-set)
1469 (define-key eww-mode-map "B" #'bookmark-jump))
1470
1471;; Transforming URLs
1472;; `eww-transform-url' exists, but I like my package better.
1473
1474(when (package-ensure 'browse-url-transform t)
1475 (setopt browse-url-transform-alist
1476 `(;; Privacy-respecting alternatives
1477 ("twitter\\.com" . "nitter.snopyta.org")
1478 ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de")
1479 ("medium\\.com" . "scribe.rip")
1480 ;; Text-mode of non-text-mode sites
1481 ("www\\.npr\\.org" . "text.npr.org")
1482 ;; Ask for raw versions of paste sites
1483 ("^.*dpaste\\.com.*$" . "\\&.txt")
1484 ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1")
1485 ("\\(paste\\.debian\\.net\\)/\\(.*\\)" . "\\1/plain/\\2")
1486 ("\\(pastebin\\.com\\)/\\\(.*\\)" . "\\1/raw/\\2")
1487 ("\\(paste\\.centos\\.org/view\\)/\\(.*\\)" . "\\1/raw/\\2")))
1488 (browse-url-transform-mode)
1489 (hide-minor-mode 'browse-url-transform-mode))
1490
1491(with-eval-after-load 'browse-url-transform
1492 (setq eww-url-transformers ; `setopt' causes a warning about custom-type
1493 '(eww-remove-tracking
1494 browse-url-transform-url)))
1495
1496;; External browsers: firefox > chromium > chrome
1497(setq browse-url-firefox-program
1498 (or (executable-find "firefox")
1499 (executable-find "firefox-esr"))
1500 browse-url-firefox-new-window-is-tab t
1501 browse-url-firefox-arguments '("--new-tab")
1502
1503 browse-url-chromium-program
1504 (or (executable-find "chromium")
1505 (executable-find "chromium-browser"))
1506
1507 browse-url-chrome-program
1508 (or (executable-find "chrome")
1509 (executable-find "google-chrome-stable"))
1510
1511 browse-url-secondary-browser-function
1512 (cond (browse-url-firefox-program #'browse-url-firefox)
1513 (browse-url-chromium-program #'browse-url-chromium)
1514 (browse-url-chrome-program #'browse-url-chrome)
1515 (t #'browse-url-default-browser)))
1516
1517(defmacro open-url-with (commandline &optional buffer error-buffer)
1518 (let ((buffer (or buffer " *open-url-with*"))
1519 (error-buffer (or error-buffer " *open-url-with/errors*")))
1520 `(lambda (url &rest _)
1521 (cl-letf (((alist-get ,buffer
1522 display-buffer-alist
1523 nil nil #'equal)
1524 '(display-buffer-no-window)))
1525 (async-shell-command (format ,commandline url)
1526 ,buffer
1527 ,error-buffer)))))
1528
1529(defun add-browse-url-handler (regexp opener)
1530 "Add OPENER to open REGEXP urls."
1531 (setf/assoc browse-url-handlers
1532 regexp
1533 opener))
1534
1535(add-browse-url-handler (rx (or (: ".pdf" eos)
1536 (: ".PDF" eos)))
1537 (open-url-with "zathura %s"))
1538(add-browse-url-handler (rx (or (: ".mp4" eos)
1539 "youtube.com"
1540 "piped.kavin.rocks"))
1541 (open-url-with "mpv %s"))
1542
1543(when (package-ensure 'elpher)
1544 (add-browse-url-handler (rx bos "gemini:")
1545 #'elpher-browse-url-elpher))
1546
1547;; Hinting at links
1548(when (package-ensure 'link-hint)
1549 (setopt link-hint-avy-style 'at-full)
1550 (setopt link-hint-avy-all-windows t)
1551 (defvar link-hint-map
1552 (define-keymap
1553 :name "Open a link"
1554 :prefix 'link-hint-map
1555 "M-l" #'link-hint-open-link
1556 "M-w" #'link-hint-copy-link))
1557 (keymap-global-set "M-l" 'link-hint-map))
1558
1559
1560;;; Eshell
1561
1562(setopt eshell-modules-list
1563 '(eshell-alias
1564 eshell-banner
1565 eshell-basic
1566 eshell-cmpl
1567 eshell-dirs
1568 eshell-elecslash
1569 eshell-extpipe
1570 eshell-glob
1571 eshell-hist
1572 eshell-ls
1573 eshell-pred
1574 eshell-prompt
1575 eshell-script
1576 eshell-smart
1577 eshell-unix))
1578
1579(setopt eshell-banner-message "")
1580(setopt eshell-destroy-buffer-when-process-dies t)
1581(setopt eshell-error-if-no-glob t)
1582(setopt eshell-hist-ignoredups 'erase)
1583(setopt eshell-kill-on-exit t)
1584(setopt eshell-prefer-lisp-functions t)
1585(setopt eshell-prefer-lisp-variables t)
1586(setopt eshell-scroll-to-bottom-on-input 'this)
1587(setopt eshell-history-size 1024)
1588(setopt eshell-input-filter (lambda (input)
1589 (or (eshell-input-filter-default input)
1590 (eshell-input-filter-initial-space input))))
1591(setopt eshell-prompt-function
1592 (lambda ()
1593 (concat (if (= 0 eshell-last-command-status)
1594 "^_^"
1595 ";_;")
1596 " "
1597 (abbreviate-file-name (eshell/pwd))
1598 (if (= (user-uid) 0)
1599 " # "
1600 " $ "))))
1601(setopt eshell-scroll-to-bottom-on-input 'this)
1602
1603(add-hook 'eshell-mode-hook
1604 (defun eshell-setup ()
1605 (setq-local outline-regexp eshell-prompt-regexp)
1606 (setq-local page-delimiter eshell-prompt-regexp)
1607 (setq-local imenu-generic-expression
1608 '(("Prompt" " \\($\\|#\\) \\(.*\\)" 2)))
1609 (setq-local truncate-lines t)))
1610
1611(setenv "PAGER" (executable-find "cat"))
1612
1613(setopt eshell-where-to-jump 'begin)
1614(setopt eshell-review-quick-commands nil)
1615(setopt eshell-smart-space-goes-to-end t)
1616
1617(when (package-ensure 'eat)
1618 (add-hook 'eshell-first-time-mode-hook #'eat-eshell-mode)
1619 (with-eval-after-load 'eat
1620 (keymap-unset eat-eshell-semi-char-mode-map "M-o" t)))
1621
1622(if (package-ensure 'eshell-toggle)
1623 (keymap-global-set "C-z" #'eshell-toggle)
1624 ;; If the package doesn't load for some reason, do the dumb thing instead
1625 (defun eshellp (buffer-or-name)
1626 (with-current-buffer buffer-or-name
1627 (derived-mode-p 'eshell-mode)))
1628
1629 (defun eshell-pop-up (&optional arg)
1630 "Pop up an eshell in the `default-directory'.
1631NEW is passed to `eshell'."
1632 (interactive "P")
1633 (require 'eshell)
1634 (let ((dir default-directory)
1635 (display-comint-buffer-action 'pop-to-buffer))
1636 (if-let ((buf (and (not arg)
1637 (or (get-buffer eshell-buffer-name)
1638 (seq-find #'eshellp (reverse (buffer-list)))))))
1639 (pop-to-buffer buf)
1640 (eshell arg))
1641 ;; In the eshell buffer
1642 (unless (file-equal-p default-directory dir)
1643 (eshell/cd dir)
1644 (eshell-send-input)
1645 (goto-char (point-max)))))
1646
1647 (keymap-global-set "C-z" #'eshell-pop-up)
1648 (with-eval-after-load 'esh-mode
1649 (keymap-set eshell-mode-map "C-z" #'quit-window)))
1650
1651(when (package-ensure 'wiki-abbrev t)
1652 (wiki-abbrev-insinuate)
1653 (add-hook 'text-mode-hook #'abbrev-mode))
1654
1655;;; Dinghie
1656
1657(add-to-list 'mode-line-misc-info
1658 '(buffer-ding-cookie (:propertize buffer-ding-cookie
1659 face error))
1660 :append)
1661
1662(defvar buffer-ding-timer nil
1663 "Timer for `buffer-ding'.")
1664(defvar buffer-ding-cookie nil
1665 "Variable to hold the `face-remap-add-relative' cookie.")
1666(defcustom buffer-ding-timeout 0.25
1667 "How long to ding the buffer for.")
1668
1669(defun buffer-unding ()
1670 "Unflash the buffer after done `ding'ing."
1671 ;; (face-remap-remove-relative buffer-ding-cookie)
1672 (setq buffer-ding-cookie nil)
1673 (force-mode-line-update t))
1674
1675(defun buffer-ding ()
1676 "Flash the buffer for `ding'."
1677 (cond
1678 ((timerp buffer-ding-timer)
1679 (cancel-timer buffer-ding-timer)
1680 (setq buffer-ding-timer nil)
1681 (buffer-ding))
1682 ((and (null buffer-ding-timer)
1683 buffer-ding-cookie)
1684 (setq buffer-ding-cookie nil)
1685 (buffer-unding))
1686 (t
1687 ;; (setq buffer-ding-cookie (face-remap-add-relative 'default 'error))
1688 (setq buffer-ding-cookie " Ding!")
1689 (force-mode-line-update)
1690 (run-with-timer buffer-ding-timeout nil #'buffer-unding))))
1691
1692;; (setopt ring-bell-function (lambda () (pulse-momentary-highlight-region
1693;; (window-start) (window-end))))
1694
1695(setopt ring-bell-function #'buffer-ding)
1696(add-hook 'isearch-mode-end-hook #'buffer-unding)
1697
1698(defun mapc-buffers (fn &rest modes)
1699 (cl-loop for buf being the buffers
1700 do (with-current-buffer buf
1701 (when (or (null modes)
1702 (apply #'derived-mode-p modes))
1703 (funcall fn)))))
1704
1705(defun mapc-buffers/progress (msg fn &rest modes)
1706 (dolist-with-progress-reporter (buf (buffer-list)) msg
1707 (with-current-buffer buf
1708 (when (or (null modes)
1709 (apply #'derived-mode-p modes))
1710 (funcall fn)))))
1711
1712;;; Flash!
1713
1714(defun flash-region@ (orig start end &rest args)
1715 (apply orig start end args)
1716 (pulse-momentary-highlight-region start end))
1717
1718(advice-add 'eval-region :around #'flash-region@)
1719(with-eval-after-load 'geiser
1720 (advice-add 'geiser-eval-region :around #'flash-region@))
1721
1722;;; KeepassXC Integration
1723
1724(when (package-ensure 'keepassxc-shim t)
1725 (keepassxc-shim-activate))
1726
1727;;; RCIRC
1728
1729(when (require 'rcirc)
1730 (setopt rcirc-default-full-name user-full-name)
1731 (setopt rcirc-default-user-name user-login-name)
1732 (setopt rcirc-authenticate-before-join t)
1733 (setopt rcirc-display-server-buffer nil)
1734 (setopt rcirc-buffer-maximum-lines 1000)
1735 (setopt rcirc-kill-channel-buffers t)
1736 (setopt rcirc-track-ignore-server-buffer-flag t)
1737
1738 ;; Theming
1739 (setopt rcirc-prompt "%t> ")
1740 (setopt rcirc-default-part-reason "See You Space Cowboy . . .")
1741 (setopt rcirc-default-quit-reason "(TLS connection improperly terminated)")
1742 (setopt rcirc-url-max-length 24)
1743 (setopt rcirc-response-formats
1744 '(("PRIVMSG" . "<%N> %m")
1745 ("NOTICE" . "-%N- %m")
1746 ("ACTION" . "* %N %m")
1747 ("COMMAND" . "%fs%m%f-")
1748 ("ERROR" . "%fw!!! %m")
1749 ("FAIL" . "(%fwFAIL%f-) %m")
1750 ("WARN" . "(%fwWARN%f-) %m")
1751 ("NOTE" . "(%fwNOTE%f-) %m")
1752 (t . "%fp*** %fs%n %r %m")))
1753
1754 (face-spec-set 'rcirc-nick-in-message-full-line
1755 '((t :foreground unspecified
1756 :background unspecified
1757 :weight unspecified
1758 :inherit nil)))
1759
1760 (add-to-list 'rcirc-omit-responses "NAMES")
1761
1762 (defun chat/setup ()
1763 (whitespace-mode -1)
1764 (electric-pair-local-mode -1)
1765 ;; IDK what's the deal with this
1766 (olivetti-mode +1)
1767 (visual-line-mode -1)
1768 (word-wrap-whitespace-mode +1))
1769
1770 (setq rcirc-debug-flag t)
1771
1772 (advice-add 'rcirc :after
1773 (defun enable-rcirc-track-minor-mode (&rest _)
1774 (rcirc-track-minor-mode 1)))
1775
1776 (defun rcirc-kill ()
1777 "Kill all rcirc buffers and turn off `rcirc-track-minor-mode'."
1778 (interactive)
1779 (mapc-buffers/progress "Killing rcirc buffers..."
1780 (lambda ()
1781 (let ((kill-buffer-hook))
1782 (kill-buffer)))
1783 'rcirc-mode)
1784 (dolist-with-progress-reporter (server rcirc-server-alist)
1785 "Removing cached passwords..."
1786 (when-let ((pwf (plist-get server :password-function)))
1787 (setf (plist-get server :password)
1788 pwf)))
1789 (rcirc-track-minor-mode -1))
1790
1791 (add-hook 'rcirc-mode-hook #'chat/setup)
1792 (add-hook 'rcirc-mode-hook #'rcirc-omit-mode)
1793 (add-hook 'rcirc-track-minor-mode-hook
1794 (defun rcirc-track@buffer-list-change ()
1795 (add-hook 'buffer-list-update-hook
1796 #'rcirc-window-configuration-change)))
1797
1798 ;; "Fix" some things
1799 (setf rcirc-implemented-capabilities
1800 ;; I don't use these, and they mess up display in a few of my chats
1801 (delete "message-tags" rcirc-implemented-capabilities))
1802
1803 ;; Adding servers more better-er
1804 (defun rcirc-add-server (name &rest spec)
1805 "Add a server to `rcirc-server-alist' and `rcirc-authinfo' at once.
1806TODO: fully document"
1807 (let ((name* (if (plist-get spec :host)
1808 (plist-get spec :host)
1809 name))
1810 (nick (or (plist-get spec :nick)
1811 (bound-and-true-p rcirc-default-nick)
1812 (bound-and-true-p user-login-name)))
1813 (user-name (or (plist-get spec :user-name)
1814 (plist-get spec :user)
1815 (plist-get spec :nick)
1816 (bound-and-true-p rcirc-default-user-name)
1817 (bound-and-true-p rcirc-default-nick)
1818 (bound-and-true-p user-login-name)))
1819 (password (let ((password (or (plist-get spec :password)
1820 (plist-get spec :pass))))
1821 ;; (cond
1822 ;; ((functionp password) (funcall password))
1823 ;; ((stringp password) password))
1824 password
1825 )))
1826 ;; Add the server to `rcirc-server-alist'
1827 (setf (alist-get name* rcirc-server-alist nil nil #'equal)
1828 (append
1829 (list :nick nick
1830 :user-name user-name)
1831 (when password (list :password password))
1832 (when-let ((full-name (plist-get spec :full-name)))
1833 (list :full-name full-name))
1834 (when-let ((channels (plist-get spec :channels)))
1835 (list :channels channels))
1836 (when-let ((port (plist-get spec :port)))
1837 (list :port port))
1838 (when-let ((encryption (plist-get spec :encryption)))
1839 (list :encryption encryption))
1840 (when-let ((server-alias (or (plist-get spec :server-alias)
1841 (and (plist-get spec :host)
1842 name))))
1843 (list :server-alias server-alias))))
1844 ;; Add it to `rcirc-authinfo'
1845 (when-let ((auth (plist-get spec :auth)))
1846 (unless password (user-error "Trying to auth without a password"))
1847 (setf (alist-get name* rcirc-authinfo nil nil #'equal)
1848 (cl-case auth
1849 (nickserv (list 'nickserv nick password))
1850 (bitlbee (list 'bitlbee nick password))
1851 (quakenet (list 'quakenet user-name password))
1852 (sasl (list 'sasl user-name password))
1853 ;; (chanserv) ; These two aren't supported.
1854 ;; (certfp)
1855 (t (user-error "Unsupported :auth type `%s'"
1856 (plist-get plist :auth))))))
1857 ;; Return the server's name so that we don't leak authinfo
1858 name))
1859
1860 (defun rcirc-resolve-passwords (&rest _)
1861 (dolist-with-progress-reporter (s rcirc-server-alist)
1862 "Resolving lambda passwords in `rcirc-server-alist...'"
1863 (let ((pw (plist-get (cdr s) :password)))
1864 (setf (plist-get (cdr s) :password-function) pw)
1865 (setf (plist-get (cdr s) :password)
1866 (if (functionp pw) (funcall pw) pw))))
1867 (dolist-with-progress-reporter (i rcirc-authinfo)
1868 "Resolving lambda passwords in `rcirc-authinfo...'"
1869 (let ((pw (cadddr i)))
1870 (setf (cadddr i)
1871 (if-let ((s (assoc (car i) rcirc-server-alist)))
1872 (plist-get (cdr s) :password)
1873 (if (functionp pw) (funcall pw) pw))))))
1874
1875 (advice-add 'rcirc :before #'rcirc-resolve-passwords)
1876
1877 (defun rcirc/password (&rest spec)
1878 (lambda ()
1879 (auth-info-password
1880 (car
1881 (apply #'auth-source-search spec)))))
1882
1883 (setq rcirc-server-alist nil)
1884
1885 (rcirc-add-server "tilde.town"
1886 :host "localhost" :port 6969
1887 :channels '("#tildetown" "#newbirc")
1888 :auth 'sasl
1889 :password
1890 (rcirc/password :host "localhost:6969" :user "acdw"))
1891 ;; (rcirc-add-server "43beans.casa"
1892 ;; :host "localhost" :port 6970
1893 ;; :channels '("#beans")
1894 ;; :auth nil
1895 ;; :password nil)
1896 (rcirc-add-server "tilde.chat"
1897 :host "irc.tilde.chat" :port 6697 :encryption 'tls
1898 ;; :channels left blank. There's some kind of race
1899 ;; condition in SASL and identd that means I authenticate
1900 ;; before being fully authenticated? Or something.
1901 ;; Tilde.chat's NickServ does autojoin, though, so that
1902 ;; works out without an afternoon of debugging.
1903 :auth 'sasl
1904 :password (rcirc/password :host "tilde.chat" :user "acdw"))
1905 (rcirc-add-server "m455.casa"
1906 :port 6697 :encryption 'tls
1907 :channels '("#basement" "#43beans")
1908 :auth 'sasl
1909 :password (rcirc/password :host "m455.casa" :user "acdw"))
1910 (rcirc-add-server "libera.chat"
1911 :host "irc.libera.chat" :port 6697 :encryption 'tls
1912 :channels '("#emacs" "#rcirc")
1913 :auth 'sasl
1914 :password (rcirc/password :host "libera.chat" :user "acdw"))
1915 ;; End of rcirc configuration.
1916 )
1917
1918(when (package-ensure 'web-mode)
1919 (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
1920 (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
1921 (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
1922 (add-to-list 'auto-mode-alist '("\\. [agj]sp\\'" . web-mode))
1923 (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
1924 (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
1925 (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
1926 (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode)))
diff --git a/readme.txt b/readme.txt deleted file mode 100644 index a140bbf..0000000 --- a/readme.txt +++ /dev/null
@@ -1,4 +0,0 @@
1~/.config/emacs
2
3this is my 10th bankruptcy, or the beginning of it.
4I'm moving my ~/.emacs to, yes, ~/.emacs and adding this repo to my etc repo.